diff --git a/.changeset/add_resize-side-panel.md b/.changeset/add_resize-side-panel.md new file mode 100644 index 000000000..4abe2d1c4 --- /dev/null +++ b/.changeset/add_resize-side-panel.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Add Resize the room sidepanel using a hoverable tool diff --git a/src/app/components/nav/styles.css.ts b/src/app/components/nav/styles.css.ts index f72d3fe38..5c9b70335 100644 --- a/src/app/components/nav/styles.css.ts +++ b/src/app/components/nav/styles.css.ts @@ -9,6 +9,7 @@ export const NavCategory = style([ DefaultReset, { position: 'relative', + overflow: 'scroll', }, ]); diff --git a/src/app/components/page/style.css.ts b/src/app/components/page/style.css.ts index d35fa651d..8a5472c48 100644 --- a/src/app/components/page/style.css.ts +++ b/src/app/components/page/style.css.ts @@ -6,6 +6,9 @@ import { DefaultReset, color, config, toRem } from 'folds'; export const PageNav = recipe({ variants: { size: { + '100%': { + width: '100%', + }, '400': { width: toRem(256), }, @@ -15,7 +18,7 @@ export const PageNav = recipe({ }, }, defaultVariants: { - size: '400', + size: '100%', }, }); export type PageNavVariants = RecipeVariants; diff --git a/src/app/features/room/CallChatView.tsx b/src/app/features/room/CallChatView.tsx index 3acc8c562..d979a144c 100644 --- a/src/app/features/room/CallChatView.tsx +++ b/src/app/features/room/CallChatView.tsx @@ -1,10 +1,15 @@ import { useSetAtom } from 'jotai'; import { useParams } from 'react-router-dom'; -import { Box, Text, TooltipProvider, Tooltip, Icon, Icons, IconButton, toRem } from 'folds'; +import { Box, Text, TooltipProvider, Tooltip, Icon, Icons, IconButton } from 'folds'; import { Page, PageHeader } from '../../components/page'; import { callChatAtom } from '../../state/callEmbed'; import { RoomView } from './RoomView'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { useSetting } from '$state/hooks/settings'; +import { settingsAtom } from '$state/settings'; +import { mobileOrTablet } from '$utils/user-agent'; +import { useState, useEffect } from 'react'; export function CallChatView() { const { eventId } = useParams(); @@ -13,44 +18,59 @@ export function CallChatView() { const handleClose = () => setChat(false); + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting(settingsAtom, 'vcmsgSidebarWidth'); + const [curWidth, setCurWidth] = useState(threadSidebarWidth); + useEffect(() => { + setCurWidth(threadSidebarWidth); + }, [threadSidebarWidth]); return ( - - - - - - Chat - - - - - Close - - } - > - {(triggerRef) => ( - - - - )} - + <> + {!mobileOrTablet() && ( + + )} + + + + + + Chat + + + + + Close + + } + > + {(triggerRef) => ( + + + + )} + + + + + - - - - - + + ); } diff --git a/src/app/features/room/MembersDrawer.tsx b/src/app/features/room/MembersDrawer.tsx index f751bcf31..82e831c41 100644 --- a/src/app/features/room/MembersDrawer.tsx +++ b/src/app/features/room/MembersDrawer.tsx @@ -1,5 +1,5 @@ import type { ChangeEventHandler, MouseEventHandler } from 'react'; -import { useCallback, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { RectCords } from 'folds'; import { Avatar, @@ -56,6 +56,8 @@ import { useRoomCreators } from '$hooks/useRoomCreators'; import { useSableCosmetics } from '$hooks/useSableCosmetics'; import { formatCompactNumber } from '$utils/formatCompactNumber'; import * as css from './MembersDrawer.css'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; type MemberDrawerHeaderProps = { room: Room; @@ -286,200 +288,224 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) { openUserRoomProfile(room.roomId, space?.roomId, userId, cords, 'Left'); }; + const [memberSidebarWidth, setMemberSidebarWidth] = useSetting( + settingsAtom, + 'memberSidebarWidth' + ); + const [curWidth, setCurWidth] = useState(memberSidebarWidth); + useEffect(() => { + setCurWidth(memberSidebarWidth); + }, [memberSidebarWidth]); return ( - - - - - - - - - {(anchor: RectCords | undefined, setAnchor) => ( - setAnchor(undefined)} - /> - } - > - - setAnchor( - evt.currentTarget.getBoundingClientRect() - )) as MouseEventHandler + <> + {!mobileOrTablet() && ( + + )} + + + + + + + + + {(anchor: RectCords | undefined, setAnchor) => ( + setAnchor(undefined)} + /> } - variant="Background" - size="400" - radii="300" - before={} > - {membershipFilter.name} - - - )} - - - {(anchor: RectCords | undefined, setAnchor) => ( - setAnchor(undefined)} - /> - } - > - - setAnchor( - evt.currentTarget.getBoundingClientRect() - )) as MouseEventHandler + + setAnchor( + evt.currentTarget.getBoundingClientRect() + )) as MouseEventHandler + } + variant="Background" + size="400" + radii="300" + before={} + > + {membershipFilter.name} + + + )} + + + {(anchor: RectCords | undefined, setAnchor) => ( + setAnchor(undefined)} + /> } - variant="Background" - size="400" - radii="300" - after={} > - {memberSort.name} - - - )} - - - - } - after={ - result && ( - 0 ? 'Success' : 'Critical'} - size="400" - radii="Pill" - aria-pressed - onClick={() => { - if (searchInputRef.current) { - searchInputRef.current.value = ''; - searchInputRef.current.focus(); + + setAnchor( + evt.currentTarget.getBoundingClientRect() + )) as MouseEventHandler } - resetSearch(); - }} - after={} - > - {`${result.items.length || 'No'} ${ - result.items.length === 1 ? 'Result' : 'Results' - }`} - - ) - } - /> + variant="Background" + size="400" + radii="300" + after={} + > + {memberSort.name} + + + )} + + + + } + after={ + result && ( + 0 ? 'Success' : 'Critical'} + size="400" + radii="Pill" + aria-pressed + onClick={() => { + if (searchInputRef.current) { + searchInputRef.current.value = ''; + searchInputRef.current.focus(); + } + resetSearch(); + }} + after={} + > + {`${result.items.length || 'No'} ${ + result.items.length === 1 ? 'Result' : 'Results' + }`} + + ) + } + /> + - - - virtualizer.scrollToOffset(0)} - variant="Surface" - radii="Pill" - outlined - size="300" - aria-label="Scroll to Top" - > - - - + + virtualizer.scrollToOffset(0)} + variant="Surface" + radii="Pill" + outlined + size="300" + aria-label="Scroll to Top" + > + + + - {!fetchingMembers && !result && processMembers.length === 0 && ( - - {`No "${membershipFilter.name}" Members`} - - )} + {!fetchingMembers && !result && processMembers.length === 0 && ( + + {`No "${membershipFilter.name}" Members`} + + )} + + +
+ {virtualizer.getVirtualItems().map((vItem) => { + const tagOrMember = PLTagOrRoomMember[vItem.index]; + if (!tagOrMember) return null; + if (!('userId' in tagOrMember)) { + return ( + + {tagOrMember.name} + + ); + } - -
- {virtualizer.getVirtualItems().map((vItem) => { - const tagOrMember = PLTagOrRoomMember[vItem.index]; - if (!tagOrMember) return null; - if (!('userId' in tagOrMember)) { return ( - - {tagOrMember.name} - + receipt.userId === tagOrMember.userId + )} + /> +
); - } + })} +
+
- return ( -
- receipt.userId === tagOrMember.userId - )} - /> -
- ); - })} - + {fetchingMembers && ( + + + + )}
- - {fetchingMembers && ( - - - - )} -
- + + - + ); } diff --git a/src/app/features/room/RoomView.tsx b/src/app/features/room/RoomView.tsx index 90afe3498..a5329bed4 100644 --- a/src/app/features/room/RoomView.tsx +++ b/src/app/features/room/RoomView.tsx @@ -1,7 +1,7 @@ import { useCallback, useRef, useState } from 'react'; import { useAtomValue } from 'jotai'; import { Transforms } from 'slate'; -import { Box, Text, config, toRem } from 'folds'; +import { Box, Text, config } from 'folds'; import { EventType } from '$types/matrix-sdk'; import { ReactEditor } from 'slate-react'; import { isKeyHotkey } from 'is-hotkey'; @@ -141,14 +141,7 @@ export function RoomView({ eventId }: { eventId?: string }) { return ( {(onBack) => ( - + {showCallView && ( diff --git a/src/app/features/room/ThreadBrowser.tsx b/src/app/features/room/ThreadBrowser.tsx index f9891eeda..391d0514e 100644 --- a/src/app/features/room/ThreadBrowser.tsx +++ b/src/app/features/room/ThreadBrowser.tsx @@ -13,6 +13,7 @@ import { Avatar, config, Chip, + toRem, } from 'folds'; import type { EventTimelineSet, MatrixEvent, Room, Thread } from '$types/matrix-sdk'; import { NotificationCountType, RoomEvent, ThreadEvent } from '$types/matrix-sdk'; @@ -52,6 +53,8 @@ import { import { UnreadBadge, UnreadBadgeCenter } from '$components/unread-badge'; import { EncryptedContent } from './message'; import * as css from './ThreadDrawer.css'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; type ThreadPreviewProps = { room: Room; @@ -436,130 +439,149 @@ export function ThreadBrowser({ room, onOpenThread, onClose, overlay }: ThreadBr setQuery(e.target.value); }; + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting( + settingsAtom, + 'threadSidebarWidth' + ); + const [curWidth, setCurWidth] = useState(threadSidebarWidth); + useEffect(() => { + setCurWidth(threadSidebarWidth); + }, [threadSidebarWidth]); return ( - -
- - - - Threads - - - - - - - -
- + <> + {!mobileOrTablet() && ( + + )} - } - after={ - query ? ( - { - setQuery(''); - searchRef.current?.focus(); - }} - aria-label="Clear search" - > - - - ) : undefined - } - /> - +
+ + + + Threads + + + + + + + +
- - - {(() => { - if (threads.length === 0 && loadingMore) - return ( - - - - ); - if (threads.length === 0) - return ( - - - - {lowerQuery ? 'No threads match your search.' : 'No threads yet.'} - - - ); - return ( - <> - } + after={ + query ? ( + { + setQuery(''); + searchRef.current?.focus(); + }} + aria-label="Clear search" > - {threads.map((thread: Thread) => ( - - ))} - - {loadingMore && ( + + + ) : undefined + } + /> + + + + + {(() => { + if (threads.length === 0 && loadingMore) + return ( - )} - - ); - })()} - + ); + if (threads.length === 0) + return ( + + + + {lowerQuery ? 'No threads match your search.' : 'No threads yet.'} + + + ); + return ( + <> + + {threads.map((thread: Thread) => ( + + ))} + + {loadingMore && ( + + + + )} + + ); + })()} + +
-
+ ); } diff --git a/src/app/features/room/ThreadDrawer.css.ts b/src/app/features/room/ThreadDrawer.css.ts index 4b2d9c1a1..d44d1b103 100644 --- a/src/app/features/room/ThreadDrawer.css.ts +++ b/src/app/features/room/ThreadDrawer.css.ts @@ -1,8 +1,7 @@ import { style, globalStyle } from '@vanilla-extract/css'; -import { config, color, toRem } from 'folds'; +import { config, color } from 'folds'; export const ThreadDrawer = style({ - width: toRem(440), height: '100%', display: 'flex', flexDirection: 'column', diff --git a/src/app/features/room/ThreadDrawer.tsx b/src/app/features/room/ThreadDrawer.tsx index 39cc66728..dba1067ac 100644 --- a/src/app/features/room/ThreadDrawer.tsx +++ b/src/app/features/room/ThreadDrawer.tsx @@ -1,6 +1,18 @@ import type { MouseEventHandler } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Box, Header, Icon, IconButton, Icons, Scroll, Spinner, Text, config } from 'folds'; +import { + Box, + Header, + Icon, + IconButton, + Icons, + Scroll, + Spinner, + Text, + color, + config, + toRem, +} from 'folds'; import type { IEvent, Room } from '$types/matrix-sdk'; import { Direction, @@ -63,6 +75,8 @@ import { useTimelineEventRenderer } from '$hooks/timeline/useTimelineEventRender import { RoomInput } from './RoomInput'; import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing'; import * as css from './ThreadDrawer.css'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; /** * Resolve the list of reply events to show in the thread drawer. @@ -726,172 +740,206 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra ); const latestThreadEventId = processedEvents.at(-1)?.id ?? rootEvent?.getId(); + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting( + settingsAtom, + 'threadSidebarWidth' + ); + const [curWidth, setCurWidth] = useState(threadSidebarWidth); + useEffect(() => { + setCurWidth(threadSidebarWidth); + }, [threadSidebarWidth]); + + const [threadRootHeight, setThreadRootHeight] = useSetting(settingsAtom, 'threadRootHeight'); + const [curHeight, setCurHeight] = useState(threadRootHeight); + useEffect(() => { + setCurHeight(threadRootHeight); + }, [threadRootHeight]); return ( - - {/* Header */} -
- - - - Thread - - - - + {!mobileOrTablet() && ( + + )} + + {/* Header */} +
+ + + + Thread + + + + + + + +
+ + {/* Thread root message */} + {rootEvent && ( + <> + + + {renderMatrixEvent( + rootEvent.getType(), + typeof rootEvent.getStateKey() === 'string', + rootEvent.getId()!, + rootEvent, + processedEvents.find((e) => e.id === threadRootId)?.itemIndex ?? 0, + thread?.timelineSet ?? room.getUnfilteredTimelineSet(), + false + )} + + + +
+ + )} + {/* Replies */} + + - - - -
- - {/* Thread root message */} - {rootEvent && ( - - - {renderMatrixEvent( - rootEvent.getType(), - typeof rootEvent.getStateKey() === 'string', - rootEvent.getId()!, - rootEvent, - processedEvents.find((e) => e.id === threadRootId)?.itemIndex ?? 0, - thread?.timelineSet ?? room.getUnfilteredTimelineSet(), - false - )} - - - )} - - {/* Replies */} - - - {(() => { - if (isThreadLoading) - return ( - - - - ); - if (processedReplies.length === 0) - return ( - - - - No replies yet. Start the thread below! - - - ); - return ( - <> - {loadingOlderReplies && ( + {(() => { + if (isThreadLoading) + return ( - + - )} - {/* Reply count label inside scroll area */} - - - {processedReplies.length} {processedReplies.length === 1 ? 'reply' : 'replies'} - - - - {processedReplies.map((e) => - renderMatrixEvent( - e.mEvent.getType(), - typeof e.mEvent.getStateKey() === 'string', - e.id, - e.mEvent, - e.itemIndex, - e.timelineSet, - e.collapsed - ) + ); + if (processedReplies.length === 0) + return ( + + + + No replies yet. Start the thread below! + + + ); + return ( + <> + {loadingOlderReplies && ( + + + )} - - - ); - })()} - - + {/* Reply count label inside scroll area */} + + + {processedReplies.length}{' '} + {processedReplies.length === 1 ? 'reply' : 'replies'} + + + + {processedReplies.map((e) => + renderMatrixEvent( + e.mEvent.getType(), + typeof e.mEvent.getStateKey() === 'string', + e.id, + e.mEvent, + e.itemIndex, + e.timelineSet, + e.collapsed + ) + )} + + + ); + })()} + +
- {/* Thread input */} - -
- -
- {hideReads ? ( - - ) : ( - - )} + {/* Thread input */} + +
+ +
+ {hideReads ? ( + + ) : ( + + )} +
- + ); } diff --git a/src/app/features/settings/cosmetics/Themes.tsx b/src/app/features/settings/cosmetics/Themes.tsx index 0fe2d716f..25cc2612a 100644 --- a/src/app/features/settings/cosmetics/Themes.tsx +++ b/src/app/features/settings/cosmetics/Themes.tsx @@ -1,6 +1,21 @@ import type { ChangeEventHandler, KeyboardEventHandler } from 'react'; -import { type MouseEventHandler, useState } from 'react'; -import { Box, Chip, config, Icon, Icons, Input, Switch, Text, toRem } from 'folds'; +import { type MouseEventHandler, useEffect, useMemo, useState } from 'react'; +import { + Box, + Button, + Chip, + config, + Icon, + Icons, + Input, + Menu, + MenuItem, + PopOut, + Switch, + Text, + toRem, + type RectCords, +} from 'folds'; import { isKeyHotkey } from 'is-hotkey'; import { SettingMenuSelector } from '$components/setting-menu-selector'; @@ -14,9 +29,15 @@ import { } from '$plugins/arborium'; import { ThemeKind, useActiveTheme } from '$hooks/useTheme'; import { useSetting } from '$state/hooks/settings'; +import type { ShowRoomIcon } from '$state/settings'; import { settingsAtom } from '$state/settings'; import { SequenceCardStyle } from '$features/settings/styles.css'; import { ThemeAppearanceSection } from './ThemeAppearanceSection'; +import { stopPropagation } from '$utils/keyboard'; +import FocusTrap from 'focus-trap-react'; +import { useShowRoomIcon } from '$hooks/useShowRoomIcon'; +import type { PanelSizetItem } from '$hooks/usePanelSizes'; +import { usePanelSizeItems } from '$hooks/usePanelSizes'; function makeArboriumThemeOptions(kind?: 'light' | 'dark') { const themes = kind @@ -360,11 +381,246 @@ function PageZoomInput() { ); } +function PanelSelector({ + sidebarSelector, + setSidebarSelector, +}: { + sidebarSelector: string; + setSidebarSelector: (arg0: string) => void; +}) { + const [menuCords, setMenuCords] = useState(); + const panelSizeItems = usePanelSizeItems(); + + const handleMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + + const handleSelect = (position: PanelSizetItem) => { + setSidebarSelector(position.layout); + setMenuCords(undefined); + }; + + return ( + <> + + setMenuCords(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => + evt.key === 'ArrowDown' || evt.key === 'ArrowRight', + isKeyBackward: (evt: KeyboardEvent) => + evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', + escapeDeactivates: stopPropagation, + }} + > + + + {panelSizeItems.map((item) => ( + handleSelect(item)} + > + {item.name} + + ))} + + + + } + /> + + ); +} +function SidebarWidth({ sidebarSelector }: { sidebarSelector: string }) { + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [memberSidebarWidth, setMemberSidebarWidth] = useSetting( + settingsAtom, + 'memberSidebarWidth' + ); + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting( + settingsAtom, + 'threadSidebarWidth' + ); + const [threadRootHeight, setThreadRootHeight] = useSetting(settingsAtom, 'threadRootHeight'); + const [vcmsgSidebarWidth, setvcmsgSidebarWidth] = useSetting(settingsAtom, 'vcmsgSidebarWidth'); + const [widgetSidebarWidth, setWidgetSidebarWidth] = useSetting( + settingsAtom, + 'widgetSidebarWidth' + ); + + // Yandere style code but it works and is as straight forward as can be :shrug: + const getCurValue = useMemo(() => { + if (sidebarSelector === 'roomSidebarWidth') return roomSidebarWidth; + if (sidebarSelector === 'memberSidebarWidth') return memberSidebarWidth; + if (sidebarSelector === 'threadSidebarWidth') return threadSidebarWidth; + if (sidebarSelector === 'threadRootHeight') return threadRootHeight; + if (sidebarSelector === 'vcmsgSidebarWidth') return vcmsgSidebarWidth; + if (sidebarSelector === 'widgetSidebarWidth') return widgetSidebarWidth; + return undefined; + }, [ + sidebarSelector, + roomSidebarWidth, + memberSidebarWidth, + threadSidebarWidth, + threadRootHeight, + vcmsgSidebarWidth, + widgetSidebarWidth, + ]); + const [curValue, setCurValue] = useState(getCurValue); + const setValue = (value: number) => { + if (sidebarSelector === 'roomSidebarWidth') setRoomSidebarWidth(value); + if (sidebarSelector === 'memberSidebarWidth') setMemberSidebarWidth(value); + if (sidebarSelector === 'threadSidebarWidth') setThreadSidebarWidth(value); + if (sidebarSelector === 'threadRootHeight') setThreadRootHeight(value); + if (sidebarSelector === 'vcmsgSidebarWidth') setvcmsgSidebarWidth(value); + if (sidebarSelector === 'widgetSidebarWidth') setWidgetSidebarWidth(value); + }; + + useEffect(() => { + setInputValue(curValue?.toString()); + }, [curValue]); + useEffect(() => { + setCurValue(getCurValue); + }, [getCurValue]); + + const [inputValue, setInputValue] = useState(curValue?.toString()); + + const handleChange: ChangeEventHandler = (evt) => { + const val = evt.target.value; + setInputValue(val); + + const parsed = parseInt(val, 10); + if (!Number.isNaN(parsed)) { + setValue(parsed); + } + }; + + const handleKeyDown: KeyboardEventHandler = (evt) => { + if (isKeyHotkey('escape', evt)) { + evt.stopPropagation(); + setInputValue(curValue?.toString()); + (evt.target as HTMLInputElement).blur(); + } + + if (isKeyHotkey('enter', evt)) { + (evt.target as HTMLInputElement).blur(); + } + }; + + return ( + + ); +} + +function SelectShowRoomIcon() { + const [menuCords, setMenuCords] = useState(); + const [showRoomIcon, setShowRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); + const showRoomIconItems = useShowRoomIcon(); + + const handleMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + + const handleSelect = (position: ShowRoomIcon) => { + setShowRoomIcon(position); + setMenuCords(undefined); + }; + + return ( + <> + + setMenuCords(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => + evt.key === 'ArrowDown' || evt.key === 'ArrowRight', + isKeyBackward: (evt: KeyboardEvent) => + evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', + escapeDeactivates: stopPropagation, + }} + > + + + {showRoomIconItems.map((item) => ( + handleSelect(item.layout)} + > + {item.name} + + ))} + + + + } + /> + + ); +} export function Appearance({ onThemeBrowserOpenChange, }: { onThemeBrowserOpenChange?: (open: boolean) => void; } = {}) { + const [sidebarSelector, setSidebarSelector] = useState('roomSidebarWidth'); const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); const [customDMCards, setCustomDMCards] = useSetting(settingsAtom, 'customDMCards'); const [showEasterEggs, setShowEasterEggs] = useSetting(settingsAtom, 'showEasterEggs'); @@ -448,6 +704,32 @@ export function Appearance({ after={} /> + + + } + /> + + + + + + + + } + /> + )} diff --git a/src/app/features/widgets/WidgetsDrawer.css.ts b/src/app/features/widgets/WidgetsDrawer.css.ts index 5687b57ad..f08b53e46 100644 --- a/src/app/features/widgets/WidgetsDrawer.css.ts +++ b/src/app/features/widgets/WidgetsDrawer.css.ts @@ -1,10 +1,9 @@ import { style } from '@vanilla-extract/css'; -import { config, toRem } from 'folds'; +import { config } from 'folds'; export const WidgetsDrawer = style({ - width: toRem(420), maxWidth: '100vw', - minWidth: '20vw', + minWidth: '9vw', }); export const WidgetsDrawerHeader = style({ diff --git a/src/app/features/widgets/WidgetsDrawer.tsx b/src/app/features/widgets/WidgetsDrawer.tsx index b135b3720..106ff6f51 100644 --- a/src/app/features/widgets/WidgetsDrawer.tsx +++ b/src/app/features/widgets/WidgetsDrawer.tsx @@ -1,5 +1,5 @@ import type { FormEventHandler, MouseEventHandler } from 'react'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Box, Header, @@ -15,13 +15,14 @@ import { config, Button, Line, + toRem, } from 'folds'; import type { Room } from '$types/matrix-sdk'; import { useMatrixClient } from '$hooks/useMatrixClient'; import type { RoomWidget } from '$hooks/useRoomWidgets'; import { useRoomWidgets, enrichWidgetUrl } from '$hooks/useRoomWidgets'; -import { useSetSetting } from '$state/hooks/settings'; +import { useSetSetting, useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { usePowerLevelsContext } from '$hooks/usePowerLevels'; import { useRoomCreators } from '$hooks/useRoomCreators'; @@ -32,6 +33,8 @@ import { WidgetIframe } from './WidgetIframe'; import * as css from './WidgetsDrawer.css'; import { IntegrationManager } from './IntegrationManager'; import { CustomStateEvent } from '$types/matrix/room'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; type WidgetsDrawerHeaderProps = { activeWidget: RoomWidget | null; @@ -229,6 +232,15 @@ export function WidgetsDrawer({ room }: WidgetsDrawerProps) { const permissions = useRoomPermissions(creators, powerLevels); const canManageWidgets = permissions.stateEvent(CustomStateEvent.RoomWidget, mx.getSafeUserId()); + const [widgetSidebarWidth, setWidgetSidebarWidth] = useSetting( + settingsAtom, + 'widgetSidebarWidth' + ); + const [curWidth, setCurWidth] = useState(widgetSidebarWidth); + useEffect(() => { + setCurWidth(widgetSidebarWidth); + }, [widgetSidebarWidth]); + const handleRemoveWidget = async (widget: RoomWidget) => { try { await mx.sendStateEvent(room.roomId, CustomStateEvent.RoomWidget, {}, widget.id); @@ -243,74 +255,93 @@ export function WidgetsDrawer({ room }: WidgetsDrawerProps) { const handleBack = () => setActiveWidget(null); return ( - - - {activeWidget ? ( - - - - ) : ( - - - {widgets.length === 0 && !showAddForm && ( - - - No widgets in this room. - - - )} - {widgets.map((widget) => ( - - ))} - {canManageWidgets && ( - <> - - {showAddForm ? ( - setShowAddForm(false)} /> - ) : ( - - - - - )} - - )} - - + <> + {!mobileOrTablet() && ( + )} - setShowIntegrationManager(false)} - /> - + + + {activeWidget ? ( + + + + ) : ( + + + {widgets.length === 0 && !showAddForm && ( + + + No widgets in this room. + + + )} + {widgets.map((widget) => ( + + ))} + {canManageWidgets && ( + <> + + {showAddForm ? ( + setShowAddForm(false)} /> + ) : ( + + + + + )} + + )} + + + )} + setShowIntegrationManager(false)} + /> + + ); } diff --git a/src/app/hooks/usePanelSizes.ts b/src/app/hooks/usePanelSizes.ts new file mode 100644 index 000000000..19a30b1b3 --- /dev/null +++ b/src/app/hooks/usePanelSizes.ts @@ -0,0 +1,37 @@ +import { useMemo } from 'react'; + +export type PanelSizetItem = { + layout: string; + name: string; +}; + +export const usePanelSizeItems = (): PanelSizetItem[] => + useMemo( + () => [ + { + layout: 'roomSidebarWidth', + name: 'Room Panel Width', + }, + { + layout: 'memberSidebarWidth', + name: 'Member Panel Width', + }, + { + layout: 'threadSidebarWidth', + name: 'Thread Panel Width', + }, + { + layout: 'threadRootHeight', + name: 'Thread Root Height', + }, + { + layout: 'vcmsgSidebarWidth', + name: 'VoiceCall Msg Panel Width', + }, + { + layout: 'widgetSidebarWidth', + name: 'Widget Panel Width', + }, + ], + [] + ); diff --git a/src/app/hooks/useShowRoomIcon.ts b/src/app/hooks/useShowRoomIcon.ts new file mode 100644 index 000000000..509f58aac --- /dev/null +++ b/src/app/hooks/useShowRoomIcon.ts @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; +import { ShowRoomIcon } from '$state/settings'; + +export type MessageLayoutItem = { + name: string; + layout: ShowRoomIcon; +}; + +export const useShowRoomIcon = (): MessageLayoutItem[] => + useMemo( + () => [ + { + layout: ShowRoomIcon.Always, + name: 'Always', + }, + { + layout: ShowRoomIcon.Smart, + name: 'Smart', + }, + { + layout: ShowRoomIcon.Never, + name: 'Never', + }, + ], + [] + ); diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index e84e04daa..57749ae55 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -53,6 +53,9 @@ import { } from '$hooks/useRoomsNotificationPreferences'; import { useDirectCreateSelected } from '$hooks/router/useDirectSelected'; import { useDirectRooms } from './useDirectRooms'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; +import { useScreenSizeContext, ScreenSize } from '$hooks/useScreenSize'; type DirectMenuProps = { requestClose: () => void; @@ -179,6 +182,12 @@ export function Direct() { const roomToUnread = useAtomValue(roomToUnreadAtom); const navigate = useNavigate(); const [customDMCards] = useSetting(settingsAtom, 'customDMCards'); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); + + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); const createDirectSelected = useDirectCreateSelected(); @@ -237,81 +246,98 @@ export function Direct() { closedCategories.has(categoryId) ); - return ( - - - {noRoomToDisplay ? ( - - ) : ( - - - - - navigate(getDirectCreatePath())}> - - - - - - - - Create Chat - - - - - - - - - - - Chats - - -
- {virtualizer.getVirtualItems().map((vItem) => { - const roomId = sortedDirects[vItem.index]; - if (!roomId) return null; - const room = mx.getRoom(roomId); - if (!room) return null; - const selected = selectedRoomId === roomId; + const screenSize = useScreenSizeContext(); - return ( - + + + + {noRoomToDisplay ? ( + + ) : ( + + + + + navigate(getDirectCreatePath())}> + + + + + + + + Create Chat + + + + + + + + + + - - - ); - })} -
-
-
-
+ Chats + + +
+ {virtualizer.getVirtualItems().map((vItem) => { + const roomId = sortedDirects[vItem.index]; + if (!roomId) return null; + const room = mx.getRoom(roomId); + if (!room) return null; + const selected = selectedRoomId === roomId; + + return ( + + + + ); + })} +
+ + + + )} +
+ + {!mobileOrTablet() && ( + )} - + ); } diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index 4fb5906d4..904d7251e 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -1,5 +1,5 @@ import type { FormEventHandler } from 'react'; -import { useCallback, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import FocusTrap from 'focus-trap-react'; import { @@ -18,6 +18,7 @@ import { Text, color, config, + toRem, } from 'folds'; import { NavCategory, NavCategoryHeader, NavItem, NavItemContent, NavLink } from '$components/nav'; import { getExploreFeaturedPath, getExploreServerPath } from '$pages/pathUtils'; @@ -28,7 +29,12 @@ import { AsyncStatus, useAsyncCallback } from '$hooks/useAsyncCallback'; import { useNavToActivePathMapper } from '$hooks/useNavToActivePathMapper'; import { PageNav, PageNavContent, PageNavHeader } from '$components/page'; import { stopPropagation } from '$utils/keyboard'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { settingsAtom } from '$state/settings'; +import { useSetting } from '$state/hooks/settings'; +import { mobileOrTablet } from '$utils/user-agent'; import { getMxIdServer } from '$utils/mxIdHelper'; +import { useScreenSizeContext, ScreenSize } from '$hooks/useScreenSize'; export function AddServer() { const mx = useMatrixClient(); @@ -159,101 +165,128 @@ export function Explore() { const featuredSelected = useExploreFeaturedSelected(); const selectedServer = useExploreServer(); - return ( - - - - - - Explore Community - - - - + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); - - - - - - - - - - - - - Featured - - - - - - - {userServer && ( - - - - - - - - - - {userServer} - - - - - - - )} - - {servers.length > 0 && ( - - - - Servers + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); + const screenSize = useScreenSizeContext(); + + return ( + <> + + + + + + + Explore Community - - {servers.map((server) => ( - - + + + + + + + + + - + - {server} + Featured - ))} - - )} - - - - - - + {userServer && ( + + + + + + + + + + {userServer} + + + + + + + )} + + {servers.length > 0 && ( + + + + Servers + + + {servers.map((server) => ( + + + + + + + + + + {server} + + + + + + + ))} + + )} + + + + + + + + {!mobileOrTablet() && ( + + )} + ); } diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index afd4de936..f25b984b7 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -1,5 +1,5 @@ import type { MouseEventHandler } from 'react'; -import { forwardRef, useMemo, useRef, useState } from 'react'; +import { forwardRef, useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import type { RectCords } from 'folds'; import { @@ -54,7 +54,7 @@ import { markAsRead } from '$utils/notifications'; import { useClosedNavCategoriesAtom } from '$state/hooks/closedNavCategories'; import { stopPropagation } from '$utils/keyboard'; import { useSetting } from '$state/hooks/settings'; -import { settingsAtom } from '$state/settings'; +import { settingsAtom, ShowRoomIcon } from '$state/settings'; import { getRoomNotificationMode, useRoomsNotificationPreferencesContext, @@ -62,6 +62,9 @@ import { import { UseStateProvider } from '$components/UseStateProvider'; import { JoinAddressPrompt } from '$components/join-address-prompt'; import { useHomeRooms } from './useHomeRooms'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; +import { ScreenSize, useScreenSizeContext } from '$hooks/useScreenSize'; type HomeMenuProps = { requestClose: () => void; @@ -200,6 +203,19 @@ export function Home() { const roomToUnread = useAtomValue(roomToUnreadAtom); const navigate = useNavigate(); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); + + const [showRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); + const showIcons = () => { + if (showRoomIcon === ShowRoomIcon.Always) return true; + if (showRoomIcon === ShowRoomIcon.Never) return false; + return curWidth < 96; + }; + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); + const selectedRoomId = useSelectedRoom(); const createRoomSelected = useHomeCreateSelected(); const searchSelected = useHomeSearchSelected(); @@ -233,132 +249,150 @@ export function Home() { closedCategories.has(categoryId) ); + const screenSize = useScreenSizeContext(); + return ( - - - {noRoomToDisplay ? ( - - ) : ( - - - - - navigate(getHomeCreatePath())}> - - - - - - - - Create Room - - - - - - - - {(open, setOpen) => ( - <> - - setOpen(true)}> - - - - - - - - Join with Address - - + <> + + + + {noRoomToDisplay ? ( + + ) : ( + + + + + navigate(getHomeCreatePath())}> + + + + + + + + Create Room + - - - - {open && ( - setOpen(false)} - onOpen={(roomIdOrAlias, viaServers, eventId) => { - setOpen(false); - const path = getHomeRoomPath(roomIdOrAlias, eventId); - navigate( - viaServers - ? withSearchParam(path, { - viaServers: encodeSearchParamValueArray(viaServers), - }) - : path - ); - }} - /> + + + + + + {(open, setOpen) => ( + <> + + setOpen(true)}> + + + + + + + + Join with Address + + + + + + + {open && ( + setOpen(false)} + onOpen={(roomIdOrAlias, viaServers, eventId) => { + setOpen(false); + const path = getHomeRoomPath(roomIdOrAlias, eventId); + navigate( + viaServers + ? withSearchParam(path, { + viaServers: encodeSearchParamValueArray(viaServers), + }) + : path + ); + }} + /> + )} + )} - - )} - - - - - - - - - - - Message Search - - - - - - - - - - - Rooms - - -
- {virtualizer.getVirtualItems().map((vItem) => { - const roomId = sortedRooms[vItem.index]; - if (!roomId) return null; - const room = mx.getRoom(roomId); - if (!room) return null; - const selected = selectedRoomId === roomId; - - return ( - + + + + + + + + + + Message Search + + + + + + + + + + - - - ); - })} -
-
-
-
+ Rooms + + +
+ {virtualizer.getVirtualItems().map((vItem) => { + const roomId = sortedRooms[vItem.index]; + if (!roomId) return null; + const room = mx.getRoom(roomId); + if (!room) return null; + const selected = selectedRoomId === roomId; + + return ( + + + + ); + })} +
+ + + + )} +
+ + {!mobileOrTablet() && ( + )} - + ); } diff --git a/src/app/pages/client/inbox/Inbox.tsx b/src/app/pages/client/inbox/Inbox.tsx index 661435513..8b343527c 100644 --- a/src/app/pages/client/inbox/Inbox.tsx +++ b/src/app/pages/client/inbox/Inbox.tsx @@ -1,4 +1,4 @@ -import { Avatar, Box, Icon, Icons, Text } from 'folds'; +import { Avatar, Box, Icon, Icons, Text, toRem } from 'folds'; import { useAtomValue } from 'jotai'; import { NavCategory, NavItem, NavItemContent, NavLink } from '$components/nav'; import { getInboxInvitesPath, getInboxNotificationsPath } from '$pages/pathUtils'; @@ -7,6 +7,12 @@ import { UnreadBadge } from '$components/unread-badge'; import { allInvitesAtom } from '$state/room-list/inviteList'; import { useNavToActivePathMapper } from '$hooks/useNavToActivePathMapper'; import { PageNav, PageNavContent, PageNavHeader } from '$components/page'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { useSetting } from '$state/hooks/settings'; +import { settingsAtom } from '$state/settings'; +import { useEffect, useState } from 'react'; +import { mobileOrTablet } from '$utils/user-agent'; +import { ScreenSize, useScreenSizeContext } from '$hooks/useScreenSize'; function InvitesNavItem() { const invitesSelected = useInboxInvitesSelected(); @@ -43,41 +49,68 @@ export function Inbox() { useNavToActivePathMapper('inbox'); const notificationsSelected = useInboxNotificationsSelected(); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); + + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); + const screenSize = useScreenSizeContext(); + return ( - - - - - - Inbox - - - - + <> + + + + + + + Inbox + + + + - - - - - - - - - - - - - Notifications - - - - - - - - - - - + + + + + + + + + + + + + Notifications + + + + + + + + + + + + + {!mobileOrTablet() && ( + + )} + ); } diff --git a/src/app/pages/client/sidebar/SidebarResizer.css.ts b/src/app/pages/client/sidebar/SidebarResizer.css.ts new file mode 100644 index 000000000..4f270bf09 --- /dev/null +++ b/src/app/pages/client/sidebar/SidebarResizer.css.ts @@ -0,0 +1,19 @@ +import { style } from '@vanilla-extract/css'; +import { color } from 'folds'; + +export const SidebarResizer = style({ + width: '4px', + backgroundColor: 'inherit', + transition: '0.2s', + ':hover': {}, +}); +export const SidebarResizerHover = style({ + height: '100%', + zIndex: '100', + boxShadow: `0px 0px 32px 8px ${color.Primary.Main}`, +}); +export const SideBarResizerAnimation = style({ + width: '100%', + backgroundColor: color.Primary.Main, + transition: '0.2s', +}); diff --git a/src/app/pages/client/sidebar/SidebarResizer.tsx b/src/app/pages/client/sidebar/SidebarResizer.tsx new file mode 100644 index 000000000..89927bd02 --- /dev/null +++ b/src/app/pages/client/sidebar/SidebarResizer.tsx @@ -0,0 +1,77 @@ +// The disable is because the position should only update whenever the new one is updated +// oxlint-disable eslint-plugin-react-hooks/exhaustive-deps +import { Box } from 'folds'; +import * as css from '$pages/client/sidebar/SidebarResizer.css'; +import type { Dispatch, SetStateAction } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; + +export function SidebarResizer({ + sidebarWidth, + setSidebarWidth, + setCurWidth, + rightSided, + topSided, +}: { + sidebarWidth: number; + setSidebarWidth: (arg0: number) => void; + setCurWidth?: Dispatch>; + rightSided?: boolean; + topSided?: boolean; +}) { + const [isPointerOver, setIsPointerOver] = useState(false); + const [isPointerDown, setIsPointerDown] = useState(false); + const [oldX, setOldX] = useState(0); + const [interimX, setInterimX] = useState(0); + const [newX, setNewX] = useState(0); + + useEffect(() => { + const change = rightSided ? -(oldX - newX) : oldX - newX; + if (change) setSidebarWidth(Math.min(Math.max(sidebarWidth - change, 0), 1200)); + }, [newX]); + + useEffect(() => { + const change = rightSided ? -(oldX - interimX) : oldX - interimX; + if (change && setCurWidth) setCurWidth(Math.min(Math.max(sidebarWidth - change, 0), 1200)); + }, [interimX]); + + const onPointerMove = useCallback((e: PointerEvent) => { + e.preventDefault(); + setInterimX(topSided ? e.clientY : e.clientX); + }, []); + const onPointerUp = useCallback((e: PointerEvent) => { + e.preventDefault(); + setNewX(topSided ? e.clientY : e.clientX); + setIsPointerDown(false); + window.removeEventListener('pointerup', onPointerUp); + window.removeEventListener('pointermove', onPointerMove); + }, []); + + const onPointerDown = useCallback( + (e: React.PointerEvent) => { + e.preventDefault(); + setOldX(topSided ? e.clientY : e.clientX); + setIsPointerDown(true); + window.addEventListener('pointerup', onPointerUp); + window.addEventListener('pointermove', onPointerMove); + }, + [onPointerUp, onPointerMove] + ); + + return ( + setIsPointerOver(true)} + onPointerLeave={() => setIsPointerOver(false)} + onPointerDown={onPointerDown} + style={{ + width: topSided ? '100%' : '4px', + height: topSided ? '4px' : '100%', + }} + > + + + ); +} diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index c4994d829..eb501e5ff 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -62,7 +62,7 @@ import { stopPropagation } from '$utils/keyboard'; import { getMatrixToRoom } from '$plugins/matrix-to'; import { getViaServers } from '$plugins/via-servers'; import { useSetting } from '$state/hooks/settings'; -import { settingsAtom } from '$state/settings'; +import { settingsAtom, ShowRoomIcon } from '$state/settings'; import { getRoomNotificationMode, useRoomsNotificationPreferencesContext, @@ -80,6 +80,8 @@ import { lastVisitedRoomIdAtom } from '$state/room/lastRoom'; import { SwipeableOverlayWrapper } from '$components/SwipeableOverlayWrapper'; import { useCallEmbed } from '$hooks/useCallEmbed'; import { createDebugLogger } from '$utils/debugLogger'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { ScreenSize, useScreenSizeContext } from '$hooks/useScreenSize'; const debugLog = createDebugLogger('Space'); @@ -389,6 +391,19 @@ export function Space() { const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]); const notificationPreferences = useRoomsNotificationPreferencesContext(); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); + + const [showRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); + const showIcons = () => { + if (showRoomIcon === ShowRoomIcon.Always) return true; + if (showRoomIcon === ShowRoomIcon.Never) return false; + return curWidth < 96; + }; + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); + const tombstoneEvent = useStateEvent(space, EventType.RoomTombstone); const selectedRoomId = useSelectedRoom(); const lobbySelected = useSpaceLobbySelected(spaceIdOrAlias); @@ -710,143 +725,160 @@ export function Space() { } }, [lastRoomId, spaceIdOrAlias, mx, navigate]); + const screenSize = useScreenSizeContext(); + return ( - - - - - - {tombstoneEvent && ( - - )} - - - - - - - - - - - Lobby - - - - - - - - - - - - - - - - Message Search - - - - - - - - - {virtualizedItems.map((vItem) => { - const hierarchyItem = hierarchy[vItem.index]; - if (!hierarchyItem) return null; - const { roomId, depth: itemDepth } = hierarchyItem; - const depth = itemDepth ?? 0; - const room = mx.getRoom(roomId); - const renderDepth = room?.isSpaceRoom() ? depth - 2 : depth - 1; - if (!room) return null; - if (depth === subspaceHierarchyLimit && room.isSpaceRoom()) { - return ( - -
- -
-
- ); - } - - const paddingTop = getCategoryPadding(depth); - const paddingLeft = `calc(${renderDepth} * ${config.space.S400})`; - - if (room.isSpaceRoom()) { - const categoryId = makeNavCategoryId(space.roomId, roomId); - const closedViaCategory = getInClosedCategories(space.roomId, roomId); - - return ( - -
- - + + + + + + + {tombstoneEvent && ( + + )} + + + + + + + + + + + Lobby + + + + + + + + + + + + + + + + Message Search + + + + + + + + + {virtualizedItems.map((vItem) => { + const hierarchyItem = hierarchy[vItem.index]; + if (!hierarchyItem) return null; + const { roomId, depth: itemDepth } = hierarchyItem; + const depth = itemDepth ?? 0; + const room = mx.getRoom(roomId); + const renderDepth = room?.isSpaceRoom() ? depth - 2 : depth - 1; + if (!room) return null; + if (depth === subspaceHierarchyLimit && room.isSpaceRoom()) { + return ( + +
- {roomId === space.roomId ? 'Rooms' : room?.name} - - -
-
- ); - } - - return ( - -
- -
-
- ); - })} - {getConnectorSVG(hierarchy, virtualizedItems)} -
-
-
-
-
+ +
+
+ ); + } + + const paddingTop = getCategoryPadding(depth); + const paddingLeft = `calc(${renderDepth} * ${config.space.S400})`; + + if (room.isSpaceRoom()) { + const categoryId = makeNavCategoryId(space.roomId, roomId); + const closedViaCategory = getInClosedCategories(space.roomId, roomId); + + return ( + +
+ + + {roomId === space.roomId ? 'Rooms' : room?.name} + + +
+
+ ); + } + + return ( + +
+ +
+
+ ); + })} + {getConnectorSVG(hierarchy, virtualizedItems)} +
+
+
+
+
+
+ {!mobileOrTablet() && ( + + )} + ); } diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 26985c66c..390b4e83b 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -21,6 +21,12 @@ export enum CaptionPosition { Hidden = 'hidden', Below = 'below', } + +export enum ShowRoomIcon { + Always = 'always', + Smart = 'smart', + Never = 'never', +} export type JumboEmojiSize = 'none' | 'extraSmall' | 'small' | 'normal' | 'large' | 'extraLarge'; export type ThemeRemoteFavorite = { @@ -149,6 +155,13 @@ export interface Settings { mentionInReplies: boolean; showPersonaSetting: boolean; closeFoldersByDefault: boolean; + showRoomIcon: ShowRoomIcon; + roomSidebarWidth: number; + memberSidebarWidth: number; + threadSidebarWidth: number; + threadRootHeight: number; + vcmsgSidebarWidth: number; + widgetSidebarWidth: number; // furry stuff renderAnimals: boolean; @@ -268,7 +281,13 @@ export const defaultSettings: Settings = { mentionInReplies: true, showPersonaSetting: false, closeFoldersByDefault: false, - + showRoomIcon: ShowRoomIcon.Smart, + roomSidebarWidth: 256, + memberSidebarWidth: 262, + threadSidebarWidth: 440, + threadRootHeight: 220, + vcmsgSidebarWidth: 399, + widgetSidebarWidth: 420, // furry stuff renderAnimals: true,