From 8fcb94e956c084af375a726713458e652a5a4a61 Mon Sep 17 00:00:00 2001 From: heaven Date: Wed, 3 Jun 2026 11:53:06 +0300 Subject: [PATCH] refactor: delete the dead SidebarNav rail and its sidebar tab and primitive subtree --- src/app/components/sidebar/Sidebar.css.ts | 261 ----- src/app/components/sidebar/Sidebar.tsx | 8 - src/app/components/sidebar/SidebarContent.tsx | 19 - src/app/components/sidebar/SidebarItem.tsx | 81 -- src/app/components/sidebar/SidebarStack.tsx | 10 - .../sidebar/SidebarStackSeparator.tsx | 13 - src/app/components/sidebar/index.ts | 5 - src/app/pages/MobileFriendly.tsx | 44 - src/app/pages/Router.tsx | 9 +- src/app/pages/client/SidebarNav.tsx | 50 - src/app/pages/client/sidebar/DirectTab.tsx | 142 --- src/app/pages/client/sidebar/ExploreTab.tsx | 68 -- src/app/pages/client/sidebar/SearchTab.tsx | 25 - src/app/pages/client/sidebar/SettingsTab.tsx | 59 -- src/app/pages/client/sidebar/SpaceTabs.tsx | 894 ------------------ .../pages/client/sidebar/UnverifiedTab.css.ts | 30 - .../pages/client/sidebar/UnverifiedTab.tsx | 98 -- src/app/pages/client/sidebar/index.ts | 6 - 18 files changed, 3 insertions(+), 1819 deletions(-) delete mode 100644 src/app/components/sidebar/Sidebar.css.ts delete mode 100644 src/app/components/sidebar/Sidebar.tsx delete mode 100644 src/app/components/sidebar/SidebarContent.tsx delete mode 100644 src/app/components/sidebar/SidebarItem.tsx delete mode 100644 src/app/components/sidebar/SidebarStack.tsx delete mode 100644 src/app/components/sidebar/SidebarStackSeparator.tsx delete mode 100644 src/app/components/sidebar/index.ts delete mode 100644 src/app/pages/client/SidebarNav.tsx delete mode 100644 src/app/pages/client/sidebar/DirectTab.tsx delete mode 100644 src/app/pages/client/sidebar/ExploreTab.tsx delete mode 100644 src/app/pages/client/sidebar/SearchTab.tsx delete mode 100644 src/app/pages/client/sidebar/SettingsTab.tsx delete mode 100644 src/app/pages/client/sidebar/SpaceTabs.tsx delete mode 100644 src/app/pages/client/sidebar/UnverifiedTab.css.ts delete mode 100644 src/app/pages/client/sidebar/UnverifiedTab.tsx delete mode 100644 src/app/pages/client/sidebar/index.ts diff --git a/src/app/components/sidebar/Sidebar.css.ts b/src/app/components/sidebar/Sidebar.css.ts deleted file mode 100644 index 41197b3f..00000000 --- a/src/app/components/sidebar/Sidebar.css.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { createVar, style } from '@vanilla-extract/css'; -import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { color, config, DefaultReset, Disabled, FocusOutline, toRem } from 'folds'; -import { ContainerColor } from '../../styles/ContainerColor.css'; - -export const Sidebar = style([ - DefaultReset, - { - width: toRem(66), - backgroundColor: color.Background.Container, - borderRight: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`, - - display: 'flex', - flexDirection: 'column', - color: color.Background.OnContainer, - }, -]); - -export const SidebarStack = style([ - DefaultReset, - { - width: '100%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - gap: config.space.S300, - padding: `${config.space.S300} 0`, - }, -]); - -const DropLineDist = createVar(); -export const DropTarget = style({ - vars: { - [DropLineDist]: toRem(-8), - }, - - selectors: { - '&[data-inside-folder=true]': { - vars: { - [DropLineDist]: toRem(-6), - }, - }, - '&[data-drop-child=true]': { - outline: `${config.borderWidth.B700} solid ${color.Success.Main}`, - borderRadius: config.radii.R400, - }, - '&[data-drop-above=true]::after, &[data-drop-below=true]::after': { - content: '', - display: 'block', - position: 'absolute', - left: toRem(0), - width: '100%', - height: config.borderWidth.B700, - backgroundColor: color.Success.Main, - }, - '&[data-drop-above=true]::after': { - top: DropLineDist, - }, - '&[data-drop-below=true]::after': { - bottom: DropLineDist, - }, - }, -}); - -const PUSH_X = 2; -export const SidebarItem = recipe({ - base: [ - DefaultReset, - { - minWidth: toRem(42), - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - position: 'relative', - transition: 'transform 200ms cubic-bezier(0, 0.8, 0.67, 0.97)', - - selectors: { - '&::before': { - content: '', - display: 'none', - position: 'absolute', - left: toRem(-11.5 - PUSH_X), - width: toRem(3 + PUSH_X), - height: toRem(16), - borderRadius: `0 ${toRem(4)} ${toRem(4)} 0`, - background: 'CurrentColor', - transition: 'height 200ms linear', - }, - }, - '@media': { - '(hover: hover) and (pointer: fine)': { - selectors: { - '&:hover': { - transform: `translateX(${toRem(PUSH_X)})`, - }, - '&:hover::before': { - display: 'block', - width: toRem(3), - }, - }, - }, - }, - }, - Disabled, - DropTarget, - ], - variants: { - active: { - true: { - selectors: { - '&::before': { - display: 'block', - height: toRem(24), - }, - }, - '@media': { - '(hover: hover) and (pointer: fine)': { - selectors: { - '&:hover::before': { - width: toRem(3 + PUSH_X), - }, - }, - }, - }, - }, - }, - }, -}); -export type SidebarItemVariants = RecipeVariants; - -export const SidebarItemBadge = recipe({ - base: [ - DefaultReset, - { - pointerEvents: 'none', - position: 'absolute', - zIndex: 1, - lineHeight: 0, - }, - ], - variants: { - hasCount: { - true: { - top: toRem(-6), - left: toRem(-6), - }, - false: { - top: toRem(-2), - left: toRem(-2), - }, - }, - }, - defaultVariants: { - hasCount: false, - }, -}); -export type SidebarItemBadgeVariants = RecipeVariants; - -export const SidebarAvatar = recipe({ - base: [ - { - selectors: { - 'button&': { - cursor: 'pointer', - }, - }, - }, - ], - variants: { - size: { - '200': { - width: toRem(16), - height: toRem(16), - fontSize: toRem(10), - lineHeight: config.lineHeight.T200, - letterSpacing: config.letterSpacing.T200, - }, - '300': { - width: toRem(34), - height: toRem(34), - }, - '400': { - width: toRem(42), - height: toRem(42), - }, - }, - outlined: { - true: { - border: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`, - }, - }, - }, - defaultVariants: { - size: '400', - }, -}); -export type SidebarAvatarVariants = RecipeVariants; - -export const SidebarFolder = recipe({ - base: [ - ContainerColor({ variant: 'Background' }), - { - padding: config.space.S100, - width: toRem(42), - minHeight: toRem(42), - display: 'flex', - flexWrap: 'wrap', - outline: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`, - position: 'relative', - - selectors: { - 'button&': { - cursor: 'pointer', - }, - }, - }, - FocusOutline, - DropTarget, - ], - variants: { - state: { - Close: { - gap: toRem(2), - borderRadius: config.radii.R400, - }, - Open: { - paddingLeft: 0, - paddingRight: 0, - flexDirection: 'column', - alignItems: 'center', - gap: config.space.S200, - borderRadius: config.radii.R500, - }, - }, - }, - defaultVariants: { - state: 'Close', - }, -}); -export type SidebarFolderVariants = RecipeVariants; - -export const SidebarFolderDropTarget = recipe({ - base: { - width: '100%', - height: toRem(8), - position: 'absolute', - left: 0, - }, - variants: { - position: { - Top: { - top: toRem(-4), - }, - Bottom: { - bottom: toRem(-4), - }, - }, - }, -}); -export type SidebarFolderDropTargetVariants = RecipeVariants; diff --git a/src/app/components/sidebar/Sidebar.tsx b/src/app/components/sidebar/Sidebar.tsx deleted file mode 100644 index 7caf1b21..00000000 --- a/src/app/components/sidebar/Sidebar.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import classNames from 'classnames'; -import { as } from 'folds'; -import React from 'react'; -import * as css from './Sidebar.css'; - -export const Sidebar = as<'div'>(({ as: AsSidebar = 'div', className, ...props }, ref) => ( - -)); diff --git a/src/app/components/sidebar/SidebarContent.tsx b/src/app/components/sidebar/SidebarContent.tsx deleted file mode 100644 index b09ea64a..00000000 --- a/src/app/components/sidebar/SidebarContent.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { ReactNode } from 'react'; -import { Box } from 'folds'; - -type SidebarContentProps = { - scrollable: ReactNode; - sticky: ReactNode; -}; -export function SidebarContent({ scrollable, sticky }: SidebarContentProps) { - return ( - <> - - {scrollable} - - - {sticky} - - - ); -} diff --git a/src/app/components/sidebar/SidebarItem.tsx b/src/app/components/sidebar/SidebarItem.tsx deleted file mode 100644 index b23772bb..00000000 --- a/src/app/components/sidebar/SidebarItem.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import classNames from 'classnames'; -import { as, Avatar, Text, Tooltip, TooltipProvider, toRem } from 'folds'; -import React, { ComponentProps, ReactNode, RefCallback } from 'react'; -import * as css from './Sidebar.css'; - -export const SidebarItem = as<'div', css.SidebarItemVariants>( - ({ as: AsSidebarAvatarBox = 'div', className, active, ...props }, ref) => ( - - ) -); - -export const SidebarItemBadge = as<'div', css.SidebarItemBadgeVariants>( - ({ as: AsSidebarBadgeBox = 'div', className, hasCount, ...props }, ref) => ( - - ) -); - -export function SidebarItemTooltip({ - tooltip, - children, -}: { - tooltip?: ReactNode | string; - children: (triggerRef: RefCallback) => ReactNode; -}) { - if (!tooltip) { - return children(() => undefined); - } - - return ( - - {tooltip} - - } - > - {children} - - ); -} - -export const SidebarAvatar = as<'div', css.SidebarAvatarVariants & ComponentProps>( - ({ className, size, outlined, radii, ...props }, ref) => ( - - ) -); - -export const SidebarFolder = as<'div', css.SidebarFolderVariants>( - ({ as: AsSidebarFolder = 'div', className, state, ...props }, ref) => ( - - ) -); - -export const SidebarFolderDropTarget = as<'div', css.SidebarFolderDropTargetVariants>( - ({ as: AsSidebarFolderDropTarget = 'div', className, position, ...props }, ref) => ( - - ) -); diff --git a/src/app/components/sidebar/SidebarStack.tsx b/src/app/components/sidebar/SidebarStack.tsx deleted file mode 100644 index c0e976c8..00000000 --- a/src/app/components/sidebar/SidebarStack.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { as } from 'folds'; -import * as css from './Sidebar.css'; - -export const SidebarStack = as<'div'>( - ({ as: AsSidebarStack = 'div', className, ...props }, ref) => ( - - ) -); diff --git a/src/app/components/sidebar/SidebarStackSeparator.tsx b/src/app/components/sidebar/SidebarStackSeparator.tsx deleted file mode 100644 index 110341ca..00000000 --- a/src/app/components/sidebar/SidebarStackSeparator.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { Line, toRem } from 'folds'; - -export function SidebarStackSeparator() { - return ( - - ); -} diff --git a/src/app/components/sidebar/index.ts b/src/app/components/sidebar/index.ts deleted file mode 100644 index 49e15b3e..00000000 --- a/src/app/components/sidebar/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './Sidebar'; -export * from './SidebarItem'; -export * from './SidebarContent'; -export * from './SidebarStack'; -export * from './SidebarStackSeparator'; diff --git a/src/app/pages/MobileFriendly.tsx b/src/app/pages/MobileFriendly.tsx index ebeedb7c..225b1b80 100644 --- a/src/app/pages/MobileFriendly.tsx +++ b/src/app/pages/MobileFriendly.tsx @@ -1,50 +1,6 @@ import { ReactNode } from 'react'; import { useMatch } from 'react-router-dom'; import { ScreenSize, useScreenSizeContext } from '../hooks/useScreenSize'; -import { - BOTS_PATH, - CHANNELS_PATH, - CHANNELS_SPACE_PATH, - DIRECT_PATH, - EXPLORE_PATH, - HOME_PATH, - SPACE_PATH, -} from './paths'; - -type MobileFriendlyClientNavProps = { - children: ReactNode; -}; -export function MobileFriendlyClientNav({ children }: MobileFriendlyClientNavProps) { - const screenSize = useScreenSizeContext(); - const homeMatch = useMatch({ path: HOME_PATH, caseSensitive: true, end: true }); - const directMatch = useMatch({ path: DIRECT_PATH, caseSensitive: true, end: true }); - const spaceMatch = useMatch({ path: SPACE_PATH, caseSensitive: true, end: true }); - const exploreMatch = useMatch({ path: EXPLORE_PATH, caseSensitive: true, end: true }); - const botsMatch = useMatch({ path: BOTS_PATH, caseSensitive: true, end: true }); - const channelsMatch = useMatch({ path: CHANNELS_PATH, caseSensitive: true, end: true }); - const channelsSpaceMatch = useMatch({ - path: CHANNELS_SPACE_PATH, - caseSensitive: true, - end: true, - }); - - if ( - screenSize === ScreenSize.Mobile && - !( - homeMatch || - directMatch || - spaceMatch || - exploreMatch || - botsMatch || - channelsMatch || - channelsSpaceMatch - ) - ) { - return null; - } - - return children; -} type MobileFriendlyPageNavProps = { path: string; diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index bf1b7f8f..0b13144b 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -240,12 +240,9 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) - {/* SidebarNav (66px icon-rail) временно отключён — - позже растащим его 5 кнопок (Settings, Search, - Explore, Create, Unverified) по новым поверхностям - интерфейса. Сам компонент жив: - src/app/pages/client/SidebarNav.tsx + ./sidebar/*. - См. docs/plans/redesign_overview.md → sidebar_cleanup. */} + {/* Старый 66px SidebarNav-рельс удалён в Dawn remnant + sweep — его поверхности живут в сегментах Direct/ + Channels/Bots. nav остаётся null. */} diff --git a/src/app/pages/client/SidebarNav.tsx b/src/app/pages/client/SidebarNav.tsx deleted file mode 100644 index e4cc45f6..00000000 --- a/src/app/pages/client/SidebarNav.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useRef } from 'react'; -import { Scroll } from 'folds'; - -import { - Sidebar, - SidebarContent, - SidebarStackSeparator, - SidebarStack, -} from '../../components/sidebar'; -import { - DirectTab, - SpaceTabs, - ExploreTab, - SettingsTab, - UnverifiedTab, - SearchTab, -} from './sidebar'; - -export function SidebarNav() { - const scrollRef = useRef(null); - - return ( - - - - - - - - - - - - } - sticky={ - <> - - - - - - - - } - /> - - ); -} diff --git a/src/app/pages/client/sidebar/DirectTab.tsx b/src/app/pages/client/sidebar/DirectTab.tsx deleted file mode 100644 index 91d31198..00000000 --- a/src/app/pages/client/sidebar/DirectTab.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React, { MouseEventHandler, forwardRef, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { Box, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; -import { useTranslation } from 'react-i18next'; -import FocusTrap from 'focus-trap-react'; -import { useAtomValue } from 'jotai'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { roomToUnreadAtom } from '../../../state/room/roomToUnread'; -import { getDirectPath, joinPathComponent } from '../../pathUtils'; -import { isNativePlatform } from '../../../utils/capacitor'; -import { useRoomsUnread } from '../../../state/hooks/unread'; -import { - SidebarAvatar, - SidebarItem, - SidebarItemBadge, - SidebarItemTooltip, -} from '../../../components/sidebar'; -import { useDirectSelected } from '../../../hooks/router/useDirectSelected'; -import { UnreadBadge } from '../../../components/unread-badge'; -import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; -import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath'; -import { useDirectRooms } from '../direct/useDirectRooms'; -import { markAsRead } from '../../../utils/notifications'; -import { stopPropagation } from '../../../utils/keyboard'; -import { settingsAtom } from '../../../state/settings'; -import { useSetting } from '../../../state/hooks/settings'; - -type DirectMenuProps = { - requestClose: () => void; -}; -const DirectMenu = forwardRef(({ requestClose }, ref) => { - const { t } = useTranslation(); - const orphanRooms = useDirectRooms(); - const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); - const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); - const mx = useMatrixClient(); - - const handleMarkAsRead = () => { - if (!unread) return; - orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity)); - requestClose(); - }; - - return ( - - - } - radii="300" - aria-disabled={!unread} - > - - {t('Direct.mark_as_read')} - - - - - ); -}); - -export function DirectTab() { - const { t } = useTranslation(); - const navigate = useNavigate(); - const screenSize = useScreenSizeContext(); - const navToActivePath = useAtomValue(useNavToActivePathAtom()); - - // After P3c the Direct tab is universal — every joined non-space room lives - // here. The badge / mark-as-read scope must mirror the panel itself - // (`useDirectRooms()` = orphan ∪ m.direct), otherwise unread groups would - // count toward the panel but not the sidebar dot. - const directs = useDirectRooms(); - const directUnread = useRoomsUnread(directs, roomToUnreadAtom); - const [menuAnchor, setMenuAnchor] = useState(); - - const directSelected = useDirectSelected(); - - const handleDirectClick = () => { - const navOpts = { replace: isNativePlatform() }; - const activePath = navToActivePath.get('direct'); - if (activePath && screenSize !== ScreenSize.Mobile) { - navigate(joinPathComponent(activePath), navOpts); - return; - } - - navigate(getDirectPath(), navOpts); - }; - - const handleContextMenu: MouseEventHandler = (evt) => { - evt.preventDefault(); - const cords = evt.currentTarget.getBoundingClientRect(); - setMenuAnchor((currentState) => { - if (currentState) return undefined; - return cords; - }); - }; - return ( - - - {(triggerRef) => ( - - - - )} - - {directUnread && ( - 0}> - 0} count={directUnread.total} /> - - )} - {menuAnchor && ( - setMenuAnchor(undefined), - clickOutsideDeactivates: true, - isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', - isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', - escapeDeactivates: stopPropagation, - }} - > - setMenuAnchor(undefined)} /> - - } - /> - )} - - ); -} diff --git a/src/app/pages/client/sidebar/ExploreTab.tsx b/src/app/pages/client/sidebar/ExploreTab.tsx deleted file mode 100644 index da452202..00000000 --- a/src/app/pages/client/sidebar/ExploreTab.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { Icon, Icons } from 'folds'; -import { useNavigate } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; -import { useAtomValue } from 'jotai'; -import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar'; -import { useExploreSelected } from '../../../hooks/router/useExploreSelected'; -import { - getExploreFeaturedPath, - getExplorePath, - getExploreServerPath, - joinPathComponent, -} from '../../pathUtils'; -import { useClientConfig } from '../../../hooks/useClientConfig'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { getMxIdServer } from '../../../utils/matrix'; -import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; -import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath'; -import { isNativePlatform } from '../../../utils/capacitor'; - -export function ExploreTab() { - const { t } = useTranslation(); - const mx = useMatrixClient(); - const screenSize = useScreenSizeContext(); - const clientConfig = useClientConfig(); - const navigate = useNavigate(); - const navToActivePath = useAtomValue(useNavToActivePathAtom()); - - const exploreSelected = useExploreSelected(); - - const handleExploreClick = () => { - const navOpts = { replace: isNativePlatform() }; - if (screenSize === ScreenSize.Mobile) { - navigate(getExplorePath(), navOpts); - return; - } - - const activePath = navToActivePath.get('explore'); - if (activePath) { - navigate(joinPathComponent(activePath), navOpts); - return; - } - - if (clientConfig.featuredCommunities?.openAsDefault) { - navigate(getExploreFeaturedPath(), navOpts); - return; - } - const userId = mx.getUserId(); - const userServer = userId ? getMxIdServer(userId) : undefined; - if (userServer) { - navigate(getExploreServerPath(userServer), navOpts); - return; - } - navigate(getExplorePath(), navOpts); - }; - - return ( - - - {(triggerRef) => ( - - - - )} - - - ); -} diff --git a/src/app/pages/client/sidebar/SearchTab.tsx b/src/app/pages/client/sidebar/SearchTab.tsx deleted file mode 100644 index 4377ad14..00000000 --- a/src/app/pages/client/sidebar/SearchTab.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { Icon, Icons } from 'folds'; -import { useAtom } from 'jotai'; -import { useTranslation } from 'react-i18next'; -import { SidebarAvatar, SidebarItem, SidebarItemTooltip } from '../../../components/sidebar'; -import { searchModalAtom } from '../../../state/searchModal'; - -export function SearchTab() { - const { t } = useTranslation(); - const [opened, setOpen] = useAtom(searchModalAtom); - - const open = () => setOpen(true); - - return ( - - - {(triggerRef) => ( - - - - )} - - - ); -} diff --git a/src/app/pages/client/sidebar/SettingsTab.tsx b/src/app/pages/client/sidebar/SettingsTab.tsx deleted file mode 100644 index dfa38295..00000000 --- a/src/app/pages/client/sidebar/SettingsTab.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { Text } from 'folds'; -import { useMatch, useNavigate } from 'react-router-dom'; -import { SidebarItem, SidebarItemTooltip, SidebarAvatar } from '../../../components/sidebar'; -import { UserAvatar } from '../../../components/user-avatar'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { useAuthedUserId } from '../../../hooks/useAuthedUserId'; -import { getMxIdLocalPart, mxcUrlToHttp } from '../../../utils/matrix'; -import { nameInitials } from '../../../utils/common'; -import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; -import { useUserProfile } from '../../../hooks/useUserProfile'; -import { SETTINGS_PATH } from '../../paths'; -import { getSettingsPath } from '../../pathUtils'; -import { SETTINGS_FROM_IN_APP_STATE } from '../../../features/settings/SettingsScreen'; - -export function SettingsTab() { - const mx = useMatrixClient(); - const useAuthentication = useMediaAuthentication(); - const navigate = useNavigate(); - const settingsMatch = useMatch({ path: SETTINGS_PATH, caseSensitive: true, end: false }); - const userId = useAuthedUserId(); - const profile = useUserProfile(userId); - - const displayName = profile.displayName ?? getMxIdLocalPart(userId) ?? userId; - const avatarUrl = profile.avatarUrl - ? mxcUrlToHttp(mx, profile.avatarUrl, useAuthentication, 96, 96, 'crop') ?? undefined - : undefined; - - // Use `replace` when re-tapping the icon from inside /settings (still - // visible at the top of the sidebar). Push from another route, replace - // from inside Settings, so the back-stack doesn't accumulate - // /settings → /settings → /direct/ chains. - // - // `state: SETTINGS_FROM_IN_APP_STATE` marks the entry as in-app so - // SettingsScreen's close fallback knows `navigate(-1)` is safe (vs - // a cold-loaded /settings where the previous history entry might be - // cross-origin and would bounce the user out of Vojo). - const openSettings = () => - navigate(getSettingsPath(), { - replace: !!settingsMatch, - state: SETTINGS_FROM_IN_APP_STATE, - }); - - return ( - - - {(triggerRef) => ( - - {nameInitials(displayName)}} - /> - - )} - - - ); -} diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx deleted file mode 100644 index 5766b5bb..00000000 --- a/src/app/pages/client/sidebar/SpaceTabs.tsx +++ /dev/null @@ -1,894 +0,0 @@ -import React, { - MouseEventHandler, - ReactNode, - RefObject, - forwardRef, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { useMatch, useNavigate } from 'react-router-dom'; -import { - Box, - Icon, - IconButton, - Icons, - Line, - Menu, - MenuItem, - PopOut, - RectCords, - Text, - color, - config, - toRem, -} from 'folds'; -import { useAtom, useAtomValue } from 'jotai'; -import { Room } from 'matrix-js-sdk'; -import { - draggable, - dropTargetForElements, - monitorForElements, -} from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; -import { - attachInstruction, - extractInstruction, - Instruction, -} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item'; -import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element'; -import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine'; -import FocusTrap from 'focus-trap-react'; -import { - useOrphanSpaces, - useRecursiveChildScopeFactory, - useSpaceChildren, -} from '../../../state/hooks/roomList'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { roomToParentsAtom } from '../../../state/room/roomToParents'; -import { allRoomsAtom } from '../../../state/room-list/roomList'; -import { getSpaceLobbyPath, getSpacePath, joinPathComponent } from '../../pathUtils'; -import { CHANNELS_PATH } from '../../paths'; -import { - SidebarAvatar, - SidebarItem, - SidebarItemBadge, - SidebarItemTooltip, - SidebarStack, - SidebarStackSeparator, - SidebarFolder, - SidebarFolderDropTarget, -} from '../../../components/sidebar'; -import { RoomUnreadProvider, RoomsUnreadProvider } from '../../../components/RoomUnreadProvider'; -import { useSelectedSpace } from '../../../hooks/router/useSelectedSpace'; -import { UnreadBadge } from '../../../components/unread-badge'; -import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../../utils/matrix'; -import { isNativePlatform } from '../../../utils/capacitor'; -import { RoomAvatar } from '../../../components/room-avatar'; -import { nameInitials, randomStr } from '../../../utils/common'; -import { - ISidebarFolder, - SidebarItems, - TSidebarItem, - makeVojoSpacesContent, - parseSidebar, - sidebarItemWithout, - useSidebarItems, -} from '../../../hooks/useSidebarItems'; -import { AccountDataEvent } from '../../../../types/matrix/accountData'; -import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; -import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath'; -import { useOpenedSidebarFolderAtom } from '../../../state/hooks/openedSidebarFolder'; -import { usePowerLevels } from '../../../hooks/usePowerLevels'; -import { useRoomsUnread } from '../../../state/hooks/unread'; -import { roomToUnreadAtom } from '../../../state/room/roomToUnread'; -import { markAsRead } from '../../../utils/notifications'; -import { copyToClipboard } from '../../../utils/dom'; -import { stopPropagation } from '../../../utils/keyboard'; -import { getMatrixToRoom } from '../../../plugins/matrix-to'; -import { getViaServers } from '../../../plugins/via-servers'; -import { getRoomAvatarUrl } from '../../../utils/room'; -import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; -import { useSetting } from '../../../state/hooks/settings'; -import { settingsAtom } from '../../../state/settings'; -import { useOpenSpaceSettings } from '../../../state/hooks/spaceSettings'; -import { useRoomCreators } from '../../../hooks/useRoomCreators'; -import { useRoomPermissions } from '../../../hooks/useRoomPermissions'; -import { InviteUserPrompt } from '../../../components/invite-user-prompt'; - -type SpaceMenuProps = { - room: Room; - requestClose: () => void; - onUnpin?: (roomId: string) => void; -}; -const SpaceMenu = forwardRef( - ({ room, requestClose, onUnpin }, ref) => { - const mx = useMatrixClient(); - const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); - const roomToParents = useAtomValue(roomToParentsAtom); - const powerLevels = usePowerLevels(room); - const creators = useRoomCreators(room); - - const permissions = useRoomPermissions(creators, powerLevels); - const canInvite = permissions.action('invite', mx.getSafeUserId()); - const openSpaceSettings = useOpenSpaceSettings(); - - const [invitePrompt, setInvitePrompt] = useState(false); - - const allChild = useSpaceChildren( - allRoomsAtom, - room.roomId, - useRecursiveChildScopeFactory(mx, roomToParents) - ); - const unread = useRoomsUnread(allChild, roomToUnreadAtom); - - const handleMarkAsRead = () => { - allChild.forEach((childRoomId) => markAsRead(mx, childRoomId, hideActivity)); - requestClose(); - }; - - const handleUnpin = () => { - onUnpin?.(room.roomId); - requestClose(); - }; - - const handleCopyLink = () => { - const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId); - const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room); - copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers)); - requestClose(); - }; - - const handleInvite = () => { - setInvitePrompt(true); - }; - - const handleRoomSettings = () => { - openSpaceSettings(room.roomId); - requestClose(); - }; - - return ( - - {invitePrompt && room && ( - { - setInvitePrompt(false); - requestClose(); - }} - /> - )} - - } - radii="300" - disabled={!unread} - > - - Mark as Read - - - {onUnpin && ( - } - > - - Unpin - - - )} - - - - } - radii="300" - aria-pressed={invitePrompt} - disabled={!canInvite} - > - - Invite - - - } - radii="300" - > - - Copy Link - - - } - radii="300" - > - - Space Settings - - - - - ); - } -); - -type InstructionType = Instruction['type']; -type FolderDraggable = { - folder: ISidebarFolder; - spaceId?: string; - open?: boolean; -}; -type SidebarDraggable = string | FolderDraggable; - -const useDraggableItem = ( - item: SidebarDraggable, - targetRef: RefObject, - onDragging: (item?: SidebarDraggable) => void, - dragHandleRef?: RefObject -): boolean => { - const [dragging, setDragging] = useState(false); - - useEffect(() => { - const target = targetRef.current; - const dragHandle = dragHandleRef?.current ?? undefined; - - return !target - ? undefined - : draggable({ - element: target, - dragHandle, - getInitialData: () => ({ item }), - onDragStart: () => { - setDragging(true); - onDragging?.(item); - }, - onDrop: () => { - setDragging(false); - onDragging?.(undefined); - }, - }); - }, [targetRef, dragHandleRef, item, onDragging]); - - return dragging; -}; - -const useDropTarget = ( - item: SidebarDraggable, - targetRef: RefObject -): Instruction | undefined => { - const [dropState, setDropState] = useState(); - - useEffect(() => { - const target = targetRef.current; - if (!target) return undefined; - - return dropTargetForElements({ - element: target, - canDrop: ({ source }) => { - const dragItem = source.data.item as SidebarDraggable; - return dragItem !== item; - }, - getData: ({ input, element }) => { - const block: Instruction['type'][] = ['reparent']; - if (typeof item === 'object' && item.spaceId) block.push('make-child'); - - const insData = attachInstruction( - {}, - { - input, - element, - currentLevel: 0, - indentPerLevel: 0, - mode: 'standard', - block, - } - ); - - const instruction: Instruction | null = extractInstruction(insData); - setDropState(instruction ?? undefined); - - return { - item, - instructionType: instruction ? instruction.type : undefined, - }; - }, - onDragLeave: () => setDropState(undefined), - onDrop: () => setDropState(undefined), - }); - }, [item, targetRef]); - - return dropState; -}; - -function useDropTargetInstruction( - item: SidebarDraggable, - targetRef: RefObject, - instructionType: T -): T | undefined { - const [dropState, setDropState] = useState(); - - useEffect(() => { - const target = targetRef.current; - if (!target) return undefined; - - return dropTargetForElements({ - element: target, - canDrop: ({ source }) => { - const dragItem = source.data.item as SidebarDraggable; - return dragItem !== item; - }, - getData: () => { - setDropState(instructionType); - - return { - item, - instructionType, - }; - }, - onDragLeave: () => setDropState(undefined), - onDrop: () => setDropState(undefined), - }); - }, [item, targetRef, instructionType]); - - return dropState; -} - -const useDnDMonitor = ( - scrollRef: RefObject, - onDragging: (dragItem?: SidebarDraggable) => void, - onReorder: ( - draggable: SidebarDraggable, - container: SidebarDraggable, - instruction: InstructionType - ) => void -) => { - useEffect(() => { - const scrollElement = scrollRef.current; - if (!scrollElement) { - throw Error('Scroll element ref not configured'); - } - - return combine( - monitorForElements({ - onDrop: ({ source, location }) => { - onDragging(undefined); - const { dropTargets } = location.current; - if (dropTargets.length === 0) return; - const item = source.data.item as SidebarDraggable; - const containerItem = dropTargets[0].data.item as SidebarDraggable; - const instructionType = dropTargets[0].data.instructionType as - | InstructionType - | undefined; - if (!instructionType) return; - onReorder(item, containerItem, instructionType); - }, - }), - autoScrollForElements({ - element: scrollElement, - }) - ); - }, [scrollRef, onDragging, onReorder]); -}; - -type SpaceTabProps = { - space: Room; - selected: boolean; - inChannels?: boolean; - onClick: MouseEventHandler; - folder?: ISidebarFolder; - onDragging: (dragItem?: SidebarDraggable) => void; - disabled?: boolean; - onUnpin?: (roomId: string) => void; -}; -function SpaceTab({ - space, - selected, - inChannels, - onClick, - folder, - onDragging, - disabled, - onUnpin, -}: SpaceTabProps) { - const mx = useMatrixClient(); - const useAuthentication = useMediaAuthentication(); - const targetRef = useRef(null); - - const spaceDraggable: SidebarDraggable = useMemo( - () => - folder - ? { - folder, - spaceId: space.roomId, - } - : space.roomId, - [folder, space] - ); - - useDraggableItem(spaceDraggable, targetRef, onDragging); - const dropState = useDropTarget(spaceDraggable, targetRef); - const dropType = dropState?.type; - - const [menuAnchor, setMenuAnchor] = useState(); - - const handleContextMenu: MouseEventHandler = (evt) => { - evt.preventDefault(); - const cords = evt.currentTarget.getBoundingClientRect(); - setMenuAnchor((currentState) => { - if (currentState) return undefined; - return cords; - }); - }; - - return ( - - {(unread) => ( - - - {(triggerRef) => ( - - ( - {nameInitials(space.name, 2)} - )} - /> - - )} - - {unread && ( - 0}> - 0} count={unread.total} /> - - )} - {menuAnchor && ( - setMenuAnchor(undefined), - clickOutsideDeactivates: true, - isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', - isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', - escapeDeactivates: stopPropagation, - }} - > - setMenuAnchor(undefined)} - onUnpin={onUnpin} - /> - - } - /> - )} - - )} - - ); -} - -type OpenedSpaceFolderProps = { - folder: ISidebarFolder; - onClose: MouseEventHandler; - children?: ReactNode; -}; -function OpenedSpaceFolder({ folder, onClose, children }: OpenedSpaceFolderProps) { - const aboveTargetRef = useRef(null); - const belowTargetRef = useRef(null); - - const spaceDraggable: SidebarDraggable = useMemo(() => ({ folder, open: true }), [folder]); - - const orderAbove = useDropTargetInstruction(spaceDraggable, aboveTargetRef, 'reorder-above'); - const orderBelow = useDropTargetInstruction(spaceDraggable, belowTargetRef, 'reorder-below'); - - return ( - - - - - - - - {children} - - - ); -} - -type ClosedSpaceFolderProps = { - folder: ISidebarFolder; - selected: boolean; - onOpen: MouseEventHandler; - onDragging: (dragItem?: SidebarDraggable) => void; - disabled?: boolean; -}; -function ClosedSpaceFolder({ - folder, - selected, - onOpen, - onDragging, - disabled, -}: ClosedSpaceFolderProps) { - const mx = useMatrixClient(); - const useAuthentication = useMediaAuthentication(); - const handlerRef = useRef(null); - - const spaceDraggable: FolderDraggable = useMemo(() => ({ folder }), [folder]); - useDraggableItem(spaceDraggable, handlerRef, onDragging); - const dropState = useDropTarget(spaceDraggable, handlerRef); - const dropType = dropState?.type; - - const tooltipName = - folder.name ?? folder.content.map((i) => mx.getRoom(i)?.name ?? '').join(', ') ?? 'Unnamed'; - - return ( - - {(unread) => ( - - - {(tooltipRef) => ( - - {folder.content.map((sId) => { - const space = mx.getRoom(sId); - if (!space) return null; - - return ( - - ( - - {nameInitials(space.name, 2)} - - )} - /> - - ); - })} - - )} - - {unread && ( - 0}> - 0} count={unread.total} /> - - )} - - )} - - ); -} - -type SpaceTabsProps = { - scrollRef: RefObject; -}; -export function SpaceTabs({ scrollRef }: SpaceTabsProps) { - const navigate = useNavigate(); - const mx = useMatrixClient(); - const screenSize = useScreenSizeContext(); - const roomToParents = useAtomValue(roomToParentsAtom); - const orphanSpaces = useOrphanSpaces(mx, allRoomsAtom, roomToParents); - const [sidebarItems, localEchoSidebarItem] = useSidebarItems(orphanSpaces); - const navToActivePath = useAtomValue(useNavToActivePathAtom()); - const [openedFolder, setOpenedFolder] = useAtom(useOpenedSidebarFolderAtom()); - const [draggingItem, setDraggingItem] = useState(); - - useDnDMonitor( - scrollRef, - setDraggingItem, - useCallback( - (item, containerItem, instructionType) => { - const newItems: SidebarItems = []; - - const matchDest = (sI: TSidebarItem, dI: SidebarDraggable): boolean => { - if (typeof sI === 'string' && typeof dI === 'string') { - return sI === dI; - } - if (typeof sI === 'object' && typeof dI === 'object') { - return sI.id === dI.folder.id; - } - return false; - }; - const itemAsFolderContent = (i: SidebarDraggable): string[] => { - if (typeof i === 'string') { - return [i]; - } - if (i.spaceId) { - return [i.spaceId]; - } - return [...i.folder.content]; - }; - - sidebarItems.forEach((i) => { - const sameFolders = - typeof item === 'object' && - typeof containerItem === 'object' && - item.folder.id === containerItem.folder.id; - - // remove draggable space from current position or folder - if (!sameFolders && matchDest(i, item)) { - if (typeof item === 'object' && item.spaceId) { - const folderContent = item.folder.content.filter((s) => s !== item.spaceId); - if (folderContent.length === 0) { - // remove open state from local storage - setOpenedFolder({ type: 'DELETE', id: item.folder.id }); - return; - } - newItems.push({ - ...item.folder, - content: folderContent, - }); - } - return; - } - if (matchDest(i, containerItem)) { - // we can make child only if - // container item is space or closed folder - if (instructionType === 'make-child') { - const child: string[] = itemAsFolderContent(item); - if (typeof containerItem === 'string') { - const folder: ISidebarFolder = { - id: randomStr(), - content: [containerItem].concat(child), - }; - newItems.push(folder); - return; - } - newItems.push({ - ...containerItem.folder, - content: containerItem.folder.content.concat(child), - }); - return; - } - - // drop inside opened folder - // or reordering inside same folder - if (typeof containerItem === 'object' && containerItem.spaceId) { - const child = itemAsFolderContent(item); - const newContent: string[] = []; - containerItem.folder.content - .filter((sId) => !child.includes(sId)) - .forEach((sId) => { - if (sId === containerItem.spaceId) { - if (instructionType === 'reorder-below') { - newContent.push(sId, ...child); - } - if (instructionType === 'reorder-above') { - newContent.push(...child, sId); - } - return; - } - newContent.push(sId); - }); - const folder = { - ...containerItem.folder, - content: newContent, - }; - - newItems.push(folder); - return; - } - - // drop above or below space or closed/opened folder - if (typeof item === 'string') { - if (instructionType === 'reorder-below') newItems.push(i); - newItems.push(item); - if (instructionType === 'reorder-above') newItems.push(i); - } else if (item.spaceId) { - if (instructionType === 'reorder-above') { - newItems.push(item.spaceId); - } - if (sameFolders && typeof i === 'object') { - // remove from folder if placing around itself - const newI = { ...i, content: i.content.filter((sId) => sId !== item.spaceId) }; - if (newI.content.length > 0) newItems.push(newI); - } else { - newItems.push(i); - } - if (instructionType === 'reorder-below') { - newItems.push(item.spaceId); - } - } else { - if (instructionType === 'reorder-below') newItems.push(i); - newItems.push(item.folder); - if (instructionType === 'reorder-above') newItems.push(i); - } - return; - } - newItems.push(i); - }); - - const newSpacesContent = makeVojoSpacesContent(mx, newItems); - localEchoSidebarItem(parseSidebar(mx, orphanSpaces, newSpacesContent)); - mx.setAccountData(AccountDataEvent.VojoSpaces, newSpacesContent); - }, - [mx, sidebarItems, setOpenedFolder, localEchoSidebarItem, orphanSpaces] - ) - ); - - const selectedSpaceId = useSelectedSpace(); - // Highlights the avatar that owns the URL the user is on AND that they - // reached through the Channels segment. Drives the violet ring - // (color.Primary.Main) so the rail and the channels surface read as the same - // navigation, not two parallel ones. Without this, the user sees a - // selected outline that doesn't differentiate Space tab vs Channels - // segment activations. - const inChannelsSegment = !!useMatch({ - path: CHANNELS_PATH, - caseSensitive: true, - end: false, - }); - - const handleSpaceClick: MouseEventHandler = (evt) => { - const target = evt.currentTarget; - const targetSpaceId = target.getAttribute('data-id'); - if (!targetSpaceId) return; - - const spacePath = getSpacePath(getCanonicalAliasOrRoomId(mx, targetSpaceId)); - const navOpts = { replace: isNativePlatform() }; - if (screenSize === ScreenSize.Mobile) { - navigate(spacePath, navOpts); - return; - } - - const activePath = navToActivePath.get(targetSpaceId); - if (activePath && activePath.pathname.startsWith(spacePath)) { - navigate(joinPathComponent(activePath), navOpts); - return; - } - - navigate(getSpaceLobbyPath(getCanonicalAliasOrRoomId(mx, targetSpaceId)), navOpts); - }; - - const handleFolderToggle: MouseEventHandler = (evt) => { - const target = evt.currentTarget; - const targetFolderId = target.getAttribute('data-id'); - if (!targetFolderId) return; - - setOpenedFolder({ - type: openedFolder.has(targetFolderId) ? 'DELETE' : 'PUT', - id: targetFolderId, - }); - }; - - const handleUnpin = useCallback( - (roomId: string) => { - if (orphanSpaces.includes(roomId)) return; - const newItems = sidebarItemWithout(sidebarItems, roomId); - - const newSpacesContent = makeVojoSpacesContent(mx, newItems); - localEchoSidebarItem(parseSidebar(mx, orphanSpaces, newSpacesContent)); - mx.setAccountData(AccountDataEvent.VojoSpaces, newSpacesContent); - }, - [mx, sidebarItems, orphanSpaces, localEchoSidebarItem] - ); - - if (sidebarItems.length === 0) return null; - return ( - <> - - - {sidebarItems.map((item) => { - if (typeof item === 'object') { - if (openedFolder.has(item.id)) { - return ( - - {item.content.map((sId) => { - const space = mx.getRoom(sId); - if (!space) return null; - return ( - - ); - })} - - ); - } - - return ( - - ); - } - - const space = mx.getRoom(item); - if (!space) return null; - - return ( - - ); - })} - - - ); -} diff --git a/src/app/pages/client/sidebar/UnverifiedTab.css.ts b/src/app/pages/client/sidebar/UnverifiedTab.css.ts deleted file mode 100644 index cce17ccb..00000000 --- a/src/app/pages/client/sidebar/UnverifiedTab.css.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { keyframes, style } from '@vanilla-extract/css'; -import { color, toRem } from 'folds'; - -const pushRight = keyframes({ - from: { - transform: `translateX(${toRem(2)}) scale(1)`, - }, - to: { - transform: 'translateX(0) scale(1)', - }, -}); - -export const UnverifiedTab = style({ - animationName: pushRight, - animationDuration: '400ms', - animationIterationCount: 30, - animationDirection: 'alternate', -}); - -export const UnverifiedAvatar = style({ - backgroundColor: color.Critical.Container, - color: color.Critical.OnContainer, - borderColor: color.Critical.ContainerLine, -}); - -export const UnverifiedOtherAvatar = style({ - backgroundColor: color.Warning.Container, - color: color.Warning.OnContainer, - borderColor: color.Warning.ContainerLine, -}); diff --git a/src/app/pages/client/sidebar/UnverifiedTab.tsx b/src/app/pages/client/sidebar/UnverifiedTab.tsx deleted file mode 100644 index 79e9ccad..00000000 --- a/src/app/pages/client/sidebar/UnverifiedTab.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import { Badge, color, Icon, Icons, Text } from 'folds'; -import { useTranslation } from 'react-i18next'; -import { useMatch, useNavigate } from 'react-router-dom'; -import { - SidebarAvatar, - SidebarItem, - SidebarItemBadge, - SidebarItemTooltip, -} from '../../../components/sidebar'; -import { useDeviceIds, useDeviceList, useSplitCurrentDevice } from '../../../hooks/useDeviceList'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import * as css from './UnverifiedTab.css'; -import { - useDeviceVerificationStatus, - useUnverifiedDeviceCount, - VerificationStatus, -} from '../../../hooks/useDeviceVerificationStatus'; -import { useCrossSigningActive } from '../../../hooks/useCrossSigning'; -import { SETTINGS_PARAM_DEVICES } from '../../../features/settings'; -import { SETTINGS_PATH } from '../../paths'; -import { getSettingsPath } from '../../pathUtils'; -import { SETTINGS_FROM_IN_APP_STATE } from '../../../features/settings/SettingsScreen'; - -function UnverifiedIndicator() { - const { t } = useTranslation(); - const mx = useMatrixClient(); - const navigate = useNavigate(); - const settingsMatch = useMatch({ path: SETTINGS_PATH, caseSensitive: true, end: false }); - - const crypto = mx.getCrypto(); - const [devices] = useDeviceList(); - - const [currentDevice, otherDevices] = useSplitCurrentDevice(devices); - - const verificationStatus = useDeviceVerificationStatus( - crypto, - mx.getSafeUserId(), - currentDevice?.device_id - ); - const unverified = verificationStatus === VerificationStatus.Unverified; - - const otherDevicesId = useDeviceIds(otherDevices); - const unverifiedDeviceCount = useUnverifiedDeviceCount( - crypto, - mx.getSafeUserId(), - otherDevicesId - ); - - const openDevices = () => - navigate(getSettingsPath(SETTINGS_PARAM_DEVICES), { - replace: !!settingsMatch, - state: SETTINGS_FROM_IN_APP_STATE, - }); - - const hasUnverified = - unverified || (unverifiedDeviceCount !== undefined && unverifiedDeviceCount > 0); - if (!hasUnverified) return null; - return ( - - - {(triggerRef) => ( - - - - )} - - {!unverified && unverifiedDeviceCount && unverifiedDeviceCount > 0 && ( - - - - {unverifiedDeviceCount} - - - - )} - - ); -} - -export function UnverifiedTab() { - const crossSigningActive = useCrossSigningActive(); - - if (!crossSigningActive) return null; - - return ; -} diff --git a/src/app/pages/client/sidebar/index.ts b/src/app/pages/client/sidebar/index.ts deleted file mode 100644 index bfc81fbd..00000000 --- a/src/app/pages/client/sidebar/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './DirectTab'; -export * from './SpaceTabs'; -export * from './ExploreTab'; -export * from './SettingsTab'; -export * from './UnverifiedTab'; -export * from './SearchTab';