forked from Mirrors/gomuks
web/modal: add generic modal component
This commit is contained in:
parent
692cb323a5
commit
d18b7a43a1
8 changed files with 71 additions and 16 deletions
|
@ -18,9 +18,10 @@ import { ScaleLoader } from "react-spinners"
|
||||||
import Client from "./api/client.ts"
|
import Client from "./api/client.ts"
|
||||||
import WSClient from "./api/wsclient.ts"
|
import WSClient from "./api/wsclient.ts"
|
||||||
import { ClientContext } from "./ui/ClientContext.ts"
|
import { ClientContext } from "./ui/ClientContext.ts"
|
||||||
import { LightboxWrapper } from "./ui/Lightbox.tsx"
|
|
||||||
import MainScreen from "./ui/MainScreen.tsx"
|
import MainScreen from "./ui/MainScreen.tsx"
|
||||||
import { LoginScreen, VerificationScreen } from "./ui/login"
|
import { LoginScreen, VerificationScreen } from "./ui/login"
|
||||||
|
import { LightboxWrapper } from "./ui/modal/Lightbox.tsx"
|
||||||
|
import { ModalWrapper } from "./ui/modal/Modal.tsx"
|
||||||
import { useEventAsState } from "./util/eventdispatcher.ts"
|
import { useEventAsState } from "./util/eventdispatcher.ts"
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
@ -53,7 +54,9 @@ function App() {
|
||||||
} else {
|
} else {
|
||||||
return <ClientContext value={client}>
|
return <ClientContext value={client}>
|
||||||
<LightboxWrapper>
|
<LightboxWrapper>
|
||||||
<MainScreen/>
|
<ModalWrapper>
|
||||||
|
<MainScreen/>
|
||||||
|
</ModalWrapper>
|
||||||
</LightboxWrapper>
|
</LightboxWrapper>
|
||||||
</ClientContext>
|
</ClientContext>
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ import { use, useRef } from "react"
|
||||||
import { getAvatarURL } from "@/api/media.ts"
|
import { getAvatarURL } from "@/api/media.ts"
|
||||||
import { RoomStateStore } from "@/api/statestore"
|
import { RoomStateStore } from "@/api/statestore"
|
||||||
import { useNonNullEventAsState } from "@/util/eventdispatcher.ts"
|
import { useNonNullEventAsState } from "@/util/eventdispatcher.ts"
|
||||||
import { LightboxContext } from "./Lightbox.tsx"
|
|
||||||
import MessageComposer from "./composer/MessageComposer.tsx"
|
import MessageComposer from "./composer/MessageComposer.tsx"
|
||||||
|
import { LightboxContext } from "./modal/Lightbox.tsx"
|
||||||
import { RoomContext, RoomContextData } from "./roomcontext.ts"
|
import { RoomContext, RoomContextData } from "./roomcontext.ts"
|
||||||
import TimelineView from "./timeline/TimelineView.tsx"
|
import TimelineView from "./timeline/TimelineView.tsx"
|
||||||
import BackIcon from "@/icons/back.svg?react"
|
import BackIcon from "@/icons/back.svg?react"
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
div.overlay {
|
div.overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
&.lightbox > div.controls {
|
&.dimmed {
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.lightbox {
|
||||||
|
> div.controls {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: .5rem;
|
top: .5rem;
|
||||||
right: .5rem;
|
right: .5rem;
|
||||||
|
@ -29,7 +34,7 @@ div.overlay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.lightbox > img {
|
> img {
|
||||||
max-width: 75%;
|
max-width: 75%;
|
||||||
max-height: 75%;
|
max-height: 75%;
|
||||||
|
|
|
@ -14,12 +14,12 @@
|
||||||
// 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, { Component, RefObject, createContext, createRef, useCallback, useState } from "react"
|
import React, { Component, RefObject, createContext, createRef, useCallback, useState } from "react"
|
||||||
import CloseIcon from "../icons/close.svg?react"
|
import CloseIcon from "@/icons/close.svg?react"
|
||||||
import DownloadIcon from "../icons/download.svg?react"
|
import DownloadIcon from "@/icons/download.svg?react"
|
||||||
import RotateLeftIcon from "../icons/rotate-left.svg?react"
|
import RotateLeftIcon from "@/icons/rotate-left.svg?react"
|
||||||
import RotateRightIcon from "../icons/rotate-right.svg?react"
|
import RotateRightIcon from "@/icons/rotate-right.svg?react"
|
||||||
import ZoomInIcon from "../icons/zoom-in.svg?react"
|
import ZoomInIcon from "@/icons/zoom-in.svg?react"
|
||||||
import ZoomOutIcon from "../icons/zoom-out.svg?react"
|
import ZoomOutIcon from "@/icons/zoom-out.svg?react"
|
||||||
import "./Lightbox.css"
|
import "./Lightbox.css"
|
||||||
|
|
||||||
const isTouchDevice = window.ontouchstart !== undefined
|
const isTouchDevice = window.ontouchstart !== undefined
|
||||||
|
@ -158,7 +158,7 @@ export class Lightbox extends Component<LightboxProps> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div
|
return <div
|
||||||
className="overlay lightbox"
|
className="overlay dimmed lightbox"
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onMouseMove={isTouchDevice ? undefined : this.onMouseMove}
|
onMouseMove={isTouchDevice ? undefined : this.onMouseMove}
|
||||||
>
|
>
|
47
web/src/ui/modal/Modal.tsx
Normal file
47
web/src/ui/modal/Modal.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// gomuks - A Matrix client written in Go.
|
||||||
|
// Copyright (C) 2024 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// 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, createContext, useCallback, useState } from "react"
|
||||||
|
|
||||||
|
export interface ModalState {
|
||||||
|
content: JSX.Element
|
||||||
|
dimmed?: boolean
|
||||||
|
wrapperClass?: string
|
||||||
|
onClose?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type openModal = (state: ModalState) => void
|
||||||
|
|
||||||
|
export const ModalContext = createContext<openModal>(() =>
|
||||||
|
console.error("Tried to open modal without being inside context"))
|
||||||
|
|
||||||
|
export const ModalWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [state, setState] = useState<ModalState | null>(null)
|
||||||
|
const onClose = useCallback(() => {
|
||||||
|
setState(null)
|
||||||
|
state?.onClose?.()
|
||||||
|
}, [state])
|
||||||
|
return <>
|
||||||
|
<ModalContext value={setState}>
|
||||||
|
{children}
|
||||||
|
</ModalContext>
|
||||||
|
{state && <div
|
||||||
|
className={`overlay ${state.wrapperClass ?? "modal"} ${state.dimmed ? "dimmed" : ""}`}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{state.content}
|
||||||
|
</div>}
|
||||||
|
</>
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ import { useRoomState } from "@/api/statestore"
|
||||||
import { MemDBEvent, MemberEventContent, UnreadType } from "@/api/types"
|
import { MemDBEvent, MemberEventContent, UnreadType } from "@/api/types"
|
||||||
import { isEventID } from "@/util/validation.ts"
|
import { isEventID } from "@/util/validation.ts"
|
||||||
import { ClientContext } from "../ClientContext.ts"
|
import { ClientContext } from "../ClientContext.ts"
|
||||||
import { LightboxContext } from "../Lightbox.tsx"
|
import { LightboxContext } from "../modal/Lightbox.tsx"
|
||||||
import { useRoomContext } from "../roomcontext.ts"
|
import { useRoomContext } from "../roomcontext.ts"
|
||||||
import EventMenu from "./EventMenu.tsx"
|
import EventMenu from "./EventMenu.tsx"
|
||||||
import { ReplyIDBody } from "./ReplyBody.tsx"
|
import { ReplyIDBody } from "./ReplyBody.tsx"
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import React, { use } from "react"
|
import React, { use } from "react"
|
||||||
import { getAvatarURL } from "@/api/media.ts"
|
import { getAvatarURL } from "@/api/media.ts"
|
||||||
import { MemberEventContent, UserID } from "@/api/types"
|
import { MemberEventContent, UserID } from "@/api/types"
|
||||||
import { LightboxContext } from "../../Lightbox.tsx"
|
import { LightboxContext } from "../../modal/Lightbox.tsx"
|
||||||
import EventContentProps from "./props.ts"
|
import EventContentProps from "./props.ts"
|
||||||
|
|
||||||
function useChangeDescription(
|
function useChangeDescription(
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { CSSProperties, use } from "react"
|
||||||
import { getEncryptedMediaURL, getMediaURL } from "@/api/media.ts"
|
import { getEncryptedMediaURL, getMediaURL } from "@/api/media.ts"
|
||||||
import type { EventType, MediaMessageEventContent } from "@/api/types"
|
import type { EventType, MediaMessageEventContent } from "@/api/types"
|
||||||
import { ImageContainerSize, calculateMediaSize } from "@/util/mediasize.ts"
|
import { ImageContainerSize, calculateMediaSize } from "@/util/mediasize.ts"
|
||||||
import { LightboxContext } from "../../Lightbox.tsx"
|
import { LightboxContext } from "../../modal/Lightbox.tsx"
|
||||||
import DownloadIcon from "@/icons/download.svg?react"
|
import DownloadIcon from "@/icons/download.svg?react"
|
||||||
|
|
||||||
export const useMediaContent = (
|
export const useMediaContent = (
|
||||||
|
|
Loading…
Add table
Reference in a new issue