From 4b7bc1f07c712cf2375882da4f12a8af29444590 Mon Sep 17 00:00:00 2001
From: Renaud Chaput <renchap@gmail.com>
Date: Fri, 22 Sep 2023 18:18:46 +0200
Subject: [PATCH] Convert `dropdown_menu` state to Typescript (#25585)

---
 .../mastodon/actions/dropdown_menu.js         | 10 ------
 .../mastodon/actions/dropdown_menu.ts         | 11 +++++++
 .../containers/dropdown_menu_container.js     | 13 +++++---
 .../mastodon/components/scrollable_list.jsx   |  7 +++-
 .../containers/dropdown_menu_container.js     | 11 ++++---
 app/javascript/mastodon/features/ui/index.jsx |  2 +-
 .../mastodon/reducers/dropdown_menu.js        | 19 -----------
 .../mastodon/reducers/dropdown_menu.ts        | 33 +++++++++++++++++++
 app/javascript/mastodon/reducers/index.ts     |  4 +--
 9 files changed, 69 insertions(+), 41 deletions(-)
 delete mode 100644 app/javascript/mastodon/actions/dropdown_menu.js
 create mode 100644 app/javascript/mastodon/actions/dropdown_menu.ts
 delete mode 100644 app/javascript/mastodon/reducers/dropdown_menu.js
 create mode 100644 app/javascript/mastodon/reducers/dropdown_menu.ts

diff --git a/app/javascript/mastodon/actions/dropdown_menu.js b/app/javascript/mastodon/actions/dropdown_menu.js
deleted file mode 100644
index 023151d4bf..0000000000
--- a/app/javascript/mastodon/actions/dropdown_menu.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
-export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
-
-export function openDropdownMenu(id, keyboard, scroll_key) {
-  return { type: DROPDOWN_MENU_OPEN, id, keyboard, scroll_key };
-}
-
-export function closeDropdownMenu(id) {
-  return { type: DROPDOWN_MENU_CLOSE, id };
-}
diff --git a/app/javascript/mastodon/actions/dropdown_menu.ts b/app/javascript/mastodon/actions/dropdown_menu.ts
new file mode 100644
index 0000000000..3694df1ae0
--- /dev/null
+++ b/app/javascript/mastodon/actions/dropdown_menu.ts
@@ -0,0 +1,11 @@
+import { createAction } from '@reduxjs/toolkit';
+
+export const openDropdownMenu = createAction<{
+  id: string;
+  keyboard: boolean;
+  scrollKey: string;
+}>('dropdownMenu/open');
+
+export const closeDropdownMenu = createAction<{ id: string }>(
+  'dropdownMenu/close',
+);
diff --git a/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js b/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js
index a0896d985e..726fee9076 100644
--- a/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js
+++ b/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js
@@ -4,9 +4,14 @@ import { openDropdownMenu, closeDropdownMenu } from 'mastodon/actions/dropdown_m
 import { fetchHistory } from 'mastodon/actions/history';
 import DropdownMenu from 'mastodon/components/dropdown_menu';
 
+/**
+ *
+ * @param {import('mastodon/store').RootState} state
+ * @param {*} props
+ */
 const mapStateToProps = (state, { statusId }) => ({
-  openDropdownId: state.getIn(['dropdown_menu', 'openId']),
-  openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
+  openDropdownId: state.dropdownMenu.openId,
+  openedViaKeyboard: state.dropdownMenu.keyboard,
   items: state.getIn(['history', statusId, 'items']),
   loading: state.getIn(['history', statusId, 'loading']),
 });
@@ -15,11 +20,11 @@ const mapDispatchToProps = (dispatch, { statusId }) => ({
 
   onOpen (id, onItemClick, keyboard) {
     dispatch(fetchHistory(statusId));
-    dispatch(openDropdownMenu(id, keyboard));
+    dispatch(openDropdownMenu({ id, keyboard }));
   },
 
   onClose (id) {
-    dispatch(closeDropdownMenu(id));
+    dispatch(closeDropdownMenu({ id }));
   },
 
 });
diff --git a/app/javascript/mastodon/components/scrollable_list.jsx b/app/javascript/mastodon/components/scrollable_list.jsx
index ce0b579f50..7672a4e424 100644
--- a/app/javascript/mastodon/components/scrollable_list.jsx
+++ b/app/javascript/mastodon/components/scrollable_list.jsx
@@ -23,9 +23,14 @@ const MOUSE_IDLE_DELAY = 300;
 
 const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
 
+/**
+ *
+ * @param {import('mastodon/store').RootState} state
+ * @param {*} props
+ */
 const mapStateToProps = (state, { scrollKey }) => {
   return {
-    preventScroll: scrollKey === state.getIn(['dropdown_menu', 'scroll_key']),
+    preventScroll: scrollKey === state.dropdownMenu.scrollKey,
   };
 };
 
diff --git a/app/javascript/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js
index 6cf180cd53..bc9124c041 100644
--- a/app/javascript/mastodon/containers/dropdown_menu_container.js
+++ b/app/javascript/mastodon/containers/dropdown_menu_container.js
@@ -7,9 +7,12 @@ import { openModal, closeModal } from '../actions/modal';
 import DropdownMenu from '../components/dropdown_menu';
 import { isUserTouching } from '../is_mobile';
 
+/**
+ * @param {import('mastodon/store').RootState} state
+ */
 const mapStateToProps = state => ({
-  openDropdownId: state.getIn(['dropdown_menu', 'openId']),
-  openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
+  openDropdownId: state.dropdownMenu.openId,
+  openedViaKeyboard: state.dropdownMenu.keyboard,
 });
 
 const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
@@ -25,7 +28,7 @@ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
         actions: items,
         onClick: onItemClick,
       },
