replace sdk from space lobby
This commit is contained in:
parent
5f69cc671e
commit
57ca5e4615
20 changed files with 647 additions and 466 deletions
|
|
@ -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<SpaceHierarchyRoomChunk, Error>) => ReactNode;
|
||||
children: (state: AsyncState<SpaceHierarchyRoomChunk, Error>) => ReactElement;
|
||||
}) {
|
||||
const ct = useClient();
|
||||
|
||||
|
|
|
|||
|
|
@ -186,11 +186,20 @@ export function RoomMentionAutocomplete({
|
|||
src={avatar}
|
||||
alt={name}
|
||||
renderFallback={() => (
|
||||
<RoomIcon size="50" joinRule={ct.roomToJoinRule.read.get(rId)} filled />
|
||||
<RoomIcon
|
||||
size="50"
|
||||
joinRule={ct.roomToJoinRule.read.get(rId)}
|
||||
roomType={ct.roomToType.read.get(rId)}
|
||||
filled
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<RoomIcon size="100" joinRule={ct.roomToJoinRule.read.get(rId)} />
|
||||
<RoomIcon
|
||||
size="100"
|
||||
joinRule={ct.roomToJoinRule.read.get(rId)}
|
||||
roomType={ct.roomToType.read.get(rId)}
|
||||
/>
|
||||
)}
|
||||
</Avatar>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ export const RoomCard = as<'div', RoomCardProps>(
|
|||
)}
|
||||
/>
|
||||
</Avatar>
|
||||
{(roomType === RoomType.Space || localSummary.space) && (
|
||||
{(roomType === RoomType.Space || localSummary.roomType === RoomType.Space) && (
|
||||
<Badge variant="Secondary" fill="Soft" outlined>
|
||||
<Text size="L400">Space</Text>
|
||||
</Badge>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
<Text as="span" size="T300" truncate>
|
||||
Invite
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{invitePrompt && room && (
|
||||
{invitePrompt && (
|
||||
<InviteUserPrompt
|
||||
room={room}
|
||||
roomId={item.roomId}
|
||||
requestClose={() => {
|
||||
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<RectCords>();
|
||||
|
||||
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<HTMLButtonElement> = (evt) => {
|
||||
|
|
|
|||
|
|
@ -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<string, IPowerLevels>,
|
||||
getRoom: (roomId: string) => Room | undefined
|
||||
spaceId: string,
|
||||
roomsPowerLevels: Map<string, IPowerLevels>
|
||||
): 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<HTMLDivElement>(null);
|
||||
const heroSectionRef = useRef<HTMLDivElement>(null);
|
||||
const [heroSectionHeight, setHeroSectionHeight] = useState<number>();
|
||||
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<AccountDataEvent<InCinnySpacesContent>>(
|
||||
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<Map<string, IHierarchyRoom>>(() => new Map());
|
||||
const [spacesItems, setSpacesItem] = useState<Map<string, SpaceHierarchyRoomChunk>>(
|
||||
() => new Map()
|
||||
);
|
||||
|
||||
useElementSizeObserver(
|
||||
useCallback(() => heroSectionRef.current, []),
|
||||
useCallback((w, height) => setHeroSectionHeight(height), [])
|
||||
);
|
||||
|
||||
const getRoom = useGetRoom(allJoinedRooms);
|
||||
|
||||
const [draggingItem, setDraggingItem] = useState<HierarchyItem>();
|
||||
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<RoomJoinRulesEventContent>();
|
||||
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<HTMLButtonElement> = (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 (
|
||||
<PowerLevelsContextProvider value={spacePowerLevels}>
|
||||
<Box grow="Yes">
|
||||
<Page>
|
||||
<LobbyHeader
|
||||
showProfile={!onTop}
|
||||
powerLevels={roomsPowerLevels.get(space.roomId) ?? {}}
|
||||
/>
|
||||
<Box style={{ position: 'relative' }} grow="Yes">
|
||||
<Scroll ref={scrollRef} hideTrack visibility="Hover">
|
||||
<PageContent>
|
||||
<PageContentCenter>
|
||||
<ScrollTopContainer
|
||||
scrollRef={scrollRef}
|
||||
anchorRef={heroSectionRef}
|
||||
onVisibilityChange={setOnTop}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => virtualizer.scrollToOffset(0)}
|
||||
variant="SurfaceVariant"
|
||||
radii="Pill"
|
||||
outlined
|
||||
size="300"
|
||||
aria-label="Scroll to Top"
|
||||
>
|
||||
<Icon src={Icons.ChevronTop} size="300" />
|
||||
</IconButton>
|
||||
</ScrollTopContainer>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
height: virtualizer.getTotalSize(),
|
||||
}}
|
||||
>
|
||||
<PageHeroSection ref={heroSectionRef} style={{ paddingTop: 0 }}>
|
||||
<LobbyHero />
|
||||
</PageHeroSection>
|
||||
{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 (
|
||||
<VirtualTile
|
||||
virtualItem={vItem}
|
||||
style={{
|
||||
paddingTop: vItem.index === 0 ? 0 : config.space.S500,
|
||||
}}
|
||||
ref={virtualizer.measureElement}
|
||||
key={vItem.index}
|
||||
>
|
||||
<SpaceHierarchy
|
||||
spaceItem={item.space}
|
||||
summary={spacesItems.get(item.space.roomId)}
|
||||
roomItems={item.rooms}
|
||||
allJoinedRooms={allJoinedRooms}
|
||||
mDirects={mDirects}
|
||||
roomsPowerLevels={roomsPowerLevels}
|
||||
categoryId={categoryId}
|
||||
closed={
|
||||
closedCategories.has(categoryId) ||
|
||||
(draggingItem ? 'space' in draggingItem : false)
|
||||
}
|
||||
handleClose={handleCategoryClick}
|
||||
draggingItem={draggingItem}
|
||||
onDragging={setDraggingItem}
|
||||
canDrop={canDrop}
|
||||
disabledReorder={reordering}
|
||||
nextSpaceId={nextSpaceId}
|
||||
getRoom={getRoom}
|
||||
pinned={sidebarSpaces.has(item.space.roomId)}
|
||||
togglePinToSidebar={togglePinToSidebar}
|
||||
onSpacesFound={handleSpacesFound}
|
||||
onOpenRoom={handleOpenRoom}
|
||||
/>
|
||||
</VirtualTile>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{reordering && (
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: config.space.S400,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 2,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
justifyContent="Center"
|
||||
>
|
||||
<Chip
|
||||
variant="Secondary"
|
||||
outlined
|
||||
radii="Pill"
|
||||
before={<Spinner variant="Secondary" fill="Soft" size="100" />}
|
||||
<RoomMembersAPIProvider value={membersAPI}>
|
||||
<PowerLevelsContextProvider value={spacePowerLevels}>
|
||||
<PowerLevelTagsContextProvider value={powerLevelTags}>
|
||||
<Box grow="Yes">
|
||||
<Page>
|
||||
<LobbyHeader showProfile={!onTop} powerLevels={roomsPowerLevels.get(spaceId) ?? {}} />
|
||||
<Box style={{ position: 'relative' }} grow="Yes">
|
||||
<Scroll ref={scrollRef} hideTrack visibility="Hover">
|
||||
<PageContent>
|
||||
<PageContentCenter>
|
||||
<ScrollTopContainer
|
||||
scrollRef={scrollRef}
|
||||
anchorRef={heroSectionRef}
|
||||
onVisibilityChange={setOnTop}
|
||||
>
|
||||
<Text size="L400">Reordering</Text>
|
||||
</Chip>
|
||||
</Box>
|
||||
)}
|
||||
</PageContentCenter>
|
||||
</PageContent>
|
||||
</Scroll>
|
||||
<IconButton
|
||||
onClick={() => virtualizer.scrollToOffset(0)}
|
||||
variant="SurfaceVariant"
|
||||
radii="Pill"
|
||||
outlined
|
||||
size="300"
|
||||
aria-label="Scroll to Top"
|
||||
>
|
||||
<Icon src={Icons.ChevronTop} size="300" />
|
||||
</IconButton>
|
||||
</ScrollTopContainer>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
height: virtualizer.getTotalSize(),
|
||||
}}
|
||||
>
|
||||
<PageHeroSection ref={heroSectionRef} style={{ paddingTop: 0 }}>
|
||||
<LobbyHero />
|
||||
</PageHeroSection>
|
||||
{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 (
|
||||
<VirtualTile
|
||||
virtualItem={vItem}
|
||||
style={{
|
||||
paddingTop: vItem.index === 0 ? 0 : config.space.S500,
|
||||
}}
|
||||
ref={virtualizer.measureElement}
|
||||
key={vItem.index}
|
||||
>
|
||||
<SpaceHierarchy
|
||||
spaceItem={item.space}
|
||||
summary={spacesItems.get(item.space.roomId)}
|
||||
roomItems={item.rooms}
|
||||
allJoinedRooms={allJoinedRooms}
|
||||
roomsPowerLevels={roomsPowerLevels}
|
||||
categoryId={categoryId}
|
||||
closed={
|
||||
closedCategories.has(categoryId) ||
|
||||
(draggingItem ? 'space' in draggingItem : false)
|
||||
}
|
||||
handleClose={handleCategoryClick}
|
||||
draggingItem={draggingItem}
|
||||
onDragging={setDraggingItem}
|
||||
canDrop={canDrop}
|
||||
disabledReorder={reordering}
|
||||
nextSpaceId={nextSpaceId}
|
||||
pinned={sidebarSpaces.has(item.space.roomId)}
|
||||
togglePinToSidebar={togglePinToSidebar}
|
||||
onSpacesFound={handleSpacesFound}
|
||||
onOpenRoom={handleOpenRoom}
|
||||
/>
|
||||
</VirtualTile>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{reordering && (
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: config.space.S400,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 2,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
justifyContent="Center"
|
||||
>
|
||||
<Chip
|
||||
variant="Secondary"
|
||||
outlined
|
||||
radii="Pill"
|
||||
before={<Spinner variant="Secondary" fill="Soft" size="100" />}
|
||||
>
|
||||
<Text size="L400">Reordering</Text>
|
||||
</Chip>
|
||||
</Box>
|
||||
)}
|
||||
</PageContentCenter>
|
||||
</PageContent>
|
||||
</Scroll>
|
||||
</Box>
|
||||
</Page>
|
||||
{screenSize === ScreenSize.Desktop && isDrawer && (
|
||||
<RoomProvider value={spaceId}>
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
<MembersDrawer />
|
||||
</RoomProvider>
|
||||
)}
|
||||
</Box>
|
||||
</Page>
|
||||
{screenSize === ScreenSize.Desktop && isDrawer && (
|
||||
<>
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
<MembersDrawer room={space} members={members} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</PowerLevelsContextProvider>
|
||||
</PowerLevelTagsContextProvider>
|
||||
</PowerLevelsContextProvider>
|
||||
</RoomMembersAPIProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement, LobbyMenuProps>(
|
||||
({ 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<HTMLDivElement, LobbyMenuProps>(
|
|||
};
|
||||
|
||||
const handleRoomSettings = () => {
|
||||
openSpaceSettings(space.roomId);
|
||||
openSpaceSettings(spaceId);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
|
|
@ -68,7 +70,7 @@ const LobbyMenu = forwardRef<HTMLDivElement, LobbyMenuProps>(
|
|||
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
|
||||
{invitePrompt && (
|
||||
<InviteUserPrompt
|
||||
room={space}
|
||||
roomId={spaceId}
|
||||
requestClose={() => {
|
||||
setInvitePrompt(false);
|
||||
requestClose();
|
||||
|
|
@ -121,7 +123,7 @@ const LobbyMenu = forwardRef<HTMLDivElement, LobbyMenuProps>(
|
|||
</MenuItem>
|
||||
{promptLeave && (
|
||||
<LeaveSpacePrompt
|
||||
roomId={space.roomId}
|
||||
roomId={spaceId}
|
||||
onDone={requestClose}
|
||||
onCancel={() => 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<RectCords>();
|
||||
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<HTMLButtonElement> = (evt) => {
|
||||
setMenuAnchor(evt.currentTarget.getBoundingClientRect());
|
||||
};
|
||||
|
|
@ -187,7 +198,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
|
|||
<>
|
||||
<Avatar size="300">
|
||||
<RoomAvatar
|
||||
roomId={space.roomId}
|
||||
roomId={spaceId}
|
||||
src={avatarUrl}
|
||||
alt={name}
|
||||
renderFallback={() => <Text size="H4">{nameInitials(name)}</Text>}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<PageHero
|
||||
icon={
|
||||
<Avatar size="500">
|
||||
<RoomAvatar
|
||||
roomId={space.roomId}
|
||||
roomId={spaceId}
|
||||
src={avatarUrl}
|
||||
alt={name}
|
||||
renderFallback={() => <Text size="H4">{nameInitials(name)}</Text>}
|
||||
|
|
|
|||
|
|
@ -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<Room, MatrixError, []>(
|
||||
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<RectCords>();
|
||||
const handleErrorMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
setCords(evt.currentTarget.getBoundingClientRect());
|
||||
};
|
||||
|
||||
return (
|
||||
<Box shrink="No" gap="200" alignItems="Center">
|
||||
{joinState.status === AsyncStatus.Error && (
|
||||
<TooltipProvider
|
||||
tooltip={
|
||||
<PopOut
|
||||
anchor={cords}
|
||||
content={
|
||||
<Tooltip variant="Critical" style={{ maxWidth: toRem(200) }}>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text style={{ wordBreak: 'break-word' }} size="T400">
|
||||
{joinState.error.data?.error || joinState.error.message}
|
||||
<Text className={BreakWord} size="T400">
|
||||
{typeof joinState.error === 'object' && joinState.error instanceof Error
|
||||
? joinState.error.message
|
||||
: 'Unknown error'}
|
||||
</Text>
|
||||
<Text size="T200">{joinState.error.name}</Text>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
{(triggerRef) => (
|
||||
<IconButton variant="SurfaceVariant" radii="300" size="300" onClick={handleErrorMenu}>
|
||||
<Icon
|
||||
ref={triggerRef}
|
||||
style={{ color: color.Critical.Main, cursor: 'pointer' }}
|
||||
src={Icons.Warning}
|
||||
size="400"
|
||||
size="200"
|
||||
filled
|
||||
tabIndex={0}
|
||||
aria-label={joinState.error.data?.error || joinState.error.message}
|
||||
aria-label="Join room error message"
|
||||
/>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
)}
|
||||
<Chip
|
||||
variant="Secondary"
|
||||
|
|
@ -135,11 +153,7 @@ function RoomProfileError({ roomId, suggested, inaccessibleRoom, via }: RoomProf
|
|||
src={undefined}
|
||||
alt={roomId}
|
||||
renderFallback={() => (
|
||||
<RoomIcon
|
||||
size="300"
|
||||
joinRule={inaccessibleRoom ? JoinRule.Invite : JoinRule.Restricted}
|
||||
filled
|
||||
/>
|
||||
<RoomIcon size="300" joinRule={inaccessibleRoom ? 'invite' : 'restricted'} filled />
|
||||
)}
|
||||
/>
|
||||
</Avatar>
|
||||
|
|
@ -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<HTMLButtonElement>;
|
||||
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<HTMLDivElement>(null);
|
||||
const targetHandleRef = useRef<HTMLDivElement>(null);
|
||||
useDraggableItem(item, targetRef, onDragging, targetHandleRef);
|
||||
|
||||
const joined = room?.getMyMembership() === Membership.Join;
|
||||
|
||||
return (
|
||||
<SequenceCard
|
||||
className={css.RoomItemCard}
|
||||
|
|
@ -333,18 +342,26 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
|||
{before}
|
||||
<Box ref={canReorder ? targetRef : null} grow="Yes">
|
||||
{canReorder && <ItemDraggableTarget ref={targetHandleRef} />}
|
||||
{room ? (
|
||||
<LocalRoomSummaryLoader room={room}>
|
||||
{joined ? (
|
||||
<LocalRoomSummaryLoader roomId={roomId}>
|
||||
{(localSummary) => (
|
||||
<RoomProfile
|
||||
roomId={roomId}
|
||||
roomType={localSummary.roomType}
|
||||
name={localSummary.name}
|
||||
name={localSummary.name ?? 'Unknown'}
|
||||
topic={localSummary.topic}
|
||||
avatarUrl={
|
||||
dm
|
||||
? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||
: getRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||
localSummary.avatarUrl
|
||||
? mxcToHttp(
|
||||
ct.session.baseUrl,
|
||||
localSummary.avatarUrl,
|
||||
useAuthentication
|
||||
).thumbnail({
|
||||
width: 96,
|
||||
height: 96,
|
||||
method: 'crop',
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
memberCount={localSummary.memberCount}
|
||||
suggested={content.suggested}
|
||||
|
|
@ -402,13 +419,20 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
|||
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={<RoomJoinButton roomId={roomId} via={content.via} />}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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<string>;
|
||||
mDirects: Set<string>;
|
||||
roomsPowerLevels: Map<string, IPowerLevels>;
|
||||
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<HTMLButtonElement>;
|
||||
};
|
||||
export const SpaceHierarchy = forwardRef<HTMLDivElement, SpaceHierarchyProps>(
|
||||
|
|
@ -47,7 +45,6 @@ export const SpaceHierarchy = forwardRef<HTMLDivElement, SpaceHierarchyProps>(
|
|||
spaceItem,
|
||||
roomItems,
|
||||
allJoinedRooms,
|
||||
mDirects,
|
||||
roomsPowerLevels,
|
||||
categoryId,
|
||||
closed,
|
||||
|
|
@ -57,7 +54,6 @@ export const SpaceHierarchy = forwardRef<HTMLDivElement, SpaceHierarchyProps>(
|
|||
canDrop,
|
||||
disabledReorder,
|
||||
nextSpaceId,
|
||||
getRoom,
|
||||
pinned,
|
||||
togglePinToSidebar,
|
||||
onOpenRoom,
|
||||
|
|
@ -65,12 +61,11 @@ export const SpaceHierarchy = forwardRef<HTMLDivElement, SpaceHierarchyProps>(
|
|||
},
|
||||
ref
|
||||
) => {
|
||||
const mx = useMatrixClient();
|
||||
|
||||
const ct = useClient();
|
||||
const { fetching, error, rooms } = useFetchSpaceHierarchyLevel(spaceItem.roomId, true);
|
||||
|
||||
const subspaces = useMemo(() => {
|
||||
const s: Map<string, IHierarchyRoom> = new Map();
|
||||
const s: Map<string, SpaceHierarchyRoomChunk> = 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<HTMLDivElement, SpaceHierarchyProps>(
|
|||
}, [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<HTMLDivElement, SpaceHierarchyProps>(
|
|||
|
||||
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<HTMLDivElement, SpaceHierarchyProps>(
|
|||
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<HTMLDivElement, SpaceHierarchyProps>(
|
|||
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<HTMLDivElement, SpaceHierarchyProps>(
|
|||
|
||||
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<HTMLDivElement, SpaceHierarchyProps>(
|
|||
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<HTMLDivElement, SpaceHierarchyProps>(
|
|||
powerLevels={roomPowerLevels}
|
||||
joined={allJoinedRooms.has(roomItem.roomId)}
|
||||
canEditChild={
|
||||
!!spacePermissions?.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId())
|
||||
!!spacePermissions?.stateEvent(StateEvent.SpaceChild, ct.session.userId)
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Room, MatrixError, []>(
|
||||
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 && (
|
||||
<Badge variant="Critical" fill="Soft" radii="Pill" outlined>
|
||||
<Text size="L400" truncate>
|
||||
{joinState.error.name}
|
||||
Error
|
||||
</Text>
|
||||
</Badge>
|
||||
)}
|
||||
|
|
@ -165,22 +183,28 @@ function UnjoinedSpaceProfile({
|
|||
|
||||
type SpaceProfileProps = {
|
||||
roomId: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
suggested?: boolean;
|
||||
closed: boolean;
|
||||
categoryId: string;
|
||||
handleClose?: MouseEventHandler<HTMLButtonElement>;
|
||||
};
|
||||
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 (
|
||||
<Chip
|
||||
data-category-id={categoryId}
|
||||
|
|
@ -330,7 +354,7 @@ function AddSpaceButton({ item }: { item: HierarchyItem }) {
|
|||
};
|
||||
|
||||
const handleCreateSpace = () => {
|
||||
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<HTMLDivElement>(null);
|
||||
useDraggableItem(item, targetRef, onDragging);
|
||||
|
||||
|
|
@ -444,28 +465,25 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>(
|
|||
{before}
|
||||
<Box grow="Yes" gap="100" alignItems="Inherit" justifyContent="SpaceBetween">
|
||||
<Box ref={canReorder ? targetRef : null}>
|
||||
{space ? (
|
||||
<LocalRoomSummaryLoader room={space}>
|
||||
{(localSummary) =>
|
||||
item.parentId ? (
|
||||
<SpaceProfile
|
||||
roomId={roomId}
|
||||
name={localSummary.name}
|
||||
avatarUrl={getRoomAvatarUrl(mx, space, 96, useAuthentication)}
|
||||
suggested={content.suggested}
|
||||
closed={closed}
|
||||
categoryId={categoryId}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
) : (
|
||||
<RootSpaceProfile
|
||||
closed={closed}
|
||||
categoryId={categoryId}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</LocalRoomSummaryLoader>
|
||||
{joined ? (
|
||||
<>
|
||||
{item.parentId && (
|
||||
<SpaceProfile
|
||||
roomId={roomId}
|
||||
suggested={content.suggested}
|
||||
closed={closed}
|
||||
categoryId={categoryId}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
{!item.parentId && (
|
||||
<RootSpaceProfile
|
||||
closed={closed}
|
||||
categoryId={categoryId}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{!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>(
|
|||
</>
|
||||
)}
|
||||
</Box>
|
||||
{space && canEditChild && (
|
||||
{joined && canEditChild && (
|
||||
<Box shrink="No" alignItems="Inherit" gap="200">
|
||||
<AddRoomButton item={item} />
|
||||
{item.parentId === undefined && <AddSpaceButton item={item} />}
|
||||
|
|
|
|||
|
|
@ -318,6 +318,7 @@ export function RoomNavItem({
|
|||
filled={selected}
|
||||
size="100"
|
||||
joinRule={joinRule}
|
||||
roomType={ct.roomToType.read.get(roomId)}
|
||||
/>
|
||||
)}
|
||||
</Avatar>
|
||||
|
|
|
|||
|
|
@ -341,7 +341,12 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
|
|||
src={avatarUrl}
|
||||
alt={name}
|
||||
renderFallback={() => (
|
||||
<RoomIcon size="200" joinRule={roomToJoinRule.get(roomId)} filled />
|
||||
<RoomIcon
|
||||
size="200"
|
||||
joinRule={roomToJoinRule.get(roomId)}
|
||||
roomType={ct.roomToType.read.get(roomId)}
|
||||
filled
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Avatar>
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<string, IPowerLevels> => {
|
||||
const mx = useMatrixClient();
|
||||
const getRoomsPowerLevels = useCallback(() => {
|
||||
const rToPl = new Map<string, IPowerLevels>();
|
||||
export const useRoomsPowerLevels = (rooms: string[]): Map<string, IPowerLevels> => {
|
||||
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<string, IPowerLevels> = useMemo(() => {
|
||||
const rToP = new Map<string, IPowerLevels>();
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ const getSpaceHierarchy = (
|
|||
rootSpaceId: string,
|
||||
roomToChildEvents: Map<string, StateEventWithoutRoomId<SpaceChildContent>[]>,
|
||||
spaceRooms: Set<string>,
|
||||
spaceRoomsCache: Set<string>
|
||||
spaceRoomsCache: Set<string>,
|
||||
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<string, SpaceHierarchyRoomChunk>;
|
||||
};
|
||||
export const useFetchSpaceHierarchyLevel = (
|
||||
|
|
|
|||
|
|
@ -230,8 +230,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||
element={<WelcomePage />}
|
||||
/>
|
||||
)}
|
||||
{/* <Route path={_LOBBY_PATH} element={<Lobby />} />
|
||||
<Route path={_SEARCH_PATH} element={<SpaceSearch />} /> */}
|
||||
<Route path={_LOBBY_PATH} element={<Lobby />} />
|
||||
{/*<Route path={_SEARCH_PATH} element={<SpaceSearch />} /> */}
|
||||
<Route
|
||||
path={_ROOM_PATH}
|
||||
element={
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { ComputedObservableValue, ObservableValue } from 'neurone/lib/utils/observable';
|
||||
import { MatrixRequest } from 'neurone/lib/matrix-request';
|
||||
import { HistoryVisibility, Machine } from 'neurone/lib/crypto/machine';
|
||||
import { ObjectReader } from 'neurone/lib/utils';
|
||||
import { Disposer } from 'neurone/lib/utils/disposable';
|
||||
import {
|
||||
MessageEventWithoutRoomId,
|
||||
|
|
@ -22,7 +23,6 @@ import { GetTxnId } from './txn';
|
|||
import { Timeline } from './room';
|
||||
import { AccountDataType } from '../types/matrix/accountData';
|
||||
import { DecryptionManager } from './decryption';
|
||||
import { ObjectReader } from 'neurone/lib/utils';
|
||||
|
||||
export type StateContext = {
|
||||
join: {
|
||||
|
|
@ -89,6 +89,7 @@ export type ClientContext = {
|
|||
roomToAvatar: ObservableValue<Map<string, string>>;
|
||||
roomToHistoryVisibility: ObservableValue<Map<string, HistoryVisibility>>;
|
||||
roomToJoinRule: ObservableValue<Map<string, JoinRule>>;
|
||||
roomToType: ObservableValue<Map<string, string | undefined>>;
|
||||
roomToReplacement: ObservableValue<Map<string, string>>;
|
||||
roomToCreators: ObservableValue<Map<string, Set<string>>>;
|
||||
roomToVersion: ObservableValue<Map<string, string>>;
|
||||
|
|
@ -389,10 +390,10 @@ export const createRoomContext = async (
|
|||
};
|
||||
};
|
||||
|
||||
export const createEncryptedRoomsObservable = async (
|
||||
export const createEncryptedRoomsObservable = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): Promise<ObservableValue<Set<string>>> => {
|
||||
): ObservableValue<Set<string>> => {
|
||||
const encryptedRoomsObservable: ObservableValue<Set<string>> = new ComputedObservableValue(() => {
|
||||
const encryptedRooms = new Set<string>();
|
||||
|
||||
|
|
@ -440,10 +441,10 @@ export const createDirectRoomsObservable = async (
|
|||
return directRoomsObservable;
|
||||
};
|
||||
|
||||
export const createRoomToReplacementObservable = async (
|
||||
export const createRoomToReplacementObservable = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): Promise<ObservableValue<Map<string, string>>> => {
|
||||
): ObservableValue<Map<string, string>> => {
|
||||
const roomToReplacementObservable: ObservableValue<Map<string, string>> =
|
||||
new ComputedObservableValue(() => {
|
||||
const roomToReplacement = new Map<string, string>();
|
||||
|
|
@ -463,13 +464,13 @@ export const createRoomToReplacementObservable = async (
|
|||
return roomToReplacementObservable;
|
||||
};
|
||||
|
||||
export const createSpaceMapping = async (
|
||||
export const createSpaceMapping = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): Promise<{
|
||||
): {
|
||||
roomToParents: ObservableValue<Map<string, Set<string>>>;
|
||||
roomToChildEvents: ObservableValue<Map<string, StateEventWithoutRoomId<SpaceChildContent>[]>>;
|
||||
}> => {
|
||||
} => {
|
||||
const roomToParentsObservable: ObservableValue<Map<string, Set<string>>> =
|
||||
new ComputedObservableValue(() => {
|
||||
const roomToParents = new Map<string, Set<string>>();
|
||||
|
|
@ -533,10 +534,10 @@ export const createRoomToSummaryObservable = async (
|
|||
return roomToSummaryObservable;
|
||||
};
|
||||
|
||||
export const createRoomToAliasObservable = async (
|
||||
export const createRoomToAliasObservable = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): Promise<ObservableValue<Map<string, string>>> => {
|
||||
): ObservableValue<Map<string, string>> => {
|
||||
const roomToAliasObservable: ObservableValue<Map<string, string>> =
|
||||
new ComputedObservableValue(() => {
|
||||
const roomToAlias = new Map<string, string>();
|
||||
|
|
@ -570,10 +571,10 @@ export const createRoomToAliasObservable = async (
|
|||
return roomToAliasObservable;
|
||||
};
|
||||
|
||||
export const createRoomToNameObservable = async (
|
||||
export const createRoomToNameObservable = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): Promise<ObservableValue<Map<string, string>>> => {
|
||||
): ObservableValue<Map<string, string>> => {
|
||||
const roomToNameObservable: ObservableValue<Map<string, string>> =
|
||||
new ComputedObservableValue(() => {
|
||||
const roomToName = new Map<string, string>();
|
||||
|
|
@ -601,10 +602,10 @@ export const createRoomToNameObservable = async (
|
|||
return roomToNameObservable;
|
||||
};
|
||||
|
||||
export const createRoomToAvatarObservable = async (
|
||||
export const createRoomToAvatarObservable = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): Promise<ObservableValue<Map<string, string>>> => {
|
||||
): ObservableValue<Map<string, string>> => {
|
||||
const roomToAvatarObservable: ObservableValue<Map<string, string>> =
|
||||
new ComputedObservableValue(() => {
|
||||
const roomToAvatar = new Map<string, string>();
|
||||
|
|
@ -631,10 +632,10 @@ export const createRoomToAvatarObservable = async (
|
|||
return roomToAvatarObservable;
|
||||
};
|
||||
|
||||
export const createRoomToHistoryVisibilityObservable = async (
|
||||
export const createRoomToHistoryVisibilityObservable = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): Promise<ObservableValue<Map<string, HistoryVisibility>>> => {
|
||||
): ObservableValue<Map<string, HistoryVisibility>> => {
|
||||
const roomToHistoryVisibilityObservable: ObservableValue<Map<string, HistoryVisibility>> =
|
||||
new ComputedObservableValue(() => {
|
||||
const roomToHistoryVisibility = new Map<string, HistoryVisibility>();
|
||||
|
|
@ -657,10 +658,10 @@ export const createRoomToHistoryVisibilityObservable = async (
|
|||
return roomToHistoryVisibilityObservable;
|
||||
};
|
||||
|
||||
export const createRoomToJoinRuleObservable = async (
|
||||
export const createRoomToJoinRuleObservable = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): Promise<ObservableValue<Map<string, JoinRule>>> => {
|
||||
): ObservableValue<Map<string, JoinRule>> => {
|
||||
const roomToJoinRuleObservable: ObservableValue<Map<string, JoinRule>> =
|
||||
new ComputedObservableValue(() => {
|
||||
const roomToJoinRule = new Map<string, JoinRule>();
|
||||
|
|
@ -689,6 +690,32 @@ export const createRoomToJoinRuleObservable = async (
|
|||
return roomToJoinRuleObservable;
|
||||
};
|
||||
|
||||
export const createRoomToTypeObservable = (
|
||||
state: StateContext,
|
||||
disposer: Disposer
|
||||
): ObservableValue<Map<string, string | undefined>> => {
|
||||
const roomToTypeObservable: ObservableValue<Map<string, string | undefined>> =
|
||||
new ComputedObservableValue(() => {
|
||||
const roomToJoinRule = new Map<string, string | undefined>();
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
createRoomToNameObservable,
|
||||
createRoomToReplacementObservable,
|
||||
createRoomToSummaryObservable,
|
||||
createRoomToTypeObservable,
|
||||
createSpaceMapping,
|
||||
createStateContext,
|
||||
createSyncTokenObservable,
|
||||
|
|
@ -81,17 +82,18 @@ export const initializeClient = async (session: Session): Promise<ClientContext>
|
|||
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<ClientContext>
|
|||
roomToAvatar,
|
||||
roomToHistoryVisibility,
|
||||
roomToJoinRule,
|
||||
roomToType,
|
||||
roomToReplacement,
|
||||
roomToLatestEvent,
|
||||
roomToCreators,
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
|
|
|
|||
Loading…
Reference in a new issue