From 57ca5e461587c2ece40c397804fa647dfd8aa973 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:30:29 +0530 Subject: [PATCH] replace sdk from space lobby --- src/app/components/RoomSummaryLoader.tsx | 19 +- .../autocomplete/RoomMentionAutocomplete.tsx | 13 +- src/app/components/room-card/RoomCard.tsx | 2 +- src/app/features/lobby/HierarchyItemMenu.tsx | 45 +- src/app/features/lobby/Lobby.tsx | 471 ++++++++++-------- src/app/features/lobby/LobbyHeader.tsx | 43 +- src/app/features/lobby/LobbyHero.tsx | 34 +- src/app/features/lobby/RoomItem.tsx | 116 +++-- src/app/features/lobby/SpaceHierarchy.tsx | 53 +- src/app/features/lobby/SpaceItem.tsx | 127 +++-- src/app/features/room-nav/RoomNavItem.tsx | 1 + src/app/features/room/RoomViewHeader.tsx | 7 +- src/app/hooks/useLocalRoomSummary.ts | 6 +- src/app/hooks/usePowerLevels.ts | 66 +-- src/app/hooks/useSpaceHierarchy.ts | 18 +- src/app/pages/Router.tsx | 4 +- src/client/context.ts | 63 ++- src/client/initialize.ts | 19 +- src/client/reader/Error.ts | 6 +- src/types/matrix/room.ts | 4 +- 20 files changed, 649 insertions(+), 468 deletions(-) diff --git a/src/app/components/RoomSummaryLoader.tsx b/src/app/components/RoomSummaryLoader.tsx index 88b10446..4d025869 100644 --- a/src/app/components/RoomSummaryLoader.tsx +++ b/src/app/components/RoomSummaryLoader.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useCallback, useState } from 'react'; +import { ReactElement, useCallback, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { parseMatrixResp, printMatrixError } from 'neurone/lib/matrix-request'; import { getRoomSummary, GetRoomSummaryOK } from 'neurone/lib/api/matrix/client/v1/roomSummary'; @@ -7,10 +7,11 @@ import { SpaceHierarchyRoomChunk } from 'neurone/lib/api/types'; import { AsyncState, AsyncStatus } from '../hooks/useAsyncCallback'; import { useClient } from '../hooks/useClient'; import { useSpecVersionSupport } from '../hooks/useSpecVersions'; +import { LocalRoomSummary, useLocalRoomSummary } from '../hooks/useLocalRoomSummary'; type RoomSummaryLoaderProps = { roomIdOrAlias: string; - children: (roomSummary?: GetRoomSummaryOK) => ReactNode; + children: (roomSummary?: GetRoomSummaryOK) => ReactElement; }; export function RoomSummaryLoader({ roomIdOrAlias, children }: RoomSummaryLoaderProps) { @@ -40,15 +41,23 @@ export function RoomSummaryLoader({ roomIdOrAlias, children }: RoomSummaryLoader return children(data); } -export function LocalRoomSummaryLoader({ children }: { children: () => ReactNode }) { - return children(); +export function LocalRoomSummaryLoader({ + roomId, + children, +}: { + roomId: string; + children: (summary: LocalRoomSummary) => ReactElement; +}) { + const summary = useLocalRoomSummary(roomId); + + return children(summary); } export function HierarchyRoomSummaryLoader({ roomId, children, }: { roomId: string; - children: (state: AsyncState) => ReactNode; + children: (state: AsyncState) => ReactElement; }) { const ct = useClient(); diff --git a/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx b/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx index dffb4515..15a9fe1e 100644 --- a/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx @@ -186,11 +186,20 @@ export function RoomMentionAutocomplete({ src={avatar} alt={name} renderFallback={() => ( - + )} /> ) : ( - + )} } diff --git a/src/app/components/room-card/RoomCard.tsx b/src/app/components/room-card/RoomCard.tsx index 9fb283c2..0ce5f337 100644 --- a/src/app/components/room-card/RoomCard.tsx +++ b/src/app/components/room-card/RoomCard.tsx @@ -219,7 +219,7 @@ export const RoomCard = as<'div', RoomCardProps>( )} /> - {(roomType === RoomType.Space || localSummary.space) && ( + {(roomType === RoomType.Space || localSummary.roomType === RoomType.Space) && ( Space diff --git a/src/app/features/lobby/HierarchyItemMenu.tsx b/src/app/features/lobby/HierarchyItemMenu.tsx index 58ed2109..91d9af90 100644 --- a/src/app/features/lobby/HierarchyItemMenu.tsx +++ b/src/app/features/lobby/HierarchyItemMenu.tsx @@ -15,8 +15,8 @@ import { Spinner, toRem, } from 'folds'; +import { putStateEvent } from 'neurone/lib/api/matrix/client/v3/rooms'; import { HierarchyItem } from '../../hooks/useSpaceHierarchy'; -import { useMatrixClient } from '../../hooks/useMatrixClient'; import { SpaceChildContent, StateEvent } from '../../../types/matrix/room'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { UseStateProvider } from '../../components/UseStateProvider'; @@ -27,9 +27,9 @@ import { useOpenRoomSettings } from '../../state/hooks/roomSettings'; import { useSpaceOptionally } from '../../hooks/useSpace'; import { useOpenSpaceSettings } from '../../state/hooks/spaceSettings'; import { IPowerLevels } from '../../hooks/usePowerLevels'; -import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators'; import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions'; import { InviteUserPrompt } from '../../components/invite-user-prompt'; +import { useClient } from '../../hooks/useClient'; type HierarchyItemWithParent = HierarchyItem & { parentId: string; @@ -42,14 +42,19 @@ function SuggestMenuItem({ item: HierarchyItemWithParent; requestClose: () => void; }) { - const mx = useMatrixClient(); + const ct = useClient(); const { roomId, parentId, content } = item; const [toggleState, handleToggleSuggested] = useAsyncCallback( useCallback(() => { const newContent: SpaceChildContent = { ...content, suggested: !content.suggested }; - return mx.sendStateEvent(parentId, StateEvent.SpaceChild as any, newContent, roomId); - }, [mx, parentId, roomId, content]) + return ct.requestAuthJSON( + putStateEvent({ + pathParams: { roomId: parentId, eventType: StateEvent.SpaceChild, stateKey: roomId }, + body: newContent, + }) + ); + }, [ct, parentId, roomId, content]) ); useEffect(() => { @@ -80,13 +85,19 @@ function RemoveMenuItem({ item: HierarchyItemWithParent; requestClose: () => void; }) { - const mx = useMatrixClient(); + const ct = useClient(); const { roomId, parentId } = item; const [removeState, handleRemove] = useAsyncCallback( useCallback( - () => mx.sendStateEvent(parentId, StateEvent.SpaceChild as any, {}, roomId), - [mx, parentId, roomId] + () => + ct.requestAuthJSON( + putStateEvent({ + pathParams: { roomId: parentId, eventType: StateEvent.SpaceChild, stateKey: roomId }, + body: {}, + }) + ), + [ct, parentId, roomId] ) ); @@ -126,8 +137,6 @@ function InviteMenuItem({ requestClose: () => void; disabled?: boolean; }) { - const mx = useMatrixClient(); - const room = mx.getRoom(item.roomId); const [invitePrompt, setInvitePrompt] = useState(false); const handleInvite = () => { @@ -143,15 +152,15 @@ function InviteMenuItem({ variant="Primary" fill="None" aria-pressed={invitePrompt} - disabled={disabled || !room} + disabled={disabled} > Invite - {invitePrompt && room && ( + {invitePrompt && ( { setInvitePrompt(false); requestClose(); @@ -173,13 +182,13 @@ function SettingsMenuItem({ }) { const openRoomSettings = useOpenRoomSettings(); const openSpaceSettings = useOpenSpaceSettings(); - const space = useSpaceOptionally(); + const spaceId = useSpaceOptionally(); const handleSettings = () => { if ('space' in item) { openSpaceSettings(item.roomId, item.parentId); } else { - openRoomSettings(item.roomId, space?.roomId); + openRoomSettings(item.roomId, spaceId); } requestClose(); }; @@ -211,15 +220,15 @@ export function HierarchyItemMenu({ pinned, onTogglePin, }: HierarchyItemMenuProps) { - const mx = useMatrixClient(); + const ct = useClient(); const [menuAnchor, setMenuAnchor] = useState(); const canInvite = (): boolean => { if (!powerLevels) return false; - const creators = getRoomCreatorsForRoomId(mx, item.roomId); + const creators = ct.roomToCreators.read.get(item.roomId); const permissions = getRoomPermissionsAPI(creators, powerLevels); - return permissions.action('invite', mx.getSafeUserId()); + return permissions.action('invite', ct.session.userId); }; const handleOpenMenu: MouseEventHandler = (evt) => { diff --git a/src/app/features/lobby/Lobby.tsx b/src/app/features/lobby/Lobby.tsx index c182223d..6d9ae85c 100644 --- a/src/app/features/lobby/Lobby.tsx +++ b/src/app/features/lobby/Lobby.tsx @@ -1,9 +1,14 @@ import React, { MouseEventHandler, useCallback, useMemo, useRef, useState } from 'react'; import { Box, Chip, Icon, IconButton, Icons, Line, Scroll, Spinner, Text, config } from 'folds'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { useAtom, useAtomValue } from 'jotai'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { useNavigate } from 'react-router-dom'; import produce from 'immer'; +import { AccountDataEvent, SpaceHierarchyRoomChunk } from 'neurone/lib/api/types'; +import { putStateEvent } from 'neurone/lib/api/matrix/client/v3/rooms'; +import { parseMatrixResp } from 'neurone/lib/matrix-request'; +import { ObjectReader } from 'neurone/lib/utils'; +import { putAccountData } from 'neurone/lib/api/matrix/client/v3/user'; import { useSpace } from '../../hooks/useSpace'; import { Page, PageContent, PageContentCenter, PageHeroSection } from '../../components/page'; import { @@ -26,39 +31,46 @@ import { PowerLevelsContextProvider, usePowerLevels, useRoomsPowerLevels, + usePowerLevelsState, } from '../../hooks/usePowerLevels'; -import { mDirectAtom } from '../../state/mDirectList'; import { makeLobbyCategoryId } from '../../state/closedLobbyCategories'; import { useCategoryHandler } from '../../hooks/useCategoryHandler'; import { allRoomsAtom } from '../../state/room-list/roomList'; -import { getCanonicalAliasOrRoomId, rateLimitedActions } from '../../utils/matrix'; +import { rateLimitedActions } from '../../utils/matrix'; import { getSpaceRoomPath } from '../../pages/pathUtils'; -import { StateEvent } from '../../../types/matrix/room'; +import { AllowCondition, StateEvent } from '../../../types/matrix/room'; import { CanDropCallback, useDnDMonitor } from './DnD'; import { ASCIILexicalTable, orderKeys } from '../../utils/ASCIILexicalTable'; -import { getStateEvent } from '../../utils/room'; import { useClosedLobbyCategoriesAtom } from '../../state/hooks/closedLobbyCategories'; import { makeCinnySpacesContent, sidebarItemWithout, useSidebarItems, + InCinnySpacesContent, } from '../../hooks/useSidebarItems'; import { useOrphanSpaces } from '../../state/hooks/roomList'; import { roomToParentsAtom } from '../../state/room/roomToParents'; -import { AccountDataEvent } from '../../../types/matrix/accountData'; -import { useRoomMembers } from '../../hooks/useRoomMembers'; import { SpaceHierarchy } from './SpaceHierarchy'; -import { useGetRoom } from '../../hooks/useGetRoom'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions'; -import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators'; +import { useClient } from '../../hooks/useClient'; +import { RoomProvider } from '../../hooks/useRoom'; +import { RoomMembersAPIProvider, useCreateRoomMembersAPI } from '../../hooks/useRoomMembers'; +import { usePendingMemberLoader, usePendingMembers } from '../../hooks/usePendingMembers'; +import { useAccountData } from '../../hooks/storage/useAccountData'; +import { AccountDataType } from '../../../types/matrix/accountData'; +import { useAllJoinedRoomsSet } from '../../hooks/useGetRoom'; +import { + PowerLevelTagsContextProvider, + usePowerLevelTags, + usePowerLevelTagsState, +} from '../../hooks/usePowerLevelTags'; const useCanDropLobbyItem = ( - space: Room, - roomsPowerLevels: Map, - getRoom: (roomId: string) => Room | undefined + spaceId: string, + roomsPowerLevels: Map ): CanDropCallback => { - const mx = useMatrixClient(); + const ct = useClient(); const canDropSpace: CanDropCallback = useCallback( (item, container) => { @@ -68,22 +80,20 @@ const useCanDropLobbyItem = ( return false; } - const containerSpaceId = space.roomId; - - const powerLevels = roomsPowerLevels.get(containerSpaceId) ?? {}; - const creators = getRoomCreatorsForRoomId(mx, containerSpaceId); + const powerLevels = roomsPowerLevels.get(spaceId) ?? {}; + const creators = ct.roomToCreators.read.get(spaceId); const permissions = getRoomPermissionsAPI(creators, powerLevels); if ( - getRoom(containerSpaceId) === undefined || - !permissions.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId()) + !ct.rooms.join.read.includes(spaceId) || + !permissions.stateEvent(StateEvent.SpaceChild, ct.session.userId) ) { return false; } return true; }, - [space, roomsPowerLevels, getRoom, mx] + [spaceId, roomsPowerLevels, ct] ); const canDropRoom: CanDropCallback = useCallback( @@ -92,18 +102,18 @@ const useCanDropLobbyItem = ( 'space' in container.item ? container.item.roomId : container.item.parentId; const draggingOutsideSpace = item.parentId !== containerSpaceId; - const restrictedItem = mx.getRoom(item.roomId)?.getJoinRule() === JoinRule.Restricted; + const restrictedItem = ct.roomToJoinRule.read.get(item.roomId) === 'restricted'; // check and do not allow restricted room to be dragged outside // current space if can't change `m.room.join_rules` `content.allow` if (draggingOutsideSpace && restrictedItem) { const itemPowerLevels = roomsPowerLevels.get(item.roomId) ?? {}; - const itemCreators = getRoomCreatorsForRoomId(mx, item.roomId); + const itemCreators = ct.roomToCreators.read.get(item.roomId); const itemPermissions = getRoomPermissionsAPI(itemCreators, itemPowerLevels); const canChangeJoinRuleAllow = itemPermissions.stateEvent( StateEvent.RoomJoinRules, - mx.getSafeUserId() + ct.session.userId ); if (!canChangeJoinRuleAllow) { return false; @@ -111,17 +121,17 @@ const useCanDropLobbyItem = ( } const powerLevels = roomsPowerLevels.get(containerSpaceId) ?? {}; - const creators = getRoomCreatorsForRoomId(mx, containerSpaceId); + const creators = ct.roomToCreators.read.get(containerSpaceId); const permissions = getRoomPermissionsAPI(creators, powerLevels); if ( - getRoom(containerSpaceId) === undefined || - !permissions.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId()) + !ct.rooms.join.read.includes(containerSpaceId) || + !permissions.stateEvent(StateEvent.SpaceChild, ct.session.userId) ) { return false; } return true; }, - [mx, getRoom, roomsPowerLevels] + [ct, roomsPowerLevels] ); const canDrop: CanDropCallback = useCallback( @@ -145,27 +155,37 @@ const useCanDropLobbyItem = ( }; export function Lobby() { + const ct = useClient(); const navigate = useNavigate(); - const mx = useMatrixClient(); - const mDirects = useAtomValue(mDirectAtom); - const allRooms = useAtomValue(allRoomsAtom); - const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]); - const space = useSpace(); - const spacePowerLevels = usePowerLevels(space); + const spaceId = useSpace(); + const allJoinedRooms = useAllJoinedRoomsSet(); + + const powerLevelStateResult = usePowerLevelsState(spaceId); + const spacePowerLevels = usePowerLevels(powerLevelStateResult.value); const lex = useMemo(() => new ASCIILexicalTable(' '.charCodeAt(0), '~'.charCodeAt(0), 6), []); - const members = useRoomMembers(mx, space.roomId); + + const powerLevelTagsStateResult = usePowerLevelTagsState(spaceId); + const powerLevelTags = usePowerLevelTags(spacePowerLevels, powerLevelTagsStateResult.value); + const membersAPI = useCreateRoomMembersAPI(spaceId); + const membersPending = usePendingMembers(spaceId); + usePendingMemberLoader(spaceId, membersPending); const scrollRef = useRef(null); const heroSectionRef = useRef(null); const [heroSectionHeight, setHeroSectionHeight] = useState(); - const [spaceRooms, setSpaceRooms] = useAtom(spaceRoomsAtom); + const setSpaceRooms = useSetAtom(spaceRoomsAtom); const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer'); const screenSize = useScreenSizeContext(); const [onTop, setOnTop] = useState(true); const [closedCategories, setClosedCategories] = useAtom(useClosedLobbyCategoriesAtom()); - const [sidebarItems] = useSidebarItems( - useOrphanSpaces(mx, allRoomsAtom, useAtomValue(roomToParentsAtom)) + + const roomToParents = useAtomValue(roomToParentsAtom); + const orphanSpaces = useOrphanSpaces(ct, allRoomsAtom, roomToParents); + const { value: cinnySpacesEvent } = useAccountData>( + AccountDataType.CinnySpaces ); + const sidebarItems = useSidebarItems(orphanSpaces, cinnySpacesEvent); + const sidebarSpaces = useMemo(() => { const sideSpaces = sidebarItems.flatMap((item) => { if (typeof item === 'string') return item; @@ -175,25 +195,23 @@ export function Lobby() { return new Set(sideSpaces); }, [sidebarItems]); - const [spacesItems, setSpacesItem] = useState>(() => new Map()); + const [spacesItems, setSpacesItem] = useState>( + () => new Map() + ); useElementSizeObserver( useCallback(() => heroSectionRef.current, []), useCallback((w, height) => setHeroSectionHeight(height), []) ); - const getRoom = useGetRoom(allJoinedRooms); - const [draggingItem, setDraggingItem] = useState(); const hierarchy = useSpaceHierarchy( - space.roomId, - spaceRooms, - getRoom, + spaceId, useCallback( (childId) => - closedCategories.has(makeLobbyCategoryId(space.roomId, childId)) || + closedCategories.has(makeLobbyCategoryId(spaceId, childId)) || (draggingItem ? 'space' in draggingItem : false), - [closedCategories, space.roomId, draggingItem] + [closedCategories, spaceId, draggingItem] ) ); @@ -210,17 +228,17 @@ export function Lobby() { useMemo( () => hierarchy - .flatMap((i) => { - const childRooms = Array.isArray(i.rooms) ? i.rooms.map((r) => getRoom(r.roomId)) : []; + .flatMap((i): string[] => { + const childRooms = Array.isArray(i.rooms) ? i.rooms.map((r) => r.roomId) : []; - return [getRoom(i.space.roomId), ...childRooms]; + return [i.space.roomId, ...childRooms]; }) - .filter((r) => !!r) as Room[], - [hierarchy, getRoom] + .filter((r) => ct.rooms.join.read.includes(r)), + [hierarchy, ct] ) ); - const canDrop: CanDropCallback = useCanDropLobbyItem(space, roomsPowerLevels, getRoom); + const canDrop: CanDropCallback = useCanDropLobbyItem(spaceId, roomsPowerLevels); const [reorderSpaceState, reorderSpace] = useAsyncCallback( useCallback( @@ -258,25 +276,31 @@ export function Lobby() { const parentPL = roomsPowerLevels.get(reorder.item.parentId); if (!parentPL) return false; - const creators = getRoomCreatorsForRoomId(mx, reorder.item.parentId); + const creators = ct.roomToCreators.read.get(reorder.item.parentId); const permissions = getRoomPermissionsAPI(creators, parentPL); - const canEdit = permissions.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId()); + const canEdit = permissions.stateEvent(StateEvent.SpaceChild, ct.session.userId); return canEdit && reorder.orderKey !== currentOrders[index]; }); if (reorders) { await rateLimitedActions(reorders, async (reorder) => { - if (!reorder.item.parentId) return; - await mx.sendStateEvent( - reorder.item.parentId, - StateEvent.SpaceChild as any, - { ...reorder.item.content, order: reorder.orderKey }, - reorder.item.roomId + if (!reorder.item.parentId) return undefined; + const resp = await ct.requestAuthJSON( + putStateEvent({ + pathParams: { + roomId: reorder.item.parentId, + eventType: StateEvent.SpaceChild, + stateKey: reorder.item.roomId, + }, + body: { ...reorder.item.content, order: reorder.orderKey }, + }) ); + const [err] = await parseMatrixResp(resp); + return err; }); } }, - [mx, hierarchy, lex, roomsPowerLevels] + [ct, hierarchy, lex, roomsPowerLevels] ) ); const reorderingSpace = reorderSpaceState.status === AsyncStatus.Loading; @@ -284,7 +308,6 @@ export function Lobby() { const [reorderRoomState, reorderRoom] = useAsyncCallback( useCallback( async (item: HierarchyItem, containerItem: HierarchyItem) => { - const itemRoom = mx.getRoom(item.roomId); if (!item.parentId) { return; } @@ -294,49 +317,70 @@ export function Lobby() { // remove from current space if (item.parentId !== containerParentId) { - mx.sendStateEvent(item.parentId, StateEvent.SpaceChild as any, {}, item.roomId); + ct.requestAuthJSON( + putStateEvent({ + pathParams: { + roomId: item.parentId, + eventType: StateEvent.SpaceChild, + stateKey: item.roomId, + }, + body: {}, + }) + ); } if ( - itemRoom && - itemRoom.getJoinRule() === JoinRule.Restricted && + ct.roomToJoinRule.read.get(item.roomId) === 'restricted' && item.parentId !== containerParentId ) { // change join rule allow parameter when dragging // restricted room from one space to another - const joinRuleContent = getStateEvent( - itemRoom, - StateEvent.RoomJoinRules - )?.getContent(); + const joinRuleContent = ct.state.join.joinRules.read.find( + (e) => e.roomId === item.roomId && e.event.state_key === '' + )?.event.content; if (joinRuleContent) { const allow = - joinRuleContent.allow?.filter((allowRule) => allowRule.room_id !== item.parentId) ?? - []; - allow.push({ type: RestrictedAllowType.RoomMembership, room_id: containerParentId }); - mx.sendStateEvent(itemRoom.roomId, StateEvent.RoomJoinRules as any, { - ...joinRuleContent, - allow, - }); + ObjectReader.array(joinRuleContent, 'allow')?.filter( + (allowRule) => ObjectReader.string(allowRule, 'room_id') !== item.parentId + ) ?? []; + const allowCondition: AllowCondition = { + type: 'm.room_membership', + room_id: containerParentId, + }; + allow.push(allowCondition); + ct.requestAuthJSON( + putStateEvent({ + pathParams: { + roomId: item.roomId, + eventType: StateEvent.RoomJoinRules, + stateKey: '', + }, + body: { + ...joinRuleContent, + allow, + }, + }) + ); } } - const itemSpaces = Array.from( + const itemRooms = Array.from( hierarchy?.find((i) => i.space.roomId === containerParentId)?.rooms ?? [] - ); + ).filter((i) => i.roomId !== item.roomId); const beforeItem: HierarchyItem | undefined = 'space' in containerItem ? undefined : containerItem; - const beforeIndex = itemSpaces.findIndex((i) => i.roomId === beforeItem?.roomId); + const beforeIndex = itemRooms.findIndex((i) => i.roomId === beforeItem?.roomId); const insertIndex = beforeIndex + 1; - itemSpaces.splice(insertIndex, 0, { + itemRooms.splice(insertIndex, 0, { ...item, parentId: containerParentId, content: { ...itemContent, order: undefined }, }); - const currentOrders = itemSpaces.map((i) => { + const currentOrders = itemRooms.map((i) => { if (typeof i.content.order === 'string' && lex.has(i.content.order)) { return i.content.order; } @@ -347,23 +391,29 @@ export function Lobby() { const reorders = newOrders ?.map((orderKey, index) => ({ - item: itemSpaces[index], + item: itemRooms[index], orderKey, })) .filter((reorder, index) => reorder.item && reorder.orderKey !== currentOrders[index]); if (reorders) { await rateLimitedActions(reorders, async (reorder) => { - await mx.sendStateEvent( - containerParentId, - StateEvent.SpaceChild as any, - { ...reorder.item.content, order: reorder.orderKey }, - reorder.item.roomId + const resp = await ct.requestAuthJSON( + putStateEvent({ + pathParams: { + roomId: containerParentId, + eventType: StateEvent.SpaceChild, + stateKey: reorder.item.roomId, + }, + body: { ...reorder.item.content, order: reorder.orderKey }, + }) ); + const [err] = await parseMatrixResp(resp); + return err; }); } }, - [mx, hierarchy, lex] + [ct, hierarchy, lex] ) ); const reorderingRoom = reorderRoomState.status === AsyncStatus.Loading; @@ -388,7 +438,7 @@ export function Lobby() { ); const handleSpacesFound = useCallback( - (sItems: IHierarchyRoom[]) => { + (sItems: SpaceHierarchyRoomChunk[]) => { setSpaceRooms({ type: 'PUT', roomIds: sItems.map((i) => i.room_id) }); setSpacesItem((current) => { const newItems = produce(current, (draft) => { @@ -407,8 +457,10 @@ export function Lobby() { const handleOpenRoom: MouseEventHandler = (evt) => { const rId = evt.currentTarget.getAttribute('data-room-id'); if (!rId) return; - const pSpaceIdOrAlias = getCanonicalAliasOrRoomId(mx, space.roomId); - navigate(getSpaceRoomPath(pSpaceIdOrAlias, getCanonicalAliasOrRoomId(mx, rId))); + const spaceAlias = ct.roomToAlias.read.get(spaceId); + const roomAlias = ct.roomToAlias.read.get(rId); + + navigate(getSpaceRoomPath(spaceAlias ?? spaceId, roomAlias ?? rId)); }; const togglePinToSidebar = useCallback( @@ -417,127 +469,134 @@ export function Lobby() { if (!sidebarSpaces.has(rId)) { newItems.push(rId); } - const newSpacesContent = makeCinnySpacesContent(mx, newItems); - mx.setAccountData(AccountDataEvent.CinnySpaces as any, newSpacesContent as any); + const newSpacesContent = makeCinnySpacesContent(newItems, cinnySpacesEvent); + ct.requestAuthJSON( + putAccountData({ + pathParams: { + userId: ct.session.userId, + type: AccountDataType.CinnySpaces, + }, + body: newSpacesContent, + }) + ); }, - [mx, sidebarItems, sidebarSpaces] + [ct, sidebarItems, sidebarSpaces, cinnySpacesEvent] ); return ( - - - - - - - - - - virtualizer.scrollToOffset(0)} - variant="SurfaceVariant" - radii="Pill" - outlined - size="300" - aria-label="Scroll to Top" - > - - - -
- - - - {vItems.map((vItem) => { - const item = hierarchy[vItem.index]; - if (!item) return null; - const nextSpaceId = hierarchy[vItem.index + 1]?.space.roomId; - - const categoryId = makeLobbyCategoryId(space.roomId, item.space.roomId); - - return ( - - - - ); - })} -
- {reordering && ( - - } + + + + + + + + + + + - Reordering - - - )} -
-
-
+ virtualizer.scrollToOffset(0)} + variant="SurfaceVariant" + radii="Pill" + outlined + size="300" + aria-label="Scroll to Top" + > + + + +
+ + + + {vItems.map((vItem) => { + const item = hierarchy[vItem.index]; + if (!item) return null; + const nextSpaceId = hierarchy[vItem.index + 1]?.space.roomId; + + const categoryId = makeLobbyCategoryId(spaceId, item.space.roomId); + + return ( + + + + ); + })} +
+ {reordering && ( + + } + > + Reordering + + + )} + + + +
+
+ {screenSize === ScreenSize.Desktop && isDrawer && ( + + + + + )}
- - {screenSize === ScreenSize.Desktop && isDrawer && ( - <> - - - - )} - -
+ + + ); } diff --git a/src/app/features/lobby/LobbyHeader.tsx b/src/app/features/lobby/LobbyHeader.tsx index ed526c68..f9692525 100644 --- a/src/app/features/lobby/LobbyHeader.tsx +++ b/src/app/features/lobby/LobbyHeader.tsx @@ -20,9 +20,7 @@ import FocusTrap from 'focus-trap-react'; import { PageHeader } from '../../components/page'; import { useSetSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; -import { useRoomAvatar, useRoomName } from '../../hooks/useRoomMeta'; import { useSpace } from '../../hooks/useSpace'; -import { useMatrixClient } from '../../hooks/useMatrixClient'; import { RoomAvatar } from '../../components/room-avatar'; import { nameInitials } from '../../utils/common'; import * as css from './LobbyHeader.css'; @@ -32,12 +30,16 @@ import { LeaveSpacePrompt } from '../../components/leave-space-prompt'; import { stopPropagation } from '../../utils/keyboard'; 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'; import { useRoomCreators } from '../../hooks/useRoomCreators'; import { useRoomPermissions } from '../../hooks/useRoomPermissions'; import { InviteUserPrompt } from '../../components/invite-user-prompt'; +import { useClient } from '../../hooks/useClient'; +import { useRoomHeroes } from '../../hooks/storage/useRoomHeros'; +import { useGetRoomName } from '../../hooks/storage/useGetRoomName'; +import { useGetRoomAvatar } from '../../hooks/storage/useGetRoomAvatar'; +import { mxcToHttp } from '../../../client/mxcToHttp'; type LobbyMenuProps = { powerLevels: IPowerLevels; @@ -45,12 +47,12 @@ type LobbyMenuProps = { }; const LobbyMenu = forwardRef( ({ powerLevels, requestClose }, ref) => { - const mx = useMatrixClient(); - const space = useSpace(); - const creators = useRoomCreators(space); + const ct = useClient(); + const spaceId = useSpace(); + const creators = useRoomCreators(spaceId); const permissions = useRoomPermissions(creators, powerLevels); - const canInvite = permissions.action('invite', mx.getSafeUserId()); + const canInvite = permissions.action('invite', ct.session.userId); const openSpaceSettings = useOpenSpaceSettings(); const [invitePrompt, setInvitePrompt] = useState(false); @@ -60,7 +62,7 @@ const LobbyMenu = forwardRef( }; const handleRoomSettings = () => { - openSpaceSettings(space.roomId); + openSpaceSettings(spaceId); requestClose(); }; @@ -68,7 +70,7 @@ const LobbyMenu = forwardRef( {invitePrompt && ( { setInvitePrompt(false); requestClose(); @@ -121,7 +123,7 @@ const LobbyMenu = forwardRef( {promptLeave && ( setPromptLeave(false)} /> @@ -140,19 +142,28 @@ type LobbyHeaderProps = { powerLevels: IPowerLevels; }; export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) { - const mx = useMatrixClient(); + const ct = useClient(); const useAuthentication = useMediaAuthentication(); - const space = useSpace(); + const spaceId = useSpace(); const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer'); const [menuAnchor, setMenuAnchor] = useState(); const screenSize = useScreenSizeContext(); - const name = useRoomName(space); - const avatarMxc = useRoomAvatar(space); + const [getRoomHeroes, roomToHeroMembersResult] = useRoomHeroes(spaceId); + const getRoomName = useGetRoomName(getRoomHeroes, roomToHeroMembersResult.value); + const getRoomAvatar = useGetRoomAvatar(getRoomHeroes, roomToHeroMembersResult.value); + + const avatarMxc = getRoomAvatar(spaceId); const avatarUrl = avatarMxc - ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined + ? mxcToHttp(ct.session.baseUrl, avatarMxc, useAuthentication).thumbnail({ + width: 96, + height: 96, + method: 'crop', + }) : undefined; + const name = getRoomName(spaceId) ?? 'Unknown'; + const handleOpenMenu: MouseEventHandler = (evt) => { setMenuAnchor(evt.currentTarget.getBoundingClientRect()); }; @@ -187,7 +198,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) { <> {nameInitials(name)}} diff --git a/src/app/features/lobby/LobbyHero.tsx b/src/app/features/lobby/LobbyHero.tsx index 1cd0b8e2..e6fa24d0 100644 --- a/src/app/features/lobby/LobbyHero.tsx +++ b/src/app/features/lobby/LobbyHero.tsx @@ -1,9 +1,7 @@ import React from 'react'; import { Avatar, Overlay, OverlayBackdrop, OverlayCenter, Text } from 'folds'; import FocusTrap from 'focus-trap-react'; -import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta'; import { useSpace } from '../../hooks/useSpace'; -import { useMatrixClient } from '../../hooks/useMatrixClient'; import { RoomAvatar } from '../../components/room-avatar'; import { nameInitials } from '../../utils/common'; import { UseStateProvider } from '../../components/UseStateProvider'; @@ -11,25 +9,41 @@ import { RoomTopicViewer } from '../../components/room-topic-viewer'; import * as css from './LobbyHero.css'; import { PageHero } from '../../components/page'; import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard'; -import { mxcUrlToHttp } from '../../utils/matrix'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; +import { useClient } from '../../hooks/useClient'; +import { useRoomHeroes } from '../../hooks/storage/useRoomHeros'; +import { useGetRoomName } from '../../hooks/storage/useGetRoomName'; +import { useGetRoomAvatar } from '../../hooks/storage/useGetRoomAvatar'; +import { useRoomTopic } from '../../hooks/storage/useRoomTopic'; +import { mxcToHttp } from '../../../client/mxcToHttp'; export function LobbyHero() { - const mx = useMatrixClient(); + const ct = useClient(); const useAuthentication = useMediaAuthentication(); - const space = useSpace(); + const spaceId = useSpace(); - const name = useRoomName(space); - const topic = useRoomTopic(space); - const avatarMxc = useRoomAvatar(space); - const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined; + const [getRoomHeroes, roomToHeroMembersResult] = useRoomHeroes(spaceId); + const getRoomName = useGetRoomName(getRoomHeroes, roomToHeroMembersResult.value); + const getRoomAvatar = useGetRoomAvatar(getRoomHeroes, roomToHeroMembersResult.value); + const topic = useRoomTopic(spaceId); + + const avatarMxc = getRoomAvatar(spaceId); + const avatarUrl = avatarMxc + ? mxcToHttp(ct.session.baseUrl, avatarMxc, useAuthentication).thumbnail({ + width: 96, + height: 96, + method: 'crop', + }) + : undefined; + + const name = getRoomName(spaceId) ?? 'Unknown'; return ( {nameInitials(name)}} diff --git a/src/app/features/lobby/RoomItem.tsx b/src/app/features/lobby/RoomItem.tsx index 7de59acd..8db5506e 100644 --- a/src/app/features/lobby/RoomItem.tsx +++ b/src/app/features/lobby/RoomItem.tsx @@ -1,4 +1,4 @@ -import React, { MouseEventHandler, ReactNode, useCallback, useRef } from 'react'; +import React, { MouseEventHandler, ReactNode, useCallback, useRef, useState } from 'react'; import { Avatar, Badge, @@ -13,72 +13,90 @@ import { Spinner, Text, Tooltip, - TooltipProvider, as, color, toRem, + PopOut, + RectCords, + IconButton, } from 'folds'; import FocusTrap from 'focus-trap-react'; -import { JoinRule, MatrixError, Room } from 'matrix-js-sdk'; -import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; +import { SpaceHierarchyRoomChunk } from 'neurone/lib/api/types'; +import { parseMatrixResp, printMatrixError } from 'neurone/lib/matrix-request'; +import { postJoin } from 'neurone/lib/api/matrix/client/v3/join'; import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; import { SequenceCard } from '../../components/sequence-card'; -import { useMatrixClient } from '../../hooks/useMatrixClient'; import { HierarchyItem } from '../../hooks/useSpaceHierarchy'; import { millify } from '../../plugins/millify'; import { LocalRoomSummaryLoader } from '../../components/RoomSummaryLoader'; import { UseStateProvider } from '../../components/UseStateProvider'; import { RoomTopicViewer } from '../../components/room-topic-viewer'; import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard'; -import { Membership } from '../../../types/matrix/room'; +import { JoinRule } from '../../../types/matrix/room'; import * as css from './RoomItem.css'; import * as styleCss from './style.css'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; -import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../../utils/room'; import { ItemDraggableTarget, useDraggableItem } from './DnD'; -import { mxcUrlToHttp } from '../../utils/matrix'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; +import { useClient } from '../../hooks/useClient'; +import { BreakWord } from '../../styles/Text.css'; +import { mxcToHttp } from '../../../client/mxcToHttp'; type RoomJoinButtonProps = { roomId: string; via?: string[]; }; function RoomJoinButton({ roomId, via }: RoomJoinButtonProps) { - const mx = useMatrixClient(); + const ct = useClient(); - const [joinState, join] = useAsyncCallback( - useCallback(() => mx.joinRoom(roomId, { viaServers: via }), [mx, roomId, via]) + const [joinState, join] = useAsyncCallback( + useCallback(async () => { + const resp = await ct.requestAuthJSON( + postJoin({ + pathParams: { roomIdOrAlias: roomId }, + searchParams: { via, server_name: via }, + }) + ); + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(printMatrixError(err)); + }, [ct, roomId, via]) ); const canJoin = joinState.status === AsyncStatus.Idle || joinState.status === AsyncStatus.Error; + const [cords, setCords] = useState(); + const handleErrorMenu: MouseEventHandler = (evt) => { + setCords(evt.currentTarget.getBoundingClientRect()); + }; + return ( {joinState.status === AsyncStatus.Error && ( - - - {joinState.error.data?.error || joinState.error.message} + + {typeof joinState.error === 'object' && joinState.error instanceof Error + ? joinState.error.message + : 'Unknown error'} - {joinState.error.name} } > - {(triggerRef) => ( + - )} - + + )} ( - + )} /> @@ -279,18 +293,17 @@ function RoomProfile({ type RoomItemCardProps = { item: HierarchyItem; loading: boolean; - error: Error | null; - summary: IHierarchyRoom | undefined; - dm?: boolean; + error: unknown; + summary: SpaceHierarchyRoomChunk | undefined; firstChild?: boolean; lastChild?: boolean; onOpen: MouseEventHandler; + joined?: boolean; options?: ReactNode; before?: ReactNode; after?: ReactNode; onDragging: (item?: HierarchyItem) => void; canReorder: boolean; - getRoom: (roomId: string) => Room | undefined; }; export const RoomItemCard = as<'div', RoomItemCardProps>( ( @@ -299,28 +312,24 @@ export const RoomItemCard = as<'div', RoomItemCardProps>( loading, error, summary, - dm, onOpen, + joined, options, before, after, onDragging, canReorder, - getRoom, ...props }, ref ) => { - const mx = useMatrixClient(); + const ct = useClient(); const useAuthentication = useMediaAuthentication(); const { roomId, content } = item; - const room = getRoom(roomId); const targetRef = useRef(null); const targetHandleRef = useRef(null); useDraggableItem(item, targetRef, onDragging, targetHandleRef); - const joined = room?.getMyMembership() === Membership.Join; - return ( ( {before} {canReorder && } - {room ? ( - + {joined ? ( + {(localSummary) => ( ( topic={summary.topic} avatarUrl={ summary?.avatar_url - ? mxcUrlToHttp(mx, summary.avatar_url, useAuthentication, 96, 96, 'crop') ?? - undefined + ? mxcToHttp( + ct.session.baseUrl, + summary.avatar_url, + useAuthentication + ).thumbnail({ + width: 96, + height: 96, + method: 'crop', + }) : undefined } memberCount={summary.num_joined_members} suggested={content.suggested} - joinRule={summary.join_rule} + joinRule={summary.join_rule as JoinRule} options={} /> )} diff --git a/src/app/features/lobby/SpaceHierarchy.tsx b/src/app/features/lobby/SpaceHierarchy.tsx index 280b8a5a..7178feb2 100644 --- a/src/app/features/lobby/SpaceHierarchy.tsx +++ b/src/app/features/lobby/SpaceHierarchy.tsx @@ -1,7 +1,6 @@ import React, { forwardRef, MouseEventHandler, useEffect, useMemo } from 'react'; -import { MatrixError, Room } from 'matrix-js-sdk'; -import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; import { Box, config, Text } from 'folds'; +import { SpaceHierarchyRoomChunk } from 'neurone/lib/api/types'; import { HierarchyItem, HierarchyItemRoom, @@ -9,22 +8,22 @@ import { useFetchSpaceHierarchyLevel, } from '../../hooks/useSpaceHierarchy'; import { IPowerLevels } from '../../hooks/usePowerLevels'; -import { useMatrixClient } from '../../hooks/useMatrixClient'; import { SpaceItemCard } from './SpaceItem'; import { AfterItemDropTarget, CanDropCallback } from './DnD'; import { HierarchyItemMenu } from './HierarchyItemMenu'; import { RoomItemCard } from './RoomItem'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { SequenceCard } from '../../components/sequence-card'; -import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators'; import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions'; +import { useClient } from '../../hooks/useClient'; +import { rError } from '../../../client/reader'; +import { ErrorCode } from '../../cs-errorcode'; type SpaceHierarchyProps = { - summary: IHierarchyRoom | undefined; + summary: SpaceHierarchyRoomChunk | undefined; spaceItem: HierarchyItemSpace; roomItems?: HierarchyItemRoom[]; allJoinedRooms: Set; - mDirects: Set; roomsPowerLevels: Map; categoryId: string; closed: boolean; @@ -34,10 +33,9 @@ type SpaceHierarchyProps = { canDrop: CanDropCallback; disabledReorder?: boolean; nextSpaceId?: string; - getRoom: (roomId: string) => Room | undefined; pinned: boolean; togglePinToSidebar: (roomId: string) => void; - onSpacesFound: (spaceItems: IHierarchyRoom[]) => void; + onSpacesFound: (spaceItems: SpaceHierarchyRoomChunk[]) => void; onOpenRoom: MouseEventHandler; }; export const SpaceHierarchy = forwardRef( @@ -47,7 +45,6 @@ export const SpaceHierarchy = forwardRef( spaceItem, roomItems, allJoinedRooms, - mDirects, roomsPowerLevels, categoryId, closed, @@ -57,7 +54,6 @@ export const SpaceHierarchy = forwardRef( canDrop, disabledReorder, nextSpaceId, - getRoom, pinned, togglePinToSidebar, onOpenRoom, @@ -65,12 +61,11 @@ export const SpaceHierarchy = forwardRef( }, ref ) => { - const mx = useMatrixClient(); - + const ct = useClient(); const { fetching, error, rooms } = useFetchSpaceHierarchyLevel(spaceItem.roomId, true); const subspaces = useMemo(() => { - const s: Map = new Map(); + const s: Map = new Map(); rooms.forEach((r) => { if (r.room_type === RoomType.Space) { s.set(r.room_id, r); @@ -80,7 +75,7 @@ export const SpaceHierarchy = forwardRef( }, [rooms]); const spacePowerLevels = roomsPowerLevels.get(spaceItem.roomId); - const spaceCreators = getRoomCreatorsForRoomId(mx, spaceItem.roomId); + const spaceCreators = ct.roomToCreators.read.get(spaceItem.roomId); const spacePermissions = spacePowerLevels && getRoomPermissionsAPI(spaceCreators, spacePowerLevels); @@ -89,21 +84,19 @@ export const SpaceHierarchy = forwardRef( const { parentId } = spaceItem; const parentPowerLevels = parentId ? roomsPowerLevels.get(parentId) : undefined; - const parentCreators = parentId ? getRoomCreatorsForRoomId(mx, parentId) : undefined; + const parentCreators = parentId ? ct.roomToCreators.read.get(parentId) : undefined; const parentPermissions = - parentCreators && - parentPowerLevels && - getRoomPermissionsAPI(parentCreators, parentPowerLevels); + parentPowerLevels && getRoomPermissionsAPI(parentCreators, parentPowerLevels); useEffect(() => { onSpacesFound(Array.from(subspaces.values())); }, [subspaces, onSpacesFound]); let childItems = roomItems?.filter((i) => !subspaces.has(i.roomId)); - if (!spacePermissions?.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId())) { + if (!spacePermissions?.stateEvent(StateEvent.SpaceChild, ct.session.userId)) { // hide unknown rooms for normal user childItems = childItems?.filter((i) => { - const forbidden = error instanceof MatrixError ? error.errcode === 'M_FORBIDDEN' : false; + const forbidden = rError.errcode(error) === ErrorCode.M_FORBIDDEN; const inaccessibleRoom = !rooms.get(i.roomId) && !fetching && (error ? forbidden : true); return !inaccessibleRoom; }); @@ -119,11 +112,10 @@ export const SpaceHierarchy = forwardRef( categoryId={categoryId} closed={closed} handleClose={handleClose} - getRoom={getRoom} - canEditChild={!!spacePermissions?.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId())} + canEditChild={!!spacePermissions?.stateEvent(StateEvent.SpaceChild, ct.session.userId)} canReorder={ - parentPowerLevels && !disabledReorder && parentPermissions - ? parentPermissions.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId()) + !disabledReorder && parentPermissions + ? parentPermissions.stateEvent(StateEvent.SpaceChild, ct.session.userId) : false } options={ @@ -134,7 +126,7 @@ export const SpaceHierarchy = forwardRef( powerLevels={spacePowerLevels} joined={allJoinedRooms.has(spaceItem.roomId)} canEditChild={ - !!parentPermissions?.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId()) + !!parentPermissions?.stateEvent(StateEvent.SpaceChild, ct.session.userId) } pinned={pinned} onTogglePin={togglePinToSidebar} @@ -159,8 +151,8 @@ export const SpaceHierarchy = forwardRef( const roomPowerLevels = roomsPowerLevels.get(roomItem.roomId) ?? {}; - const lastItem = index === childItems.length; - const nextRoomId = lastItem ? nextSpaceId : childItems[index + 1]?.roomId; + const lastItem = index === childItems?.length; + const nextRoomId = lastItem ? nextSpaceId : childItems?.[index + 1]?.roomId; const roomDragging = draggingItem?.roomId === roomItem.roomId && @@ -173,11 +165,10 @@ export const SpaceHierarchy = forwardRef( loading={fetching} error={error} summary={roomSummary} - dm={mDirects.has(roomItem.roomId)} onOpen={onOpenRoom} - getRoom={getRoom} + joined={allJoinedRooms.has(roomItem.roomId)} canReorder={ - !!spacePermissions?.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId()) && + !!spacePermissions?.stateEvent(StateEvent.SpaceChild, ct.session.userId) && !disabledReorder } options={ @@ -186,7 +177,7 @@ export const SpaceHierarchy = forwardRef( powerLevels={roomPowerLevels} joined={allJoinedRooms.has(roomItem.roomId)} canEditChild={ - !!spacePermissions?.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId()) + !!spacePermissions?.stateEvent(StateEvent.SpaceChild, ct.session.userId) } /> } diff --git a/src/app/features/lobby/SpaceItem.tsx b/src/app/features/lobby/SpaceItem.tsx index ed3eaff9..64a86d44 100644 --- a/src/app/features/lobby/SpaceItem.tsx +++ b/src/app/features/lobby/SpaceItem.tsx @@ -1,4 +1,11 @@ -import React, { MouseEventHandler, ReactNode, useCallback, useRef, useState } from 'react'; +import React, { + Fragment, + MouseEventHandler, + ReactNode, + useCallback, + useRef, + useState, +} from 'react'; import { Box, Avatar, @@ -18,26 +25,28 @@ import { } from 'folds'; import FocusTrap from 'focus-trap-react'; import classNames from 'classnames'; -import { MatrixError, Room } from 'matrix-js-sdk'; -import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces'; +import { postJoin } from 'neurone/lib/api/matrix/client/v3/join'; +import { parseMatrixResp, printMatrixError } from 'neurone/lib/matrix-request'; +import { SpaceHierarchyRoomChunk } from 'neurone/lib/api/types'; import { HierarchyItem } from '../../hooks/useSpaceHierarchy'; -import { useMatrixClient } from '../../hooks/useMatrixClient'; import { RoomAvatar } from '../../components/room-avatar'; import { nameInitials } from '../../utils/common'; -import { LocalRoomSummaryLoader } from '../../components/RoomSummaryLoader'; -import { getRoomAvatarUrl } from '../../utils/room'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import * as css from './SpaceItem.css'; import * as styleCss from './style.css'; import { useDraggableItem } from './DnD'; import { stopPropagation } from '../../utils/keyboard'; -import { mxcUrlToHttp } from '../../utils/matrix'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal'; import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal'; import { AddExistingModal } from '../add-existing'; import { CreateRoomType } from '../../components/create-room/types'; import { BetaNoticeBadge } from '../../components/BetaNoticeBadge'; +import { useClient } from '../../hooks/useClient'; +import { useGetRoomName } from '../../hooks/storage/useGetRoomName'; +import { useRoomHeroes } from '../../hooks/storage/useRoomHeros'; +import { useGetRoomAvatar } from '../../hooks/storage/useGetRoomAvatar'; +import { mxcToHttp } from '../../../client/mxcToHttp'; function SpaceProfileLoading() { return ( @@ -110,10 +119,19 @@ function UnjoinedSpaceProfile({ avatarUrl, suggested, }: UnjoinedSpaceProfileProps) { - const mx = useMatrixClient(); + const ct = useClient(); - const [joinState, join] = useAsyncCallback( - useCallback(() => mx.joinRoom(roomId, { viaServers: via }), [mx, roomId, via]) + const [joinState, join] = useAsyncCallback( + useCallback(async () => { + const resp = await ct.requestAuthJSON( + postJoin({ + pathParams: { roomIdOrAlias: roomId }, + searchParams: { via, server_name: via }, + }) + ); + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(printMatrixError(err)); + }, [ct, roomId, via]) ); const canJoin = joinState.status === AsyncStatus.Idle || joinState.status === AsyncStatus.Error; @@ -154,7 +172,7 @@ function UnjoinedSpaceProfile({ {joinState.status === AsyncStatus.Error && ( - {joinState.error.name} + Error )} @@ -165,22 +183,28 @@ function UnjoinedSpaceProfile({ type SpaceProfileProps = { roomId: string; - name: string; - avatarUrl?: string; suggested?: boolean; closed: boolean; categoryId: string; handleClose?: MouseEventHandler; }; -function SpaceProfile({ - roomId, - name, - avatarUrl, - suggested, - closed, - categoryId, - handleClose, -}: SpaceProfileProps) { +function SpaceProfile({ roomId, suggested, closed, categoryId, handleClose }: SpaceProfileProps) { + const ct = useClient(); + const useAuthentication = useMediaAuthentication(); + const [getRoomHeroes, roomToHeroMembersResult] = useRoomHeroes(roomId); + const getRoomName = useGetRoomName(getRoomHeroes, roomToHeroMembersResult.value); + const getRoomAvatar = useGetRoomAvatar(getRoomHeroes, roomToHeroMembersResult.value); + + const name = getRoomName(roomId) ?? 'Unknown'; + const avatar = getRoomAvatar(roomId); + const avatarUrl = avatar + ? mxcToHttp(ct.session.baseUrl, avatar, useAuthentication).thumbnail({ + width: 96, + height: 96, + method: 'crop', + }) + : undefined; + return ( { - openCreateSpaceModal(item.roomId as any); + openCreateSpaceModal(item.roomId); setCords(undefined); }; @@ -388,7 +412,7 @@ function AddSpaceButton({ item }: { item: HierarchyItem }) { } type SpaceItemCardProps = { - summary: IHierarchyRoom | undefined; + summary: SpaceHierarchyRoomChunk | undefined; loading?: boolean; item: HierarchyItem; joined?: boolean; @@ -401,7 +425,6 @@ type SpaceItemCardProps = { canEditChild: boolean; canReorder: boolean; onDragging: (item?: HierarchyItem) => void; - getRoom: (roomId: string) => Room | undefined; }; export const SpaceItemCard = as<'div', SpaceItemCardProps>( ( @@ -420,15 +443,13 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>( canEditChild, canReorder, onDragging, - getRoom, ...props }, ref ) => { - const mx = useMatrixClient(); + const ct = useClient(); const useAuthentication = useMediaAuthentication(); const { roomId, content } = item; - const space = getRoom(roomId); const targetRef = useRef(null); useDraggableItem(item, targetRef, onDragging); @@ -444,28 +465,25 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>( {before} - {space ? ( - - {(localSummary) => - item.parentId ? ( - - ) : ( - - ) - } - + {joined ? ( + <> + {item.parentId && ( + + )} + {!item.parentId && ( + + )} + ) : ( <> {!summary && @@ -484,8 +502,11 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>( name={summary.name || summary.canonical_alias || roomId} avatarUrl={ summary?.avatar_url - ? mxcUrlToHttp(mx, summary.avatar_url, useAuthentication, 96, 96, 'crop') ?? - undefined + ? mxcToHttp( + ct.session.baseUrl, + summary.avatar_url, + useAuthentication + ).thumbnail({ width: 96, height: 96, method: 'crop' }) ?? undefined : undefined } suggested={content.suggested} @@ -494,7 +515,7 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>( )} - {space && canEditChild && ( + {joined && canEditChild && ( {item.parentId === undefined && } diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index ff9565b2..71d0dc77 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -318,6 +318,7 @@ export function RoomNavItem({ filled={selected} size="100" joinRule={joinRule} + roomType={ct.roomToType.read.get(roomId)} /> )} diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index a18fb35d..29d5e351 100644 --- a/src/app/features/room/RoomViewHeader.tsx +++ b/src/app/features/room/RoomViewHeader.tsx @@ -341,7 +341,12 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) { src={avatarUrl} alt={name} renderFallback={() => ( - + )} /> diff --git a/src/app/hooks/useLocalRoomSummary.ts b/src/app/hooks/useLocalRoomSummary.ts index 4802a3cf..9f105cff 100644 --- a/src/app/hooks/useLocalRoomSummary.ts +++ b/src/app/hooks/useLocalRoomSummary.ts @@ -13,7 +13,7 @@ export type LocalRoomSummary = { avatarUrl?: string; canonicalAlias?: string; memberCount?: number; - space?: boolean; + roomType?: string; joinRule?: JoinRule; }; export const useLocalRoomSummary = (roomId: string): LocalRoomSummary => { @@ -25,7 +25,7 @@ export const useLocalRoomSummary = (roomId: string): LocalRoomSummary => { const roomToAlias = useObservableValue(ct.roomToAlias); const roomToJoinRule = useObservableValue(ct.roomToJoinRule); const roomToSummary = useObservableValue(ct.roomToSummary); - const spaceRooms = useObservableValue(ct.spaceRooms); + const roomToType = useObservableValue(ct.roomToType); return { roomId, @@ -34,7 +34,7 @@ export const useLocalRoomSummary = (roomId: string): LocalRoomSummary => { avatarUrl: getRoomAvatar(roomId), canonicalAlias: roomToAlias.get(roomId), memberCount: roomToSummary.get(roomId)?.['m.joined_member_count'], - space: spaceRooms.has(roomId), + roomType: roomToType.get(roomId), joinRule: roomToJoinRule.get(roomId), }; }; diff --git a/src/app/hooks/usePowerLevels.ts b/src/app/hooks/usePowerLevels.ts index 52c9c566..631b5395 100644 --- a/src/app/hooks/usePowerLevels.ts +++ b/src/app/hooks/usePowerLevels.ts @@ -1,11 +1,8 @@ -import { MatrixEvent, Room } from 'matrix-js-sdk'; -import { createContext, useCallback, useContext, useMemo, useState } from 'react'; +import { MatrixEvent } from 'matrix-js-sdk'; +import { createContext, useCallback, useContext, useMemo } from 'react'; import produce from 'immer'; import { PendingObservableResult } from 'neurone/lib/utils/observable'; import { StateEvent, StateEventType } from '../../types/matrix/room'; -import { useStateEventCallback } from './useStateEventCallback'; -import { useMatrixClient } from './useMatrixClient'; -import { getStateEvent } from '../utils/room'; import { getJoinRoomStateId, JoinRoomStateObject } from '../../client/storage/collections'; import { useClient } from './useClient'; import { useObservableValuePromise } from './useObservableValue'; @@ -84,12 +81,14 @@ export function usePowerLevelsState( return useObservableValuePromise(eventObservablePromise); } -export function usePowerLevels(eventObj: JoinRoomStateObject | undefined): IPowerLevels { - const powerLevels: IPowerLevels = useMemo(() => { - const content: IPowerLevels | undefined = eventObj?.event.content; +function parsePowerLevels(eventObj?: JoinRoomStateObject): IPowerLevels { + const content: IPowerLevels | undefined = eventObj?.event.content; - return !content ? DEFAULT_POWER_LEVELS : fillMissingPowers(content); - }, [eventObj]); + return !content ? DEFAULT_POWER_LEVELS : fillMissingPowers(content); +} + +export function usePowerLevels(eventObj: JoinRoomStateObject | undefined): IPowerLevels { + const powerLevels: IPowerLevels = useMemo(() => parsePowerLevels(eventObj), [eventObj]); return powerLevels; } @@ -104,38 +103,27 @@ export const usePowerLevelsContext = (): IPowerLevels => { return pl; }; -export const useRoomsPowerLevels = (rooms: Room[]): Map => { - const mx = useMatrixClient(); - const getRoomsPowerLevels = useCallback(() => { - const rToPl = new Map(); +export const useRoomsPowerLevels = (rooms: string[]): Map => { + const ct = useClient(); + const powerLevelEventsPromise = useMemo( + () => ct.storage.joinRoomState.observableRead.queryIndex('type', StateEvent.RoomPowerLevels), + [ct] + ); + const plEventsResult = useObservableValuePromise(powerLevelEventsPromise); - rooms.forEach((room) => { - const mEvent = getStateEvent(room, StateEvent.RoomPowerLevels, ''); - rToPl.set(room.roomId, getPowersLevelFromMatrixEvent(mEvent)); + const roomToPowerLevels: Map = useMemo(() => { + const rToP = new Map(); + + console.log(plEventsResult.value?.find((v) => v.roomId === '!KqyBSJuhpIHbWaROMB:kde.org')); + rooms.forEach((roomId) => { + const evtObj = plEventsResult.value?.find( + (o) => o.roomId === roomId && o.event.state_key === '' + ); + rToP.set(roomId, parsePowerLevels(evtObj)); }); - return rToPl; - }, [rooms]); - - const [roomToPowerLevels, setRoomToPowerLevels] = useState(() => getRoomsPowerLevels()); - - useStateEventCallback( - mx, - useCallback( - (event) => { - const roomId = event.getRoomId(); - if ( - roomId && - event.getType() === StateEvent.RoomPowerLevels && - event.getStateKey() === '' && - rooms.find((r) => r.roomId === roomId) - ) { - setRoomToPowerLevels(getRoomsPowerLevels()); - } - }, - [rooms, getRoomsPowerLevels] - ) - ); + return rToP; + }, [plEventsResult, rooms]); return roomToPowerLevels; }; diff --git a/src/app/hooks/useSpaceHierarchy.ts b/src/app/hooks/useSpaceHierarchy.ts index 8379fd86..f2031de0 100644 --- a/src/app/hooks/useSpaceHierarchy.ts +++ b/src/app/hooks/useSpaceHierarchy.ts @@ -95,7 +95,8 @@ const getSpaceHierarchy = ( rootSpaceId: string, roomToChildEvents: Map[]>, spaceRooms: Set, - spaceRoomsCache: Set + spaceRoomsCache: Set, + closedCategory: (spaceId: string) => boolean ): SpaceHierarchy[] => { const spaceItems: HierarchyItemSpace[] = getHierarchySpaces( rootSpaceId, @@ -106,6 +107,9 @@ const getSpaceHierarchy = ( const hierarchy: SpaceHierarchy[] = spaceItems.map((spaceItem) => { const childEvents = roomToChildEvents.get(spaceItem.roomId); + if (!spaceRooms.has(spaceItem.roomId) || closedCategory(spaceItem.roomId)) { + return { space: spaceItem }; + } const childItems: HierarchyItemRoom[] = []; childEvents?.forEach((childEvent) => { @@ -131,15 +135,19 @@ const getSpaceHierarchy = ( return hierarchy; }; -export const useSpaceHierarchy = (spaceId: string): SpaceHierarchy[] => { +export const useSpaceHierarchy = ( + spaceId: string, + closedCategory: (spaceId: string) => boolean +): SpaceHierarchy[] => { const ct = useClient(); const roomToChildEvents = useObservableValue(ct.roomToChildEvents); const spaceRooms = useObservableValue(ct.spaceRooms); const spaceRoomsCache = useAtomValue(spaceRoomsAtom); const hierarchy = useMemo( - () => getSpaceHierarchy(spaceId, roomToChildEvents, spaceRooms, spaceRoomsCache), - [spaceId, roomToChildEvents, spaceRooms, spaceRoomsCache] + () => + getSpaceHierarchy(spaceId, roomToChildEvents, spaceRooms, spaceRoomsCache, closedCategory), + [spaceId, roomToChildEvents, spaceRooms, spaceRoomsCache, closedCategory] ); return hierarchy; @@ -216,7 +224,7 @@ const PER_PAGE_COUNT = 100; const MAX_AUTO_PAGE_COUNT = 50; export type FetchSpaceHierarchyLevelData = { fetching: boolean; - error: Error | null; + error: unknown; rooms: Map; }; export const useFetchSpaceHierarchyLevel = ( diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index c5318e06..0fec4863 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -230,8 +230,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) element={} /> )} - {/* } /> - } /> */} + } /> + {/*} /> */} >; roomToHistoryVisibility: ObservableValue>; roomToJoinRule: ObservableValue>; + roomToType: ObservableValue>; roomToReplacement: ObservableValue>; roomToCreators: ObservableValue>>; roomToVersion: ObservableValue>; @@ -389,10 +390,10 @@ export const createRoomContext = async ( }; }; -export const createEncryptedRoomsObservable = async ( +export const createEncryptedRoomsObservable = ( state: StateContext, disposer: Disposer -): Promise>> => { +): ObservableValue> => { const encryptedRoomsObservable: ObservableValue> = new ComputedObservableValue(() => { const encryptedRooms = new Set(); @@ -440,10 +441,10 @@ export const createDirectRoomsObservable = async ( return directRoomsObservable; }; -export const createRoomToReplacementObservable = async ( +export const createRoomToReplacementObservable = ( state: StateContext, disposer: Disposer -): Promise>> => { +): ObservableValue> => { const roomToReplacementObservable: ObservableValue> = new ComputedObservableValue(() => { const roomToReplacement = new Map(); @@ -463,13 +464,13 @@ export const createRoomToReplacementObservable = async ( return roomToReplacementObservable; }; -export const createSpaceMapping = async ( +export const createSpaceMapping = ( state: StateContext, disposer: Disposer -): Promise<{ +): { roomToParents: ObservableValue>>; roomToChildEvents: ObservableValue[]>>; -}> => { +} => { const roomToParentsObservable: ObservableValue>> = new ComputedObservableValue(() => { const roomToParents = new Map>(); @@ -533,10 +534,10 @@ export const createRoomToSummaryObservable = async ( return roomToSummaryObservable; }; -export const createRoomToAliasObservable = async ( +export const createRoomToAliasObservable = ( state: StateContext, disposer: Disposer -): Promise>> => { +): ObservableValue> => { const roomToAliasObservable: ObservableValue> = new ComputedObservableValue(() => { const roomToAlias = new Map(); @@ -570,10 +571,10 @@ export const createRoomToAliasObservable = async ( return roomToAliasObservable; }; -export const createRoomToNameObservable = async ( +export const createRoomToNameObservable = ( state: StateContext, disposer: Disposer -): Promise>> => { +): ObservableValue> => { const roomToNameObservable: ObservableValue> = new ComputedObservableValue(() => { const roomToName = new Map(); @@ -601,10 +602,10 @@ export const createRoomToNameObservable = async ( return roomToNameObservable; }; -export const createRoomToAvatarObservable = async ( +export const createRoomToAvatarObservable = ( state: StateContext, disposer: Disposer -): Promise>> => { +): ObservableValue> => { const roomToAvatarObservable: ObservableValue> = new ComputedObservableValue(() => { const roomToAvatar = new Map(); @@ -631,10 +632,10 @@ export const createRoomToAvatarObservable = async ( return roomToAvatarObservable; }; -export const createRoomToHistoryVisibilityObservable = async ( +export const createRoomToHistoryVisibilityObservable = ( state: StateContext, disposer: Disposer -): Promise>> => { +): ObservableValue> => { const roomToHistoryVisibilityObservable: ObservableValue> = new ComputedObservableValue(() => { const roomToHistoryVisibility = new Map(); @@ -657,10 +658,10 @@ export const createRoomToHistoryVisibilityObservable = async ( return roomToHistoryVisibilityObservable; }; -export const createRoomToJoinRuleObservable = async ( +export const createRoomToJoinRuleObservable = ( state: StateContext, disposer: Disposer -): Promise>> => { +): ObservableValue> => { const roomToJoinRuleObservable: ObservableValue> = new ComputedObservableValue(() => { const roomToJoinRule = new Map(); @@ -689,6 +690,32 @@ export const createRoomToJoinRuleObservable = async ( return roomToJoinRuleObservable; }; +export const createRoomToTypeObservable = ( + state: StateContext, + disposer: Disposer +): ObservableValue> => { + const roomToTypeObservable: ObservableValue> = + new ComputedObservableValue(() => { + const roomToJoinRule = new Map(); + + const addType = (obj: RoomStateObject) => { + const { content } = obj.event; + const type = ObjectReader.string(content, 'type'); + roomToJoinRule.set(obj.roomId, type); + }; + + state.join.create.read.forEach(addType); + state.invite.create.read.forEach(addType); + state.knock.create.read.forEach(addType); + state.leave.create.read.forEach(addType); + + return roomToJoinRule; + }, [state.join.create, state.invite.create, state.knock.create, state.leave.create]); + + disposer.collect(roomToTypeObservable); + return roomToTypeObservable; +}; + export const createRoomToLatestEventObservable = async ( storage: Storage, disposer: Disposer diff --git a/src/client/initialize.ts b/src/client/initialize.ts index 505a636d..8228ef76 100644 --- a/src/client/initialize.ts +++ b/src/client/initialize.ts @@ -19,6 +19,7 @@ import { createRoomToNameObservable, createRoomToReplacementObservable, createRoomToSummaryObservable, + createRoomToTypeObservable, createSpaceMapping, createStateContext, createSyncTokenObservable, @@ -81,17 +82,18 @@ export const initializeClient = async (session: Session): Promise state, disposer ); - const encryptedRooms = await createEncryptedRoomsObservable(state, disposer); + const encryptedRooms = createEncryptedRoomsObservable(state, disposer); const directRooms = await createDirectRoomsObservable(storage, disposer); - const { roomToParents, roomToChildEvents } = await createSpaceMapping(state, disposer); + const { roomToParents, roomToChildEvents } = createSpaceMapping(state, disposer); const roomToSummary = await createRoomToSummaryObservable(storage, disposer); - const roomToAlias = await createRoomToAliasObservable(state, disposer); - const roomToName = await createRoomToNameObservable(state, disposer); - const roomToAvatar = await createRoomToAvatarObservable(state, disposer); - const roomToHistoryVisibility = await createRoomToHistoryVisibilityObservable(state, disposer); - const roomToJoinRule = await createRoomToJoinRuleObservable(state, disposer); - const roomToReplacement = await createRoomToReplacementObservable(state, disposer); + const roomToAlias = createRoomToAliasObservable(state, disposer); + const roomToName = createRoomToNameObservable(state, disposer); + const roomToAvatar = createRoomToAvatarObservable(state, disposer); + const roomToHistoryVisibility = createRoomToHistoryVisibilityObservable(state, disposer); + const roomToJoinRule = createRoomToJoinRuleObservable(state, disposer); + const roomToType = createRoomToTypeObservable(state, disposer); + const roomToReplacement = createRoomToReplacementObservable(state, disposer); const roomToLatestEvent = await createRoomToLatestEventObservable(storage, disposer); const syncTokenObservable = await createSyncTokenObservable(storage, disposer); @@ -137,6 +139,7 @@ export const initializeClient = async (session: Session): Promise roomToAvatar, roomToHistoryVisibility, roomToJoinRule, + roomToType, roomToReplacement, roomToLatestEvent, roomToCreators, diff --git a/src/client/reader/Error.ts b/src/client/reader/Error.ts index dbbfe52d..073a50ca 100644 --- a/src/client/reader/Error.ts +++ b/src/client/reader/Error.ts @@ -1,15 +1,15 @@ import { ObjectReader } from 'neurone/lib/utils'; export class rError { - static error(data?: object): string | undefined { + static error(data?: unknown): string | undefined { return ObjectReader.string(data, 'error'); } - static errcode(data?: object): string | undefined { + static errcode(data?: unknown): string | undefined { return ObjectReader.string(data, 'errcode'); } - static retryAfterMs(data?: object): number | undefined { + static retryAfterMs(data?: unknown): number | undefined { return ObjectReader.number(data, 'retry_after_ms'); } } diff --git a/src/types/matrix/room.ts b/src/types/matrix/room.ts index 124e9d4e..8b1bde82 100644 --- a/src/types/matrix/room.ts +++ b/src/types/matrix/room.ts @@ -92,9 +92,11 @@ export type JoinRule = | 'restricted' | 'knock_restricted'; +export type AllowConditionType = 'm.room_membership'; + export type AllowCondition = { room_id: string; - type: string[]; + type: AllowConditionType; }; export type JoinRulesContent = { allow?: AllowCondition[];