From 1a2833ed53c42a8c6a326bd52b3c735e3c682c5f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 4 Dec 2024 01:22:16 +0200 Subject: [PATCH] web/main: add support for back button --- web/src/api/statestore/main.ts | 4 ++-- web/src/ui/MainScreen.tsx | 17 +++++++++++++++-- web/src/ui/modal/Lightbox.tsx | 22 +++++++++++++++++++--- web/src/ui/modal/Modal.tsx | 21 ++++++++++++++++++++- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/web/src/api/statestore/main.ts b/web/src/api/statestore/main.ts index fc77e6a..52bea6a 100644 --- a/web/src/api/statestore/main.ts +++ b/web/src/api/statestore/main.ts @@ -81,7 +81,7 @@ export class StateStore { readonly localPreferenceCache: Preferences = getLocalStoragePreferences("global_prefs", this.preferenceSub.notify) serverPreferenceCache: Preferences = {} switchRoom?: (roomID: RoomID | null) => void - activeRoomID?: RoomID + activeRoomID: RoomID | null = null imageAuthToken?: string getFilteredRoomList(): RoomListEntry[] { @@ -397,6 +397,6 @@ export class StateStore { this.#watchedRoomEmojiPacks = null this.#personalEmojiPack = null this.serverPreferenceCache = {} - this.activeRoomID = undefined + this.activeRoomID = null } } diff --git a/web/src/ui/MainScreen.tsx b/web/src/ui/MainScreen.tsx index 8272bb5..5434682 100644 --- a/web/src/ui/MainScreen.tsx +++ b/web/src/ui/MainScreen.tsx @@ -62,13 +62,13 @@ class ContextFields implements MainScreenContextFields { client.store.switchRoom = this.setActiveRoom } - setActiveRoom = (roomID: RoomID | null) => { + setActiveRoom = (roomID: RoomID | null, pushState = true) => { console.log("Switching to room", roomID) const room = (roomID && this.client.store.rooms.get(roomID)) || null window.activeRoom = room this.directSetActiveRoom(room) this.setRightPanel(null) - this.client.store.activeRoomID = room?.roomID + this.client.store.activeRoomID = room?.roomID ?? null this.keybindings.activeRoom = room if (room) { room.lastOpened = Date.now() @@ -80,6 +80,9 @@ class ContextFields implements MainScreenContextFields { .querySelector(`div.room-entry[data-room-id="${CSS.escape(room.roomID)}"]`) ?.scrollIntoView({ block: "nearest" }) } + if (pushState) { + history.pushState({ room_id: roomID }, "") + } } clickRoom = (evt: React.MouseEvent) => { @@ -120,6 +123,16 @@ const MainScreen = () => { useLayoutEffect(() => { window.mainScreenContext = context }, [context]) + useEffect(() => { + const listener = (evt: PopStateEvent) => { + const roomID = evt.state?.room_id ?? null + if (roomID !== client.store.activeRoomID) { + context.setActiveRoom(roomID, false) + } + } + window.addEventListener("popstate", listener) + return () => window.removeEventListener("popstate", listener) + }, [context, client]) useEffect(() => context.keybindings.listen(), [context]) useInsertionEffect(() => { const styleTags = document.createElement("style") diff --git a/web/src/ui/modal/Lightbox.tsx b/web/src/ui/modal/Lightbox.tsx index 82384fc..a6d9164 100644 --- a/web/src/ui/modal/Lightbox.tsx +++ b/web/src/ui/modal/Lightbox.tsx @@ -44,18 +44,34 @@ export const LightboxWrapper = ({ children }: { children: React.ReactNode }) => if (!target.src) { return } - setParams({ + params = { src: target.src, alt: target.alt, - }) + } + setParams(params) } else { setParams(params as LightboxParams) } + history.pushState({ ...(history.state ?? {}), lightbox: params }, "") }, []) useLayoutEffect(() => { window.openLightbox = onOpen + const listener = (evt: PopStateEvent) => { + if (evt.state?.lightbox) { + setParams(evt.state.lightbox) + } else { + setParams(null) + } + } + window.addEventListener("popstate", listener) + return () => window.removeEventListener("popstate", listener) }, [onOpen]) - const onClose = useCallback(() => setParams(null), []) + const onClose = useCallback(() => { + setParams(null) + if (params?.src && history.state?.lightbox?.src === params?.src) { + history.back() + } + }, [params]) return <> {children} diff --git a/web/src/ui/modal/Modal.tsx b/web/src/ui/modal/Modal.tsx index 5a1df7e..52ee783 100644 --- a/web/src/ui/modal/Modal.tsx +++ b/web/src/ui/modal/Modal.tsx @@ -41,18 +41,37 @@ export const ModalWrapper = ({ children }: { children: React.ReactNode }) => { return } setState(null) + if (history.state?.modal) { + history.back() + } }, []) const onKeyWrapper = useCallback((evt: React.KeyboardEvent) => { if (evt.key === "Escape") { setState(null) + if (history.state?.modal) { + history.back() + } } evt.stopPropagation() }, []) + const openModal = useCallback((newState: ModalState) => { + if (!history.state?.modal) { + history.pushState({ ...(history.state ?? {}), modal: true }, "") + } + setState(newState) + }, []) const wrapperRef = useRef(null) useLayoutEffect(() => { if (wrapperRef.current && (!document.activeElement || !wrapperRef.current.contains(document.activeElement))) { wrapperRef.current.focus() } + const listener = (evt: PopStateEvent) => { + if (!evt.state?.modal) { + setState(null) + } + } + window.addEventListener("popstate", listener) + return () => window.removeEventListener("popstate", listener) }, [state]) let modal: JSX.Element | null = null if (state) { @@ -74,7 +93,7 @@ export const ModalWrapper = ({ children }: { children: React.ReactNode }) => { {content} } - return + return {children} {modal}