You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-04 11:51:45 +03:00 
			
		
		
		
	Make space hierarchy a treeview
This commit is contained in:
		@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React, { ReactNode, useMemo, useState } from "react";
 | 
			
		||||
import React, { ReactNode, KeyboardEvent, useMemo, useState } from "react";
 | 
			
		||||
import { Room } from "matrix-js-sdk/src/models/room";
 | 
			
		||||
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
 | 
			
		||||
import { ISpaceSummaryRoom, ISpaceSummaryEvent } from "matrix-js-sdk/src/@types/spaces";
 | 
			
		||||
@@ -46,6 +46,8 @@ import { getDisplayAliasForAliasSet } from "../../Rooms";
 | 
			
		||||
import { useDispatcher } from "../../hooks/useDispatcher";
 | 
			
		||||
import defaultDispatcher from "../../dispatcher/dispatcher";
 | 
			
		||||
import { Action } from "../../dispatcher/actions";
 | 
			
		||||
import { Key } from "../../Keyboard";
 | 
			
		||||
import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex";
 | 
			
		||||
 | 
			
		||||
interface IHierarchyProps {
 | 
			
		||||
    space: Room;
 | 
			
		||||
@@ -80,6 +82,7 @@ const Tile: React.FC<ITileProps> = ({
 | 
			
		||||
        || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room"));
 | 
			
		||||
 | 
			
		||||
    const [showChildren, toggleShowChildren] = useStateToggle(true);
 | 
			
		||||
    const [onFocus, isActive, ref] = useRovingTabIndex();
 | 
			
		||||
 | 
			
		||||
    const onPreviewClick = (ev: ButtonEvent) => {
 | 
			
		||||
        ev.preventDefault();
 | 
			
		||||
@@ -94,11 +97,21 @@ const Tile: React.FC<ITileProps> = ({
 | 
			
		||||
 | 
			
		||||
    let button;
 | 
			
		||||
    if (joinedRoom) {
 | 
			
		||||
        button = <AccessibleButton onClick={onPreviewClick} kind="primary_outline">
 | 
			
		||||
        button = <AccessibleButton
 | 
			
		||||
            onClick={onPreviewClick}
 | 
			
		||||
            kind="primary_outline"
 | 
			
		||||
            onFocus={onFocus}
 | 
			
		||||
            tabIndex={isActive ? 0 : -1}
 | 
			
		||||
        >
 | 
			
		||||
            { _t("View") }
 | 
			
		||||
        </AccessibleButton>;
 | 
			
		||||
    } else if (onJoinClick) {
 | 
			
		||||
        button = <AccessibleButton onClick={onJoinClick} kind="primary">
 | 
			
		||||
        button = <AccessibleButton
 | 
			
		||||
            onClick={onJoinClick}
 | 
			
		||||
            kind="primary"
 | 
			
		||||
            onFocus={onFocus}
 | 
			
		||||
            tabIndex={isActive ? 0 : -1}
 | 
			
		||||
        >
 | 
			
		||||
            { _t("Join") }
 | 
			
		||||
        </AccessibleButton>;
 | 
			
		||||
    }
 | 
			
		||||
@@ -106,13 +119,13 @@ const Tile: React.FC<ITileProps> = ({
 | 
			
		||||
    let checkbox;
 | 
			
		||||
    if (onToggleClick) {
 | 
			
		||||
        if (hasPermissions) {
 | 
			
		||||
            checkbox = <StyledCheckbox checked={!!selected} onChange={onToggleClick} />;
 | 
			
		||||
            checkbox = <StyledCheckbox checked={!!selected} onChange={onToggleClick} tabIndex={isActive ? 0 : -1} />;
 | 
			
		||||
        } else {
 | 
			
		||||
            checkbox = <TextWithTooltip
 | 
			
		||||
                tooltip={_t("You don't have permission")}
 | 
			
		||||
                onClick={ev => { ev.stopPropagation(); }}
 | 
			
		||||
            >
 | 
			
		||||
                <StyledCheckbox disabled={true} />
 | 
			
		||||
                <StyledCheckbox disabled={true} tabIndex={isActive ? 0 : -1} />
 | 
			
		||||
            </TextWithTooltip>;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -185,19 +198,65 @@ const Tile: React.FC<ITileProps> = ({
 | 
			
		||||
                toggleShowChildren();
 | 
			
		||||
            }}
 | 
			
		||||
        />;
 | 
			
		||||
 | 
			
		||||
        if (showChildren) {
 | 
			
		||||
            childSection = <div className="mx_SpaceRoomDirectory_subspace_children">
 | 
			
		||||
            const onChildrenKeyDown = (e) => {
 | 
			
		||||
                if (e.key === Key.ARROW_LEFT) {
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
                    e.stopPropagation();
 | 
			
		||||
                    ref.current?.focus();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            childSection = <div
 | 
			
		||||
                className="mx_SpaceRoomDirectory_subspace_children"
 | 
			
		||||
                onKeyDown={onChildrenKeyDown}
 | 
			
		||||
                role="group"
 | 
			
		||||
            >
 | 
			
		||||
                { children }
 | 
			
		||||
            </div>;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const onKeyDown = children ? (e) => {
 | 
			
		||||
        let handled = false;
 | 
			
		||||
 | 
			
		||||
        switch (e.key) {
 | 
			
		||||
            case Key.ARROW_LEFT:
 | 
			
		||||
                if (showChildren) {
 | 
			
		||||
                    handled = true;
 | 
			
		||||
                    toggleShowChildren();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case Key.ARROW_RIGHT:
 | 
			
		||||
                handled = true;
 | 
			
		||||
                if (showChildren) {
 | 
			
		||||
                    (ref.current?.nextElementSibling?.firstElementChild as HTMLElement)?.focus();
 | 
			
		||||
                } else {
 | 
			
		||||
                    toggleShowChildren();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (handled) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
        }
 | 
			
		||||
    } : undefined;
 | 
			
		||||
 | 
			
		||||
    return <>
 | 
			
		||||
        <AccessibleButton
 | 
			
		||||
            className={classNames("mx_SpaceRoomDirectory_roomTile", {
 | 
			
		||||
                mx_SpaceRoomDirectory_subspace: room.room_type === RoomType.Space,
 | 
			
		||||
            })}
 | 
			
		||||
            onClick={(hasPermissions && onToggleClick) ? onToggleClick : onPreviewClick}
 | 
			
		||||
            onKeyDown={onKeyDown}
 | 
			
		||||
            inputRef={ref}
 | 
			
		||||
            onFocus={onFocus}
 | 
			
		||||
            tabIndex={isActive ? 0 : -1}
 | 
			
		||||
            aria-expanded={children ? showChildren : undefined}
 | 
			
		||||
            role="treeitem"
 | 
			
		||||
        >
 | 
			
		||||
            { content }
 | 
			
		||||
            { childToggle }
 | 
			
		||||
@@ -414,176 +473,191 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
 | 
			
		||||
        return <p>{ _t("Your server does not support showing space hierarchies.") }</p>;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let content;
 | 
			
		||||
    if (roomsMap) {
 | 
			
		||||
        const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length;
 | 
			
		||||
        const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at
 | 
			
		||||
 | 
			
		||||
        let countsStr;
 | 
			
		||||
        if (numSpaces > 1) {
 | 
			
		||||
            countsStr = _t("%(count)s rooms and %(numSpaces)s spaces", { count: numRooms, numSpaces });
 | 
			
		||||
        } else if (numSpaces > 0) {
 | 
			
		||||
            countsStr = _t("%(count)s rooms and 1 space", { count: numRooms, numSpaces });
 | 
			
		||||
        } else {
 | 
			
		||||
            countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces });
 | 
			
		||||
    const onKeyDown = (ev: KeyboardEvent, state: IState) => {
 | 
			
		||||
        if (ev.key === Key.ARROW_DOWN && ev.currentTarget.classList.contains("mx_SpaceRoomDirectory_search")) {
 | 
			
		||||
            state.refs[0]?.current?.focus();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let manageButtons;
 | 
			
		||||
        if (space.getMyMembership() === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
 | 
			
		||||
            const selectedRelations = Array.from(selected.keys()).flatMap(parentId => {
 | 
			
		||||
                return [...selected.get(parentId).values()].map(childId => [parentId, childId]) as [string, string][];
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
 | 
			
		||||
                return parentChildMap.get(parentId)?.get(childId)?.content.suggested;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const disabled = !selectedRelations.length || removing || saving;
 | 
			
		||||
 | 
			
		||||
            let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
 | 
			
		||||
            let props = {};
 | 
			
		||||
            if (!selectedRelations.length) {
 | 
			
		||||
                Button = AccessibleTooltipButton;
 | 
			
		||||
                props = {
 | 
			
		||||
                    tooltip: _t("Select a room below first"),
 | 
			
		||||
                    yOffset: -40,
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            manageButtons = <>
 | 
			
		||||
                <Button
 | 
			
		||||
                    {...props}
 | 
			
		||||
                    onClick={async () => {
 | 
			
		||||
                        setRemoving(true);
 | 
			
		||||
                        try {
 | 
			
		||||
                            for (const [parentId, childId] of selectedRelations) {
 | 
			
		||||
                                await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
 | 
			
		||||
                                parentChildMap.get(parentId).delete(childId);
 | 
			
		||||
                                if (parentChildMap.get(parentId).size > 0) {
 | 
			
		||||
                                    parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    parentChildMap.delete(parentId);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } catch (e) {
 | 
			
		||||
                            setError(_t("Failed to remove some rooms. Try again later"));
 | 
			
		||||
                        }
 | 
			
		||||
                        setRemoving(false);
 | 
			
		||||
                    }}
 | 
			
		||||
                    kind="danger_outline"
 | 
			
		||||
                    disabled={disabled}
 | 
			
		||||
                >
 | 
			
		||||
                    { removing ? _t("Removing...") : _t("Remove") }
 | 
			
		||||
                </Button>
 | 
			
		||||
                <Button
 | 
			
		||||
                    {...props}
 | 
			
		||||
                    onClick={async () => {
 | 
			
		||||
                        setSaving(true);
 | 
			
		||||
                        try {
 | 
			
		||||
                            for (const [parentId, childId] of selectedRelations) {
 | 
			
		||||
                                const suggested = !selectionAllSuggested;
 | 
			
		||||
                                const existingContent = parentChildMap.get(parentId)?.get(childId)?.content;
 | 
			
		||||
                                if (!existingContent || existingContent.suggested === suggested) continue;
 | 
			
		||||
 | 
			
		||||
                                const content = {
 | 
			
		||||
                                    ...existingContent,
 | 
			
		||||
                                    suggested: !selectionAllSuggested,
 | 
			
		||||
                                };
 | 
			
		||||
 | 
			
		||||
                                await cli.sendStateEvent(parentId, EventType.SpaceChild, content, childId);
 | 
			
		||||
 | 
			
		||||
                                parentChildMap.get(parentId).get(childId).content = content;
 | 
			
		||||
                                parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
 | 
			
		||||
                            }
 | 
			
		||||
                        } catch (e) {
 | 
			
		||||
                            setError("Failed to update some suggestions. Try again later");
 | 
			
		||||
                        }
 | 
			
		||||
                        setSaving(false);
 | 
			
		||||
                        setSelected(new Map());
 | 
			
		||||
                    }}
 | 
			
		||||
                    kind="primary_outline"
 | 
			
		||||
                    disabled={disabled}
 | 
			
		||||
                >
 | 
			
		||||
                    { saving
 | 
			
		||||
                        ? _t("Saving...")
 | 
			
		||||
                        : (selectionAllSuggested ? _t("Mark as not suggested") : _t("Mark as suggested"))
 | 
			
		||||
                    }
 | 
			
		||||
                </Button>
 | 
			
		||||
            </>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let results;
 | 
			
		||||
        if (roomsMap.size) {
 | 
			
		||||
            const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
 | 
			
		||||
 | 
			
		||||
            results = <>
 | 
			
		||||
                <HierarchyLevel
 | 
			
		||||
                    spaceId={space.roomId}
 | 
			
		||||
                    rooms={roomsMap}
 | 
			
		||||
                    relations={parentChildMap}
 | 
			
		||||
                    parents={new Set()}
 | 
			
		||||
                    selectedMap={selected}
 | 
			
		||||
                    onToggleClick={hasPermissions ? (parentId, childId) => {
 | 
			
		||||
                        setError("");
 | 
			
		||||
                        if (!selected.has(parentId)) {
 | 
			
		||||
                            setSelected(new Map(selected.set(parentId, new Set([childId]))));
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        const parentSet = selected.get(parentId);
 | 
			
		||||
                        if (!parentSet.has(childId)) {
 | 
			
		||||
                            setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId]))));
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        parentSet.delete(childId);
 | 
			
		||||
                        setSelected(new Map(selected.set(parentId, new Set(parentSet))));
 | 
			
		||||
                    } : undefined}
 | 
			
		||||
                    onViewRoomClick={(roomId, autoJoin) => {
 | 
			
		||||
                        showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
                { children && <hr /> }
 | 
			
		||||
            </>;
 | 
			
		||||
        } else {
 | 
			
		||||
            results = <div className="mx_SpaceRoomDirectory_noResults">
 | 
			
		||||
                <h3>{ _t("No results found") }</h3>
 | 
			
		||||
                <div>{ _t("You may want to try a different search or check for typos.") }</div>
 | 
			
		||||
            </div>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        content = <>
 | 
			
		||||
            <div className="mx_SpaceRoomDirectory_listHeader">
 | 
			
		||||
                { countsStr }
 | 
			
		||||
                <span>
 | 
			
		||||
                    { additionalButtons }
 | 
			
		||||
                    { manageButtons }
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            { error && <div className="mx_SpaceRoomDirectory_error">
 | 
			
		||||
                { error }
 | 
			
		||||
            </div> }
 | 
			
		||||
            <AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
 | 
			
		||||
                { results }
 | 
			
		||||
                { children }
 | 
			
		||||
            </AutoHideScrollbar>
 | 
			
		||||
        </>;
 | 
			
		||||
    } else {
 | 
			
		||||
        content = <Spinner />;
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // TODO loading state/error state
 | 
			
		||||
    return <>
 | 
			
		||||
        <SearchBox
 | 
			
		||||
            className="mx_textinput_icon mx_textinput_search"
 | 
			
		||||
            placeholder={_t("Search names and descriptions")}
 | 
			
		||||
            onSearch={setQuery}
 | 
			
		||||
            autoFocus={true}
 | 
			
		||||
            initialValue={initialText}
 | 
			
		||||
        />
 | 
			
		||||
    return <RovingTabIndexProvider onKeyDown={onKeyDown} handleHomeEnd handleUpDown>
 | 
			
		||||
        { ({ onKeyDownHandler }) => {
 | 
			
		||||
            let content;
 | 
			
		||||
            if (roomsMap) {
 | 
			
		||||
                const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length;
 | 
			
		||||
                const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at
 | 
			
		||||
 | 
			
		||||
        { content }
 | 
			
		||||
    </>;
 | 
			
		||||
                let countsStr;
 | 
			
		||||
                if (numSpaces > 1) {
 | 
			
		||||
                    countsStr = _t("%(count)s rooms and %(numSpaces)s spaces", { count: numRooms, numSpaces });
 | 
			
		||||
                } else if (numSpaces > 0) {
 | 
			
		||||
                    countsStr = _t("%(count)s rooms and 1 space", { count: numRooms, numSpaces });
 | 
			
		||||
                } else {
 | 
			
		||||
                    countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let manageButtons;
 | 
			
		||||
                if (space.getMyMembership() === "join" &&
 | 
			
		||||
                    space.currentState.maySendStateEvent(EventType.SpaceChild, userId)
 | 
			
		||||
                ) {
 | 
			
		||||
                    const selectedRelations = Array.from(selected.keys()).flatMap(parentId => {
 | 
			
		||||
                        return [
 | 
			
		||||
                            ...selected.get(parentId).values(),
 | 
			
		||||
                        ].map(childId => [parentId, childId]) as [string, string][];
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
 | 
			
		||||
                        return parentChildMap.get(parentId)?.get(childId)?.content.suggested;
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    const disabled = !selectedRelations.length || removing || saving;
 | 
			
		||||
 | 
			
		||||
                    let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
 | 
			
		||||
                    let props = {};
 | 
			
		||||
                    if (!selectedRelations.length) {
 | 
			
		||||
                        Button = AccessibleTooltipButton;
 | 
			
		||||
                        props = {
 | 
			
		||||
                            tooltip: _t("Select a room below first"),
 | 
			
		||||
                            yOffset: -40,
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    manageButtons = <>
 | 
			
		||||
                        <Button
 | 
			
		||||
                            {...props}
 | 
			
		||||
                            onClick={async () => {
 | 
			
		||||
                                setRemoving(true);
 | 
			
		||||
                                try {
 | 
			
		||||
                                    for (const [parentId, childId] of selectedRelations) {
 | 
			
		||||
                                        await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
 | 
			
		||||
                                        parentChildMap.get(parentId).delete(childId);
 | 
			
		||||
                                        if (parentChildMap.get(parentId).size > 0) {
 | 
			
		||||
                                            parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            parentChildMap.delete(parentId);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                } catch (e) {
 | 
			
		||||
                                    setError(_t("Failed to remove some rooms. Try again later"));
 | 
			
		||||
                                }
 | 
			
		||||
                                setRemoving(false);
 | 
			
		||||
                            }}
 | 
			
		||||
                            kind="danger_outline"
 | 
			
		||||
                            disabled={disabled}
 | 
			
		||||
                        >
 | 
			
		||||
                            { removing ? _t("Removing...") : _t("Remove") }
 | 
			
		||||
                        </Button>
 | 
			
		||||
                        <Button
 | 
			
		||||
                            {...props}
 | 
			
		||||
                            onClick={async () => {
 | 
			
		||||
                                setSaving(true);
 | 
			
		||||
                                try {
 | 
			
		||||
                                    for (const [parentId, childId] of selectedRelations) {
 | 
			
		||||
                                        const suggested = !selectionAllSuggested;
 | 
			
		||||
                                        const existingContent = parentChildMap.get(parentId)?.get(childId)?.content;
 | 
			
		||||
                                        if (!existingContent || existingContent.suggested === suggested) continue;
 | 
			
		||||
 | 
			
		||||
                                        const content = {
 | 
			
		||||
                                            ...existingContent,
 | 
			
		||||
                                            suggested: !selectionAllSuggested,
 | 
			
		||||
                                        };
 | 
			
		||||
 | 
			
		||||
                                        await cli.sendStateEvent(parentId, EventType.SpaceChild, content, childId);
 | 
			
		||||
 | 
			
		||||
                                        parentChildMap.get(parentId).get(childId).content = content;
 | 
			
		||||
                                        parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
 | 
			
		||||
                                    }
 | 
			
		||||
                                } catch (e) {
 | 
			
		||||
                                    setError("Failed to update some suggestions. Try again later");
 | 
			
		||||
                                }
 | 
			
		||||
                                setSaving(false);
 | 
			
		||||
                                setSelected(new Map());
 | 
			
		||||
                            }}
 | 
			
		||||
                            kind="primary_outline"
 | 
			
		||||
                            disabled={disabled}
 | 
			
		||||
                        >
 | 
			
		||||
                            { saving
 | 
			
		||||
                                ? _t("Saving...")
 | 
			
		||||
                                : (selectionAllSuggested ? _t("Mark as not suggested") : _t("Mark as suggested"))
 | 
			
		||||
                            }
 | 
			
		||||
                        </Button>
 | 
			
		||||
                    </>;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let results;
 | 
			
		||||
                if (roomsMap.size) {
 | 
			
		||||
                    const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
 | 
			
		||||
 | 
			
		||||
                    results = <>
 | 
			
		||||
                        <HierarchyLevel
 | 
			
		||||
                            spaceId={space.roomId}
 | 
			
		||||
                            rooms={roomsMap}
 | 
			
		||||
                            relations={parentChildMap}
 | 
			
		||||
                            parents={new Set()}
 | 
			
		||||
                            selectedMap={selected}
 | 
			
		||||
                            onToggleClick={hasPermissions ? (parentId, childId) => {
 | 
			
		||||
                                setError("");
 | 
			
		||||
                                if (!selected.has(parentId)) {
 | 
			
		||||
                                    setSelected(new Map(selected.set(parentId, new Set([childId]))));
 | 
			
		||||
                                    return;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                const parentSet = selected.get(parentId);
 | 
			
		||||
                                if (!parentSet.has(childId)) {
 | 
			
		||||
                                    setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId]))));
 | 
			
		||||
                                    return;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                parentSet.delete(childId);
 | 
			
		||||
                                setSelected(new Map(selected.set(parentId, new Set(parentSet))));
 | 
			
		||||
                            } : undefined}
 | 
			
		||||
                            onViewRoomClick={(roomId, autoJoin) => {
 | 
			
		||||
                                showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
                        { children && <hr /> }
 | 
			
		||||
                    </>;
 | 
			
		||||
                } else {
 | 
			
		||||
                    results = <div className="mx_SpaceRoomDirectory_noResults">
 | 
			
		||||
                        <h3>{ _t("No results found") }</h3>
 | 
			
		||||
                        <div>{ _t("You may want to try a different search or check for typos.") }</div>
 | 
			
		||||
                    </div>;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                content = <>
 | 
			
		||||
                    <div className="mx_SpaceRoomDirectory_listHeader">
 | 
			
		||||
                        { countsStr }
 | 
			
		||||
                        <span>
 | 
			
		||||
                            { additionalButtons }
 | 
			
		||||
                            { manageButtons }
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    { error && <div className="mx_SpaceRoomDirectory_error">
 | 
			
		||||
                        { error }
 | 
			
		||||
                    </div> }
 | 
			
		||||
                    <AutoHideScrollbar className="mx_SpaceRoomDirectory_list" onKeyDown={onKeyDownHandler} role="tree">
 | 
			
		||||
                        { results }
 | 
			
		||||
                        { children }
 | 
			
		||||
                    </AutoHideScrollbar>
 | 
			
		||||
                </>;
 | 
			
		||||
            } else {
 | 
			
		||||
                content = <Spinner />;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return <>
 | 
			
		||||
                <SearchBox
 | 
			
		||||
                    className="mx_SpaceRoomDirectory_search mx_textinput_icon mx_textinput_search"
 | 
			
		||||
                    placeholder={_t("Search names and descriptions")}
 | 
			
		||||
                    onSearch={setQuery}
 | 
			
		||||
                    autoFocus={true}
 | 
			
		||||
                    initialValue={initialText}
 | 
			
		||||
                    onKeyDown={onKeyDownHandler}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                { content }
 | 
			
		||||
            </>;
 | 
			
		||||
        } }
 | 
			
		||||
    </RovingTabIndexProvider>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user