forked from Mirrors/gomuks
web/roomlist: restore open space when using browser history
This commit is contained in:
parent
59e1b760d6
commit
8b7d0fe6b6
6 changed files with 61 additions and 23 deletions
|
@ -1,3 +1,4 @@
|
||||||
export * from "./main.ts"
|
export * from "./main.ts"
|
||||||
export * from "./room.ts"
|
export * from "./room.ts"
|
||||||
export * from "./hooks.ts"
|
export * from "./hooks.ts"
|
||||||
|
export * from "./space.ts"
|
||||||
|
|
|
@ -111,6 +111,23 @@ export class StateStore {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSpaceByID(spaceID: string | undefined): RoomListFilter | null {
|
||||||
|
if (!spaceID) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const realSpace = this.spaceEdges.get(spaceID)
|
||||||
|
if (realSpace) {
|
||||||
|
return realSpace
|
||||||
|
}
|
||||||
|
for (const pseudoSpace of this.pseudoSpaces) {
|
||||||
|
if (pseudoSpace.id === spaceID) {
|
||||||
|
return pseudoSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.warn("Failed to find space", spaceID)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
get roomListFilterFunc(): ((entry: RoomListEntry) => boolean) | null {
|
get roomListFilterFunc(): ((entry: RoomListEntry) => boolean) | null {
|
||||||
if (!this.currentRoomListFilter && !this.currentRoomListQuery) {
|
if (!this.currentRoomListFilter && !this.currentRoomListQuery) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import { JSX, use, useEffect, useMemo, useReducer, useRef, useState } from "react"
|
import { JSX, use, useEffect, useMemo, useReducer, useRef, useState } from "react"
|
||||||
import { SyncLoader } from "react-spinners"
|
import { SyncLoader } from "react-spinners"
|
||||||
import Client from "@/api/client.ts"
|
import Client from "@/api/client.ts"
|
||||||
import { RoomStateStore } from "@/api/statestore"
|
import { RoomListFilter, RoomStateStore } from "@/api/statestore"
|
||||||
import type { RoomID } from "@/api/types"
|
import type { RoomID } from "@/api/types"
|
||||||
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
||||||
import { ensureString, ensureStringArray, parseMatrixURI } from "@/util/validation.ts"
|
import { ensureString, ensureStringArray, parseMatrixURI } from "@/util/validation.ts"
|
||||||
|
@ -52,6 +52,7 @@ class ContextFields implements MainScreenContextFields {
|
||||||
constructor(
|
constructor(
|
||||||
private directSetRightPanel: (props: RightPanelProps | null) => void,
|
private directSetRightPanel: (props: RightPanelProps | null) => void,
|
||||||
private directSetActiveRoom: (room: RoomStateStore | RoomPreviewProps | null) => void,
|
private directSetActiveRoom: (room: RoomStateStore | RoomPreviewProps | null) => void,
|
||||||
|
private directSetSpace: (space: RoomListFilter | null) => void,
|
||||||
private client: Client,
|
private client: Client,
|
||||||
) {
|
) {
|
||||||
this.keybindings = new Keybindings(client.store, this)
|
this.keybindings = new Keybindings(client.store, this)
|
||||||
|
@ -109,6 +110,24 @@ class ContextFields implements MainScreenContextFields {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSpace = (space: RoomListFilter | null, pushState = true) => {
|
||||||
|
if (space === this.client.store.currentRoomListFilter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log("Switching to space", space?.id)
|
||||||
|
this.directSetSpace(space)
|
||||||
|
this.client.store.currentRoomListFilter = space
|
||||||
|
if (pushState) {
|
||||||
|
if (this.client.store.activeRoomID && space) {
|
||||||
|
const entry = this.client.store.roomListEntries.get(this.client.store.activeRoomID)
|
||||||
|
if (entry && !space.include(entry)) {
|
||||||
|
this.setActiveRoom(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
history.replaceState({ ...(history.state || {}), space_id: space?.id }, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#setPreviewRoom(roomID: RoomID, pushState: boolean, meta?: Partial<RoomPreviewProps>) {
|
#setPreviewRoom(roomID: RoomID, pushState: boolean, meta?: Partial<RoomPreviewProps>) {
|
||||||
const invite = this.client.store.inviteRooms.get(roomID)
|
const invite = this.client.store.inviteRooms.get(roomID)
|
||||||
this.#closeActiveRoom(false)
|
this.#closeActiveRoom(false)
|
||||||
|
@ -120,6 +139,7 @@ class ContextFields implements MainScreenContextFields {
|
||||||
room_id: roomID,
|
room_id: roomID,
|
||||||
source_via: meta?.via,
|
source_via: meta?.via,
|
||||||
source_alias: meta?.alias,
|
source_alias: meta?.alias,
|
||||||
|
space_id: history.state.space_id,
|
||||||
}, "")
|
}, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +168,7 @@ class ContextFields implements MainScreenContextFields {
|
||||||
.querySelector(`div.room-entry[data-room-id="${CSS.escape(room.roomID)}"]`)
|
.querySelector(`div.room-entry[data-room-id="${CSS.escape(room.roomID)}"]`)
|
||||||
?.scrollIntoView({ block: "nearest" })
|
?.scrollIntoView({ block: "nearest" })
|
||||||
if (pushState) {
|
if (pushState) {
|
||||||
history.pushState({ room_id: room.roomID }, "")
|
history.pushState({ room_id: room.roomID, space_id: history.state.space_id }, "")
|
||||||
}
|
}
|
||||||
let roomNameForTitle = room.meta.current.name
|
let roomNameForTitle = room.meta.current.name
|
||||||
if (roomNameForTitle && roomNameForTitle.length > 48) {
|
if (roomNameForTitle && roomNameForTitle.length > 48) {
|
||||||
|
@ -166,7 +186,7 @@ class ContextFields implements MainScreenContextFields {
|
||||||
this.client.store.activeRoomIsPreview = false
|
this.client.store.activeRoomIsPreview = false
|
||||||
this.keybindings.activeRoom = null
|
this.keybindings.activeRoom = null
|
||||||
if (pushState) {
|
if (pushState) {
|
||||||
history.pushState({}, "")
|
history.pushState({ space_id: history.state.space_id }, "")
|
||||||
}
|
}
|
||||||
document.title = this.#getWindowTitle()
|
document.title = this.#getWindowTitle()
|
||||||
}
|
}
|
||||||
|
@ -279,12 +299,13 @@ const activeRoomReducer = (
|
||||||
|
|
||||||
const MainScreen = () => {
|
const MainScreen = () => {
|
||||||
const [[prevActiveRoom, activeRoom], directSetActiveRoom] = useReducer(activeRoomReducer, [null, null])
|
const [[prevActiveRoom, activeRoom], directSetActiveRoom] = useReducer(activeRoomReducer, [null, null])
|
||||||
|
const [space, directSetSpace] = useState<RoomListFilter | null>(null)
|
||||||
const skipNextTransitionRef = useRef(false)
|
const skipNextTransitionRef = useRef(false)
|
||||||
const [rightPanel, directSetRightPanel] = useState<RightPanelProps | null>(null)
|
const [rightPanel, directSetRightPanel] = useState<RightPanelProps | null>(null)
|
||||||
const client = use(ClientContext)!
|
const client = use(ClientContext)!
|
||||||
const syncStatus = useEventAsState(client.syncStatus)
|
const syncStatus = useEventAsState(client.syncStatus)
|
||||||
const context = useMemo(
|
const context = useMemo(
|
||||||
() => new ContextFields(directSetRightPanel, directSetActiveRoom, client),
|
() => new ContextFields(directSetRightPanel, directSetActiveRoom, directSetSpace, client),
|
||||||
[client],
|
[client],
|
||||||
)
|
)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -292,6 +313,10 @@ const MainScreen = () => {
|
||||||
const listener = (evt: PopStateEvent) => {
|
const listener = (evt: PopStateEvent) => {
|
||||||
skipNextTransitionRef.current = evt.hasUAVisualTransition
|
skipNextTransitionRef.current = evt.hasUAVisualTransition
|
||||||
const roomID = evt.state?.room_id ?? null
|
const roomID = evt.state?.room_id ?? null
|
||||||
|
const spaceID = evt.state?.space_id ?? undefined
|
||||||
|
if (spaceID !== client.store.currentRoomListFilter?.id) {
|
||||||
|
context.setSpace(client.store.getSpaceByID(spaceID), false)
|
||||||
|
}
|
||||||
if (roomID !== client.store.activeRoomID) {
|
if (roomID !== client.store.activeRoomID) {
|
||||||
context.setActiveRoom(roomID, {
|
context.setActiveRoom(roomID, {
|
||||||
alias: ensureString(evt.state?.source_alias) || undefined,
|
alias: ensureString(evt.state?.source_alias) || undefined,
|
||||||
|
@ -372,7 +397,7 @@ const MainScreen = () => {
|
||||||
<ModalWrapper>
|
<ModalWrapper>
|
||||||
<StylePreferences client={client} activeRoom={activeRealRoom}/>
|
<StylePreferences client={client} activeRoom={activeRealRoom}/>
|
||||||
<main className={classNames.join(" ")} style={extraStyle}>
|
<main className={classNames.join(" ")} style={extraStyle}>
|
||||||
<RoomList activeRoomID={activeRoom?.roomID ?? null}/>
|
<RoomList activeRoomID={activeRoom?.roomID ?? null} space={space}/>
|
||||||
{resizeHandle1}
|
{resizeHandle1}
|
||||||
{renderedRoom
|
{renderedRoom
|
||||||
? renderedRoom instanceof RoomStateStore
|
? renderedRoom instanceof RoomStateStore
|
||||||
|
|
|
@ -14,12 +14,14 @@
|
||||||
// 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 { createContext } from "react"
|
import { createContext } from "react"
|
||||||
|
import { RoomListFilter } from "@/api/statestore"
|
||||||
import type { RoomID } from "@/api/types"
|
import type { RoomID } from "@/api/types"
|
||||||
import type { RightPanelProps } from "./rightpanel/RightPanel.tsx"
|
import type { RightPanelProps } from "./rightpanel/RightPanel.tsx"
|
||||||
import type { RoomPreviewProps } from "./roomview/RoomPreview.tsx"
|
import type { RoomPreviewProps } from "./roomview/RoomPreview.tsx"
|
||||||
|
|
||||||
export interface MainScreenContextFields {
|
export interface MainScreenContextFields {
|
||||||
setActiveRoom: (roomID: RoomID | null, previewMeta?: Partial<RoomPreviewProps>) => void
|
setActiveRoom: (roomID: RoomID | null, previewMeta?: Partial<RoomPreviewProps>) => void
|
||||||
|
setSpace: (space: RoomListFilter | null, pushState?: boolean) => void
|
||||||
clickRoom: (evt: React.MouseEvent) => void
|
clickRoom: (evt: React.MouseEvent) => void
|
||||||
clearActiveRoom: () => void
|
clearActiveRoom: () => void
|
||||||
|
|
||||||
|
@ -32,6 +34,9 @@ const stubContext = {
|
||||||
get setActiveRoom(): never {
|
get setActiveRoom(): never {
|
||||||
throw new Error("MainScreenContext used outside main screen")
|
throw new Error("MainScreenContext used outside main screen")
|
||||||
},
|
},
|
||||||
|
get setSpace(): never {
|
||||||
|
throw new Error("MainScreenContext used outside main screen")
|
||||||
|
},
|
||||||
get clickRoom(): never {
|
get clickRoom(): never {
|
||||||
throw new Error("MainScreenContext used outside main screen")
|
throw new Error("MainScreenContext used outside main screen")
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
// 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, { use, useCallback, useRef, useState } from "react"
|
import React, { use, useCallback, useRef, useState } from "react"
|
||||||
import { RoomListFilter, Space as SpaceStore, SpaceUnreadCounts } from "@/api/statestore/space.ts"
|
import { RoomListFilter, Space as SpaceStore, SpaceUnreadCounts } from "@/api/statestore"
|
||||||
import type { RoomID } from "@/api/types"
|
import type { RoomID } from "@/api/types"
|
||||||
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
||||||
import reverseMap from "@/util/reversemap.ts"
|
import reverseMap from "@/util/reversemap.ts"
|
||||||
|
@ -31,35 +31,25 @@ import "./RoomList.css"
|
||||||
|
|
||||||
interface RoomListProps {
|
interface RoomListProps {
|
||||||
activeRoomID: RoomID | null
|
activeRoomID: RoomID | null
|
||||||
|
space: RoomListFilter | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomList = ({ activeRoomID }: RoomListProps) => {
|
const RoomList = ({ activeRoomID, space }: RoomListProps) => {
|
||||||
const client = use(ClientContext)!
|
const client = use(ClientContext)!
|
||||||
const mainScreen = use(MainScreenContext)
|
const mainScreen = use(MainScreenContext)
|
||||||
const roomList = useEventAsState(client.store.roomList)
|
const roomList = useEventAsState(client.store.roomList)
|
||||||
const spaces = useEventAsState(client.store.topLevelSpaces)
|
const spaces = useEventAsState(client.store.topLevelSpaces)
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null)
|
const searchInputRef = useRef<HTMLInputElement>(null)
|
||||||
const [query, directSetQuery] = useState("")
|
const [query, directSetQuery] = useState("")
|
||||||
const [space, directSetSpace] = useState<RoomListFilter | null>(null)
|
|
||||||
|
|
||||||
const setQuery = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
const setQuery = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
client.store.currentRoomListQuery = toSearchableString(evt.target.value)
|
client.store.currentRoomListQuery = toSearchableString(evt.target.value)
|
||||||
directSetQuery(evt.target.value)
|
directSetQuery(evt.target.value)
|
||||||
}
|
}
|
||||||
const setSpace = useCallback((space: RoomListFilter | null) => {
|
|
||||||
directSetSpace(space)
|
|
||||||
client.store.currentRoomListFilter = space
|
|
||||||
if (client.store.activeRoomID && space) {
|
|
||||||
const entry = client.store.roomListEntries.get(client.store.activeRoomID)
|
|
||||||
if (entry && !space.include(entry)) {
|
|
||||||
mainScreen.setActiveRoom(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [client, mainScreen])
|
|
||||||
const onClickSpace = useCallback((evt: React.MouseEvent<HTMLDivElement>) => {
|
const onClickSpace = useCallback((evt: React.MouseEvent<HTMLDivElement>) => {
|
||||||
const store = client.store.getSpaceStore(evt.currentTarget.getAttribute("data-target-space")!)
|
const store = client.store.getSpaceStore(evt.currentTarget.getAttribute("data-target-space")!)
|
||||||
setSpace(store)
|
mainScreen.setSpace(store)
|
||||||
}, [setSpace, client])
|
}, [mainScreen, client])
|
||||||
const onClickSpaceUnread = useCallback((
|
const onClickSpaceUnread = useCallback((
|
||||||
evt: React.MouseEvent<HTMLDivElement> | null, space?: SpaceStore | null,
|
evt: React.MouseEvent<HTMLDivElement> | null, space?: SpaceStore | null,
|
||||||
) => {
|
) => {
|
||||||
|
@ -130,11 +120,11 @@ const RoomList = ({ activeRoomID }: RoomListProps) => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-bar">
|
<div className="space-bar">
|
||||||
<FakeSpace space={null} setSpace={setSpace} isActive={space === null} />
|
<FakeSpace space={null} setSpace={mainScreen.setSpace} isActive={space === null} />
|
||||||
{client.store.pseudoSpaces.map(pseudoSpace => <FakeSpace
|
{client.store.pseudoSpaces.map(pseudoSpace => <FakeSpace
|
||||||
key={pseudoSpace.id}
|
key={pseudoSpace.id}
|
||||||
space={pseudoSpace}
|
space={pseudoSpace}
|
||||||
setSpace={setSpace}
|
setSpace={mainScreen.setSpace}
|
||||||
onClickUnread={onClickSpaceUnread}
|
onClickUnread={onClickSpaceUnread}
|
||||||
isActive={space?.id === pseudoSpace.id}
|
isActive={space?.id === pseudoSpace.id}
|
||||||
/>)}
|
/>)}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
//
|
//
|
||||||
// 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 { SpaceUnreadCounts } from "@/api/statestore/space.ts"
|
import { SpaceUnreadCounts } from "@/api/statestore"
|
||||||
|
|
||||||
interface UnreadCounts extends SpaceUnreadCounts {
|
interface UnreadCounts extends SpaceUnreadCounts {
|
||||||
marked_unread?: boolean
|
marked_unread?: boolean
|
||||||
|
|
Loading…
Add table
Reference in a new issue