forked from Mirrors/gomuks
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 MainScreenContext, { MainScreenContextFields } from "./MainScreenContext.ts"
|
||||||
import StylePreferences from "./StylePreferences.tsx"
|
import StylePreferences from "./StylePreferences.tsx"
|
||||||
import Keybindings from "./keybindings.ts"
|
import Keybindings from "./keybindings.ts"
|
||||||
import { ModalWrapper } from "./modal"
|
import { ModalContext, ModalWrapper, NestableModalContext } from "./modal"
|
||||||
import RightPanel, { RightPanelProps } from "./rightpanel/RightPanel.tsx"
|
import RightPanel, { RightPanelProps } from "./rightpanel/RightPanel.tsx"
|
||||||
import RoomList from "./roomlist/RoomList.tsx"
|
import RoomList from "./roomlist/RoomList.tsx"
|
||||||
import RoomPreview, { RoomPreviewProps } from "./roomview/RoomPreview.tsx"
|
import RoomPreview, { RoomPreviewProps } from "./roomview/RoomPreview.tsx"
|
||||||
|
@ -422,28 +422,31 @@ const MainScreen = () => {
|
||||||
return () => clearTimeout(timeout)
|
return () => clearTimeout(timeout)
|
||||||
}
|
}
|
||||||
}, [activeRoom, prevActiveRoom])
|
}, [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}>
|
return <MainScreenContext value={context}>
|
||||||
<ModalWrapper>
|
<ModalWrapper ContextType={ModalContext} historyStateKey="modal">
|
||||||
<StylePreferences client={client} activeRoom={activeRealRoom}/>
|
<ModalWrapper ContextType={NestableModalContext} historyStateKey="nestable_modal">
|
||||||
<main className={classNames.join(" ")} style={extraStyle}>
|
<StylePreferences client={client} activeRoom={activeRealRoom}/>
|
||||||
<RoomList activeRoomID={activeRoom?.roomID ?? null} space={space}/>
|
{mainContent}
|
||||||
{resizeHandle1}
|
{syncLoader}
|
||||||
{renderedRoom
|
</ModalWrapper>
|
||||||
? 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>
|
</ModalWrapper>
|
||||||
</MainScreenContext>
|
</MainScreenContext>
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,16 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import React, { JSX, useCallback, useEffect, useLayoutEffect, useReducer, useRef } from "react"
|
import React, { Context, JSX, useCallback, useEffect, useLayoutEffect, useReducer, useRef } from "react"
|
||||||
import { ModalCloseContext, ModalContext, ModalState } from "./contexts.ts"
|
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) => {
|
const [state, setState] = useReducer((prevState: ModalState | null, newState: ModalState | null) => {
|
||||||
prevState?.onClose?.()
|
prevState?.onClose?.()
|
||||||
return newState
|
return newState
|
||||||
|
@ -25,26 +31,28 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
if (evt && evt.target !== evt.currentTarget) {
|
if (evt && evt.target !== evt.currentTarget) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.log("MEOW", evt)
|
||||||
|
evt?.stopPropagation()
|
||||||
setState(null)
|
setState(null)
|
||||||
if (history.state?.modal) {
|
if (history.state?.[historyStateKey]) {
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [historyStateKey])
|
||||||
const onKeyWrapper = (evt: React.KeyboardEvent<HTMLDivElement>) => {
|
const onKeyWrapper = (evt: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (evt.key === "Escape") {
|
if (evt.key === "Escape") {
|
||||||
setState(null)
|
setState(null)
|
||||||
if (history.state?.modal) {
|
if (history.state?.[historyStateKey]) {
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
}
|
}
|
||||||
const openModal = useCallback((newState: ModalState) => {
|
const openModal = useCallback((newState: ModalState) => {
|
||||||
if (!history.state?.modal && newState.captureInput !== false) {
|
if (!history.state?.[historyStateKey] && newState.captureInput !== false) {
|
||||||
history.pushState({ ...(history.state ?? {}), modal: true }, "")
|
history.pushState({ ...(history.state ?? {}), [historyStateKey]: true }, "")
|
||||||
}
|
}
|
||||||
setState(newState)
|
setState(newState)
|
||||||
}, [])
|
}, [historyStateKey])
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null)
|
const wrapperRef = useRef<HTMLDivElement>(null)
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (wrapperRef.current && (!document.activeElement || !wrapperRef.current.contains(document.activeElement))) {
|
if (wrapperRef.current && (!document.activeElement || !wrapperRef.current.contains(document.activeElement))) {
|
||||||
|
@ -54,13 +62,13 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.closeModal = onClickWrapper
|
window.closeModal = onClickWrapper
|
||||||
const listener = (evt: PopStateEvent) => {
|
const listener = (evt: PopStateEvent) => {
|
||||||
if (!evt.state?.modal) {
|
if (!evt.state?.[historyStateKey]) {
|
||||||
setState(null)
|
setState(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.addEventListener("popstate", listener)
|
window.addEventListener("popstate", listener)
|
||||||
return () => window.removeEventListener("popstate", listener)
|
return () => window.removeEventListener("popstate", listener)
|
||||||
}, [onClickWrapper])
|
}, [historyStateKey, onClickWrapper])
|
||||||
let modal: JSX.Element | null = null
|
let modal: JSX.Element | null = null
|
||||||
if (state) {
|
if (state) {
|
||||||
let content = <ModalCloseContext value={onClickWrapper}>{state.content}</ModalCloseContext>
|
let content = <ModalCloseContext value={onClickWrapper}>{state.content}</ModalCloseContext>
|
||||||
|
@ -85,10 +93,10 @@ const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
modal = content
|
modal = content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return <ModalContext value={openModal}>
|
return <ContextType value={openModal}>
|
||||||
{children}
|
{children}
|
||||||
{modal}
|
{modal}
|
||||||
</ModalContext>
|
</ContextType>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ModalWrapper
|
export default ModalWrapper
|
||||||
|
|
|
@ -35,9 +35,12 @@ export interface ModalState {
|
||||||
captureInput?: boolean
|
captureInput?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type openModal = (state: ModalState) => void
|
export type openModal = (state: ModalState) => void
|
||||||
|
|
||||||
export const ModalContext = createContext<openModal>(() =>
|
export const ModalContext = createContext<openModal>(() =>
|
||||||
console.error("Tried to open modal without being inside context"))
|
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>(() => {})
|
export const ModalCloseContext = createContext<() => void>(() => {})
|
||||||
|
|
Loading…
Add table
Reference in a new issue