Make join button on space hierarchy action in the background (#7041)

pull/21833/head
Michael Telatynski 2021-10-27 15:24:31 +01:00 committed by GitHub
parent 43cbb947b6
commit 27e16362b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 97 additions and 61 deletions

View File

@ -288,6 +288,11 @@ limitations under the License.
visibility: visible; visibility: visible;
} }
} }
&.mx_SpaceHierarchy_joining .mx_AccessibleButton {
visibility: visible;
padding: 4px 18px;
}
} }
li.mx_SpaceHierarchy_roomTileWrapper { li.mx_SpaceHierarchy_roomTileWrapper {

View File

@ -60,18 +60,15 @@ import { getDisplayAliasForRoom } from "./RoomDirectory";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import { useEventEmitterState } from "../../hooks/useEventEmitter"; import { useEventEmitterState } from "../../hooks/useEventEmitter";
import { IOOBData } from "../../stores/ThreepidInviteStore"; import { IOOBData } from "../../stores/ThreepidInviteStore";
import { awaitRoomDownSync } from "../../utils/RoomUpgrade";
import RoomViewStore from "../../stores/RoomViewStore";
interface IProps { interface IProps {
space: Room; space: Room;
initialText?: string; initialText?: string;
additionalButtons?: ReactNode; additionalButtons?: ReactNode;
showRoom( showRoom(cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string, roomType?: RoomType): void;
cli: MatrixClient, joinRoom(cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string): void;
hierarchy: RoomHierarchy,
roomId: string,
autoJoin?: boolean,
roomType?: RoomType,
): void;
} }
interface ITileProps { interface ITileProps {
@ -80,7 +77,8 @@ interface ITileProps {
selected?: boolean; selected?: boolean;
numChildRooms?: number; numChildRooms?: number;
hasPermissions?: boolean; hasPermissions?: boolean;
onViewRoomClick(autoJoin: boolean, roomType: RoomType): void; onViewRoomClick(): void;
onJoinRoomClick(): void;
onToggleClick?(): void; onToggleClick?(): void;
} }
@ -91,31 +89,50 @@ const Tile: React.FC<ITileProps> = ({
hasPermissions, hasPermissions,
onToggleClick, onToggleClick,
onViewRoomClick, onViewRoomClick,
onJoinRoomClick,
numChildRooms, numChildRooms,
children, children,
}) => { }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const joinedRoom = cli.getRoom(room.room_id)?.getMyMembership() === "join" ? cli.getRoom(room.room_id) : null; const [joinedRoom, setJoinedRoom] = useState<Room>(() => {
const cliRoom = cli.getRoom(room.room_id);
return cliRoom?.getMyMembership() === "join" ? cliRoom : null;
});
const joinedRoomName = useEventEmitterState(joinedRoom, "Room.name", room => room?.name); const joinedRoomName = useEventEmitterState(joinedRoom, "Room.name", room => room?.name);
const name = joinedRoomName || room.name || room.canonical_alias || room.aliases?.[0] const name = joinedRoomName || room.name || room.canonical_alias || room.aliases?.[0]
|| (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room")); || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room"));
const [showChildren, toggleShowChildren] = useStateToggle(true); const [showChildren, toggleShowChildren] = useStateToggle(true);
const [onFocus, isActive, ref] = useRovingTabIndex(); const [onFocus, isActive, ref] = useRovingTabIndex();
const [busy, setBusy] = useState(false);
const onPreviewClick = (ev: ButtonEvent) => { const onPreviewClick = (ev: ButtonEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
onViewRoomClick(false, room.room_type as RoomType); onViewRoomClick();
}; };
const onJoinClick = (ev: ButtonEvent) => { const onJoinClick = async (ev: ButtonEvent) => {
setBusy(true);
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
onViewRoomClick(true, room.room_type as RoomType); onJoinRoomClick();
setJoinedRoom(await awaitRoomDownSync(cli, room.room_id));
setBusy(false);
}; };
let button; let button;
if (joinedRoom) { if (busy) {
button = <AccessibleTooltipButton
disabled={true}
onClick={onJoinClick}
kind="primary_outline"
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
title={_t("Joining")}
>
<Spinner w={24} h={24} />
</AccessibleTooltipButton>;
} else if (joinedRoom) {
button = <AccessibleButton button = <AccessibleButton
onClick={onPreviewClick} onClick={onPreviewClick}
kind="primary_outline" kind="primary_outline"
@ -282,6 +299,7 @@ const Tile: React.FC<ITileProps> = ({
<AccessibleButton <AccessibleButton
className={classNames("mx_SpaceHierarchy_roomTile", { className={classNames("mx_SpaceHierarchy_roomTile", {
mx_SpaceHierarchy_subspace: room.room_type === RoomType.Space, mx_SpaceHierarchy_subspace: room.room_type === RoomType.Space,
mx_SpaceHierarchy_joining: busy,
})} })}
onClick={(hasPermissions && onToggleClick) ? onToggleClick : onPreviewClick} onClick={(hasPermissions && onToggleClick) ? onToggleClick : onPreviewClick}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
@ -296,13 +314,7 @@ const Tile: React.FC<ITileProps> = ({
</li>; </li>;
}; };
export const showRoom = ( export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string, roomType?: RoomType): void => {
cli: MatrixClient,
hierarchy: RoomHierarchy,
roomId: string,
autoJoin = false,
roomType?: RoomType,
) => {
const room = hierarchy.roomMap.get(roomId); 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:
@ -317,7 +329,6 @@ export const showRoom = (
const roomAlias = getDisplayAliasForRoom(room) || undefined; const roomAlias = getDisplayAliasForRoom(room) || undefined;
dis.dispatch({ dis.dispatch({
action: "view_room", action: "view_room",
auto_join: autoJoin,
should_peek: true, should_peek: true,
_type: "room_directory", // instrumentation _type: "room_directory", // instrumentation
room_alias: roomAlias, room_alias: roomAlias,
@ -332,13 +343,29 @@ export const showRoom = (
}); });
}; };
export const joinRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string): void => {
// 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.
if (cli.isGuest()) {
dis.dispatch({ action: "require_registration" });
return;
}
cli.joinRoom(roomId, {
viaServers: Array.from(hierarchy.viaMap.get(roomId) || []),
}).catch(err => {
RoomViewStore.showJoinRoomError(err, roomId);
});
};
interface IHierarchyLevelProps { interface IHierarchyLevelProps {
root: IHierarchyRoom; root: IHierarchyRoom;
roomSet: Set<IHierarchyRoom>; roomSet: Set<IHierarchyRoom>;
hierarchy: RoomHierarchy; hierarchy: RoomHierarchy;
parents: Set<string>; parents: Set<string>;
selectedMap?: Map<string, Set<string>>; selectedMap?: Map<string, Set<string>>;
onViewRoomClick(roomId: string, autoJoin: boolean, roomType?: RoomType): void; onViewRoomClick(roomId: string, roomType?: RoomType): void;
onJoinRoomClick(roomId: string): void;
onToggleClick?(parentId: string, childId: string): void; onToggleClick?(parentId: string, childId: string): void;
} }
@ -373,6 +400,7 @@ export const HierarchyLevel = ({
parents, parents,
selectedMap, selectedMap,
onViewRoomClick, onViewRoomClick,
onJoinRoomClick,
onToggleClick, onToggleClick,
}: IHierarchyLevelProps) => { }: IHierarchyLevelProps) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
@ -400,9 +428,8 @@ export const HierarchyLevel = ({
room={room} room={room}
suggested={hierarchy.isSuggested(root.room_id, room.room_id)} suggested={hierarchy.isSuggested(root.room_id, room.room_id)}
selected={selectedMap?.get(root.room_id)?.has(room.room_id)} selected={selectedMap?.get(root.room_id)?.has(room.room_id)}
onViewRoomClick={(autoJoin, roomType) => { onViewRoomClick={() => onViewRoomClick(room.room_id, room.room_type as RoomType)}
onViewRoomClick(room.room_id, autoJoin, roomType); onJoinRoomClick={() => onJoinRoomClick(room.room_id)}
}}
hasPermissions={hasPermissions} hasPermissions={hasPermissions}
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined} onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined}
/> />
@ -420,9 +447,8 @@ export const HierarchyLevel = ({
}).length} }).length}
suggested={hierarchy.isSuggested(root.room_id, space.room_id)} suggested={hierarchy.isSuggested(root.room_id, space.room_id)}
selected={selectedMap?.get(root.room_id)?.has(space.room_id)} selected={selectedMap?.get(root.room_id)?.has(space.room_id)}
onViewRoomClick={(autoJoin, roomType) => { onViewRoomClick={() => onViewRoomClick(space.room_id, RoomType.Space)}
onViewRoomClick(space.room_id, autoJoin, roomType); onJoinRoomClick={() => onJoinRoomClick(space.room_id)}
}}
hasPermissions={hasPermissions} hasPermissions={hasPermissions}
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined} onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined}
> >
@ -433,6 +459,7 @@ export const HierarchyLevel = ({
parents={newParents} parents={newParents}
selectedMap={selectedMap} selectedMap={selectedMap}
onViewRoomClick={onViewRoomClick} onViewRoomClick={onViewRoomClick}
onJoinRoomClick={onJoinRoomClick}
onToggleClick={onToggleClick} onToggleClick={onToggleClick}
/> />
</Tile> </Tile>
@ -696,9 +723,8 @@ const SpaceHierarchy = ({
parents={new Set()} parents={new Set()}
selectedMap={selected} selectedMap={selected}
onToggleClick={hasPermissions ? onToggleClick : undefined} onToggleClick={hasPermissions ? onToggleClick : undefined}
onViewRoomClick={(roomId, autoJoin, roomType) => { onViewRoomClick={(roomId, roomType) => showRoom(cli, hierarchy, roomId, roomType)}
showRoom(cli, hierarchy, roomId, autoJoin, roomType); onJoinRoomClick={(roomId) => joinRoom(cli, hierarchy, roomId)}
}}
/> />
</>; </>;
} else if (!hierarchy.canLoadMore) { } else if (!hierarchy.canLoadMore) {

View File

@ -55,7 +55,7 @@ import {
showSpaceInvite, showSpaceInvite,
showSpaceSettings, showSpaceSettings,
} from "../../utils/space"; } from "../../utils/space";
import SpaceHierarchy, { showRoom } from "./SpaceHierarchy"; import SpaceHierarchy, { joinRoom, showRoom } from "./SpaceHierarchy";
import MemberAvatar from "../views/avatars/MemberAvatar"; import MemberAvatar from "../views/avatars/MemberAvatar";
import SpaceStore from "../../stores/SpaceStore"; import SpaceStore from "../../stores/SpaceStore";
import FacePile from "../views/elements/FacePile"; import FacePile from "../views/elements/FacePile";
@ -508,7 +508,7 @@ const SpaceLanding = ({ space }: { space: Room }) => {
) } ) }
</RoomTopic> </RoomTopic>
<SpaceHierarchy space={space} showRoom={showRoom} additionalButtons={addRoomButton} /> <SpaceHierarchy space={space} showRoom={showRoom} joinRoom={joinRoom} additionalButtons={addRoomButton} />
</div>; </div>;
}; };

