Move space room discovery & management into the space room view

This commit is contained in:
Michael Telatynski 2021-03-26 09:44:52 +00:00
parent 09601f1071
commit 8c6d92cc99
7 changed files with 100 additions and 123 deletions

View file

@ -330,10 +330,6 @@ $activeBorderColor: $secondary-fg-color;
mask-image: url('$(res)/img/element-icons/leave.svg'); mask-image: url('$(res)/img/element-icons/leave.svg');
} }
.mx_SpacePanel_iconHome::before {
mask-image: url('$(res)/img/element-icons/roomlist/home.svg');
}
.mx_SpacePanel_iconMembers::before { .mx_SpacePanel_iconMembers::before {
mask-image: url('$(res)/img/element-icons/room/members.svg'); mask-image: url('$(res)/img/element-icons/room/members.svg');
} }

View file

@ -365,12 +365,13 @@ $SpaceRoomViewInnerWidth: 428px;
} }
} }
.mx_SpaceRoomDirectory_list { .mx_SearchBox {
max-width: 600px; margin-left: 4px;
max-width: 670px;
}
.mx_SpaceRoomDirectory_roomTile_actions { .mx_SpaceRoomDirectory_list {
display: none; max-width: 700px;
}
} }
} }

View file

@ -80,10 +80,10 @@ import DialPadModal from "../views/voip/DialPadModal";
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast'; import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
import { shouldUseLoginForWelcome } from "../../utils/pages"; import { shouldUseLoginForWelcome } from "../../utils/pages";
import SpaceStore from "../../stores/SpaceStore"; import SpaceStore from "../../stores/SpaceStore";
import SpaceRoomDirectory from "./SpaceRoomDirectory";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
import RoomListStore from "../../stores/room-list/RoomListStore"; import RoomListStore from "../../stores/room-list/RoomListStore";
import {RoomUpdateCause} from "../../stores/room-list/models"; import {RoomUpdateCause} from "../../stores/room-list/models";
import defaultDispatcher from "../../dispatcher/dispatcher";
/** constants for MatrixChat.state.view */ /** constants for MatrixChat.state.view */
export enum Views { export enum Views {
@ -690,10 +690,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
case Action.ViewRoomDirectory: { case Action.ViewRoomDirectory: {
if (SpaceStore.instance.activeSpace) { if (SpaceStore.instance.activeSpace) {
Modal.createTrackedDialog("Space room directory", "", SpaceRoomDirectory, { defaultDispatcher.dispatch({
space: SpaceStore.instance.activeSpace, action: "view_room",
initialText: payload.initialText, room_id: SpaceStore.instance.activeSpace.roomId,
}, "mx_SpaceRoomDirectory_dialogWrapper", false, true); });
} else { } else {
const RoomDirectory = sdk.getComponent("structures.RoomDirectory"); const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
Modal.createTrackedDialog('Room directory', '', RoomDirectory, { Modal.createTrackedDialog('Room directory', '', RoomDirectory, {

View file

@ -40,10 +40,11 @@ import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip"; import TextWithTooltip from "../views/elements/TextWithTooltip";
import {useStateToggle} from "../../hooks/useStateToggle"; import {useStateToggle} from "../../hooks/useStateToggle";
interface IProps { interface IHierarchyProps {
space: Room; space: Room;
initialText?: string; initialText?: string;
onFinished(): void; refreshToken?: any;
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
} }
/* eslint-disable camelcase */ /* eslint-disable camelcase */
@ -344,22 +345,20 @@ export const useSpaceSummary = (cli: MatrixClient, space: Room, refreshToken?: a
}, [space, refreshToken], []); }, [space, refreshToken], []);
}; };
const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinished }) => { export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
space,
initialText = "",
showRoom,
refreshToken,
children,
}) => {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const userId = cli.getUserId(); const userId = cli.getUserId();
const [query, setQuery] = useState(initialText); const [query, setQuery] = useState(initialText);
const onCreateRoomClick = () => {
dis.dispatch({
action: 'view_create_room',
public: true,
});
onFinished();
};
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 [rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(cli, space); const [rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(cli, space, refreshToken);
const roomsMap = useMemo(() => { const roomsMap = useMemo(() => {
if (!rooms) return null; if (!rooms) return null;
@ -394,21 +393,6 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
return roomsMap; return roomsMap;
}, [rooms, childParentMap, query]); }, [rooms, childParentMap, query]);
const title = <React.Fragment>
<RoomAvatar room={space} height={32} width={32} />
<div>
<h1>{ _t("Explore rooms") }</h1>
<div><RoomName room={space} /></div>
</div>
</React.Fragment>;
const explanation =
_t("If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.", null,
{a: sub => {
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
}},
);
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);
@ -528,10 +512,9 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
}} }}
onViewRoomClick={(roomId, autoJoin) => { onViewRoomClick={(roomId, autoJoin) => {
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin); showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
onFinished();
}} }}
/> />
<hr /> { children && <hr /> }
</>; </>;
} else { } else {
results = <div className="mx_SpaceRoomDirectory_noResults"> results = <div className="mx_SpaceRoomDirectory_noResults">
@ -550,34 +533,78 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
</div> } </div> }
<AutoHideScrollbar className="mx_SpaceRoomDirectory_list"> <AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
{ results } { results }
<AccessibleButton { children }
onClick={onCreateRoomClick}
kind="primary"
className="mx_SpaceRoomDirectory_createRoom"
>
{ _t("Create room") }
</AccessibleButton>
</AutoHideScrollbar> </AutoHideScrollbar>
</>; </>;
} else { } else if (!rooms) {
content = <Spinner />; content = <Spinner />;
} else {
content = <p>{_t("Your server does not support showing space hierarchies.")}</p>;
} }
// TODO loading state/error state // TODO loading state/error state
return <>
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={ _t("Search names and description") }
onSearch={setQuery}
autoFocus={true}
initialValue={initialText}
/>
{ content }
</>;
};
interface IProps {
space: Room;
initialText?: string;
onFinished(): void;
}
const SpaceRoomDirectory: React.FC<IProps> = ({ space, onFinished, initialText }) => {
const onCreateRoomClick = () => {
dis.dispatch({
action: 'view_create_room',
public: true,
});
onFinished();
};
const title = <React.Fragment>
<RoomAvatar room={space} height={32} width={32} />
<div>
<h1>{ _t("Explore rooms") }</h1>
<div><RoomName room={space} /></div>
</div>
</React.Fragment>;
return ( return (
<BaseDialog className="mx_SpaceRoomDirectory" hasCancel={true} onFinished={onFinished} title={title}> <BaseDialog className="mx_SpaceRoomDirectory" hasCancel={true} onFinished={onFinished} title={title}>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
{ explanation } { _t("If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
null,
{a: sub => {
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
}},
) }
<SearchBox <SpaceHierarchy
className="mx_textinput_icon mx_textinput_search" space={space}
placeholder={ _t("Search names and description") } showRoom={(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => {
onSearch={setQuery} showRoom(room, viaServers, autoJoin);
autoFocus={true} onFinished();
initialValue={initialText} }}
/> initialText={initialText}
>
{ content } <AccessibleButton
onClick={onCreateRoomClick}
kind="primary"
className="mx_SpaceRoomDirectory_createRoom"
>
{ _t("Create room") }
</AccessibleButton>
</SpaceHierarchy>
</div> </div>
</BaseDialog> </BaseDialog>
); );

View file

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {RefObject, useContext, useMemo, useRef, useState} from "react"; import React, {RefObject, useContext, useRef, useState} from "react";
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event"; import {EventType} 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 {EventSubscription} from "fbemitter"; import {EventSubscription} from "fbemitter";
@ -46,8 +46,7 @@ import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanel
import {useStateArray} from "../../hooks/useStateArray"; import {useStateArray} from "../../hooks/useStateArray";
import SpacePublicShare from "../views/spaces/SpacePublicShare"; import SpacePublicShare from "../views/spaces/SpacePublicShare";
import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
import {HierarchyLevel, ISpaceSummaryRoom, showRoom, useSpaceSummary} from "./SpaceRoomDirectory"; import {showRoom, SpaceHierarchy} from "./SpaceRoomDirectory";
import AutoHideScrollbar from "./AutoHideScrollbar";
import MemberAvatar from "../views/avatars/MemberAvatar"; import MemberAvatar from "../views/avatars/MemberAvatar";
import {useStateToggle} from "../../hooks/useStateToggle"; import {useStateToggle} from "../../hooks/useStateToggle";
import SpaceStore from "../../stores/SpaceStore"; import SpaceStore from "../../stores/SpaceStore";
@ -260,37 +259,6 @@ const SpaceLanding = ({ space }) => {
</AccessibleButton>; </AccessibleButton>;
} }
const [rooms, relations, viaMap] = useSpaceSummary(cli, space, refreshToken);
const [roomsMap, numRooms] = useMemo(() => {
if (!rooms) return [];
const roomsMap = new Map<string, ISpaceSummaryRoom>(rooms.map(r => [r.room_id, r]));
const numRooms = rooms.filter(r => r.room_type !== RoomType.Space).length;
return [roomsMap, numRooms];
}, [rooms]);
let previewRooms;
if (roomsMap) {
previewRooms = <AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
<div className="mx_SpaceRoomDirectory_roomCount">
<h3>{ myMembership === "join" ? _t("Rooms") : _t("Default Rooms")}</h3>
<span>{ numRooms }</span>
</div>
<HierarchyLevel
spaceId={space.roomId}
rooms={roomsMap}
relations={relations}
parents={new Set()}
onViewRoomClick={(roomId, autoJoin) => {
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
}}
/>
</AutoHideScrollbar>;
} else if (!rooms) {
previewRooms = <InlineSpinner />;
} else {
previewRooms = <p>{_t("Your server does not support showing space hierarchies.")}</p>;
}
return <div className="mx_SpaceRoomView_landing"> return <div className="mx_SpaceRoomView_landing">
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} /> <RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
<div className="mx_SpaceRoomView_landing_name"> <div className="mx_SpaceRoomView_landing_name">
@ -336,7 +304,7 @@ const SpaceLanding = ({ space }) => {
{ settingsButton } { settingsButton }
</div> </div>
{ previewRooms } <SpaceHierarchy space={space} showRoom={showRoom} refreshToken={refreshToken} />
</div>; </div>;
}; };

