forked from Mirrors/gomuks
web/roomlist: make room list panel resizable
This commit is contained in:
parent
336f0aa100
commit
39bfa7d084
16 changed files with 345 additions and 75 deletions
1
web/src/icons/group.svg
Normal file
1
web/src/icons/group.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M40-160v-112q0-34 17.5-62.5T104-378q62-31 126-46.5T360-440q66 0 130 15.5T616-378q29 15 46.5 43.5T680-272v112H40Zm720 0v-120q0-44-24.5-84.5T666-434q51 6 96 20.5t84 35.5q36 20 55 44.5t19 53.5v120H760ZM360-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm400-160q0 66-47 113t-113 47q-11 0-28-2.5t-28-5.5q27-32 41.5-71t14.5-81q0-42-14.5-81T544-792q14-5 28-6.5t28-1.5q66 0 113 47t47 113ZM120-240h480v-32q0-11-5.5-20T580-306q-54-27-109-40.5T360-360q-56 0-111 13.5T140-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T440-640q0-33-23.5-56.5T360-720q-33 0-56.5 23.5T280-640q0 33 23.5 56.5T360-560Zm0 320Zm0-400Z"/></svg>
|
After Width: | Height: | Size: 767 B |
1
web/src/icons/settings.svg
Normal file
1
web/src/icons/settings.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>
|
After Width: | Height: | Size: 772 B |
|
@ -1,31 +1,49 @@
|
|||
main.matrix-main {
|
||||
--room-list-width: 300px;
|
||||
--right-panel-width: 300px;
|
||||
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
||||
display: grid;
|
||||
grid-template: "roomlist roomview" 1fr / 300px 1fr;
|
||||
grid-template:
|
||||
" roomlist rh1 roomview" 1fr
|
||||
/ var(--room-list-width) 0 1fr;
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
grid-template: "roomlist roomview" 1fr / 250px 1fr;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
grid-template: "roomlist roomview" 1fr / 200px 1fr;
|
||||
&.right-panel-open {
|
||||
grid-template:
|
||||
" roomlist rh1 roomview rh2 rightpanel " 1fr
|
||||
/ var(--room-list-width) 0 1fr 0 var(--right-panel-width);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
&.room-selected {
|
||||
&.right-panel-open {
|
||||
grid-template: "rightpanel" 1fr / 1fr;
|
||||
> div.room-list-wrapper {
|
||||
display: none;
|
||||
}
|
||||
> div.room-view {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.room-selected:not(.right-panel-open) {
|
||||
grid-template: "roomview" 1fr / 1fr;
|
||||
> div.room-list-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.room-selected) {
|
||||
&:not(.room-selected):not(.right-panel-open) {
|
||||
grid-template: "roomlist" 1fr / 1fr;
|
||||
> div.room-view {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div.room-list-resizer {
|
||||
grid-area: rh1;
|
||||
}
|
||||
|
||||
> div.right-panel-resizer {
|
||||
grid-area: rh2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,32 +13,64 @@
|
|||
//
|
||||
// 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, useLayoutEffect, useState } from "react"
|
||||
import { use, useCallback, useLayoutEffect, useMemo, useState } from "react"
|
||||
import type { RoomID } from "@/api/types"
|
||||
import ClientContext from "./ClientContext.ts"
|
||||
import MainScreenContext, { MainScreenContextFields } from "./MainScreenContext.ts"
|
||||
import RoomView from "./RoomView.tsx"
|
||||
import RightPanel, { RightPanelProps } from "./rightpanel/RightPanel.tsx"
|
||||
import RoomList from "./roomlist/RoomList.tsx"
|
||||
import { useResizeHandle } from "./useResizeHandle.tsx"
|
||||
import "./MainScreen.css"
|
||||
|
||||
const MainScreen = () => {
|
||||
const [activeRoomID, setActiveRoomID] = useState<RoomID | null>(null)
|
||||
const [rightPanel, setRightPanel] = useState<RightPanelProps | null>(null)
|
||||
const client = use(ClientContext)!
|
||||
const activeRoom = activeRoomID && client.store.rooms.get(activeRoomID)
|
||||
const setActiveRoom = useCallback((roomID: RoomID) => {
|
||||
console.log("Switching to room", roomID)
|
||||
setActiveRoomID(roomID)
|
||||
setRightPanel(null)
|
||||
if (client.store.rooms.get(roomID)?.stateLoaded === false) {
|
||||
client.loadRoomState(roomID)
|
||||
.catch(err => console.error("Failed to load room state", err))
|
||||
}
|
||||
}, [client])
|
||||
const context: MainScreenContextFields = useMemo(() => ({
|
||||
setActiveRoom,
|
||||
clickRoom: (evt: React.MouseEvent) => {
|
||||
const roomID = evt.currentTarget.getAttribute("data-room-id")
|
||||
if (roomID) {
|
||||
setActiveRoom(roomID)
|
||||
} else {
|
||||
console.warn("No room ID :(", evt.currentTarget)
|
||||
}
|
||||
},
|
||||
clearActiveRoom: () => setActiveRoomID(null),
|
||||
setRightPanel,
|
||||
}), [setRightPanel, setActiveRoom])
|
||||
useLayoutEffect(() => {
|
||||
client.store.switchRoom = setActiveRoom
|
||||
}, [client, setActiveRoom])
|
||||
const clearActiveRoom = useCallback(() => setActiveRoomID(null), [])
|
||||
return <main className={`matrix-main ${activeRoom ? "room-selected" : ""}`}>
|
||||
<RoomList setActiveRoom={setActiveRoom} activeRoomID={activeRoomID} />
|
||||
{activeRoom && <RoomView key={activeRoomID} clearActiveRoom={clearActiveRoom} room={activeRoom} />}
|
||||
const [roomListWidth, resizeHandle1] = useResizeHandle(
|
||||
300, 48, 900, "roomListWidth", { className: "room-list-resizer" },
|
||||
)
|
||||
const [rightPanelWidth, resizeHandle2] = useResizeHandle(
|
||||
300, 100, 900, "rightPanelWidth", { className: "right-panel-resizer" },
|
||||
)
|
||||
const extraStyle = {
|
||||
["--room-list-width" as string]: `${roomListWidth}px`,
|
||||
["--right-panel-width" as string]: `${rightPanelWidth}px`,
|
||||
}
|
||||
return <main className={`matrix-main ${activeRoom ? "room-selected" : ""}`} style={extraStyle}>
|
||||
<MainScreenContext value={context}>
|
||||
<RoomList activeRoomID={activeRoomID}/>
|
||||
{resizeHandle1}
|
||||
{activeRoom && <RoomView key={activeRoomID} room={activeRoom}/>}
|
||||
{resizeHandle2}
|
||||
{rightPanel && <RightPanel {...rightPanel}/>}
|
||||
</MainScreenContext>
|
||||
</main>
|
||||
}
|
||||
|
||||
|
|
43
web/src/ui/MainScreenContext.ts
Normal file
43
web/src/ui/MainScreenContext.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 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 { createContext } from "react"
|
||||
import type { RoomID } from "@/api/types"
|
||||
import type { RightPanelProps } from "./rightpanel/RightPanel.tsx"
|
||||
|
||||
export interface MainScreenContextFields {
|
||||
setActiveRoom: (roomID: RoomID) => void
|
||||
clickRoom: (evt: React.MouseEvent) => void
|
||||
clearActiveRoom: () => void
|
||||
|
||||
setRightPanel: (props: RightPanelProps) => void
|
||||
}
|
||||
|
||||
const MainScreenContext = createContext<MainScreenContextFields>({
|
||||
get setActiveRoom(): never {
|
||||
throw new Error("MainScreenContext used outside main screen")
|
||||
},
|
||||
get clickRoom(): never {
|
||||
throw new Error("MainScreenContext used outside main screen")
|
||||
},
|
||||
get clearActiveRoom(): never {
|
||||
throw new Error("MainScreenContext used outside main screen")
|
||||
},
|
||||
get setRightPanel(): never {
|
||||
throw new Error("MainScreenContext used outside main screen")
|
||||
},
|
||||
})
|
||||
|
||||
export default MainScreenContext
|
12
web/src/ui/ResizeHandle.css
Normal file
12
web/src/ui/ResizeHandle.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
div.resize-handle-outer {
|
||||
position: relative;
|
||||
|
||||
> div.resize-handle-inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -.25rem;
|
||||
right: -.25rem;
|
||||
cursor: col-resize;
|
||||
}
|
||||
}
|
50
web/src/ui/ResizeHandle.tsx
Normal file
50
web/src/ui/ResizeHandle.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
// 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, { CSSProperties } from "react"
|
||||
import useEvent from "@/util/useEvent.ts"
|
||||
import "./ResizeHandle.css"
|
||||
|
||||
export interface ResizeHandleProps {
|
||||
width: number
|
||||
minWidth: number
|
||||
maxWidth: number
|
||||
setWidth: (width: number) => void
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
const ResizeHandle = ({ width, minWidth, maxWidth, setWidth, style, className }: ResizeHandleProps) => {
|
||||
const onMouseDown = useEvent((evt: React.MouseEvent<HTMLDivElement>) => {
|
||||
const origWidth = width
|
||||
const startPos = evt.clientX
|
||||
const onMouseMove = (evt: MouseEvent) => {
|
||||
setWidth(Math.max(minWidth, Math.min(maxWidth, origWidth + evt.clientX - startPos)))
|
||||
evt.preventDefault()
|
||||
}
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener("mousemove", onMouseMove)
|
||||
document.removeEventListener("mouseup", onMouseUp)
|
||||
}
|
||||
document.addEventListener("mousemove", onMouseMove)
|
||||
document.addEventListener("mouseup", onMouseUp)
|
||||
evt.preventDefault()
|
||||
})
|
||||
return <div className={`resize-handle-outer ${className ?? ""}`} style={style}>
|
||||
<div className="resize-handle-inner" onMouseDown={onMouseDown}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default ResizeHandle
|
|
@ -1,4 +1,5 @@
|
|||
div.room-view {
|
||||
grid-area: roomview;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
|
@ -8,21 +9,4 @@ div.room-view {
|
|||
"messageview" 1fr
|
||||
"input" auto
|
||||
/ 1fr;
|
||||
|
||||
> div.room-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
padding-left: .5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
> span.room-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> button.back {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,39 +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 { use, useRef } from "react"
|
||||
import { getAvatarURL } from "@/api/media.ts"
|
||||
import { useRef } from "react"
|
||||
import { RoomStateStore } from "@/api/statestore"
|
||||
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
||||
import RoomViewHeader from "./RoomViewHeader.tsx"
|
||||
import MessageComposer from "./composer/MessageComposer.tsx"
|
||||
import { LightboxContext } from "./modal/Lightbox.tsx"
|
||||
import { RoomContext, RoomContextData } from "./roomcontext.ts"
|
||||
import TimelineView from "./timeline/TimelineView.tsx"
|
||||
import BackIcon from "@/icons/back.svg?react"
|
||||
import "./RoomView.css"
|
||||
|
||||
interface RoomViewProps {
|
||||
room: RoomStateStore
|
||||
clearActiveRoom: () => void
|
||||
}
|
||||
|
||||
const RoomHeader = ({ room, clearActiveRoom }: RoomViewProps) => {
|
||||
const roomMeta = useEventAsState(room.meta)
|
||||
const avatarSourceID = roomMeta.lazy_load_summary?.heroes?.length === 1
|
||||
? roomMeta.lazy_load_summary.heroes[0] : room.roomID
|
||||
return <div className="room-header">
|
||||
<button className="back" onClick={clearActiveRoom}><BackIcon/></button>
|
||||
<img
|
||||
className="avatar"
|
||||
loading="lazy"
|
||||
src={getAvatarURL(avatarSourceID, { avatar_url: roomMeta.avatar, displayname: roomMeta.name })}
|
||||
onClick={use(LightboxContext)!}
|
||||
alt=""
|
||||
/>
|
||||
<span className="room-name">
|
||||
{roomMeta.name ?? roomMeta.room_id}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
const onKeyDownRoomView = (evt: React.KeyboardEvent) => {
|
||||
|
@ -54,14 +31,14 @@ const onKeyDownRoomView = (evt: React.KeyboardEvent) => {
|
|||
}
|
||||
}
|
||||
|
||||
const RoomView = ({ room, clearActiveRoom }: RoomViewProps) => {
|
||||
const RoomView = ({ room }: RoomViewProps) => {
|
||||
const roomContextDataRef = useRef<RoomContextData | undefined>(undefined)
|
||||
if (roomContextDataRef.current === undefined) {
|
||||
roomContextDataRef.current = new RoomContextData(room)
|
||||
}
|
||||
return <div className="room-view" onKeyDown={onKeyDownRoomView} tabIndex={-1}>
|
||||
<RoomContext value={roomContextDataRef.current}>
|
||||
<RoomHeader room={room} clearActiveRoom={clearActiveRoom}/>
|
||||
<RoomViewHeader room={room}/>
|
||||
<TimelineView/>
|
||||
<MessageComposer/>
|
||||
</RoomContext>
|
||||
|
|
31
web/src/ui/RoomViewHeader.css
Normal file
31
web/src/ui/RoomViewHeader.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
div.room-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
padding-left: .5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
> span.room-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> div.divider {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
> button.back {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
||||
> div.right-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: .5rem;
|
||||
|
||||
> button {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
58
web/src/ui/RoomViewHeader.tsx
Normal file
58
web/src/ui/RoomViewHeader.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
// 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 { use } from "react"
|
||||
import { getAvatarURL } from "@/api/media.ts"
|
||||
import { RoomStateStore } from "@/api/statestore"
|
||||
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
||||
import MainScreenContext from "./MainScreenContext.ts"
|
||||
import { LightboxContext } from "./modal/Lightbox.tsx"
|
||||
import BackIcon from "@/icons/back.svg?react"
|
||||
// import PeopleIcon from "@/icons/group.svg?react"
|
||||
// import PinIcon from "@/icons/pin.svg?react"
|
||||
// import SettingsIcon from "@/icons/settings.svg?react"
|
||||
import "./RoomViewHeader.css"
|
||||
|
||||
interface RoomViewHeaderProps {
|
||||
room: RoomStateStore
|
||||
}
|
||||
|
||||
const RoomViewHeader = ({ room }: RoomViewHeaderProps) => {
|
||||
const roomMeta = useEventAsState(room.meta)
|
||||
const avatarSourceID = roomMeta.lazy_load_summary?.heroes?.length === 1
|
||||
? roomMeta.lazy_load_summary.heroes[0] : room.roomID
|
||||
const mainScreen = use(MainScreenContext)
|
||||
return <div className="room-header">
|
||||
<button className="back" onClick={mainScreen.clearActiveRoom}><BackIcon/></button>
|
||||
<img
|
||||
className="avatar"
|
||||
loading="lazy"
|
||||
src={getAvatarURL(avatarSourceID, { avatar_url: roomMeta.avatar, displayname: roomMeta.name })}
|
||||
onClick={use(LightboxContext)!}
|
||||
alt=""
|
||||
/>
|
||||
<span className="room-name">
|
||||
{roomMeta.name ?? roomMeta.room_id}
|
||||
</span>
|
||||
<div className="divider"/>
|
||||
{/*<div className="right-buttons">
|
||||
<button><PinIcon/></button>
|
||||
<button><PeopleIcon/></button>
|
||||
<button><SettingsIcon/></button>
|
||||
</div>*/}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default RoomViewHeader
|
3
web/src/ui/rightpanel/RightPanel.css
Normal file
3
web/src/ui/rightpanel/RightPanel.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
div.right-panel {
|
||||
|
||||
}
|
27
web/src/ui/rightpanel/RightPanel.tsx
Normal file
27
web/src/ui/rightpanel/RightPanel.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 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/>.
|
||||
|
||||
export interface RightPanelProps {
|
||||
meow?: never
|
||||
}
|
||||
|
||||
const RightPanel = ({ meow }: RightPanelProps) => {
|
||||
return <div className="right-panel">
|
||||
{meow}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default RightPanel
|
|
@ -19,10 +19,10 @@ import type { RoomListEntry } from "@/api/statestore"
|
|||
import type { MemDBEvent, MemberEventContent } from "@/api/types"
|
||||
import useContentVisibility from "@/util/contentvisibility.ts"
|
||||
import ClientContext from "../ClientContext.ts"
|
||||
import MainScreenContext from "../MainScreenContext.ts"
|
||||
|
||||
export interface RoomListEntryProps {
|
||||
room: RoomListEntry
|
||||
setActiveRoom: (evt: React.MouseEvent) => void
|
||||
isActive: boolean
|
||||
hidden: boolean
|
||||
}
|
||||
|
@ -77,12 +77,12 @@ const EntryInner = ({ room }: InnerProps) => {
|
|||
</>
|
||||
}
|
||||
|
||||
const Entry = ({ room, setActiveRoom, isActive, hidden }: RoomListEntryProps) => {
|
||||
const Entry = ({ room, isActive, hidden }: RoomListEntryProps) => {
|
||||
const [isVisible, divRef] = useContentVisibility<HTMLDivElement>()
|
||||
return <div
|
||||
ref={divRef}
|
||||
className={`room-entry ${isActive ? "active" : ""} ${hidden ? "hidden" : ""}`}
|
||||
onClick={setActiveRoom}
|
||||
onClick={use(MainScreenContext).clickRoom}
|
||||
data-room-id={room.room_id}
|
||||
>
|
||||
{isVisible ? <EntryInner room={room}/> : null}
|
||||
|
|
|
@ -22,23 +22,14 @@ import Entry from "./Entry.tsx"
|
|||
import "./RoomList.css"
|
||||
|
||||
interface RoomListProps {
|
||||
setActiveRoom: (room_id: RoomID) => void
|
||||
activeRoomID: RoomID | null
|
||||
}
|
||||
|
||||
const RoomList = ({ setActiveRoom, activeRoomID }: RoomListProps) => {
|
||||
const RoomList = ({ activeRoomID }: RoomListProps) => {
|
||||
const roomList = useEventAsState(use(ClientContext)!.store.roomList)
|
||||
const roomFilterRef = useRef<HTMLInputElement>(null)
|
||||
const [roomFilter, setRoomFilter] = useState("")
|
||||
const [realRoomFilter, setRealRoomFilter] = useState("")
|
||||
const clickRoom = useCallback((evt: React.MouseEvent) => {
|
||||
const roomID = evt.currentTarget.getAttribute("data-room-id")
|
||||
if (roomID) {
|
||||
setActiveRoom(roomID)
|
||||
} else {
|
||||
console.warn("No room ID :(", evt.currentTarget)
|
||||
}
|
||||
}, [setActiveRoom])
|
||||
|
||||
const updateRoomFilter = useCallback((evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRoomFilter(evt.target.value)
|
||||
|
@ -61,7 +52,6 @@ const RoomList = ({ setActiveRoom, activeRoomID }: RoomListProps) => {
|
|||
isActive={room.room_id === activeRoomID}
|
||||
hidden={roomFilter ? !room.search_name.includes(realRoomFilter) : false}
|
||||
room={room}
|
||||
setActiveRoom={clickRoom}
|
||||
/>,
|
||||
)}
|
||||
</div>
|
||||
|
|
43
web/src/ui/useResizeHandle.tsx
Normal file
43
web/src/ui/useResizeHandle.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 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 { useEffect, useState } from "react"
|
||||
import ResizeHandle, { ResizeHandleProps } from "./ResizeHandle.tsx"
|
||||
|
||||
export const useResizeHandle = (
|
||||
defaultSize: number,
|
||||
minWidth: number,
|
||||
maxWidth: number,
|
||||
localStorageKey: string,
|
||||
extraProps: Partial<ResizeHandleProps>,
|
||||
) => {
|
||||
const [width, setWidth] = useState(() => {
|
||||
const savedWidth = localStorage.getItem(localStorageKey)
|
||||
if (savedWidth) {
|
||||
return Number(savedWidth)
|
||||
}
|
||||
return defaultSize
|
||||
})
|
||||
useEffect(() => {
|
||||
localStorage.setItem(localStorageKey, width.toString())
|
||||
}, [localStorageKey, width])
|
||||
return [width, <ResizeHandle
|
||||
width={width}
|
||||
minWidth={minWidth}
|
||||
maxWidth={maxWidth}
|
||||
setWidth={setWidth}
|
||||
{...extraProps}
|
||||
/>] as const
|
||||
}
|
Loading…
Add table
Reference in a new issue