View File

@ -17,6 +17,7 @@ limitations under the License.
import React, { ComponentProps } from 'react'; import React, { ComponentProps } from 'react';
import { Room } from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials'; import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import classNames from "classnames"; import classNames from "classnames";
import BaseAvatar from './BaseAvatar'; import BaseAvatar from './BaseAvatar';
@ -83,8 +84,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
}; };
} }
// TODO: type when js-sdk has types private onRoomStateEvents = (ev: MatrixEvent) => {
private onRoomStateEvents = (ev: any) => {
if (!this.props.room || if (!this.props.room ||
ev.getRoomId() !== this.props.room.roomId || ev.getRoomId() !== this.props.room.roomId ||
ev.getType() !== 'm.room.avatar' ev.getType() !== 'm.room.avatar'

View File

@ -2930,6 +2930,7 @@
"Drop file here to upload": "Drop file here to upload", "Drop file here to upload": "Drop file here to upload",
"You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.",
"You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.",
"Joining": "Joining",
"You don't have permission": "You don't have permission", "You don't have permission": "You don't have permission",
"Joined": "Joined", "Joined": "Joined",
"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",

View File

@ -308,7 +308,7 @@ class RoomViewStore extends Store<ActionPayload> {
} }
} }
private getInvitingUserId(roomId: string): string { private static getInvitingUserId(roomId: string): string {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (room && room.getMyMembership() === "invite") { if (room && room.getMyMembership() === "invite") {
@ -318,12 +318,7 @@ class RoomViewStore extends Store<ActionPayload> {
} }
} }
private joinRoomError(payload: ActionPayload) { public showJoinRoomError(err: Error | MatrixError, roomId: string) {
this.setState({
joining: false,
joinError: payload.err,
});
const err = payload.err;
let msg = err.message ? err.message : JSON.stringify(err); let msg = err.message ? err.message : JSON.stringify(err);
logger.log("Failed to join room:", msg); logger.log("Failed to join room:", msg);
@ -335,7 +330,7 @@ class RoomViewStore extends Store<ActionPayload> {
{ _t("Please contact your homeserver administrator.") } { _t("Please contact your homeserver administrator.") }
</div>; </div>;
} else if (err.httpStatus === 404) { } else if (err.httpStatus === 404) {
const invitingUserId = this.getInvitingUserId(this.state.roomId); const invitingUserId = RoomViewStore.getInvitingUserId(roomId);
// only provide a better error message for invites // only provide a better error message for invites
if (invitingUserId) { if (invitingUserId) {
// if the inviting user is on the same HS, there can only be one cause: they left. // if the inviting user is on the same HS, there can only be one cause: they left.
@ -355,6 +350,14 @@ class RoomViewStore extends Store<ActionPayload> {
}); });
} }
private joinRoomError(payload: ActionPayload) {
this.setState({
joining: false,
joinError: payload.err,
});
this.showJoinRoomError(payload.err, this.state.roomId);
}
public reset() { public reset() {
this.state = Object.assign({}, INITIAL_STATE); this.state = Object.assign({}, INITIAL_STATE);
} }

View File

@ -25,6 +25,7 @@ import SpaceStore from "../stores/SpaceStore";
import Spinner from "../components/views/elements/Spinner"; import Spinner from "../components/views/elements/Spinner";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from "matrix-js-sdk/src/client";
interface IProgress { interface IProgress {
roomUpgraded: boolean; roomUpgraded: boolean;
@ -35,6 +36,23 @@ interface IProgress {
updateSpacesTotal: number; updateSpacesTotal: number;
} }
export async function awaitRoomDownSync(cli: MatrixClient, roomId: string): Promise<Room> {
const room = cli.getRoom(roomId);
if (room) return room; // already have the room
return new Promise<Room>(resolve => {
// We have to wait for the js-sdk to give us the room back so
// we can more effectively abuse the MultiInviter behaviour
// which heavily relies on the Room object being available.
const checkForRoomFn = (room: Room) => {
if (room.roomId !== roomId) return;
resolve(room);
cli.off("Room", checkForRoomFn);
};
cli.on("Room", checkForRoomFn);
});
}
export async function upgradeRoom( export async function upgradeRoom(
room: Room, room: Room,
targetVersion: string, targetVersion: string,
@ -93,24 +111,7 @@ export async function upgradeRoom(
progressCallback?.(progress); progressCallback?.(progress);
if (awaitRoom || inviteUsers) { if (awaitRoom || inviteUsers) {
await new Promise<void>(resolve => { await awaitRoomDownSync(room.client, newRoomId);
// already have the room
if (room.client.getRoom(newRoomId)) {
resolve();
return;
}
// We have to wait for the js-sdk to give us the room back so
// we can more effectively abuse the MultiInviter behaviour
// which heavily relies on the Room object being available.
const checkForRoomFn = (newRoom: Room) => {
if (newRoom.roomId !== newRoomId) return;
resolve();
cli.off("Room", checkForRoomFn);
};
cli.on("Room", checkForRoomFn);
});
progress.roomSynced = true; progress.roomSynced = true;
progressCallback?.(progress); progressCallback?.(progress);
} }