View file

@ -48,7 +48,6 @@ import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {showRoomInviteDialog} from "../../../RoomInvite"; import {showRoomInviteDialog} from "../../../RoomInvite";
import InfoDialog from "../dialogs/InfoDialog"; import InfoDialog from "../dialogs/InfoDialog";
import {EventType} from "matrix-js-sdk/src/@types/event"; import {EventType} from "matrix-js-sdk/src/@types/event";
import SpaceRoomDirectory from "../../structures/SpaceRoomDirectory";
interface IItemProps { interface IItemProps {
space?: Room; space?: Room;
@ -115,17 +114,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
this.setState({contextMenuPosition: null}); this.setState({contextMenuPosition: null});
}; };
private onHomeClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
defaultDispatcher.dispatch({
action: "view_room",
room_id: this.props.space.roomId,
});
this.setState({contextMenuPosition: null}); // also close the menu
};
private onInviteClick = (ev: ButtonEvent) => { private onInviteClick = (ev: ButtonEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
@ -206,9 +194,10 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
Modal.createTrackedDialog("Space room directory", "Space panel", SpaceRoomDirectory, { defaultDispatcher.dispatch({
space: this.props.space, action: "view_room",
}, "mx_SpaceRoomDirectory_dialogWrapper", false, true); room_id: this.props.space.roomId,
});
this.setState({contextMenuPosition: null}); // also close the menu this.setState({contextMenuPosition: null}); // also close the menu
}; };
@ -249,6 +238,8 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
</IconizedContextMenuOptionList>; </IconizedContextMenuOptionList>;
} }
const canAddRooms = this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
let newRoomSection; let newRoomSection;
if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) { if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
newRoomSection = <IconizedContextMenuOptionList first> newRoomSection = <IconizedContextMenuOptionList first>
@ -276,11 +267,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
</div> </div>
<IconizedContextMenuOptionList first> <IconizedContextMenuOptionList first>
{ inviteOption } { inviteOption }
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconHome"
label={_t("Space Home")}
onClick={this.onHomeClick}
/>
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconMembers" iconClassName="mx_SpacePanel_iconMembers"
label={_t("Members")} label={_t("Members")}
@ -289,7 +275,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
{ settingsOption } { settingsOption }
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconExplore" iconClassName="mx_SpacePanel_iconExplore"
label={_t("Explore rooms")} label={canAddRooms ? _t("Manage & explore rooms") : _t("Explore rooms")}
onClick={this.onExploreRoomsClick} onClick={this.onExploreRoomsClick}
/> />
</IconizedContextMenuOptionList> </IconizedContextMenuOptionList>

View file

@ -1018,8 +1018,8 @@
"Leave space": "Leave space", "Leave space": "Leave space",
"Create new room": "Create new room", "Create new room": "Create new room",
"Add existing room": "Add existing room", "Add existing room": "Add existing room",
"Space Home": "Space Home",
"Members": "Members", "Members": "Members",
"Manage & explore rooms": "Manage & explore rooms",
"Explore rooms": "Explore rooms", "Explore rooms": "Explore rooms",
"Space options": "Space options", "Space options": "Space options",
"Remove": "Remove", "Remove": "Remove",
@ -2618,7 +2618,6 @@
"%(count)s rooms|one": "%(count)s room", "%(count)s rooms|one": "%(count)s room",
"This room is suggested as a good one to join": "This room is suggested as a good one to join", "This room is suggested as a good one to join": "This room is suggested as a good one to join",
"Suggested": "Suggested", "Suggested": "Suggested",
"If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
"%(count)s rooms and %(numSpaces)s spaces|other": "%(count)s rooms and %(numSpaces)s spaces", "%(count)s rooms and %(numSpaces)s spaces|other": "%(count)s rooms and %(numSpaces)s spaces",
"%(count)s rooms and %(numSpaces)s spaces|one": "%(count)s room and %(numSpaces)s spaces", "%(count)s rooms and %(numSpaces)s spaces|one": "%(count)s room and %(numSpaces)s spaces",
"%(count)s rooms and 1 space|other": "%(count)s rooms and 1 space", "%(count)s rooms and 1 space|other": "%(count)s rooms and 1 space",
@ -2629,14 +2628,14 @@
"Mark as suggested": "Mark as suggested", "Mark as suggested": "Mark as suggested",
"No results found": "No results found", "No results found": "No results found",
"You may want to try a different search or check for typos.": "You may want to try a different search or check for typos.", "You may want to try a different search or check for typos.": "You may want to try a different search or check for typos.",
"Create room": "Create room", "Your server does not support showing space hierarchies.": "Your server does not support showing space hierarchies.",
"Search names and description": "Search names and description", "Search names and description": "Search names and description",
"If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
"Create room": "Create room",
"<inviter/> invites you": "<inviter/> invites you", "<inviter/> invites you": "<inviter/> invites you",
"Public space": "Public space", "Public space": "Public space",
"Private space": "Private space", "Private space": "Private space",
"Add existing rooms & spaces": "Add existing rooms & spaces", "Add existing rooms & spaces": "Add existing rooms & spaces",
"Default Rooms": "Default Rooms",
"Your server does not support showing space hierarchies.": "Your server does not support showing space hierarchies.",
"Your public space <name/>": "Your public space <name/>", "Your public space <name/>": "Your public space <name/>",
"Your private space <name/>": "Your private space <name/>", "Your private space <name/>": "Your private space <name/>",
"Welcome to <name/>": "Welcome to <name/>", "Welcome to <name/>": "Welcome to <name/>",