diff --git a/.changeset/add_per_room_icon_display.md b/.changeset/add_per_room_icon_display.md new file mode 100644 index 000000000..6e20d1371 --- /dev/null +++ b/.changeset/add_per_room_icon_display.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Add per Space setting for when to show room icons in sidebar diff --git a/.changeset/fix-various-banner-fixes.md b/.changeset/fix-various-banner-fixes.md new file mode 100644 index 000000000..0c012d451 --- /dev/null +++ b/.changeset/fix-various-banner-fixes.md @@ -0,0 +1,5 @@ +--- +default: fix +--- + +Various small banner changes diff --git a/src/app/components/room-card/RoomCard.tsx b/src/app/components/room-card/RoomCard.tsx index ed905872e..9c35f0186 100644 --- a/src/app/components/room-card/RoomCard.tsx +++ b/src/app/components/room-card/RoomCard.tsx @@ -18,6 +18,7 @@ import { as, color, config, + toRem, } from 'folds'; import classNames from 'classnames'; import FocusTrap from 'focus-trap-react'; @@ -36,6 +37,9 @@ import { KnockRoomPrompt } from '$components/knock-room-prompt'; import { RoomAvatar } from '$components/room-avatar'; import { formatCompactNumber } from '$utils/formatCompactNumber'; import * as css from './style.css'; +import type { RoomBannerContent } from '$types/matrix-sdk-events'; +import { CustomStateEvent } from '$types/matrix/room'; +import colorMXID from '$utils/colorMXID'; type GridColumnCount = '1' | '2' | '3'; const getGridColumnCount = (gridWidth: number): GridColumnCount => { @@ -66,7 +70,6 @@ export function RoomCardGrid({ children }: { children: ReactNode }) { export const RoomCardBase = as<'div'>(({ className, ...props }, ref) => ( ( ? getRoomAvatarUrl(mx, joinedRoom, 96, useAuthentication) : avatarUrl && mxcUrlToHttp(mx, avatarUrl, useAuthentication, 96, 96, 'crop'); + const bannerState = joinedRoom + ? getStateEvent(joinedRoom, CustomStateEvent.RoomBanner) + : undefined; + const bannerMXC = bannerState?.getContent()?.url; + const bannerURI = mxcUrlToHttp(mx, bannerMXC ?? '', true); const roomName = joinedRoom?.name || name || fallbackName; const roomTopic = (topicEvent?.getContent().topic as string) || undefined || topic || fallbackTopic; @@ -215,11 +223,25 @@ export const RoomCard = as<'div', RoomCardProps>( const [viewTopic, setViewTopic] = useState(false); const closeTopic = () => setViewTopic(false); const openTopic = () => setViewTopic(true); - return ( - - + + {!bannerURI && !avatar ? ( + + ) : ( + {`${name} + )} + ( )} /> - {(roomType === RoomType.Space || joinedRoom?.isSpaceRoom()) && ( - - Space - - )} - - {roomName} - - {roomTopic} - - - }> - - - {renderTopicViewer(roomName, roomTopic, closeTopic)} - - - - - {typeof joinedMemberCount === 'number' && ( - - - {`${formatCompactNumber(joinedMemberCount)} Members`} + + + + {roomName} + + {roomTopic} + + + }> + + + {renderTopicViewer(roomName, roomTopic, closeTopic)} + + + + {(roomType === RoomType.Space || joinedRoom?.isSpaceRoom()) && ( + + Space + + )} - )} - {typeof joinedRoomId === 'string' && ( - - )} - {typeof joinedRoomId !== 'string' && - joinState.status !== AsyncStatus.Error && - (joinRule === JoinRule.Knock ? ( - <> - - - {knocking && ( - setKnocking(false)} - onCancel={() => setKnocking(false)} - /> - )} - - ) : ( + {typeof joinedMemberCount === 'number' && ( + + + {`${formatCompactNumber(joinedMemberCount)} Members`} + + )} + {typeof joinedRoomId === 'string' && ( - ))} - {typeof joinedRoomId !== 'string' && joinState.status === AsyncStatus.Error && ( - - - - {(openError) => ( - - )} - - - )} + + {knocking && ( + setKnocking(false)} + onCancel={() => setKnocking(false)} + /> + )} + + ) : ( + + ))} + {typeof joinedRoomId !== 'string' && joinState.status === AsyncStatus.Error && ( + + + + {(openError) => ( + + )} + + + )} + ); } diff --git a/src/app/components/room-card/style.css.ts b/src/app/components/room-card/style.css.ts index 8afe6704b..fbe4b4dce 100644 --- a/src/app/components/room-card/style.css.ts +++ b/src/app/components/room-card/style.css.ts @@ -1,6 +1,7 @@ import { style } from '@vanilla-extract/css'; -import { DefaultReset, config } from 'folds'; +import { DefaultReset, color, config, toRem } from 'folds'; import { ContainerColor } from '$styles/ContainerColor.css'; +import { recipe } from '@vanilla-extract/recipes'; export const CardGrid = style({ display: 'grid', @@ -12,11 +13,15 @@ export const RoomCardBase = style([ DefaultReset, ContainerColor({ variant: 'SurfaceVariant' }), { - padding: config.space.S500, borderRadius: config.radii.R500, + overflow: 'hidden', }, ]); +export const RoomCardItems = style({ + padding: config.space.S500, + backgroundColor: color.SurfaceVariant.Container, +}); export const RoomCardTopic = style({ minHeight: `calc(3 * ${config.lineHeight.T200})`, display: '-webkit-box', @@ -34,3 +39,27 @@ export const ActionButton = style({ flex: '1 1 0', minWidth: 1, }); + +export const RoomCardBanner = recipe({ + base: { + height: toRem(96), + minHeight: toRem(96), + width: '100%', + objectFit: 'cover', + objectPosition: 'center center', + }, + variants: { + trueBanner: { + true: {}, + false: { + filter: 'blur(10px)', + }, + }, + }, +}); +export const RoomCardAvatar = style({ + position: 'sticky', + transform: 'translateY(-50%)', + marginLeft: config.space.S500, + outline: `${config.borderWidth.B600} solid ${color.Surface.Container}`, +}); diff --git a/src/app/features/common-settings/appearance/Appearance.tsx b/src/app/features/common-settings/appearance/Appearance.tsx new file mode 100644 index 000000000..b7bc803e0 --- /dev/null +++ b/src/app/features/common-settings/appearance/Appearance.tsx @@ -0,0 +1,147 @@ +import { useState, type MouseEventHandler } from 'react'; +import { + Box, + Text, + IconButton, + Icon, + Icons, + Scroll, + Button, + config, + Menu, + MenuItem, + PopOut, + type RectCords, +} from 'folds'; +import { Page, PageContent, PageHeader } from '$components/page'; +import { SequenceCard } from '$components/sequence-card'; +import { SettingTile } from '$components/setting-tile'; +import { useRoom } from '$hooks/useRoom'; + +import { SequenceCardStyle } from '$features/common-settings/styles.css'; +import { useShowPerRoomRoomIcon } from '$hooks/useShowRoomIcon'; +import { useSetting } from '$state/hooks/settings'; +import type { ShowRoomIcon } from '$state/settings'; +import { settingsAtom } from '$state/settings'; +import { stopPropagation } from '$utils/keyboard'; +import FocusTrap from 'focus-trap-react'; + +export function SelectShowPerRoomRoomIcon({ roomId }: { roomId: string }) { + const [menuCords, setMenuCords] = useState(); + const showRoomIconItems = useShowPerRoomRoomIcon(); + const [showRoomIconArray, setShowRoomIconArray] = useSetting(settingsAtom, 'perRoomShowRoomIcon'); + const showRoomIcon = showRoomIconArray?.find((item) => item.roomId === roomId)?.display; + + const handleMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + + const handleSelect = (position?: ShowRoomIcon) => { + let newShowRoomIconArray = showRoomIconArray.filter((item) => item.roomId !== roomId); + if (position) newShowRoomIconArray = [...newShowRoomIconArray, { roomId, display: position }]; + setShowRoomIconArray(newShowRoomIconArray); + 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} + + ))} + + + + } + /> + + ); +} + +type AppearanceProps = { + requestClose: () => void; +}; +export function Appearance({ requestClose }: AppearanceProps) { + const room = useRoom(); + + return ( + + + + + + Appearance + + + + + + + + + + + + + + + Visual Tweaks + + } + /> + + + + + + + + ); +} diff --git a/src/app/features/common-settings/general/RoomProfile.tsx b/src/app/features/common-settings/general/RoomProfile.tsx index cb49900ff..a65d7ef1a 100644 --- a/src/app/features/common-settings/general/RoomProfile.tsx +++ b/src/app/features/common-settings/general/RoomProfile.tsx @@ -44,7 +44,7 @@ import { createUploadAtom } from '$state/upload'; import { useFilePicker } from '$hooks/useFilePicker'; import { AsyncStatus, useAsyncCallback } from '$hooks/useAsyncCallback'; import { useAlive } from '$hooks/useAlive'; -import type { RoomPermissionsAPI } from '$hooks/useRoomPermissions'; +import { type RoomPermissionsAPI } from '$hooks/useRoomPermissions'; import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { useStateEvent } from '$hooks/useStateEvent'; @@ -310,13 +310,18 @@ export function RoomProfileEdit({ } export type ProfileProps = { + permissions: RoomPermissionsAPI; bannerURI?: string; }; -function RoomBannerEdit({ bannerURI }: Readonly) { +function RoomBannerEdit({ bannerURI, permissions }: Readonly) { const mx = useMatrixClient(); const [alertRemove, setAlertRemove] = useState(false); const space = useRoom(); + + const userId = mx.getUserId() ?? ''; + const canEdit = permissions.stateEvent(CustomStateEvent.RoomBanner, userId); + const [stagedUrl, setStagedUrl] = useState(); const [isRemoving, setIsRemoving] = useState(false); @@ -420,6 +425,7 @@ function RoomBannerEdit({ bannerURI }: Readonly) { fill="Soft" outlined radii="300" + disabled={!canEdit} > {bannerUrl ? 'Change Banner' : 'Upload Banner'} @@ -430,6 +436,7 @@ function RoomBannerEdit({ bannerURI }: Readonly) { fill="None" radii="300" onClick={() => setAlertRemove(true)} + disabled={!canEdit} > Remove @@ -466,7 +473,7 @@ function RoomBannerEdit({ bannerURI }: Readonly) { Are you sure you want to remove profile banner? - @@ -585,8 +592,9 @@ export function RoomProfile({ permissions }: RoomProfileProps) { variant="SurfaceVariant" direction="Column" gap="400" + disabled={!canEdit} > - + )} diff --git a/src/app/features/room/MembersDrawer.tsx b/src/app/features/room/MembersDrawer.tsx index 2641899d4..3aeb18c15 100644 --- a/src/app/features/room/MembersDrawer.tsx +++ b/src/app/features/room/MembersDrawer.tsx @@ -296,8 +296,6 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) { ); const handleMemberClick: MouseEventHandler = (evt) => { - // oxlint-disable-next-line no-console - console.log(evt); const btn = evt.currentTarget as HTMLButtonElement; const userId = btn.getAttribute('data-user-id'); if (!userId) return; diff --git a/src/app/features/settings/cosmetics/Themes.tsx b/src/app/features/settings/cosmetics/Themes.tsx index e00e5f425..fa93a3890 100644 --- a/src/app/features/settings/cosmetics/Themes.tsx +++ b/src/app/features/settings/cosmetics/Themes.tsx @@ -38,6 +38,7 @@ import FocusTrap from 'focus-trap-react'; import { useShowRoomIcon } from '$hooks/useShowRoomIcon'; import type { PanelSizetItem } from '$hooks/usePanelSizes'; import { usePanelSizeItems } from '$hooks/usePanelSizes'; +import { SelectShowPerRoomRoomIcon } from '$features/common-settings/appearance/Appearance'; const clampIncomingInlineImageHeight = (n: number) => Math.max(1, Math.min(4096, n)); @@ -717,7 +718,8 @@ function SelectShowRoomIcon() { setMenuCords(evt.currentTarget.getBoundingClientRect()); }; - const handleSelect = (position: ShowRoomIcon) => { + const handleSelect = (position?: ShowRoomIcon) => { + if (!position) return; setShowRoomIcon(position); setMenuCords(undefined); }; @@ -868,12 +870,22 @@ export function Appearance({ } /> + {/*THIS SHOULD BE MOVED TO A NEW SETTINGS MENU INSIDE OF THE HOME SETTINGS AS SOON AS THERE IS A REASON TO CREATE A HOME MENU SETTINGS PANEL + it is currently here because it would be eerie to have an entire home settings menu for just one single setting*/} + + } + /> + name: 'Developer Tools', icon: Icons.Terminal, }, + { + page: SpaceSettingsPage.AppearancePage, + name: 'Appearance', + icon: Icons.Alphabet, + activeIcon: Icons.AlphabetUnderline, + }, ], [] ); @@ -197,6 +204,9 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps) {activePage === SpaceSettingsPage.AbbreviationsPage && ( )} + {activePage === SpaceSettingsPage.AppearancePage && ( + + )} ); } diff --git a/src/app/hooks/useShowRoomIcon.ts b/src/app/hooks/useShowRoomIcon.ts index 509f58aac..9c548c940 100644 --- a/src/app/hooks/useShowRoomIcon.ts +++ b/src/app/hooks/useShowRoomIcon.ts @@ -2,8 +2,8 @@ import { useMemo } from 'react'; import { ShowRoomIcon } from '$state/settings'; export type MessageLayoutItem = { + layout?: ShowRoomIcon; name: string; - layout: ShowRoomIcon; }; export const useShowRoomIcon = (): MessageLayoutItem[] => @@ -24,3 +24,26 @@ export const useShowRoomIcon = (): MessageLayoutItem[] => ], [] ); + +export const useShowPerRoomRoomIcon = (): MessageLayoutItem[] => + useMemo( + () => [ + { + layout: undefined, + name: 'Default', + }, + { + 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 dce68f104..81dc6181d 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -338,6 +338,7 @@ export function Direct() { width: '100%', aspectRatio: 1, display: 'flex', + flexDirection: 'column', } : {} } diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index 38022a2e6..cddcefbb0 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -213,16 +213,20 @@ export function Home() { const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); - const [showRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); + const [showRoomIconGeneral] = useSetting(settingsAtom, 'showRoomIcon'); + const [showRoomIconArray] = useSetting(settingsAtom, 'perRoomShowRoomIcon'); + const showRoomIcon = + showRoomIconArray.find((item) => item.roomId === 'Home')?.display ?? showRoomIconGeneral; const showIcons = () => { if (showRoomIcon === ShowRoomIcon.Always) return true; if (showRoomIcon === ShowRoomIcon.Never) return false; - return curWidth < 96; + return curWidth < 144; }; - useEffect(() => { - setCurWidth(roomSidebarWidth); - }, [roomSidebarWidth]); + const [joinCallOnSingleClick] = useSetting(settingsAtom, 'joinCallOnSingleClick'); const selectedRoomId = useSelectedRoom(); @@ -423,6 +427,7 @@ export function Home() { width: '100%', aspectRatio: 1, display: 'flex', + flexDirection: 'column', } : {} } diff --git a/src/app/pages/client/sidebar/SidebarResizer.css.ts b/src/app/pages/client/sidebar/SidebarResizer.css.ts index b37504b98..5acd9d4b9 100644 --- a/src/app/pages/client/sidebar/SidebarResizer.css.ts +++ b/src/app/pages/client/sidebar/SidebarResizer.css.ts @@ -1,5 +1,6 @@ import { style } from '@vanilla-extract/css'; -import { color } from 'folds'; +import { recipe } from '@vanilla-extract/recipes'; +import { color, toRem } from 'folds'; /** Out-of-flow so flex siblings (e.g. PageRoot vertical Line) stay flush with the panel edge. */ export const SidebarResizerDockRight = style({ @@ -29,17 +30,33 @@ export const SidebarResizerDockTop = style({ cursor: 'ns-resize', }); -export const SidebarResizer = style({ - backgroundColor: 'inherit', - transition: '0.2s', - ':hover': {}, +export const SidebarResizer = recipe({ + base: { + backgroundColor: 'inherit', + transition: '0.2s', + ':hover': {}, + touchAction: 'none', + }, + variants: { + topSided: { + true: { + width: '100%', + height: toRem(8), + }, + false: { + width: toRem(4), + height: '100%', + }, + }, + }, }); export const SidebarResizerHover = style({ - zIndex: 100, + zIndex: 110, }); export const SideBarResizerAnimation = style({ width: '100%', height: '100%', backgroundColor: color.Surface.ContainerLine, transition: '0.5s', + touchAction: 'none', }); diff --git a/src/app/pages/client/sidebar/SidebarResizer.tsx b/src/app/pages/client/sidebar/SidebarResizer.tsx index 6b7d0144c..b54d258f0 100644 --- a/src/app/pages/client/sidebar/SidebarResizer.tsx +++ b/src/app/pages/client/sidebar/SidebarResizer.tsx @@ -1,6 +1,6 @@ // 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, toRem } from 'folds'; +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'; @@ -64,6 +64,7 @@ export function SidebarResizer({ const onPointerDown = useCallback( (e: React.PointerEvent) => { e.preventDefault(); + e.currentTarget.setPointerCapture(e.pointerId); setOldPos(topSided ? e.clientY : e.clientX); setIsPointerDown(true); window.addEventListener('pointerup', onPointerUp); @@ -80,14 +81,10 @@ export function SidebarResizer({ return ( setIsPointerOver(true)} onPointerLeave={() => setIsPointerOver(false)} onPointerDown={onPointerDown} - style={{ - width: topSided ? '100%' : toRem(4), - height: topSided ? toRem(4) : '100%', - }} shrink="No" > {hasBanner && ( <> -
+ -
+
)} {hasBanner && bannerViewerOpen && ( @@ -520,16 +520,19 @@ export function Space() { const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); - const [showRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); + const [showRoomIconGeneral] = useSetting(settingsAtom, 'showRoomIcon'); + const [showRoomIconArray] = useSetting(settingsAtom, 'perRoomShowRoomIcon'); + const showRoomIcon = + showRoomIconArray.find((item) => item.roomId === space.roomId)?.display ?? showRoomIconGeneral; const showIcons = () => { if (showRoomIcon === ShowRoomIcon.Always) return true; if (showRoomIcon === ShowRoomIcon.Never) return false; return curWidth < 144; }; - useEffect(() => { - setCurWidth(roomSidebarWidth); - }, [roomSidebarWidth]); const [joinCallOnSingleClick] = useSetting(settingsAtom, 'joinCallOnSingleClick'); const tombstoneEvent = useStateEvent(space, EventType.RoomTombstone); @@ -1017,6 +1020,7 @@ export function Space() { width: '100%', aspectRatio: 1, display: 'flex', + flexDirection: 'column', } : { paddingLeft } } diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index b1b744c1f..aab58f600 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -28,6 +28,11 @@ export enum ShowRoomIcon { Smart = 'smart', Never = 'never', } +export type PerRoomShowRoomIcon = { + roomId: string; + display: ShowRoomIcon; +}; + export type JumboEmojiSize = 'none' | 'extraSmall' | 'small' | 'normal' | 'large' | 'extraLarge'; export type ThemeRemoteFavorite = { @@ -160,6 +165,7 @@ export interface Settings { mentionInReplies: boolean; showPersonaSetting: boolean; closeFoldersByDefault: boolean; + perRoomShowRoomIcon: PerRoomShowRoomIcon[]; showRoomIcon: ShowRoomIcon; showRoomBanners: boolean; roomSidebarWidth: number; @@ -292,6 +298,7 @@ export const defaultSettings: Settings = { mentionInReplies: true, showPersonaSetting: false, closeFoldersByDefault: false, + perRoomShowRoomIcon: [], showRoomIcon: ShowRoomIcon.Smart, showRoomBanners: true, roomSidebarWidth: 256, diff --git a/src/app/state/spaceSettings.ts b/src/app/state/spaceSettings.ts index c33b5e957..de91d6f3a 100644 --- a/src/app/state/spaceSettings.ts +++ b/src/app/state/spaceSettings.ts @@ -9,6 +9,7 @@ export enum SpaceSettingsPage { // Sable pages CosmeticsPage, AbbreviationsPage, + AppearancePage, } export type SpaceSettingsState = {