From a9e459b44872de6c5c972456640a365977c70130 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 23 Feb 2025 17:49:42 +0200 Subject: [PATCH] web/modal: allow two layers of modals --- web/src/ui/MainScreen.tsx | 47 +++++++++++++++++++----------------- web/src/ui/modal/Modal.tsx | 34 ++++++++++++++++---------- web/src/ui/modal/contexts.ts | 5 +++- 3 files changed, 50 insertions(+), 36 deletions(-) diff --git a/web/src/ui/MainScreen.tsx b/web/src/ui/MainScreen.tsx index c0a5b48..bd7c4ed 100644 --- a/web/src/ui/MainScreen.tsx +++ b/web/src/ui/MainScreen.tsx @@ -24,7 +24,7 @@ import ClientContext from "./ClientContext.ts" import MainScreenContext, { MainScreenContextFields } from "./MainScreenContext.ts" import StylePreferences from "./StylePreferences.tsx" import Keybindings from "./keybindings.ts" -import { ModalWrapper } from "./modal" +import { ModalContext, ModalWrapper, NestableModalContext } from "./modal" import RightPanel, { RightPanelProps } from "./rightpanel/RightPanel.tsx" import RoomList from "./roomlist/RoomList.tsx" import RoomPreview, { RoomPreviewProps } from "./roomview/RoomPreview.tsx" @@ -422,28 +422,31 @@ const MainScreen = () => { return () => clearTimeout(timeout) } }, [activeRoom, prevActiveRoom]) + const mainContent =
+ + {resizeHandle1} + {renderedRoom + ? renderedRoom instanceof RoomStateStore + ? + : + : rightPanel && <> +
+ {resizeHandle2} + {rightPanel && } + } +
return - - -
- - {resizeHandle1} - {renderedRoom - ? renderedRoom instanceof RoomStateStore - ? - : - : rightPanel && <> -
- {resizeHandle2} - {rightPanel && } - } -
- {syncLoader} + + + + {mainContent} + {syncLoader} +
} diff --git a/web/src/ui/modal/Modal.tsx b/web/src/ui/modal/Modal.tsx index 4d0baf9..668a266 100644 --- a/web/src/ui/modal/Modal.tsx +++ b/web/src/ui/modal/Modal.tsx @@ -13,10 +13,16 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { JSX, useCallback, useEffect, useLayoutEffect, useReducer, useRef } from "react" -import { ModalCloseContext, ModalContext, ModalState } from "./contexts.ts" +import React, { Context, JSX, useCallback, useEffect, useLayoutEffect, useReducer, useRef } from "react" +import { ModalCloseContext, ModalState, openModal } from "./contexts.ts" -const ModalWrapper = ({ children }: { children: React.ReactNode }) => { +interface ModalWrapperProps { + children: React.ReactNode + ContextType: Context + historyStateKey: string +} + +const ModalWrapper = ({ children, ContextType, historyStateKey }: ModalWrapperProps) => { const [state, setState] = useReducer((prevState: ModalState | null, newState: ModalState | null) => { prevState?.onClose?.() return newState @@ -25,26 +31,28 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => { if (evt && evt.target !== evt.currentTarget) { return } + console.log("MEOW", evt) + evt?.stopPropagation() setState(null) - if (history.state?.modal) { + if (history.state?.[historyStateKey]) { history.back() } - }, []) + }, [historyStateKey]) const onKeyWrapper = (evt: React.KeyboardEvent) => { if (evt.key === "Escape") { setState(null) - if (history.state?.modal) { + if (history.state?.[historyStateKey]) { history.back() } } evt.stopPropagation() } const openModal = useCallback((newState: ModalState) => { - if (!history.state?.modal && newState.captureInput !== false) { - history.pushState({ ...(history.state ?? {}), modal: true }, "") + if (!history.state?.[historyStateKey] && newState.captureInput !== false) { + history.pushState({ ...(history.state ?? {}), [historyStateKey]: true }, "") } setState(newState) - }, []) + }, [historyStateKey]) const wrapperRef = useRef(null) useLayoutEffect(() => { if (wrapperRef.current && (!document.activeElement || !wrapperRef.current.contains(document.activeElement))) { @@ -54,13 +62,13 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => { useEffect(() => { window.closeModal = onClickWrapper const listener = (evt: PopStateEvent) => { - if (!evt.state?.modal) { + if (!evt.state?.[historyStateKey]) { setState(null) } } window.addEventListener("popstate", listener) return () => window.removeEventListener("popstate", listener) - }, [onClickWrapper]) + }, [historyStateKey, onClickWrapper]) let modal: JSX.Element | null = null if (state) { let content = {state.content} @@ -85,10 +93,10 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => { modal = content } } - return + return {children} {modal} - + } export default ModalWrapper diff --git a/web/src/ui/modal/contexts.ts b/web/src/ui/modal/contexts.ts index dad5963..083a039 100644 --- a/web/src/ui/modal/contexts.ts +++ b/web/src/ui/modal/contexts.ts @@ -35,9 +35,12 @@ export interface ModalState { captureInput?: boolean } -type openModal = (state: ModalState) => void +export type openModal = (state: ModalState) => void export const ModalContext = createContext(() => console.error("Tried to open modal without being inside context")) +export const NestableModalContext = createContext(() => + console.error("Tried to open nestable modal without being inside context")) + export const ModalCloseContext = createContext<() => void>(() => {})