-    }) : openDropdownMenu(id, keyboard, scrollKey));
+    }) : openDropdownMenu({ id, keyboard, scrollKey }));
   },
 
   onClose(id) {
@@ -33,7 +36,7 @@ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
       modalType: 'ACTIONS',
       ignoreFocus: false,
     }));
-    dispatch(closeDropdownMenu(id));
+    dispatch(closeDropdownMenu({ id }));
   },
 });
 
diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx
index 55ccde72f5..ac5e2d9361 100644
--- a/app/javascript/mastodon/features/ui/index.jsx
+++ b/app/javascript/mastodon/features/ui/index.jsx
@@ -79,7 +79,7 @@ const mapStateToProps = state => ({
   hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
   hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
   canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
-  dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
+  dropdownMenuIsOpen: state.dropdownMenu.openId !== null,
   firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
   username: state.getIn(['accounts', me, 'username']),
 });
diff --git a/app/javascript/mastodon/reducers/dropdown_menu.js b/app/javascript/mastodon/reducers/dropdown_menu.js
deleted file mode 100644
index 6f92f1bbe8..0000000000
--- a/app/javascript/mastodon/reducers/dropdown_menu.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import Immutable from 'immutable';
-
-import {
-  DROPDOWN_MENU_OPEN,
-  DROPDOWN_MENU_CLOSE,
-} from '../actions/dropdown_menu';
-
-const initialState = Immutable.Map({ openId: null, keyboard: false, scroll_key: null });
-
-export default function dropdownMenu(state = initialState, action) {
-  switch (action.type) {
-  case DROPDOWN_MENU_OPEN:
-    return state.merge({ openId: action.id, keyboard: action.keyboard, scroll_key: action.scroll_key });
-  case DROPDOWN_MENU_CLOSE:
-    return state.get('openId') === action.id ? state.set('openId', null).set('scroll_key', null) : state;
-  default:
-    return state;
-  }
-}
diff --git a/app/javascript/mastodon/reducers/dropdown_menu.ts b/app/javascript/mastodon/reducers/dropdown_menu.ts
new file mode 100644
index 0000000000..59e19bb16d
--- /dev/null
+++ b/app/javascript/mastodon/reducers/dropdown_menu.ts
@@ -0,0 +1,33 @@
+import { createReducer } from '@reduxjs/toolkit';
+
+import { closeDropdownMenu, openDropdownMenu } from '../actions/dropdown_menu';
+
+interface DropdownMenuState {
+  openId: string | null;
+  keyboard: boolean;
+  scrollKey: string | null;
+}
+
+const initialState: DropdownMenuState = {
+  openId: null,
+  keyboard: false,
+  scrollKey: null,
+};
+
+export const dropdownMenuReducer = createReducer(initialState, (builder) => {
+  builder
+    .addCase(
+      openDropdownMenu,
+      (state, { payload: { id, keyboard, scrollKey } }) => {
+        state.openId = id;
+        state.keyboard = keyboard;
+        state.scrollKey = scrollKey;
+      },
+    )
+    .addCase(closeDropdownMenu, (state, { payload: { id } }) => {
+      if (state.openId === id) {
+        state.openId = null;
+        state.scrollKey = null;
+      }
+    });
+});
diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts
index c61c862cfe..722f04f370 100644
--- a/app/javascript/mastodon/reducers/index.ts
+++ b/app/javascript/mastodon/reducers/index.ts
@@ -15,7 +15,7 @@ import contexts from './contexts';
 import conversations from './conversations';
 import custom_emojis from './custom_emojis';
 import domain_lists from './domain_lists';
-import dropdown_menu from './dropdown_menu';
+import { dropdownMenuReducer } from './dropdown_menu';
 import filters from './filters';
 import followed_tags from './followed_tags';
 import height_cache from './height_cache';
@@ -46,7 +46,7 @@ import user_lists from './user_lists';
 
 const reducers = {
   announcements,
-  dropdown_menu,
+  dropdownMenu: dropdownMenuReducer,
   timelines,
   meta,
   alerts,