From 5c39a36c12a9f873848ab7f14e22591fd9766c59 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Thu, 27 Mar 2025 19:54:13 +1100 Subject: [PATCH] Add new space settings (#2293) --- src/app/components/JoinRulesSwitcher.tsx | 11 ++ .../developer-tools/DevelopTools.tsx | 0 .../developer-tools/SendRoomEvent.tsx | 0 .../developer-tools/StateEventEditor.tsx | 0 .../developer-tools/index.ts | 0 .../emojis-stickers/EmojisStickers.tsx | 0 .../emojis-stickers/RoomPacks.tsx | 0 .../emojis-stickers/index.ts | 0 .../general/RoomAddress.tsx | 4 +- .../general/RoomEncryption.tsx | 2 +- .../general/RoomHistoryVisibility.tsx | 2 +- .../general/RoomJoinRules.tsx | 14 +- .../general/RoomProfile.tsx | 16 +- .../general/RoomPublish.tsx | 2 +- .../general/RoomUpgrade.tsx | 31 +++- .../features/common-settings/general/index.ts | 7 + .../members/Members.tsx | 0 .../members/index.ts | 0 .../permissions/PermissionGroups.tsx | 7 +- .../permissions/Powers.tsx | 16 +- .../permissions/PowersEditor.tsx | 0 .../common-settings/permissions/index.ts | 4 + .../common-settings/permissions/types.ts | 12 ++ .../features/common-settings/styles.css.ts | 6 + src/app/features/lobby/HierarchyItemMenu.tsx | 6 +- src/app/features/lobby/LobbyHeader.tsx | 8 +- .../features/room-settings/RoomSettings.tsx | 6 +- .../room-settings/general/General.tsx | 17 +- .../room-settings/permissions/Permissions.tsx | 9 +- .../permissions/usePermissionItems.ts | 13 +- .../features/space-settings/SpaceSettings.tsx | 173 ++++++++++++++++++ .../space-settings/SpaceSettingsRenderer.tsx | 39 ++++ .../space-settings/general/General.tsx | 63 +++++++ .../features/space-settings/general/index.ts | 1 + src/app/features/space-settings/index.ts | 2 + .../permissions/Permissions.tsx | 67 +++++++ .../space-settings/permissions/index.ts | 1 + .../permissions/usePermissionItems.ts | 148 +++++++++++++++ src/app/features/space-settings/styles.css.ts | 6 + src/app/pages/Router.tsx | 2 + src/app/pages/client/sidebar/SpaceTabs.tsx | 4 +- src/app/pages/client/space/Space.tsx | 4 +- src/app/state/hooks/spaceSettings.ts | 34 ++++ src/app/state/spaceSettings.ts | 17 ++ 44 files changed, 691 insertions(+), 63 deletions(-) rename src/app/features/{room-settings => common-settings}/developer-tools/DevelopTools.tsx (100%) rename src/app/features/{room-settings => common-settings}/developer-tools/SendRoomEvent.tsx (100%) rename src/app/features/{room-settings => common-settings}/developer-tools/StateEventEditor.tsx (100%) rename src/app/features/{room-settings => common-settings}/developer-tools/index.ts (100%) rename src/app/features/{room-settings => common-settings}/emojis-stickers/EmojisStickers.tsx (100%) rename src/app/features/{room-settings => common-settings}/emojis-stickers/RoomPacks.tsx (100%) rename src/app/features/{room-settings => common-settings}/emojis-stickers/index.ts (100%) rename src/app/features/{room-settings => common-settings}/general/RoomAddress.tsx (98%) rename src/app/features/{room-settings => common-settings}/general/RoomEncryption.tsx (98%) rename src/app/features/{room-settings => common-settings}/general/RoomHistoryVisibility.tsx (98%) rename src/app/features/{room-settings => common-settings}/general/RoomJoinRules.tsx (89%) rename src/app/features/{room-settings => common-settings}/general/RoomProfile.tsx (95%) rename src/app/features/{room-settings => common-settings}/general/RoomPublish.tsx (97%) rename src/app/features/{room-settings => common-settings}/general/RoomUpgrade.tsx (86%) create mode 100644 src/app/features/common-settings/general/index.ts rename src/app/features/{room-settings => common-settings}/members/Members.tsx (100%) rename src/app/features/{room-settings => common-settings}/members/index.ts (100%) rename src/app/features/{room-settings => common-settings}/permissions/PermissionGroups.tsx (98%) rename src/app/features/{room-settings => common-settings}/permissions/Powers.tsx (91%) rename src/app/features/{room-settings => common-settings}/permissions/PowersEditor.tsx (100%) create mode 100644 src/app/features/common-settings/permissions/index.ts create mode 100644 src/app/features/common-settings/permissions/types.ts create mode 100644 src/app/features/common-settings/styles.css.ts create mode 100644 src/app/features/space-settings/SpaceSettings.tsx create mode 100644 src/app/features/space-settings/SpaceSettingsRenderer.tsx create mode 100644 src/app/features/space-settings/general/General.tsx create mode 100644 src/app/features/space-settings/general/index.ts create mode 100644 src/app/features/space-settings/index.ts create mode 100644 src/app/features/space-settings/permissions/Permissions.tsx create mode 100644 src/app/features/space-settings/permissions/index.ts create mode 100644 src/app/features/space-settings/permissions/usePermissionItems.ts create mode 100644 src/app/features/space-settings/styles.css.ts create mode 100644 src/app/state/hooks/spaceSettings.ts create mode 100644 src/app/state/spaceSettings.ts diff --git a/src/app/components/JoinRulesSwitcher.tsx b/src/app/components/JoinRulesSwitcher.tsx index ddd1903..e78c19c 100644 --- a/src/app/components/JoinRulesSwitcher.tsx +++ b/src/app/components/JoinRulesSwitcher.tsx @@ -29,6 +29,17 @@ export const useRoomJoinRuleIcon = (): JoinRuleIcons => }), [] ); +export const useSpaceJoinRuleIcon = (): JoinRuleIcons => + useMemo( + () => ({ + [JoinRule.Invite]: Icons.SpaceLock, + [JoinRule.Knock]: Icons.SpaceLock, + [JoinRule.Restricted]: Icons.Space, + [JoinRule.Public]: Icons.SpaceGlobe, + [JoinRule.Private]: Icons.SpaceLock, + }), + [] + ); type JoinRuleLabels = Record; export const useRoomJoinRuleLabel = (): JoinRuleLabels => diff --git a/src/app/features/room-settings/developer-tools/DevelopTools.tsx b/src/app/features/common-settings/developer-tools/DevelopTools.tsx similarity index 100% rename from src/app/features/room-settings/developer-tools/DevelopTools.tsx rename to src/app/features/common-settings/developer-tools/DevelopTools.tsx diff --git a/src/app/features/room-settings/developer-tools/SendRoomEvent.tsx b/src/app/features/common-settings/developer-tools/SendRoomEvent.tsx similarity index 100% rename from src/app/features/room-settings/developer-tools/SendRoomEvent.tsx rename to src/app/features/common-settings/developer-tools/SendRoomEvent.tsx diff --git a/src/app/features/room-settings/developer-tools/StateEventEditor.tsx b/src/app/features/common-settings/developer-tools/StateEventEditor.tsx similarity index 100% rename from src/app/features/room-settings/developer-tools/StateEventEditor.tsx rename to src/app/features/common-settings/developer-tools/StateEventEditor.tsx diff --git a/src/app/features/room-settings/developer-tools/index.ts b/src/app/features/common-settings/developer-tools/index.ts similarity index 100% rename from src/app/features/room-settings/developer-tools/index.ts rename to src/app/features/common-settings/developer-tools/index.ts diff --git a/src/app/features/room-settings/emojis-stickers/EmojisStickers.tsx b/src/app/features/common-settings/emojis-stickers/EmojisStickers.tsx similarity index 100% rename from src/app/features/room-settings/emojis-stickers/EmojisStickers.tsx rename to src/app/features/common-settings/emojis-stickers/EmojisStickers.tsx diff --git a/src/app/features/room-settings/emojis-stickers/RoomPacks.tsx b/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx similarity index 100% rename from src/app/features/room-settings/emojis-stickers/RoomPacks.tsx rename to src/app/features/common-settings/emojis-stickers/RoomPacks.tsx diff --git a/src/app/features/room-settings/emojis-stickers/index.ts b/src/app/features/common-settings/emojis-stickers/index.ts similarity index 100% rename from src/app/features/room-settings/emojis-stickers/index.ts rename to src/app/features/common-settings/emojis-stickers/index.ts diff --git a/src/app/features/room-settings/general/RoomAddress.tsx b/src/app/features/common-settings/general/RoomAddress.tsx similarity index 98% rename from src/app/features/room-settings/general/RoomAddress.tsx rename to src/app/features/common-settings/general/RoomAddress.tsx index dfe6645..9e1f1a9 100644 --- a/src/app/features/room-settings/general/RoomAddress.tsx +++ b/src/app/features/common-settings/general/RoomAddress.tsx @@ -18,7 +18,7 @@ import { MatrixError } from 'matrix-js-sdk'; import { IPowerLevels, powerLevelAPI } from '../../../hooks/usePowerLevels'; import { SettingTile } from '../../../components/setting-tile'; import { SequenceCard } from '../../../components/sequence-card'; -import { SequenceCardStyle } from '../styles.css'; +import { SequenceCardStyle } from '../../room-settings/styles.css'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useRoom } from '../../../hooks/useRoom'; import { @@ -65,7 +65,7 @@ export function RoomPublishedAddresses({ powerLevels }: RoomPublishedAddressesPr title="Published Addresses" description={ - If room access is Public, Published addresses will be used to join by anyone. + If access is Public, Published addresses will be used to join by anyone. } /> diff --git a/src/app/features/room-settings/general/RoomEncryption.tsx b/src/app/features/common-settings/general/RoomEncryption.tsx similarity index 98% rename from src/app/features/room-settings/general/RoomEncryption.tsx rename to src/app/features/common-settings/general/RoomEncryption.tsx index 7d95fe3..1bb7339 100644 --- a/src/app/features/room-settings/general/RoomEncryption.tsx +++ b/src/app/features/common-settings/general/RoomEncryption.tsx @@ -19,7 +19,7 @@ import React, { useCallback, useState } from 'react'; import { MatrixError } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; import { SequenceCard } from '../../../components/sequence-card'; -import { SequenceCardStyle } from '../styles.css'; +import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; import { IPowerLevels, powerLevelAPI } from '../../../hooks/usePowerLevels'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; diff --git a/src/app/features/room-settings/general/RoomHistoryVisibility.tsx b/src/app/features/common-settings/general/RoomHistoryVisibility.tsx similarity index 98% rename from src/app/features/room-settings/general/RoomHistoryVisibility.tsx rename to src/app/features/common-settings/general/RoomHistoryVisibility.tsx index d36e312..7b329b1 100644 --- a/src/app/features/room-settings/general/RoomHistoryVisibility.tsx +++ b/src/app/features/common-settings/general/RoomHistoryVisibility.tsx @@ -16,7 +16,7 @@ import { HistoryVisibility, MatrixError } from 'matrix-js-sdk'; import { RoomHistoryVisibilityEventContent } from 'matrix-js-sdk/lib/types'; import FocusTrap from 'focus-trap-react'; import { SequenceCard } from '../../../components/sequence-card'; -import { SequenceCardStyle } from '../styles.css'; +import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; import { IPowerLevels, powerLevelAPI } from '../../../hooks/usePowerLevels'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; diff --git a/src/app/features/room-settings/general/RoomJoinRules.tsx b/src/app/features/common-settings/general/RoomJoinRules.tsx similarity index 89% rename from src/app/features/room-settings/general/RoomJoinRules.tsx rename to src/app/features/common-settings/general/RoomJoinRules.tsx index a98fee6..158ca25 100644 --- a/src/app/features/room-settings/general/RoomJoinRules.tsx +++ b/src/app/features/common-settings/general/RoomJoinRules.tsx @@ -7,9 +7,10 @@ import { JoinRulesSwitcher, useRoomJoinRuleIcon, useRoomJoinRuleLabel, + useSpaceJoinRuleIcon, } from '../../../components/JoinRulesSwitcher'; import { SequenceCard } from '../../../components/sequence-card'; -import { SequenceCardStyle } from '../styles.css'; +import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useRoom } from '../../../hooks/useRoom'; @@ -60,6 +61,7 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) { }, [allowRestricted, allowKnock, space]); const icons = useRoomJoinRuleIcon(); + const spaceIcons = useSpaceJoinRuleIcon(); const labels = useRoomJoinRuleLabel(); const [submitState, submit] = useAsyncCallback( @@ -99,11 +101,15 @@ export function RoomJoinRules({ powerLevels }: RoomJoinRulesProps) { gap="400" > ( - + )} /> @@ -338,7 +343,12 @@ export function RoomProfile({ powerLevels }: RoomProfileProps) { src={avatarUrl} alt={name} renderFallback={() => ( - + )} /> diff --git a/src/app/features/room-settings/general/RoomPublish.tsx b/src/app/features/common-settings/general/RoomPublish.tsx similarity index 97% rename from src/app/features/room-settings/general/RoomPublish.tsx rename to src/app/features/common-settings/general/RoomPublish.tsx index d17f70e..e27c687 100644 --- a/src/app/features/room-settings/general/RoomPublish.tsx +++ b/src/app/features/common-settings/general/RoomPublish.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Box, color, Spinner, Switch, Text } from 'folds'; import { MatrixError } from 'matrix-js-sdk'; import { SequenceCard } from '../../../components/sequence-card'; -import { SequenceCardStyle } from '../styles.css'; +import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; import { useRoom } from '../../../hooks/useRoom'; import { useRoomDirectoryVisibility } from '../../../hooks/useRoomDirectoryVisibility'; diff --git a/src/app/features/room-settings/general/RoomUpgrade.tsx b/src/app/features/common-settings/general/RoomUpgrade.tsx similarity index 86% rename from src/app/features/room-settings/general/RoomUpgrade.tsx rename to src/app/features/common-settings/general/RoomUpgrade.tsx index fa1fa85..5d6bc5e 100644 --- a/src/app/features/room-settings/general/RoomUpgrade.tsx +++ b/src/app/features/common-settings/general/RoomUpgrade.tsx @@ -20,7 +20,7 @@ import FocusTrap from 'focus-trap-react'; import { MatrixError } from 'matrix-js-sdk'; import { RoomCreateEventContent, RoomTombstoneEventContent } from 'matrix-js-sdk/lib/types'; import { SequenceCard } from '../../../components/sequence-card'; -import { SequenceCardStyle } from '../styles.css'; +import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; import { useRoom } from '../../../hooks/useRoom'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; @@ -39,7 +39,7 @@ type RoomUpgradeProps = { export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) { const mx = useMatrixClient(); const room = useRoom(); - const { navigateRoom } = useRoomNavigate(); + const { navigateRoom, navigateSpace } = useRoomNavigate(); const createContent = useStateEvent( room, StateEvent.RoomCreate @@ -66,14 +66,22 @@ export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) { const handleOpenRoom = () => { if (replacementRoom) { requestClose(); - navigateRoom(replacementRoom); + if (room.isSpaceRoom()) { + navigateSpace(replacementRoom); + } else { + navigateRoom(replacementRoom); + } } }; const handleOpenOldRoom = () => { if (predecessorRoomId) { requestClose(); - navigateRoom(predecessorRoomId, createContent.predecessor?.event_id); + if (room.isSpaceRoom()) { + navigateSpace(predecessorRoomId); + } else { + navigateRoom(predecessorRoomId, createContent.predecessor?.event_id); + } } }; @@ -110,10 +118,11 @@ export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) { gap="400" > - Old Room + {room.isSpaceRoom() ? 'Old Space' : 'Old Room'} )} {replacementRoom ? ( @@ -138,7 +147,7 @@ export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) { radii="300" onClick={handleOpenRoom} > - Open New Room + {room.isSpaceRoom() ? 'Open New Space' : 'Open New Room'} ) : ( diff --git a/src/app/features/common-settings/general/index.ts b/src/app/features/common-settings/general/index.ts new file mode 100644 index 0000000..80804b0 --- /dev/null +++ b/src/app/features/common-settings/general/index.ts @@ -0,0 +1,7 @@ +export * from './RoomAddress'; +export * from './RoomEncryption'; +export * from './RoomHistoryVisibility'; +export * from './RoomJoinRules'; +export * from './RoomProfile'; +export * from './RoomPublish'; +export * from './RoomUpgrade'; diff --git a/src/app/features/room-settings/members/Members.tsx b/src/app/features/common-settings/members/Members.tsx similarity index 100% rename from src/app/features/room-settings/members/Members.tsx rename to src/app/features/common-settings/members/Members.tsx diff --git a/src/app/features/room-settings/members/index.ts b/src/app/features/common-settings/members/index.ts similarity index 100% rename from src/app/features/room-settings/members/index.ts rename to src/app/features/common-settings/members/index.ts diff --git a/src/app/features/room-settings/permissions/PermissionGroups.tsx b/src/app/features/common-settings/permissions/PermissionGroups.tsx similarity index 98% rename from src/app/features/room-settings/permissions/PermissionGroups.tsx rename to src/app/features/common-settings/permissions/PermissionGroups.tsx index add2f55..54c9c5d 100644 --- a/src/app/features/room-settings/permissions/PermissionGroups.tsx +++ b/src/app/features/common-settings/permissions/PermissionGroups.tsx @@ -12,7 +12,7 @@ import { PermissionLocation, usePowerLevelsAPI, } from '../../../hooks/usePowerLevels'; -import { usePermissionGroups } from './usePermissionItems'; +import { PermissionGroup } from './types'; import { getPowers, usePowerLevelTags } from '../../../hooks/usePowerLevelTags'; import { useRoom } from '../../../hooks/useRoom'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; @@ -27,8 +27,9 @@ const USER_DEFAULT_LOCATION: PermissionLocation = { type PermissionGroupsProps = { powerLevels: IPowerLevels; + permissionGroups: PermissionGroup[]; }; -export function PermissionGroups({ powerLevels }: PermissionGroupsProps) { +export function PermissionGroups({ powerLevels, permissionGroups }: PermissionGroupsProps) { const mx = useMatrixClient(); const room = useRoom(); const alive = useAlive(); @@ -40,8 +41,6 @@ export function PermissionGroups({ powerLevels }: PermissionGroupsProps) { const [powerLevelTags, getPowerLevelTag] = usePowerLevelTags(room, powerLevels); const maxPower = useMemo(() => Math.max(...getPowers(powerLevelTags)), [powerLevelTags]); - const permissionGroups = usePermissionGroups(); - const [permissionUpdate, setPermissionUpdate] = useState>( new Map() ); diff --git a/src/app/features/room-settings/permissions/Powers.tsx b/src/app/features/common-settings/permissions/Powers.tsx similarity index 91% rename from src/app/features/room-settings/permissions/Powers.tsx rename to src/app/features/common-settings/permissions/Powers.tsx index 24a71e7..b7dadf5 100644 --- a/src/app/features/room-settings/permissions/Powers.tsx +++ b/src/app/features/common-settings/permissions/Powers.tsx @@ -24,16 +24,16 @@ import { PowerColorBadge, PowerIcon } from '../../../components/power'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; import { stopPropagation } from '../../../utils/keyboard'; -import { usePermissionGroups } from './usePermissionItems'; +import { PermissionGroup } from './types'; type PeekPermissionsProps = { powerLevels: IPowerLevels; power: number; + permissionGroups: PermissionGroup[]; children: (handleOpen: MouseEventHandler, opened: boolean) => ReactNode; }; -function PeekPermissions({ powerLevels, power, children }: PeekPermissionsProps) { +function PeekPermissions({ powerLevels, power, permissionGroups, children }: PeekPermissionsProps) { const [menuCords, setMenuCords] = useState(); - const permissionGroups = usePermissionGroups(); const handleOpen: MouseEventHandler = (evt) => { setMenuCords(evt.currentTarget.getBoundingClientRect()); @@ -101,9 +101,10 @@ function PeekPermissions({ powerLevels, power, children }: PeekPermissionsProps) type PowersProps = { powerLevels: IPowerLevels; + permissionGroups: PermissionGroup[]; onEdit?: () => void; }; -export function Powers({ powerLevels, onEdit }: PowersProps) { +export function Powers({ powerLevels, permissionGroups, onEdit }: PowersProps) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const room = useRoom(); @@ -144,7 +145,12 @@ export function Powers({ powerLevels, onEdit }: PowersProps) { const tagIconSrc = tag.icon && getTagIconSrc(mx, useAuthentication, tag.icon); return ( - + {(openMenu, opened) => ( { if ('space' in item) { - openSpaceSettings(item.roomId); + openSpaceSettings(item.roomId, item.parentId); } else { openRoomSettings(item.roomId, space?.roomId); } diff --git a/src/app/features/lobby/LobbyHeader.tsx b/src/app/features/lobby/LobbyHeader.tsx index 6ef0044..bc4c46f 100644 --- a/src/app/features/lobby/LobbyHeader.tsx +++ b/src/app/features/lobby/LobbyHeader.tsx @@ -26,7 +26,7 @@ import { useMatrixClient } from '../../hooks/useMatrixClient'; import { RoomAvatar } from '../../components/room-avatar'; import { nameInitials } from '../../utils/common'; import * as css from './LobbyHeader.css'; -import { openInviteUser, openSpaceSettings } from '../../../client/action/navigation'; +import { openInviteUser } from '../../../client/action/navigation'; import { IPowerLevels, usePowerLevelsAPI } from '../../hooks/usePowerLevels'; import { UseStateProvider } from '../../components/UseStateProvider'; import { LeaveSpacePrompt } from '../../components/leave-space-prompt'; @@ -35,6 +35,7 @@ import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { BackRouteHandler } from '../../components/BackRouteHandler'; import { mxcUrlToHttp } from '../../utils/matrix'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; +import { useOpenSpaceSettings } from '../../state/hooks/spaceSettings'; type LobbyMenuProps = { roomId: string; @@ -46,6 +47,7 @@ const LobbyMenu = forwardRef( const mx = useMatrixClient(); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); + const openSpaceSettings = useOpenSpaceSettings(); const handleInvite = () => { openInviteUser(roomId); @@ -132,7 +134,9 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) { const name = useRoomName(space); const avatarMxc = useRoomAvatar(space); - const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined; + const avatarUrl = avatarMxc + ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined + : undefined; const handleOpenMenu: MouseEventHandler = (evt) => { setMenuAnchor(evt.currentTarget.getBoundingClientRect()); diff --git a/src/app/features/room-settings/RoomSettings.tsx b/src/app/features/room-settings/RoomSettings.tsx index 42192c0..32b5df9 100644 --- a/src/app/features/room-settings/RoomSettings.tsx +++ b/src/app/features/room-settings/RoomSettings.tsx @@ -11,12 +11,12 @@ import { useRoomAvatar, useRoomJoinRule, useRoomName } from '../../hooks/useRoom import { mDirectAtom } from '../../state/mDirectList'; import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; import { General } from './general'; -import { Members } from './members'; -import { EmojisStickers } from './emojis-stickers'; +import { Members } from '../common-settings/members'; +import { EmojisStickers } from '../common-settings/emojis-stickers'; import { Permissions } from './permissions'; import { RoomSettingsPage } from '../../state/roomSettings'; import { useRoom } from '../../hooks/useRoom'; -import { DeveloperTools } from './developer-tools'; +import { DeveloperTools } from '../common-settings/developer-tools'; type RoomSettingsMenuItem = { page: RoomSettingsPage; diff --git a/src/app/features/room-settings/general/General.tsx b/src/app/features/room-settings/general/General.tsx index 3d217f3..0c3152c 100644 --- a/src/app/features/room-settings/general/General.tsx +++ b/src/app/features/room-settings/general/General.tsx @@ -1,15 +1,18 @@ import React from 'react'; import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; import { Page, PageContent, PageHeader } from '../../../components/page'; -import { RoomProfile } from './RoomProfile'; import { usePowerLevels } from '../../../hooks/usePowerLevels'; import { useRoom } from '../../../hooks/useRoom'; -import { RoomEncryption } from './RoomEncryption'; -import { RoomHistoryVisibility } from './RoomHistoryVisibility'; -import { RoomJoinRules } from './RoomJoinRules'; -import { RoomLocalAddresses, RoomPublishedAddresses } from './RoomAddress'; -import { RoomPublish } from './RoomPublish'; -import { RoomUpgrade } from './RoomUpgrade'; +import { + RoomProfile, + RoomEncryption, + RoomHistoryVisibility, + RoomJoinRules, + RoomLocalAddresses, + RoomPublishedAddresses, + RoomPublish, + RoomUpgrade, +} from '../../common-settings/general'; type GeneralProps = { requestClose: () => void; diff --git a/src/app/features/room-settings/permissions/Permissions.tsx b/src/app/features/room-settings/permissions/Permissions.tsx index 802dd65..ae3769b 100644 --- a/src/app/features/room-settings/permissions/Permissions.tsx +++ b/src/app/features/room-settings/permissions/Permissions.tsx @@ -1,13 +1,12 @@ import React, { useState } from 'react'; import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; import { Page, PageContent, PageHeader } from '../../../components/page'; -import { Powers } from './Powers'; import { useRoom } from '../../../hooks/useRoom'; import { usePowerLevels, usePowerLevelsAPI } from '../../../hooks/usePowerLevels'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { StateEvent } from '../../../../types/matrix/room'; -import { PowersEditor } from './PowersEditor'; -import { PermissionGroups } from './PermissionGroups'; +import { usePermissionGroups } from './usePermissionItems'; +import { PermissionGroups, Powers, PowersEditor } from '../../common-settings/permissions'; type PermissionsProps = { requestClose: () => void; @@ -21,6 +20,7 @@ export function Permissions({ requestClose }: PermissionsProps) { StateEvent.PowerLevelTags, getPowerLevel(mx.getSafeUserId()) ); + const permissionGroups = usePermissionGroups(); const [powerEditor, setPowerEditor] = useState(false); @@ -55,8 +55,9 @@ export function Permissions({ requestClose }: PermissionsProps) { - + diff --git a/src/app/features/room-settings/permissions/usePermissionItems.ts b/src/app/features/room-settings/permissions/usePermissionItems.ts index f8df645..513f82b 100644 --- a/src/app/features/room-settings/permissions/usePermissionItems.ts +++ b/src/app/features/room-settings/permissions/usePermissionItems.ts @@ -1,17 +1,6 @@ import { useMemo } from 'react'; -import { PermissionLocation } from '../../../hooks/usePowerLevels'; import { MessageEvent, StateEvent } from '../../../../types/matrix/room'; - -export type PermissionItem = { - location: PermissionLocation; - name: string; - description?: string; -}; - -export type PermissionGroup = { - name: string; - items: PermissionItem[]; -}; +import { PermissionGroup } from '../../common-settings/permissions'; export const usePermissionGroups = (): PermissionGroup[] => { const groups: PermissionGroup[] = useMemo(() => { diff --git a/src/app/features/space-settings/SpaceSettings.tsx b/src/app/features/space-settings/SpaceSettings.tsx new file mode 100644 index 0000000..e565fb9 --- /dev/null +++ b/src/app/features/space-settings/SpaceSettings.tsx @@ -0,0 +1,173 @@ +import React, { useMemo, useState } from 'react'; +import { useAtomValue } from 'jotai'; +import { Avatar, Box, config, Icon, IconButton, Icons, IconSrc, MenuItem, Text } from 'folds'; +import { JoinRule } from 'matrix-js-sdk'; +import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page'; +import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { mxcUrlToHttp } from '../../utils/matrix'; +import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; +import { useRoomAvatar, useRoomJoinRule, useRoomName } from '../../hooks/useRoomMeta'; +import { mDirectAtom } from '../../state/mDirectList'; +import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; +import { SpaceSettingsPage } from '../../state/spaceSettings'; +import { useRoom } from '../../hooks/useRoom'; +import { EmojisStickers } from '../common-settings/emojis-stickers'; +import { Members } from '../common-settings/members'; +import { DeveloperTools } from '../common-settings/developer-tools'; +import { General } from './general'; +import { Permissions } from './permissions'; + +type SpaceSettingsMenuItem = { + page: SpaceSettingsPage; + name: string; + icon: IconSrc; +}; + +const useSpaceSettingsMenuItems = (): SpaceSettingsMenuItem[] => + useMemo( + () => [ + { + page: SpaceSettingsPage.GeneralPage, + name: 'General', + icon: Icons.Setting, + }, + { + page: SpaceSettingsPage.MembersPage, + name: 'Members', + icon: Icons.User, + }, + { + page: SpaceSettingsPage.PermissionsPage, + name: 'Permissions', + icon: Icons.Lock, + }, + { + page: SpaceSettingsPage.EmojisStickersPage, + name: 'Emojis & Stickers', + icon: Icons.Smile, + }, + { + page: SpaceSettingsPage.DeveloperToolsPage, + name: 'Developer Tools', + icon: Icons.Terminal, + }, + ], + [] + ); + +type SpaceSettingsProps = { + initialPage?: SpaceSettingsPage; + requestClose: () => void; +}; +export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps) { + const room = useRoom(); + const mx = useMatrixClient(); + const useAuthentication = useMediaAuthentication(); + const mDirects = useAtomValue(mDirectAtom); + + const roomAvatar = useRoomAvatar(room, mDirects.has(room.roomId)); + const roomName = useRoomName(room); + const joinRuleContent = useRoomJoinRule(room); + + const avatarUrl = roomAvatar + ? mxcUrlToHttp(mx, roomAvatar, useAuthentication, 96, 96, 'crop') ?? undefined + : undefined; + + const screenSize = useScreenSizeContext(); + const [activePage, setActivePage] = useState(() => { + if (initialPage) return initialPage; + return screenSize === ScreenSize.Mobile ? undefined : SpaceSettingsPage.GeneralPage; + }); + const menuItems = useSpaceSettingsMenuItems(); + + const handlePageRequestClose = () => { + if (screenSize === ScreenSize.Mobile) { + setActivePage(undefined); + return; + } + requestClose(); + }; + + return ( + + + + + ( + + )} + /> + + + {roomName} + + + + {screenSize === ScreenSize.Mobile && ( + + + + )} + + + + +
+ {menuItems.map((item) => ( + } + onClick={() => setActivePage(item.page)} + > + + {item.name} + + + ))} +
+
+
+ + ) + } + > + {activePage === SpaceSettingsPage.GeneralPage && ( + + )} + {activePage === SpaceSettingsPage.MembersPage && ( + + )} + {activePage === SpaceSettingsPage.PermissionsPage && ( + + )} + {activePage === SpaceSettingsPage.EmojisStickersPage && ( + + )} + {activePage === SpaceSettingsPage.DeveloperToolsPage && ( + + )} +
+ ); +} diff --git a/src/app/features/space-settings/SpaceSettingsRenderer.tsx b/src/app/features/space-settings/SpaceSettingsRenderer.tsx new file mode 100644 index 0000000..085c5e2 --- /dev/null +++ b/src/app/features/space-settings/SpaceSettingsRenderer.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { SpaceSettings } from './SpaceSettings'; +import { Modal500 } from '../../components/Modal500'; +import { useCloseSpaceSettings, useSpaceSettingsState } from '../../state/hooks/spaceSettings'; +import { useAllJoinedRoomsSet, useGetRoom } from '../../hooks/useGetRoom'; +import { SpaceSettingsState } from '../../state/spaceSettings'; +import { RoomProvider } from '../../hooks/useRoom'; +import { SpaceProvider } from '../../hooks/useSpace'; + +type RenderSettingsProps = { + state: SpaceSettingsState; +}; +function RenderSettings({ state }: RenderSettingsProps) { + const { roomId, spaceId, page } = state; + const closeSettings = useCloseSpaceSettings(); + const allJoinedRooms = useAllJoinedRoomsSet(); + const getRoom = useGetRoom(allJoinedRooms); + const room = getRoom(roomId); + const space = spaceId ? getRoom(spaceId) : undefined; + + if (!room) return null; + + return ( + + + + + + + + ); +} + +export function SpaceSettingsRenderer() { + const state = useSpaceSettingsState(); + + if (!state) return null; + return ; +} diff --git a/src/app/features/space-settings/general/General.tsx b/src/app/features/space-settings/general/General.tsx new file mode 100644 index 0000000..6f4d8d3 --- /dev/null +++ b/src/app/features/space-settings/general/General.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; +import { Page, PageContent, PageHeader } from '../../../components/page'; +import { usePowerLevels } from '../../../hooks/usePowerLevels'; +import { useRoom } from '../../../hooks/useRoom'; +import { + RoomProfile, + RoomJoinRules, + RoomLocalAddresses, + RoomPublishedAddresses, + RoomPublish, + RoomUpgrade, +} from '../../common-settings/general'; + +type GeneralProps = { + requestClose: () => void; +}; +export function General({ requestClose }: GeneralProps) { + const room = useRoom(); + const powerLevels = usePowerLevels(room); + + return ( + + + + + + General + + + + + + + + + + + + + + + + Options + + + + + Addresses + + + + + Advance Options + + + + + + + + ); +} diff --git a/src/app/features/space-settings/general/index.ts b/src/app/features/space-settings/general/index.ts new file mode 100644 index 0000000..0ab02c5 --- /dev/null +++ b/src/app/features/space-settings/general/index.ts @@ -0,0 +1 @@ +export * from './General'; diff --git a/src/app/features/space-settings/index.ts b/src/app/features/space-settings/index.ts new file mode 100644 index 0000000..e01a1b2 --- /dev/null +++ b/src/app/features/space-settings/index.ts @@ -0,0 +1,2 @@ +export * from './SpaceSettings'; +export * from './SpaceSettingsRenderer'; diff --git a/src/app/features/space-settings/permissions/Permissions.tsx b/src/app/features/space-settings/permissions/Permissions.tsx new file mode 100644 index 0000000..ae3769b --- /dev/null +++ b/src/app/features/space-settings/permissions/Permissions.tsx @@ -0,0 +1,67 @@ +import React, { useState } from 'react'; +import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; +import { Page, PageContent, PageHeader } from '../../../components/page'; +import { useRoom } from '../../../hooks/useRoom'; +import { usePowerLevels, usePowerLevelsAPI } from '../../../hooks/usePowerLevels'; +import { useMatrixClient } from '../../../hooks/useMatrixClient'; +import { StateEvent } from '../../../../types/matrix/room'; +import { usePermissionGroups } from './usePermissionItems'; +import { PermissionGroups, Powers, PowersEditor } from '../../common-settings/permissions'; + +type PermissionsProps = { + requestClose: () => void; +}; +export function Permissions({ requestClose }: PermissionsProps) { + const mx = useMatrixClient(); + const room = useRoom(); + const powerLevels = usePowerLevels(room); + const { getPowerLevel, canSendStateEvent } = usePowerLevelsAPI(powerLevels); + const canEditPowers = canSendStateEvent( + StateEvent.PowerLevelTags, + getPowerLevel(mx.getSafeUserId()) + ); + const permissionGroups = usePermissionGroups(); + + const [powerEditor, setPowerEditor] = useState(false); + + const handleEditPowers = () => { + setPowerEditor(true); + }; + + if (canEditPowers && powerEditor) { + return setPowerEditor(false)} />; + } + + return ( + + + + + + Permissions + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/features/space-settings/permissions/index.ts b/src/app/features/space-settings/permissions/index.ts new file mode 100644 index 0000000..753f2b4 --- /dev/null +++ b/src/app/features/space-settings/permissions/index.ts @@ -0,0 +1 @@ +export * from './Permissions'; diff --git a/src/app/features/space-settings/permissions/usePermissionItems.ts b/src/app/features/space-settings/permissions/usePermissionItems.ts new file mode 100644 index 0000000..8192e7b --- /dev/null +++ b/src/app/features/space-settings/permissions/usePermissionItems.ts @@ -0,0 +1,148 @@ +import { useMemo } from 'react'; +import { StateEvent } from '../../../../types/matrix/room'; +import { PermissionGroup } from '../../common-settings/permissions'; + +export const usePermissionGroups = (): PermissionGroup[] => { + const groups: PermissionGroup[] = useMemo(() => { + const messagesGroup: PermissionGroup = { + name: 'Manage', + items: [ + { + location: { + state: true, + key: StateEvent.SpaceChild, + }, + name: 'Manage space rooms', + }, + { + location: {}, + name: 'Message Events', + }, + ], + }; + + const moderationGroup: PermissionGroup = { + name: 'Moderation', + items: [ + { + location: { + action: true, + key: 'invite', + }, + name: 'Invite', + }, + { + location: { + action: true, + key: 'kick', + }, + name: 'Kick', + }, + { + location: { + action: true, + key: 'ban', + }, + name: 'Ban', + }, + ], + }; + + const roomOverviewGroup: PermissionGroup = { + name: 'Space Overview', + items: [ + { + location: { + state: true, + key: StateEvent.RoomAvatar, + }, + name: 'Space Avatar', + }, + { + location: { + state: true, + key: StateEvent.RoomName, + }, + name: 'Space Name', + }, + { + location: { + state: true, + key: StateEvent.RoomTopic, + }, + name: 'Space Topic', + }, + ], + }; + + const roomSettingsGroup: PermissionGroup = { + name: 'Settings', + items: [ + { + location: { + state: true, + key: StateEvent.RoomJoinRules, + }, + name: 'Change Space Access', + }, + { + location: { + state: true, + key: StateEvent.RoomCanonicalAlias, + }, + name: 'Publish Address', + }, + { + location: { + state: true, + key: StateEvent.RoomPowerLevels, + }, + name: 'Change All Permission', + }, + { + location: { + state: true, + key: StateEvent.PowerLevelTags, + }, + name: 'Edit Power Levels', + }, + { + location: { + state: true, + key: StateEvent.RoomTombstone, + }, + name: 'Upgrade Space', + }, + { + location: { + state: true, + }, + name: 'Other Settings', + }, + ], + }; + + const otherSettingsGroup: PermissionGroup = { + name: 'Other', + items: [ + { + location: { + state: true, + key: StateEvent.RoomServerAcl, + }, + name: 'Change Server ACLs', + }, + ], + }; + + return [ + messagesGroup, + moderationGroup, + roomOverviewGroup, + roomSettingsGroup, + otherSettingsGroup, + ]; + }, []); + + return groups; +}; diff --git a/src/app/features/space-settings/styles.css.ts b/src/app/features/space-settings/styles.css.ts new file mode 100644 index 0000000..ce89c16 --- /dev/null +++ b/src/app/features/space-settings/styles.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css'; +import { config } from 'folds'; + +export const SequenceCardStyle = style({ + padding: config.space.S300, +}); diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index 3c5f40c..8974369 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -60,6 +60,7 @@ import { ReceiveSelfDeviceVerification } from '../components/DeviceVerification' import { AutoRestoreBackupOnVerification } from '../components/BackupRestore'; import { RoomSettingsRenderer } from '../features/room-settings'; import { ClientRoomsNotificationPreferences } from './client/ClientRoomsNotificationPreferences'; +import { SpaceSettingsRenderer } from '../features/space-settings'; export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => { const { hashRouter } = clientConfig; @@ -125,6 +126,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) + diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx index 96e3b9a..5b47cb5 100644 --- a/src/app/pages/client/sidebar/SpaceTabs.tsx +++ b/src/app/pages/client/sidebar/SpaceTabs.tsx @@ -82,7 +82,7 @@ import { useRoomsUnread } from '../../../state/hooks/unread'; import { roomToUnreadAtom } from '../../../state/room/roomToUnread'; import { markAsRead } from '../../../../client/action/notifications'; import { copyToClipboard } from '../../../utils/dom'; -import { openInviteUser, openSpaceSettings } from '../../../../client/action/navigation'; +import { openInviteUser } from '../../../../client/action/navigation'; import { stopPropagation } from '../../../utils/keyboard'; import { getMatrixToRoom } from '../../../plugins/matrix-to'; import { getViaServers } from '../../../plugins/via-servers'; @@ -90,6 +90,7 @@ 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'; type SpaceMenuProps = { room: Room; @@ -104,6 +105,7 @@ const SpaceMenu = forwardRef( const powerLevels = usePowerLevels(room); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); + const openSpaceSettings = useOpenSpaceSettings(); const allChild = useSpaceChildren( allRoomsAtom, diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index 737663a..fa8c0ea 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -54,7 +54,7 @@ import { useSpaceJoinedHierarchy } from '../../../hooks/useSpaceHierarchy'; import { allRoomsAtom } from '../../../state/room-list/roomList'; import { PageNav, PageNavContent, PageNavHeader } from '../../../components/page'; import { usePowerLevels, usePowerLevelsAPI } from '../../../hooks/usePowerLevels'; -import { openInviteUser, openSpaceSettings } from '../../../../client/action/navigation'; +import { openInviteUser } from '../../../../client/action/navigation'; import { useRecursiveChildScopeFactory, useSpaceChildren } from '../../../state/hooks/roomList'; import { roomToParentsAtom } from '../../../state/room/roomToParents'; import { markAsRead } from '../../../../client/action/notifications'; @@ -74,6 +74,7 @@ import { getRoomNotificationMode, useRoomsNotificationPreferencesContext, } from '../../../hooks/useRoomsNotificationPreferences'; +import { useOpenSpaceSettings } from '../../../state/hooks/spaceSettings'; type SpaceMenuProps = { room: Room; @@ -86,6 +87,7 @@ const SpaceMenu = forwardRef(({ room, requestClo const powerLevels = usePowerLevels(room); const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels); const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? '')); + const openSpaceSettings = useOpenSpaceSettings(); const allChild = useSpaceChildren( allRoomsAtom, diff --git a/src/app/state/hooks/spaceSettings.ts b/src/app/state/hooks/spaceSettings.ts new file mode 100644 index 0000000..e589d65 --- /dev/null +++ b/src/app/state/hooks/spaceSettings.ts @@ -0,0 +1,34 @@ +import { useCallback } from 'react'; +import { useAtomValue, useSetAtom } from 'jotai'; +import { spaceSettingsAtom, SpaceSettingsPage, SpaceSettingsState } from '../spaceSettings'; + +export const useSpaceSettingsState = (): SpaceSettingsState | undefined => { + const data = useAtomValue(spaceSettingsAtom); + + return data; +}; + +type CloseCallback = () => void; +export const useCloseSpaceSettings = (): CloseCallback => { + const setSettings = useSetAtom(spaceSettingsAtom); + + const close: CloseCallback = useCallback(() => { + setSettings(undefined); + }, [setSettings]); + + return close; +}; + +type OpenCallback = (roomId: string, space?: string, page?: SpaceSettingsPage) => void; +export const useOpenSpaceSettings = (): OpenCallback => { + const setSettings = useSetAtom(spaceSettingsAtom); + + const open: OpenCallback = useCallback( + (roomId, spaceId, page) => { + setSettings({ roomId, spaceId, page }); + }, + [setSettings] + ); + + return open; +}; diff --git a/src/app/state/spaceSettings.ts b/src/app/state/spaceSettings.ts new file mode 100644 index 0000000..e79506b --- /dev/null +++ b/src/app/state/spaceSettings.ts @@ -0,0 +1,17 @@ +import { atom } from 'jotai'; + +export enum SpaceSettingsPage { + GeneralPage, + MembersPage, + PermissionsPage, + EmojisStickersPage, + DeveloperToolsPage, +} + +export type SpaceSettingsState = { + page?: SpaceSettingsPage; + roomId: string; + spaceId?: string; +}; + +export const spaceSettingsAtom = atom(undefined); -- 2.34.1