mirror of https://github.com/vector-im/riot-web
Refactor Space Hierarchy stuff in preparation for pagination
parent
56cd0e1c4f
commit
d459dbe700
|
@ -833,6 +833,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
||||||
// but works with the objects we get from the public room list
|
// but works with the objects we get from the public room list
|
||||||
function getDisplayAliasForRoom(room: IPublicRoomsChunkRoom) {
|
export function getDisplayAliasForRoom(room: IPublicRoomsChunkRoom) {
|
||||||
return getDisplayAliasForAliasSet(room.canonical_alias, room.aliases);
|
return getDisplayAliasForAliasSet(room.canonical_alias, room.aliases);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactNode, useMemo, useState } from "react";
|
import React, { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
|
||||||
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { IRoomChild, IRoomChildState } from "matrix-js-sdk/src/@types/spaces";
|
import { IHierarchyRelation, IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { sortBy } from "lodash";
|
import { sortBy } from "lodash";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||||
import BaseDialog from "../views/dialogs/BaseDialog";
|
import BaseDialog from "../views/dialogs/BaseDialog";
|
||||||
|
@ -30,8 +32,6 @@ import Spinner from "../views/elements/Spinner";
|
||||||
import SearchBox from "./SearchBox";
|
import SearchBox from "./SearchBox";
|
||||||
import RoomAvatar from "../views/avatars/RoomAvatar";
|
import RoomAvatar from "../views/avatars/RoomAvatar";
|
||||||
import RoomName from "../views/elements/RoomName";
|
import RoomName from "../views/elements/RoomName";
|
||||||
import { useAsyncMemo } from "../../hooks/useAsyncMemo";
|
|
||||||
import { EnhancedMap } from "../../utils/maps";
|
|
||||||
import StyledCheckbox from "../views/elements/StyledCheckbox";
|
import StyledCheckbox from "../views/elements/StyledCheckbox";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
import BaseAvatar from "../views/avatars/BaseAvatar";
|
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||||
|
@ -42,20 +42,19 @@ import { useStateToggle } from "../../hooks/useStateToggle";
|
||||||
import { getChildOrder } from "../../stores/SpaceStore";
|
import { getChildOrder } from "../../stores/SpaceStore";
|
||||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
import { linkifyElement } from "../../HtmlUtils";
|
import { linkifyElement } from "../../HtmlUtils";
|
||||||
import { getDisplayAliasForAliasSet } from "../../Rooms";
|
|
||||||
import { useDispatcher } from "../../hooks/useDispatcher";
|
import { useDispatcher } from "../../hooks/useDispatcher";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
import { getDisplayAliasForRoom } from "./RoomDirectory";
|
||||||
|
|
||||||
interface IHierarchyProps {
|
interface IHierarchyProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
additionalButtons?: ReactNode;
|
additionalButtons?: ReactNode;
|
||||||
showRoom(room: IRoomChild, viaServers?: string[], autoJoin?: boolean): void;
|
showRoom(hierarchy: RoomHierarchy, roomId: string, autoJoin?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITileProps {
|
interface ITileProps {
|
||||||
room: IRoomChild;
|
room: IHierarchyRoom;
|
||||||
suggested?: boolean;
|
suggested?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
numChildRooms?: number;
|
numChildRooms?: number;
|
||||||
|
@ -206,7 +205,9 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
</>;
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showRoom = (room: IRoomChild, viaServers?: string[], autoJoin = false) => {
|
export const showRoom = (hierarchy: RoomHierarchy, roomId: string, autoJoin = false) => {
|
||||||
|
const room = hierarchy.roomMap.get(roomId);
|
||||||
|
|
||||||
// Don't let the user view a room they won't be able to either peek or join:
|
// Don't let the user view a room they won't be able to either peek or join:
|
||||||
// fail earlier so they don't have to click back to the directory.
|
// fail earlier so they don't have to click back to the directory.
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
@ -224,7 +225,7 @@ export const showRoom = (room: IRoomChild, viaServers?: string[], autoJoin = fal
|
||||||
_type: "room_directory", // instrumentation
|
_type: "room_directory", // instrumentation
|
||||||
room_alias: roomAlias,
|
room_alias: roomAlias,
|
||||||
room_id: room.room_id,
|
room_id: room.room_id,
|
||||||
via_servers: viaServers,
|
via_servers: Array.from(hierarchy.viaMap.get(roomId) || []),
|
||||||
oob_data: {
|
oob_data: {
|
||||||
avatarUrl: room.avatar_url,
|
avatarUrl: room.avatar_url,
|
||||||
// XXX: This logic is duplicated from the JS SDK which would normally decide what the name is.
|
// XXX: This logic is duplicated from the JS SDK which would normally decide what the name is.
|
||||||
|
@ -234,9 +235,9 @@ export const showRoom = (room: IRoomChild, viaServers?: string[], autoJoin = fal
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IHierarchyLevelProps {
|
interface IHierarchyLevelProps {
|
||||||
spaceId: string;
|
root: IHierarchyRoom;
|
||||||
rooms: Map<string, IRoomChild>;
|
roomSet: Set<IHierarchyRoom>;
|
||||||
relations: Map<string, Map<string, IRoomChildState>>;
|
hierarchy: RoomHierarchy;
|
||||||
parents: Set<string>;
|
parents: Set<string>;
|
||||||
selectedMap?: Map<string, Set<string>>;
|
selectedMap?: Map<string, Set<string>>;
|
||||||
onViewRoomClick(roomId: string, autoJoin: boolean): void;
|
onViewRoomClick(roomId: string, autoJoin: boolean): void;
|
||||||
|
@ -244,67 +245,69 @@ interface IHierarchyLevelProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HierarchyLevel = ({
|
export const HierarchyLevel = ({
|
||||||
spaceId,
|
root,
|
||||||
rooms,
|
roomSet,
|
||||||
relations,
|
hierarchy,
|
||||||
parents,
|
parents,
|
||||||
selectedMap,
|
selectedMap,
|
||||||
onViewRoomClick,
|
onViewRoomClick,
|
||||||
onToggleClick,
|
onToggleClick,
|
||||||
}: IHierarchyLevelProps) => {
|
}: IHierarchyLevelProps) => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const space = cli.getRoom(spaceId);
|
const space = cli.getRoom(root.room_id);
|
||||||
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
||||||
|
|
||||||
const children = Array.from(relations.get(spaceId)?.values() || []);
|
const sortedChildren = sortBy(root.children_state, ev => {
|
||||||
const sortedChildren = sortBy(children, ev => {
|
return getChildOrder(ev.content.order, ev.origin_server_ts, ev.state_key);
|
||||||
// XXX: Space Summary API doesn't give the child origin_server_ts but once it does we should use it for sorting
|
|
||||||
return getChildOrder(ev.content.order, null, ev.state_key);
|
|
||||||
});
|
});
|
||||||
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: IRoomChildState) => {
|
|
||||||
const roomId = ev.state_key;
|
|
||||||
if (!rooms.has(roomId)) return result;
|
|
||||||
result[rooms.get(roomId).room_type === RoomType.Space ? 0 : 1].push(roomId);
|
|
||||||
return result;
|
|
||||||
}, [[], []]) || [[], []];
|
|
||||||
|
|
||||||
const newParents = new Set(parents).add(spaceId);
|
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: IHierarchyRelation) => {
|
||||||
|
const room = hierarchy.roomMap.get(ev.state_key);
|
||||||
|
if (room && roomSet.has(room)) {
|
||||||
|
result[room.room_type === RoomType.Space ? 0 : 1].push(room);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [[] as IHierarchyRoom[], [] as IHierarchyRoom[]]);
|
||||||
|
|
||||||
|
const newParents = new Set(parents).add(root.room_id);
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
{
|
{
|
||||||
childRooms.map(roomId => (
|
childRooms.map(room => (
|
||||||
<Tile
|
<Tile
|
||||||
key={roomId}
|
key={room.room_id}
|
||||||
room={rooms.get(roomId)}
|
room={room}
|
||||||
suggested={relations.get(spaceId)?.get(roomId)?.content.suggested}
|
suggested={hierarchy.isSuggested(root.room_id, room.room_id)}
|
||||||
selected={selectedMap?.get(spaceId)?.has(roomId)}
|
selected={selectedMap?.get(root.room_id)?.has(room.room_id)}
|
||||||
onViewRoomClick={(autoJoin) => {
|
onViewRoomClick={(autoJoin) => {
|
||||||
onViewRoomClick(roomId, autoJoin);
|
onViewRoomClick(room.room_id, autoJoin);
|
||||||
}}
|
}}
|
||||||
hasPermissions={hasPermissions}
|
hasPermissions={hasPermissions}
|
||||||
onToggleClick={onToggleClick ? () => onToggleClick(spaceId, roomId) : undefined}
|
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
subspaces.filter(roomId => !newParents.has(roomId)).map(roomId => (
|
subspaces.filter(room => !newParents.has(room.room_id)).map(space => (
|
||||||
<Tile
|
<Tile
|
||||||
key={roomId}
|
key={space.room_id}
|
||||||
room={rooms.get(roomId)}
|
room={space}
|
||||||
numChildRooms={Array.from(relations.get(roomId)?.values() || [])
|
numChildRooms={space.children_state.filter(ev => {
|
||||||
.filter(ev => rooms.has(ev.state_key) && !rooms.get(ev.state_key).room_type).length}
|
const room = hierarchy.roomMap.get(ev.state_key);
|
||||||
suggested={relations.get(spaceId)?.get(roomId)?.content.suggested}
|
return room && roomSet.has(room) && !room.room_type;
|
||||||
selected={selectedMap?.get(spaceId)?.has(roomId)}
|
}).length}
|
||||||
|
suggested={hierarchy.isSuggested(root.room_id, space.room_id)}
|
||||||
|
selected={selectedMap?.get(root.room_id)?.has(space.room_id)}
|
||||||
onViewRoomClick={(autoJoin) => {
|
onViewRoomClick={(autoJoin) => {
|
||||||
onViewRoomClick(roomId, autoJoin);
|
onViewRoomClick(space.room_id, autoJoin);
|
||||||
}}
|
}}
|
||||||
hasPermissions={hasPermissions}
|
hasPermissions={hasPermissions}
|
||||||
onToggleClick={onToggleClick ? () => onToggleClick(spaceId, roomId) : undefined}
|
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined}
|
||||||
>
|
>
|
||||||
<HierarchyLevel
|
<HierarchyLevel
|
||||||
spaceId={roomId}
|
root={space}
|
||||||
rooms={rooms}
|
roomSet={roomSet}
|
||||||
relations={relations}
|
hierarchy={hierarchy}
|
||||||
parents={newParents}
|
parents={newParents}
|
||||||
selectedMap={selectedMap}
|
selectedMap={selectedMap}
|
||||||
onViewRoomClick={onViewRoomClick}
|
onViewRoomClick={onViewRoomClick}
|
||||||
|
@ -316,49 +319,53 @@ export const HierarchyLevel = ({
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSpaceSummary = (space: Room): [
|
export const useSpaceSummary = (space: Room): {
|
||||||
null,
|
loading: boolean;
|
||||||
IRoomChild[],
|
rooms: IHierarchyRoom[];
|
||||||
Map<string, Map<string, IRoomChildState>>?,
|
hierarchy: RoomHierarchy;
|
||||||
Map<string, Set<string>>?,
|
loadMore(pageSize?: number): Promise <void>;
|
||||||
Map<string, Set<string>>?,
|
} => {
|
||||||
] | [Error] => {
|
const [rooms, setRooms] = useState<IHierarchyRoom[]>([]);
|
||||||
// crude temporary refresh token approach until we have pagination and rework the data flow here
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshToken, setRefreshToken] = useState(0);
|
|
||||||
|
const INITIAL_PAGE_SIZE = 20;
|
||||||
|
const [hierarchy, setHierarchy] = useState<RoomHierarchy>();
|
||||||
|
|
||||||
|
const resetHierarchy = useCallback(() => {
|
||||||
|
const hierarchy = new RoomHierarchy(space, INITIAL_PAGE_SIZE);
|
||||||
|
setHierarchy(hierarchy);
|
||||||
|
|
||||||
|
let discard = false;
|
||||||
|
hierarchy.load().then(() => {
|
||||||
|
if (discard) return;
|
||||||
|
setRooms(hierarchy.rooms);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
discard = true;
|
||||||
|
};
|
||||||
|
}, [space]);
|
||||||
|
useEffect(resetHierarchy, [resetHierarchy]);
|
||||||
|
|
||||||
useDispatcher(defaultDispatcher, (payload => {
|
useDispatcher(defaultDispatcher, (payload => {
|
||||||
if (payload.action === Action.UpdateSpaceHierarchy) {
|
if (payload.action === Action.UpdateSpaceHierarchy) {
|
||||||
setRefreshToken(t => t + 1);
|
setLoading(true);
|
||||||
|
setRooms([]); // TODO
|
||||||
|
resetHierarchy();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// TODO pagination
|
const loadMore = useCallback(async (pageSize?: number) => {
|
||||||
return useAsyncMemo(async () => {
|
if (!hierarchy.canLoadMore || hierarchy.noSupport) return;
|
||||||
try {
|
|
||||||
const { rooms } = await space.client.getRoomChildren(space.roomId);
|
|
||||||
|
|
||||||
const parentChildRelations = new EnhancedMap<string, Map<string, IRoomChildState>>();
|
setLoading(true);
|
||||||
const childParentRelations = new EnhancedMap<string, Set<string>>();
|
await hierarchy.load(pageSize);
|
||||||
const viaMap = new EnhancedMap<string, Set<string>>();
|
setRooms(hierarchy.rooms);
|
||||||
|
setLoading(false);
|
||||||
|
}, [hierarchy]);
|
||||||
|
|
||||||
rooms.forEach(room => {
|
return { loading, rooms, hierarchy, loadMore };
|
||||||
room.children_state.forEach((ev: IRoomChildState) => {
|
|
||||||
if (ev.type === EventType.SpaceChild) {
|
|
||||||
parentChildRelations.getOrCreate(ev.room_id, new Map()).set(ev.state_key, ev);
|
|
||||||
childParentRelations.getOrCreate(ev.state_key, new Set()).add(ev.room_id);
|
|
||||||
}
|
|
||||||
if (Array.isArray(ev.content.via)) {
|
|
||||||
const set = viaMap.getOrCreate(ev.state_key, new Set());
|
|
||||||
ev.content.via.forEach(via => set.add(via));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return [null, rooms, parentChildRelations, viaMap, childParentRelations];
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e); // TODO
|
|
||||||
return [e];
|
|
||||||
}
|
|
||||||
}, [space, refreshToken], [undefined]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
|
@ -374,14 +381,12 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
|
|
||||||
const [selected, setSelected] = useState(new Map<string, Set<string>>()); // Map<parentId, Set<childId>>
|
const [selected, setSelected] = useState(new Map<string, Set<string>>()); // Map<parentId, Set<childId>>
|
||||||
|
|
||||||
const [summaryError, rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(space);
|
const { loading, rooms, hierarchy } = useSpaceSummary(space);
|
||||||
|
|
||||||
const roomsMap = useMemo(() => {
|
const filteredRoomSet = useMemo<Set<IHierarchyRoom>>(() => {
|
||||||
if (!rooms) return null;
|
if (!rooms.length) return new Set();
|
||||||
const lcQuery = query.toLowerCase().trim();
|
const lcQuery = query.toLowerCase().trim();
|
||||||
|
if (!lcQuery) return new Set(rooms);
|
||||||
const roomsMap = new Map<string, IRoomChild>(rooms.map(r => [r.room_id, r]));
|
|
||||||
if (!lcQuery) return roomsMap;
|
|
||||||
|
|
||||||
const directMatches = rooms.filter(r => {
|
const directMatches = rooms.filter(r => {
|
||||||
return r.name?.toLowerCase().includes(lcQuery) || r.topic?.toLowerCase().includes(lcQuery);
|
return r.name?.toLowerCase().includes(lcQuery) || r.topic?.toLowerCase().includes(lcQuery);
|
||||||
|
@ -393,34 +398,30 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const roomId = queue.pop();
|
const roomId = queue.pop();
|
||||||
visited.add(roomId);
|
visited.add(roomId);
|
||||||
childParentMap.get(roomId)?.forEach(parentId => {
|
hierarchy.backRefs.get(roomId)?.forEach(parentId => {
|
||||||
if (!visited.has(parentId)) {
|
if (!visited.has(parentId)) {
|
||||||
queue.push(parentId);
|
queue.push(parentId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any mappings for rooms which were not visited in the walk
|
return new Set(rooms.filter(r => visited.has(r.room_id)));
|
||||||
Array.from(roomsMap.keys()).forEach(roomId => {
|
}, [rooms, hierarchy, query]);
|
||||||
if (!visited.has(roomId)) {
|
|
||||||
roomsMap.delete(roomId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return roomsMap;
|
|
||||||
}, [rooms, childParentMap, query]);
|
|
||||||
|
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [removing, setRemoving] = useState(false);
|
const [removing, setRemoving] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
if (summaryError) {
|
if (!loading && hierarchy.noSupport) {
|
||||||
return <p>{ _t("Your server does not support showing space hierarchies.") }</p>;
|
return <p>{ _t("Your server does not support showing space hierarchies.") }</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (roomsMap) {
|
if (loading) {
|
||||||
const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length;
|
content = <Spinner />;
|
||||||
const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at
|
} else {
|
||||||
|
const numRooms = Array.from(filteredRoomSet).filter(r => !r.room_type).length;
|
||||||
|
const numSpaces = filteredRoomSet.size - numRooms - 1; // -1 at the end to exclude the space we are looking at
|
||||||
|
|
||||||
let countsStr;
|
let countsStr;
|
||||||
if (numSpaces > 1) {
|
if (numSpaces > 1) {
|
||||||
|
@ -438,7 +439,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
|
const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
|
||||||
return parentChildMap.get(parentId)?.get(childId)?.content.suggested;
|
return hierarchy.isSuggested(parentId, childId);
|
||||||
});
|
});
|
||||||
|
|
||||||
const disabled = !selectedRelations.length || removing || saving;
|
const disabled = !selectedRelations.length || removing || saving;
|
||||||
|
@ -461,17 +462,14 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
try {
|
try {
|
||||||
for (const [parentId, childId] of selectedRelations) {
|
for (const [parentId, childId] of selectedRelations) {
|
||||||
await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
|
await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
|
||||||
parentChildMap.get(parentId).delete(childId);
|
|
||||||
if (parentChildMap.get(parentId).size > 0) {
|
hierarchy.removeRelation(parentId, childId);
|
||||||
parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
|
|
||||||
} else {
|
|
||||||
parentChildMap.delete(parentId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(_t("Failed to remove some rooms. Try again later"));
|
setError(_t("Failed to remove some rooms. Try again later"));
|
||||||
}
|
}
|
||||||
setRemoving(false);
|
setRemoving(false);
|
||||||
|
setSelected(new Map());
|
||||||
}}
|
}}
|
||||||
kind="danger_outline"
|
kind="danger_outline"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -485,7 +483,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
try {
|
try {
|
||||||
for (const [parentId, childId] of selectedRelations) {
|
for (const [parentId, childId] of selectedRelations) {
|
||||||
const suggested = !selectionAllSuggested;
|
const suggested = !selectionAllSuggested;
|
||||||
const existingContent = parentChildMap.get(parentId)?.get(childId)?.content;
|
const existingContent = hierarchy.getRelation(parentId, childId)?.content;
|
||||||
if (!existingContent || existingContent.suggested === suggested) continue;
|
if (!existingContent || existingContent.suggested === suggested) continue;
|
||||||
|
|
||||||
const content = {
|
const content = {
|
||||||
|
@ -495,8 +493,8 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
|
|
||||||
await cli.sendStateEvent(parentId, EventType.SpaceChild, content, childId);
|
await cli.sendStateEvent(parentId, EventType.SpaceChild, content, childId);
|
||||||
|
|
||||||
parentChildMap.get(parentId).get(childId).content = content;
|
// mutate the local state to save us having to refetch the world
|
||||||
parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
|
existingContent.suggested = content.suggested;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError("Failed to update some suggestions. Try again later");
|
setError("Failed to update some suggestions. Try again later");
|
||||||
|
@ -516,14 +514,14 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
let results;
|
let results;
|
||||||
if (roomsMap.size) {
|
if (filteredRoomSet.size) {
|
||||||
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
||||||
|
|
||||||
results = <>
|
results = <>
|
||||||
<HierarchyLevel
|
<HierarchyLevel
|
||||||
spaceId={space.roomId}
|
root={hierarchy.roomMap.get(space.roomId)}
|
||||||
rooms={roomsMap}
|
roomSet={filteredRoomSet}
|
||||||
relations={parentChildMap}
|
hierarchy={hierarchy}
|
||||||
parents={new Set()}
|
parents={new Set()}
|
||||||
selectedMap={selected}
|
selectedMap={selected}
|
||||||
onToggleClick={hasPermissions ? (parentId, childId) => {
|
onToggleClick={hasPermissions ? (parentId, childId) => {
|
||||||
|
@ -543,7 +541,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
setSelected(new Map(selected.set(parentId, new Set(parentSet))));
|
setSelected(new Map(selected.set(parentId, new Set(parentSet))));
|
||||||
} : undefined}
|
} : undefined}
|
||||||
onViewRoomClick={(roomId, autoJoin) => {
|
onViewRoomClick={(roomId, autoJoin) => {
|
||||||
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
|
showRoom(hierarchy, roomId, autoJoin);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{ children && <hr /> }
|
{ children && <hr /> }
|
||||||
|
@ -571,8 +569,6 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
{ children }
|
{ children }
|
||||||
</AutoHideScrollbar>
|
</AutoHideScrollbar>
|
||||||
</>;
|
</>;
|
||||||
} else {
|
|
||||||
content = <Spinner />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO loading state/error state
|
// TODO loading state/error state
|
||||||
|
@ -624,8 +620,8 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, onFinished, initialText }
|
||||||
|
|
||||||
<SpaceHierarchy
|
<SpaceHierarchy
|
||||||
space={space}
|
space={space}
|
||||||
showRoom={(room: IRoomChild, viaServers?: string[], autoJoin = false) => {
|
showRoom={(hierarchy: RoomHierarchy, roomId: string, autoJoin = false) => {
|
||||||
showRoom(room, viaServers, autoJoin);
|
showRoom(hierarchy, roomId, autoJoin);
|
||||||
onFinished();
|
onFinished();
|
||||||
}}
|
}}
|
||||||
initialText={initialText}
|
initialText={initialText}
|
||||||
|
@ -644,9 +640,3 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, onFinished, initialText }
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SpaceRoomDirectory;
|
export default SpaceRoomDirectory;
|
||||||
|
|
||||||
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
|
||||||
// but works with the objects we get from the public room list
|
|
||||||
function getDisplayAliasForRoom(room: IRoomChild) {
|
|
||||||
return getDisplayAliasForAliasSet(room.canonical_alias, room.aliases);
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { ListIteratee, Many, sortBy, throttle } from "lodash";
|
||||||
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { IRoomChild } from "matrix-js-sdk/src/@types/spaces";
|
import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
|
||||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { IRoomCapability } from "matrix-js-sdk/src/client";
|
import { IRoomCapability } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
|
||||||
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
||||||
// Space Room ID/HOME_SPACE will be emitted when a Space's children change
|
// Space Room ID/HOME_SPACE will be emitted when a Space's children change
|
||||||
|
|
||||||
export interface ISuggestedRoom extends IRoomChild {
|
export interface ISuggestedRoom extends IHierarchyRoom {
|
||||||
viaServers: string[];
|
viaServers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +297,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
|
|
||||||
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise<ISuggestedRoom[]> => {
|
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise<ISuggestedRoom[]> => {
|
||||||
try {
|
try {
|
||||||
const { rooms } = await this.matrixClient.getRoomChildren(space.roomId, limit, 1, true);
|
const { rooms } = await this.matrixClient.getRoomHierarchy(space.roomId, limit, 1, true);
|
||||||
|
|
||||||
const viaMap = new EnhancedMap<string, Set<string>>();
|
const viaMap = new EnhancedMap<string, Set<string>>();
|
||||||
rooms.forEach(room => {
|
rooms.forEach(room => {
|
||||||
|
|
|
@ -87,7 +87,7 @@ export function createTestClient() {
|
||||||
getSpaceSummary: jest.fn().mockReturnValue({
|
getSpaceSummary: jest.fn().mockReturnValue({
|
||||||
rooms: [],
|
rooms: [],
|
||||||
}),
|
}),
|
||||||
getRoomChildren: jest.fn().mockReturnValue({
|
getRoomHierarchy: jest.fn().mockReturnValue({
|
||||||
rooms: [],
|
rooms: [],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue