mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 10:33:41 -05:00
web/modal: allow two layers of modals
This commit is contained in:
parent
7c664c2700
commit
a9e459b448
3 changed files with 50 additions and 36 deletions
|
@ -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 = <main className={classNames.join(" ")} style={extraStyle}>
|
||||
<RoomList activeRoomID={activeRoom?.roomID ?? null} space={space}/>
|
||||
{resizeHandle1}
|
||||
{renderedRoom
|
||||
? renderedRoom instanceof RoomStateStore
|
||||
? <RoomView
|
||||
key={renderedRoom.roomID}
|
||||
room={renderedRoom}
|
||||
rightPanel={rightPanel}
|
||||
rightPanelResizeHandle={resizeHandle2}
|
||||
/>
|
||||
: <RoomPreview {...renderedRoom} />
|
||||
: rightPanel && <>
|
||||
<div className="room-view placeholder"/>
|
||||
{resizeHandle2}
|
||||
{rightPanel && <RightPanel {...rightPanel}/>}
|
||||
</>}
|
||||
</main>
|
||||
return <MainScreenContext value={context}>
|
||||
<ModalWrapper>
|
||||
<StylePreferences client={client} activeRoom={activeRealRoom}/>
|
||||
<main className={classNames.join(" ")} style={extraStyle}>
|
||||
<RoomList activeRoomID={activeRoom?.roomID ?? null} space={space}/>
|
||||
{resizeHandle1}
|
||||
{renderedRoom
|
||||
? renderedRoom instanceof RoomStateStore
|
||||
? <RoomView
|
||||
key={renderedRoom.roomID}
|
||||
room={renderedRoom}
|
||||
rightPanel={rightPanel}
|
||||
rightPanelResizeHandle={resizeHandle2}
|
||||
/>
|
||||
: <RoomPreview {...renderedRoom} />
|
||||
: rightPanel && <>
|
||||
<div className="room-view placeholder"/>
|
||||
{resizeHandle2}
|
||||
{rightPanel && <RightPanel {...rightPanel}/>}
|
||||
</>}
|
||||
</main>
|
||||
{syncLoader}
|
||||
<ModalWrapper ContextType={ModalContext} historyStateKey="modal">
|
||||
<ModalWrapper ContextType={NestableModalContext} historyStateKey="nestable_modal">
|
||||
<StylePreferences client={client} activeRoom={activeRealRoom}/>
|
||||
{mainContent}
|
||||
{syncLoader}
|
||||
</ModalWrapper>
|
||||
</ModalWrapper>
|
||||
</MainScreenContext>
|
||||
}
|
||||
|
|
|
@ -13,10 +13,16 @@
|
|||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
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<openModal>
|
||||
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<HTMLDivElement>) => {
|
||||
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<HTMLDivElement>(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 = <ModalCloseContext value={onClickWrapper}>{state.content}</ModalCloseContext>
|
||||
|
@ -85,10 +93,10 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
|
|||
modal = content
|
||||
}
|
||||
}
|
||||
return <ModalContext value={openModal}>
|
||||
return <ContextType value={openModal}>
|
||||
{children}
|
||||
{modal}
|
||||
</ModalContext>
|
||||
</ContextType>
|
||||
}
|
||||
|
||||
export default ModalWrapper
|
||||
|
|
|
@ -35,9 +35,12 @@ export interface ModalState {
|
|||
captureInput?: boolean
|
||||
}
|
||||
|
||||
type openModal = (state: ModalState) => void
|
||||
export type openModal = (state: ModalState) => void
|
||||
|
||||
export const ModalContext = createContext<openModal>(() =>
|
||||
console.error("Tried to open modal without being inside context"))
|
||||
|
||||
export const NestableModalContext = createContext<openModal>(() =>
|
||||
console.error("Tried to open nestable modal without being inside context"))
|
||||
|
||||
export const ModalCloseContext = createContext<() => void>(() => {})
|
||||
|
|
Loading…
Add table
Reference in a new issue