forked from Mirrors/gomuks
parent
5aacc3424d
commit
aeabda449d
8 changed files with 361 additions and 14 deletions
|
@ -18,7 +18,7 @@ import { RoomListEntry, RoomStateStore, useAccountData } from "@/api/statestore"
|
||||||
import { RoomID } from "@/api/types"
|
import { RoomID } from "@/api/types"
|
||||||
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
||||||
import ClientContext from "../ClientContext.ts"
|
import ClientContext from "../ClientContext.ts"
|
||||||
import { ModalCloseContext, ModalContext } from "../modal"
|
import { ModalCloseContext } from "../modal"
|
||||||
import SettingsView from "../settings/SettingsView.tsx"
|
import SettingsView from "../settings/SettingsView.tsx"
|
||||||
import DoorOpenIcon from "@/icons/door-open.svg?react"
|
import DoorOpenIcon from "@/icons/door-open.svg?react"
|
||||||
import MarkReadIcon from "@/icons/mark-read.svg?react"
|
import MarkReadIcon from "@/icons/mark-read.svg?react"
|
||||||
|
@ -91,11 +91,11 @@ const MarkReadButton = ({ room }: { room: RoomStateStore }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RoomMenu = ({ room, style }: RoomMenuProps) => {
|
export const RoomMenu = ({ room, style }: RoomMenuProps) => {
|
||||||
const openModal = use(ModalContext)
|
|
||||||
const closeModal = use(ModalCloseContext)
|
const closeModal = use(ModalCloseContext)
|
||||||
const client = use(ClientContext)!
|
const client = use(ClientContext)!
|
||||||
const openSettings = () => {
|
const openSettings = () => {
|
||||||
openModal({
|
closeModal()
|
||||||
|
window.openNestableModal({
|
||||||
dimmed: true,
|
dimmed: true,
|
||||||
boxed: true,
|
boxed: true,
|
||||||
innerBoxClass: "settings-view",
|
innerBoxClass: "settings-view",
|
||||||
|
|
|
@ -97,6 +97,9 @@ const ModalWrapper = ({ children, ContextType, historyStateKey }: ModalWrapperPr
|
||||||
modal = content
|
modal = content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (historyStateKey === "nestable_modal") {
|
||||||
|
window.openNestableModal = openModal
|
||||||
|
}
|
||||||
return <ContextType value={openModal}>
|
return <ContextType value={openModal}>
|
||||||
{children}
|
{children}
|
||||||
{modal}
|
{modal}
|
||||||
|
|
|
@ -18,8 +18,7 @@ import { getRoomAvatarThumbnailURL, getRoomAvatarURL } from "@/api/media.ts"
|
||||||
import { RoomStateStore } from "@/api/statestore"
|
import { RoomStateStore } from "@/api/statestore"
|
||||||
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
||||||
import MainScreenContext from "../MainScreenContext.ts"
|
import MainScreenContext from "../MainScreenContext.ts"
|
||||||
import { LightboxContext } from "../modal"
|
import { LightboxContext, NestableModalContext } from "../modal"
|
||||||
import { ModalContext } from "../modal"
|
|
||||||
import SettingsView from "../settings/SettingsView.tsx"
|
import SettingsView from "../settings/SettingsView.tsx"
|
||||||
import BackIcon from "@/icons/back.svg?react"
|
import BackIcon from "@/icons/back.svg?react"
|
||||||
import PeopleIcon from "@/icons/group.svg?react"
|
import PeopleIcon from "@/icons/group.svg?react"
|
||||||
|
@ -34,7 +33,7 @@ interface RoomViewHeaderProps {
|
||||||
const RoomViewHeader = ({ room }: RoomViewHeaderProps) => {
|
const RoomViewHeader = ({ room }: RoomViewHeaderProps) => {
|
||||||
const roomMeta = useEventAsState(room.meta)
|
const roomMeta = useEventAsState(room.meta)
|
||||||
const mainScreen = use(MainScreenContext)
|
const mainScreen = use(MainScreenContext)
|
||||||
const openModal = use(ModalContext)
|
const openModal = use(NestableModalContext)
|
||||||
const openSettings = () => {
|
const openSettings = () => {
|
||||||
openModal({
|
openModal({
|
||||||
dimmed: true,
|
dimmed: true,
|
||||||
|
|
78
web/src/ui/settings/RoomStateExplorer.css
Normal file
78
web/src/ui/settings/RoomStateExplorer.css
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
div.state-explorer-box {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.state-explorer {
|
||||||
|
width: min(50rem, 80vw);
|
||||||
|
max-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
div.state-button-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5rem;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
padding: .5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div.nav-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5rem;
|
||||||
|
margin-top: .5rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.state-event-view {
|
||||||
|
> div.state-event-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
> textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: .5rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
resize: vertical;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
outline: none;
|
||||||
|
border-radius: .5rem;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div.state-header > div.new-event-type {
|
||||||
|
display: flex;
|
||||||
|
gap: .25rem;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
|
||||||
|
> input {
|
||||||
|
flex: 1;
|
||||||
|
padding: .5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: .5rem;
|
||||||
|
outline: none;
|
||||||
|
font-family: var(--monospace-font-stack);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
246
web/src/ui/settings/RoomStateExplorer.tsx
Normal file
246
web/src/ui/settings/RoomStateExplorer.tsx
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
// gomuks - A Matrix client written in Go.
|
||||||
|
// Copyright (C) 2025 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 { use, useCallback, useState } from "react"
|
||||||
|
import { RoomStateStore, useRoomState } from "@/api/statestore"
|
||||||
|
import ClientContext from "../ClientContext.ts"
|
||||||
|
import JSONView from "../util/JSONView"
|
||||||
|
import "./RoomStateExplorer.css"
|
||||||
|
|
||||||
|
interface StateExplorerProps {
|
||||||
|
room: RoomStateStore
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateEventViewProps {
|
||||||
|
room: RoomStateStore
|
||||||
|
type?: string
|
||||||
|
stateKey?: string
|
||||||
|
onBack: () => void
|
||||||
|
onDone?: (type: string, stateKey: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateKeyListProps {
|
||||||
|
room: RoomStateStore
|
||||||
|
type: string
|
||||||
|
onSelectStateKey: (stateKey: string) => void
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const StateEventView = ({ room, type, stateKey, onBack, onDone }: StateEventViewProps) => {
|
||||||
|
const event = useRoomState(room, type, stateKey)
|
||||||
|
const isNewEvent = type === undefined
|
||||||
|
const [editingContent, setEditingContent] = useState<string | null>(isNewEvent ? "{\n\n}" : null)
|
||||||
|
const [newType, setNewType] = useState<string>("")
|
||||||
|
const [newStateKey, setNewStateKey] = useState<string>("")
|
||||||
|
const client = use(ClientContext)!
|
||||||
|
|
||||||
|
const sendEdit = () => {
|
||||||
|
let parsedContent
|
||||||
|
try {
|
||||||
|
parsedContent = JSON.parse(editingContent || "{}")
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(`Failed to parse JSON: ${err}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.rpc.setState(
|
||||||
|
room.roomID,
|
||||||
|
type ?? newType,
|
||||||
|
stateKey ?? newStateKey,
|
||||||
|
parsedContent,
|
||||||
|
).then(
|
||||||
|
() => {
|
||||||
|
console.log("Updated room state", room.roomID, type, stateKey)
|
||||||
|
setEditingContent(null)
|
||||||
|
if (isNewEvent) {
|
||||||
|
onDone?.(newType, newStateKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
console.error("Failed to update room state", err)
|
||||||
|
window.alert(`Failed to update room state: ${err}`)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const stopEdit = () => setEditingContent(null)
|
||||||
|
const startEdit = () => setEditingContent(JSON.stringify(event?.content || {}, null, 4))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="state-explorer state-event-view">
|
||||||
|
<div className="state-header">
|
||||||
|
{isNewEvent
|
||||||
|
? <>
|
||||||
|
<h3>New state event</h3>
|
||||||
|
<div className="new-event-type">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newType}
|
||||||
|
onChange={evt => setNewType(evt.target.value)}
|
||||||
|
placeholder="Event type"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newStateKey}
|
||||||
|
onChange={evt => setNewStateKey(evt.target.value)}
|
||||||
|
placeholder="State key"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
: <h3><code>{type}</code> ({stateKey ? <code>{stateKey}</code> : "no state key"})</h3>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={`state-event-content`}>
|
||||||
|
{editingContent !== null
|
||||||
|
? <textarea rows={10} value={editingContent} onChange={evt => setEditingContent(evt.target.value)}/>
|
||||||
|
: <JSONView data={event}/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="nav-buttons">
|
||||||
|
{editingContent !== null ? <>
|
||||||
|
<button onClick={isNewEvent ? onBack : stopEdit}>Back</button>
|
||||||
|
<button onClick={sendEdit}>Send</button>
|
||||||
|
</> : <>
|
||||||
|
<button onClick={onBack}>Back</button>
|
||||||
|
<button onClick={startEdit}>Edit</button>
|
||||||
|
</>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const StateKeyList = ({ room, type, onSelectStateKey, onBack }: StateKeyListProps) => {
|
||||||
|
const stateMap = room.state.get(type)
|
||||||
|
return (
|
||||||
|
<div className="state-explorer state-key-list">
|
||||||
|
<div className="state-header">
|
||||||
|
<h3>State keys under <code>{type}</code></h3>
|
||||||
|
</div>
|
||||||
|
<div className="state-button-list">
|
||||||
|
{Array.from(stateMap?.keys().map(stateKey => (
|
||||||
|
<button key={stateKey} onClick={() => onSelectStateKey(stateKey)}>
|
||||||
|
{stateKey ? <code>{stateKey}</code> : "<empty>"}
|
||||||
|
</button>
|
||||||
|
)) ?? [])}
|
||||||
|
</div>
|
||||||
|
<div className="nav-buttons">
|
||||||
|
<button onClick={onBack}>Back</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StateExplorer = ({ room }: StateExplorerProps) => {
|
||||||
|
const [creatingNew, setCreatingNew] = useState(false)
|
||||||
|
const [selectedType, setSelectedType] = useState<string | null>(null)
|
||||||
|
const [selectedStateKey, setSelectedStateKey] = useState<string | null>(null)
|
||||||
|
const [loadingState, setLoadingState] = useState(false)
|
||||||
|
const client = use(ClientContext)!
|
||||||
|
|
||||||
|
const handleTypeSelect = (type: string) => {
|
||||||
|
const stateKeysMap = room.state.get(type)
|
||||||
|
if (!stateKeysMap) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateKeys = Array.from(stateKeysMap.keys())
|
||||||
|
if (stateKeys.length === 1 && stateKeys[0] === "") {
|
||||||
|
// If there's only one state event with an empty key, view it directly
|
||||||
|
setSelectedType(type)
|
||||||
|
setSelectedStateKey("")
|
||||||
|
} else {
|
||||||
|
// Otherwise show the list of state keys
|
||||||
|
setSelectedType(type)
|
||||||
|
setSelectedStateKey(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBack = useCallback(() => {
|
||||||
|
if (creatingNew) {
|
||||||
|
setCreatingNew(false)
|
||||||
|
} else if (selectedStateKey !== null && selectedType !== null) {
|
||||||
|
setSelectedStateKey(null)
|
||||||
|
const stateKeysMap = room.state.get(selectedType)
|
||||||
|
if (stateKeysMap?.size === 1 && stateKeysMap.has("")) {
|
||||||
|
setSelectedType(null)
|
||||||
|
}
|
||||||
|
} else if (selectedType !== null) {
|
||||||
|
setSelectedType(null)
|
||||||
|
}
|
||||||
|
}, [selectedType, selectedStateKey, creatingNew, room])
|
||||||
|
const handleNewEventDone = useCallback((type: string, stateKey: string) => {
|
||||||
|
setCreatingNew(false)
|
||||||
|
setSelectedType(type)
|
||||||
|
setSelectedStateKey(stateKey)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (creatingNew) {
|
||||||
|
return <StateEventView
|
||||||
|
room={room}
|
||||||
|
onBack={handleBack}
|
||||||
|
onDone={handleNewEventDone}
|
||||||
|
/>
|
||||||
|
} else if (selectedType !== null && selectedStateKey !== null) {
|
||||||
|
return <StateEventView
|
||||||
|
room={room}
|
||||||
|
type={selectedType}
|
||||||
|
stateKey={selectedStateKey}
|
||||||
|
onBack={handleBack}
|
||||||
|
/>
|
||||||
|
} else if (selectedType !== null) {
|
||||||
|
return <StateKeyList
|
||||||
|
room={room}
|
||||||
|
type={selectedType}
|
||||||
|
onSelectStateKey={setSelectedStateKey}
|
||||||
|
onBack={handleBack}
|
||||||
|
/>
|
||||||
|
} else {
|
||||||
|
const loadRoomState = () => {
|
||||||
|
setLoadingState(true)
|
||||||
|
client.loadRoomState(room.roomID, {
|
||||||
|
omitMembers: false,
|
||||||
|
refetch: room.stateLoaded && room.fullMembersLoaded,
|
||||||
|
}).then(
|
||||||
|
() => {
|
||||||
|
console.log("Room state loaded from devtools", room.roomID)
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
console.error("Failed to fetch room state", err)
|
||||||
|
window.alert(`Failed to fetch room state: ${err}`)
|
||||||
|
},
|
||||||
|
).finally(() => setLoadingState(false))
|
||||||
|
}
|
||||||
|
return <div className="state-explorer">
|
||||||
|
<h3>Room State Explorer</h3>
|
||||||
|
<div className="state-button-list">
|
||||||
|
{Array.from(room.state?.keys().map(type => (
|
||||||
|
<button key={type} onClick={() => handleTypeSelect(type)}>
|
||||||
|
<code>{type}</code>
|
||||||
|
</button>
|
||||||
|
)) ?? [])}
|
||||||
|
</div>
|
||||||
|
<div className="nav-buttons">
|
||||||
|
<button onClick={loadRoomState} disabled={loadingState}>
|
||||||
|
{room.stateLoaded
|
||||||
|
? room.fullMembersLoaded
|
||||||
|
? "Resync full room state"
|
||||||
|
: "Load room members"
|
||||||
|
: "Load room state and members"}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setCreatingNew(true)}>Send new state event</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StateExplorer
|
|
@ -14,15 +14,22 @@ div.settings-view {
|
||||||
text-wrap: nowrap;
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.leave-room {
|
div.room-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: .5rem;
|
||||||
|
|
||||||
|
button {
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
|
|
||||||
|
&.leave-room {
|
||||||
&:hover, &:focus {
|
&:hover, &:focus {
|
||||||
background-color: var(--error-color);
|
background-color: var(--error-color);
|
||||||
color: var(--inverted-text-color);
|
color: var(--inverted-text-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
width: min(60rem, 80vw);
|
width: min(60rem, 80vw);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -29,10 +29,10 @@ import {
|
||||||
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
||||||
import useEvent from "@/util/useEvent.ts"
|
import useEvent from "@/util/useEvent.ts"
|
||||||
import ClientContext from "../ClientContext.ts"
|
import ClientContext from "../ClientContext.ts"
|
||||||
import { LightboxContext } from "../modal"
|
import { LightboxContext, ModalCloseContext, ModalContext } from "../modal"
|
||||||
import { ModalCloseContext } from "../modal"
|
|
||||||
import JSONView from "../util/JSONView.tsx"
|
import JSONView from "../util/JSONView.tsx"
|
||||||
import Toggle from "../util/Toggle.tsx"
|
import Toggle from "../util/Toggle.tsx"
|
||||||
|
import RoomStateExplorer from "./RoomStateExplorer.tsx"
|
||||||
import CloseIcon from "@/icons/close.svg?react"
|
import CloseIcon from "@/icons/close.svg?react"
|
||||||
import "./SettingsView.css"
|
import "./SettingsView.css"
|
||||||
|
|
||||||
|
@ -331,6 +331,7 @@ const SettingsView = ({ room }: SettingsViewProps) => {
|
||||||
const roomMeta = useEventAsState(room.meta)
|
const roomMeta = useEventAsState(room.meta)
|
||||||
const client = use(ClientContext)!
|
const client = use(ClientContext)!
|
||||||
const closeModal = use(ModalCloseContext)
|
const closeModal = use(ModalCloseContext)
|
||||||
|
const openModal = use(ModalContext)
|
||||||
const setPref = useCallback((
|
const setPref = useCallback((
|
||||||
context: PreferenceContext, key: keyof Preferences, value: PreferenceValueType | undefined,
|
context: PreferenceContext, key: keyof Preferences, value: PreferenceValueType | undefined,
|
||||||
) => {
|
) => {
|
||||||
|
@ -377,6 +378,14 @@ const SettingsView = ({ room }: SettingsViewProps) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const openDevtools = () => {
|
||||||
|
openModal({
|
||||||
|
dimmed: true,
|
||||||
|
boxed: true,
|
||||||
|
innerBoxClass: "state-explorer-box",
|
||||||
|
content: <RoomStateExplorer room={room} />,
|
||||||
|
})
|
||||||
|
}
|
||||||
const onClickOpenCSSApp = () => {
|
const onClickOpenCSSApp = () => {
|
||||||
client.rpc.requestOpenIDToken().then(
|
client.rpc.requestOpenIDToken().then(
|
||||||
resp => window.open(
|
resp => window.open(
|
||||||
|
@ -407,7 +416,10 @@ const SettingsView = ({ room }: SettingsViewProps) => {
|
||||||
{roomMeta.name && <div className="room-name">{roomMeta.name}</div>}
|
{roomMeta.name && <div className="room-name">{roomMeta.name}</div>}
|
||||||
<code>{room.roomID}</code>
|
<code>{room.roomID}</code>
|
||||||
<div>{roomMeta.topic}</div>
|
<div>{roomMeta.topic}</div>
|
||||||
|
<div className="room-buttons">
|
||||||
<button className="leave-room" onClick={onClickLeave}>Leave room</button>
|
<button className="leave-room" onClick={onClickLeave}>Leave room</button>
|
||||||
|
<button className="devtools" onClick={openDevtools}>Explore room state</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
|
|
2
web/src/vite-env.d.ts
vendored
2
web/src/vite-env.d.ts
vendored
|
@ -4,6 +4,7 @@
|
||||||
import type Client from "@/api/client.ts"
|
import type Client from "@/api/client.ts"
|
||||||
import type { GCSettings, RoomStateStore } from "@/api/statestore"
|
import type { GCSettings, RoomStateStore } from "@/api/statestore"
|
||||||
import type { MainScreenContextFields } from "@/ui/MainScreenContext.ts"
|
import type { MainScreenContextFields } from "@/ui/MainScreenContext.ts"
|
||||||
|
import type { openModal } from "@/ui/modal/contexts.ts"
|
||||||
import type { RoomContextData } from "@/ui/roomview/roomcontext.ts"
|
import type { RoomContextData } from "@/ui/roomview/roomcontext.ts"
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -16,6 +17,7 @@ declare global {
|
||||||
gcSettings: GCSettings
|
gcSettings: GCSettings
|
||||||
hackyOpenEventContextMenu?: string
|
hackyOpenEventContextMenu?: string
|
||||||
closeModal: () => void
|
closeModal: () => void
|
||||||
|
openNestableModal: openModal
|
||||||
gomuksAndroid?: true
|
gomuksAndroid?: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue