mirror of
				https://github.com/usememos/memos.git
				synced 2025-06-05 22:09:59 +02:00 
			
		
		
		
	feat: esc key to exit multiple dialogs (#692)
				
					
				
			* fix: `esc` key to exit multiple dialogs * update * update * update * Update web/src/components/Dialog/BaseDialog.tsx Co-authored-by: boojack <stevenlgtm@gmail.com>
This commit is contained in:
		| @@ -59,6 +59,7 @@ export default function showAboutSiteDialog(): void { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "about-site-dialog", |       className: "about-site-dialog", | ||||||
|  |       dialogName: "about-site-dialog", | ||||||
|     }, |     }, | ||||||
|     AboutSiteDialog |     AboutSiteDialog | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -69,6 +69,7 @@ export default function showArchivedMemoDialog(): void { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "archived-memo-dialog", |       className: "archived-memo-dialog", | ||||||
|  |       dialogName: "archived-memo-dialog", | ||||||
|     }, |     }, | ||||||
|     ArchivedMemoDialog, |     ArchivedMemoDialog, | ||||||
|     {} |     {} | ||||||
|   | |||||||
| @@ -117,6 +117,7 @@ function showChangeMemberPasswordDialog(user: User) { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "change-member-password-dialog", |       className: "change-member-password-dialog", | ||||||
|  |       dialogName: "change-member-password-dialog", | ||||||
|     }, |     }, | ||||||
|     ChangeMemberPasswordDialog, |     ChangeMemberPasswordDialog, | ||||||
|     { user } |     { user } | ||||||
|   | |||||||
| @@ -97,6 +97,7 @@ function showChangeMemoCreatedTsDialog(memoId: MemoId) { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "change-memo-created-ts-dialog", |       className: "change-memo-created-ts-dialog", | ||||||
|  |       dialogName: "change-memo-created-ts-dialog", | ||||||
|     }, |     }, | ||||||
|     ChangeMemoCreatedTsDialog, |     ChangeMemoCreatedTsDialog, | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -115,6 +115,7 @@ function showChangePasswordDialog() { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "change-password-dialog", |       className: "change-password-dialog", | ||||||
|  |       dialogName: "change-password-dialog", | ||||||
|     }, |     }, | ||||||
|     ChangePasswordDialog |     ChangePasswordDialog | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -87,6 +87,7 @@ function showChangeResourceFilenameDialog(resourceId: ResourceId, resourceFilena | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "change-resource-filename-dialog", |       className: "change-resource-filename-dialog", | ||||||
|  |       dialogName: "change-resource-filename-dialog", | ||||||
|     }, |     }, | ||||||
|     ChangeResourceFilenameDialog, |     ChangeResourceFilenameDialog, | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -291,6 +291,7 @@ export default function showCreateShortcutDialog(shortcutId?: ShortcutId): void | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "create-shortcut-dialog", |       className: "create-shortcut-dialog", | ||||||
|  |       dialogName: "create-shortcut-dialog", | ||||||
|     }, |     }, | ||||||
|     CreateShortcutDialog, |     CreateShortcutDialog, | ||||||
|     { shortcutId } |     { shortcutId } | ||||||
|   | |||||||
| @@ -115,6 +115,7 @@ export default function showDailyReviewDialog(datestamp: DateStamp = Date.now()) | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "daily-review-dialog", |       className: "daily-review-dialog", | ||||||
|  |       dialogName: "daily-review-dialog", | ||||||
|     }, |     }, | ||||||
|     DailyReviewDialog, |     DailyReviewDialog, | ||||||
|     { currentDateStamp: datestamp } |     { currentDateStamp: datestamp } | ||||||
|   | |||||||
| @@ -3,13 +3,15 @@ import { createRoot } from "react-dom/client"; | |||||||
| import { Provider } from "react-redux"; | import { Provider } from "react-redux"; | ||||||
| import { ANIMATION_DURATION } from "../../helpers/consts"; | import { ANIMATION_DURATION } from "../../helpers/consts"; | ||||||
| import store from "../../store"; | import store from "../../store"; | ||||||
| import "../../less/base-dialog.less"; | import { useDialogStore } from "../../store/module"; | ||||||
| import { CssVarsProvider } from "@mui/joy"; | import { CssVarsProvider } from "@mui/joy"; | ||||||
| import theme from "../../theme"; | import theme from "../../theme"; | ||||||
|  | import "../../less/base-dialog.less"; | ||||||
|  |  | ||||||
| interface DialogConfig { | interface DialogConfig { | ||||||
|   className: string; |   className: string; | ||||||
|   clickSpaceDestroy?: boolean; |   clickSpaceDestroy?: boolean; | ||||||
|  |   dialogName: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface Props extends DialogConfig, DialogProps { | interface Props extends DialogConfig, DialogProps { | ||||||
| @@ -17,13 +19,18 @@ interface Props extends DialogConfig, DialogProps { | |||||||
| } | } | ||||||
|  |  | ||||||
| const BaseDialog: React.FC<Props> = (props: Props) => { | const BaseDialog: React.FC<Props> = (props: Props) => { | ||||||
|   const { children, className, clickSpaceDestroy, destroy } = props; |   const { children, className, clickSpaceDestroy, dialogName, destroy } = props; | ||||||
|  |   const dialogStore = useDialogStore(); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|  |     dialogStore.pushDialogStack(dialogName); | ||||||
|     const handleKeyDown = (event: KeyboardEvent) => { |     const handleKeyDown = (event: KeyboardEvent) => { | ||||||
|       if (event.code === "Escape") { |       if (event.code === "Escape") { | ||||||
|  |         if (dialogName === dialogStore.topDialogStack()) { | ||||||
|  |           dialogStore.popDialogStack(); | ||||||
|           destroy(); |           destroy(); | ||||||
|         } |         } | ||||||
|  |       } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     document.body.addEventListener("keydown", handleKeyDown); |     document.body.addEventListener("keydown", handleKeyDown); | ||||||
|   | |||||||
| @@ -72,6 +72,7 @@ interface CommonDialogProps { | |||||||
|   content: string; |   content: string; | ||||||
|   className?: string; |   className?: string; | ||||||
|   style?: DialogStyle; |   style?: DialogStyle; | ||||||
|  |   dialogName: string; | ||||||
|   closeBtnText?: string; |   closeBtnText?: string; | ||||||
|   confirmBtnText?: string; |   confirmBtnText?: string; | ||||||
|   onClose?: () => void; |   onClose?: () => void; | ||||||
| @@ -82,6 +83,7 @@ export const showCommonDialog = (props: CommonDialogProps) => { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: `common-dialog ${props?.className ?? ""}`, |       className: `common-dialog ${props?.className ?? ""}`, | ||||||
|  |       dialogName: `common-dialog ${props?.className ?? ""}`, | ||||||
|     }, |     }, | ||||||
|     CommonDialog, |     CommonDialog, | ||||||
|     props |     props | ||||||
|   | |||||||
| @@ -124,6 +124,7 @@ export default function showPreviewImageDialog(imgUrls: string[] | string, initi | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "preview-image-dialog", |       className: "preview-image-dialog", | ||||||
|  |       dialogName: "preview-image-dialog", | ||||||
|     }, |     }, | ||||||
|     PreviewImageDialog, |     PreviewImageDialog, | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -125,6 +125,7 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => { | |||||||
|       title: t("resources.delete-resource"), |       title: t("resources.delete-resource"), | ||||||
|       content: warningText, |       content: warningText, | ||||||
|       style: "warning", |       style: "warning", | ||||||
|  |       dialogName: "delete-unused-resources", | ||||||
|       onConfirm: async () => { |       onConfirm: async () => { | ||||||
|         for (const resource of unusedResources) { |         for (const resource of unusedResources) { | ||||||
|           await resourceStore.deleteResourceById(resource.id); |           await resourceStore.deleteResourceById(resource.id); | ||||||
| @@ -143,6 +144,7 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => { | |||||||
|       title: t("resources.delete-resource"), |       title: t("resources.delete-resource"), | ||||||
|       content: warningText, |       content: warningText, | ||||||
|       style: "warning", |       style: "warning", | ||||||
|  |       dialogName: "delete-resource-dialog", | ||||||
|       onConfirm: async () => { |       onConfirm: async () => { | ||||||
|         await resourceStore.deleteResourceById(resource.id); |         await resourceStore.deleteResourceById(resource.id); | ||||||
|       }, |       }, | ||||||
| @@ -242,6 +244,7 @@ export default function showResourcesDialog() { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "resources-dialog", |       className: "resources-dialog", | ||||||
|  |       dialogName: "resources-dialog", | ||||||
|     }, |     }, | ||||||
|     ResourcesDialog, |     ResourcesDialog, | ||||||
|     {} |     {} | ||||||
|   | |||||||
| @@ -148,6 +148,7 @@ export default function showResourcesSelectorDialog() { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "resources-selector-dialog", |       className: "resources-selector-dialog", | ||||||
|  |       dialogName: "resources-selector-dialog", | ||||||
|     }, |     }, | ||||||
|     ResourcesSelectorDialog, |     ResourcesSelectorDialog, | ||||||
|     {} |     {} | ||||||
|   | |||||||
| @@ -92,6 +92,7 @@ export default function showSettingDialog(): void { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "setting-dialog", |       className: "setting-dialog", | ||||||
|  |       dialogName: "setting-dialog", | ||||||
|     }, |     }, | ||||||
|     SettingDialog, |     SettingDialog, | ||||||
|     {} |     {} | ||||||
|   | |||||||
| @@ -79,6 +79,7 @@ const PreferencesSection = () => { | |||||||
|       title: `Archive Member`, |       title: `Archive Member`, | ||||||
|       content: `❗️Are you sure to archive ${user.username}?`, |       content: `❗️Are you sure to archive ${user.username}?`, | ||||||
|       style: "warning", |       style: "warning", | ||||||
|  |       dialogName: "archive-user-dialog", | ||||||
|       onConfirm: async () => { |       onConfirm: async () => { | ||||||
|         await userStore.patchUser({ |         await userStore.patchUser({ | ||||||
|           id: user.id, |           id: user.id, | ||||||
| @@ -102,6 +103,7 @@ const PreferencesSection = () => { | |||||||
|       title: `Delete Member`, |       title: `Delete Member`, | ||||||
|       content: `Are you sure to delete ${user.username}? THIS ACTION IS IRREVERSIABLE.❗️`, |       content: `Are you sure to delete ${user.username}? THIS ACTION IS IRREVERSIABLE.❗️`, | ||||||
|       style: "warning", |       style: "warning", | ||||||
|  |       dialogName: "delete-user-dialog", | ||||||
|       onConfirm: async () => { |       onConfirm: async () => { | ||||||
|         await userStore.deleteUser({ |         await userStore.deleteUser({ | ||||||
|           id: user.id, |           id: user.id, | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ const MyAccountSection = () => { | |||||||
|       title: "Reset Open API", |       title: "Reset Open API", | ||||||
|       content: "❗️The existing API will be invalidated and a new one will be generated, are you sure you want to reset?", |       content: "❗️The existing API will be invalidated and a new one will be generated, are you sure you want to reset?", | ||||||
|       style: "warning", |       style: "warning", | ||||||
|  |       dialogName: "reset-openid-dialog", | ||||||
|       onConfirm: async () => { |       onConfirm: async () => { | ||||||
|         await userStore.patchUser({ |         await userStore.patchUser({ | ||||||
|           id: user.id, |           id: user.id, | ||||||
|   | |||||||
| @@ -190,6 +190,7 @@ export default function showShareMemoDialog(memo: Memo): void { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "share-memo-dialog", |       className: "share-memo-dialog", | ||||||
|  |       dialogName: "share-memo-dialog", | ||||||
|     }, |     }, | ||||||
|     ShareMemoDialog, |     ShareMemoDialog, | ||||||
|     { memo } |     { memo } | ||||||
|   | |||||||
| @@ -139,6 +139,7 @@ function showUpdateAccountDialog() { | |||||||
|   generateDialog( |   generateDialog( | ||||||
|     { |     { | ||||||
|       className: "update-account-dialog", |       className: "update-account-dialog", | ||||||
|  |       dialogName: "update-account-dialog", | ||||||
|     }, |     }, | ||||||
|     UpdateAccountDialog |     UpdateAccountDialog | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import editorReducer from "./reducer/editor"; | |||||||
| import shortcutReducer from "./reducer/shortcut"; | import shortcutReducer from "./reducer/shortcut"; | ||||||
| import locationReducer from "./reducer/location"; | import locationReducer from "./reducer/location"; | ||||||
| import resourceReducer from "./reducer/resource"; | import resourceReducer from "./reducer/resource"; | ||||||
|  | import dialogReducer from "./reducer/dialog"; | ||||||
|  |  | ||||||
| const store = configureStore({ | const store = configureStore({ | ||||||
|   reducer: { |   reducer: { | ||||||
| @@ -17,6 +18,7 @@ const store = configureStore({ | |||||||
|     shortcut: shortcutReducer, |     shortcut: shortcutReducer, | ||||||
|     location: locationReducer, |     location: locationReducer, | ||||||
|     resource: resourceReducer, |     resource: resourceReducer, | ||||||
|  |     dialog: dialogReducer, | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								web/src/store/module/dialog.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								web/src/store/module/dialog.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import store, { useAppSelector } from ".."; | ||||||
|  | import { pushDialogStack, popDialogStack } from "../reducer/dialog"; | ||||||
|  | import { last } from "lodash"; | ||||||
|  |  | ||||||
|  | export const useDialogStore = () => { | ||||||
|  |   const state = useAppSelector((state) => state.editor); | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     state, | ||||||
|  |     getState: () => { | ||||||
|  |       return store.getState().dialog; | ||||||
|  |     }, | ||||||
|  |     pushDialogStack: (dialogName: string) => { | ||||||
|  |       store.dispatch(pushDialogStack(dialogName)); | ||||||
|  |     }, | ||||||
|  |     popDialogStack: () => { | ||||||
|  |       store.dispatch(popDialogStack()); | ||||||
|  |     }, | ||||||
|  |     topDialogStack: () => { | ||||||
|  |       return last(store.getState().dialog.dialogStack); | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @@ -5,3 +5,4 @@ export * from "./memo"; | |||||||
| export * from "./resource"; | export * from "./resource"; | ||||||
| export * from "./shortcut"; | export * from "./shortcut"; | ||||||
| export * from "./user"; | export * from "./user"; | ||||||
|  | export * from "./dialog"; | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								web/src/store/reducer/dialog.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								web/src/store/reducer/dialog.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; | ||||||
|  |  | ||||||
|  | interface State { | ||||||
|  |   dialogStack: string[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const dialogSlice = createSlice({ | ||||||
|  |   name: "dialog", | ||||||
|  |   initialState: { | ||||||
|  |     dialogStack: [], | ||||||
|  |   } as State, | ||||||
|  |   reducers: { | ||||||
|  |     pushDialogStack: (state, action: PayloadAction<string>) => { | ||||||
|  |       return { | ||||||
|  |         ...state, | ||||||
|  |         dialogStack: [...state.dialogStack, action.payload], | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |     popDialogStack: (state) => { | ||||||
|  |       return { | ||||||
|  |         ...state, | ||||||
|  |         dialogStack: state.dialogStack.slice(0, state.dialogStack.length - 1), | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export const { pushDialogStack, popDialogStack } = dialogSlice.actions; | ||||||
|  |  | ||||||
|  | export default dialogSlice.reducer; | ||||||
		Reference in New Issue
	
	Block a user