From 27e16362b6eb1cdfb3a42145710293d2c3fbd680 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 27 Oct 2021 15:24:31 +0100 Subject: [PATCH] Make join button on space hierarchy action in the background (#7041) --- res/css/structures/_SpaceHierarchy.scss | 5 ++ src/components/structures/SpaceHierarchy.tsx | 88 +++++++++++++------- src/components/structures/SpaceRoomView.tsx | 4 +- src/components/views/avatars/RoomAvatar.tsx | 4 +- src/i18n/strings/en_EN.json | 1 + src/stores/RoomViewStore.tsx | 19 +++-- src/utils/RoomUpgrade.ts | 37 ++++---- 7 files changed, 97 insertions(+), 61 deletions(-) diff --git a/res/css/structures/_SpaceHierarchy.scss b/res/css/structures/_SpaceHierarchy.scss index fc7cbf4496..5735ef016d 100644 --- a/res/css/structures/_SpaceHierarchy.scss +++ b/res/css/structures/_SpaceHierarchy.scss @@ -288,6 +288,11 @@ limitations under the License. visibility: visible; } } + + &.mx_SpaceHierarchy_joining .mx_AccessibleButton { + visibility: visible; + padding: 4px 18px; + } } li.mx_SpaceHierarchy_roomTileWrapper { diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index e97ba54a83..698f24d659 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -60,18 +60,15 @@ import { getDisplayAliasForRoom } from "./RoomDirectory"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import { useEventEmitterState } from "../../hooks/useEventEmitter"; import { IOOBData } from "../../stores/ThreepidInviteStore"; +import { awaitRoomDownSync } from "../../utils/RoomUpgrade"; +import RoomViewStore from "../../stores/RoomViewStore"; interface IProps { space: Room; initialText?: string; additionalButtons?: ReactNode; - showRoom( - cli: MatrixClient, - hierarchy: RoomHierarchy, - roomId: string, - autoJoin?: boolean, - roomType?: RoomType, - ): void; + showRoom(cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string, roomType?: RoomType): void; + joinRoom(cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string): void; } interface ITileProps { @@ -80,7 +77,8 @@ interface ITileProps { selected?: boolean; numChildRooms?: number; hasPermissions?: boolean; - onViewRoomClick(autoJoin: boolean, roomType: RoomType): void; + onViewRoomClick(): void; + onJoinRoomClick(): void; onToggleClick?(): void; } @@ -91,31 +89,50 @@ const Tile: React.FC = ({ hasPermissions, onToggleClick, onViewRoomClick, + onJoinRoomClick, numChildRooms, children, }) => { const cli = useContext(MatrixClientContext); - const joinedRoom = cli.getRoom(room.room_id)?.getMyMembership() === "join" ? cli.getRoom(room.room_id) : null; + const [joinedRoom, setJoinedRoom] = useState(() => { + const cliRoom = cli.getRoom(room.room_id); + return cliRoom?.getMyMembership() === "join" ? cliRoom : null; + }); const joinedRoomName = useEventEmitterState(joinedRoom, "Room.name", room => room?.name); const name = joinedRoomName || room.name || room.canonical_alias || room.aliases?.[0] || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room")); const [showChildren, toggleShowChildren] = useStateToggle(true); const [onFocus, isActive, ref] = useRovingTabIndex(); + const [busy, setBusy] = useState(false); const onPreviewClick = (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); - onViewRoomClick(false, room.room_type as RoomType); + onViewRoomClick(); }; - const onJoinClick = (ev: ButtonEvent) => { + const onJoinClick = async (ev: ButtonEvent) => { + setBusy(true); ev.preventDefault(); ev.stopPropagation(); - onViewRoomClick(true, room.room_type as RoomType); + onJoinRoomClick(); + setJoinedRoom(await awaitRoomDownSync(cli, room.room_id)); + setBusy(false); }; let button; - if (joinedRoom) { + if (busy) { + button = + + ; + } else if (joinedRoom) { button = = ({ = ({ ; }; -export const showRoom = ( - cli: MatrixClient, - hierarchy: RoomHierarchy, - roomId: string, - autoJoin = false, - roomType?: RoomType, -) => { +export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string, roomType?: RoomType): void => { const room = hierarchy.roomMap.get(roomId); // 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; dis.dispatch({ action: "view_room", - auto_join: autoJoin, should_peek: true, _type: "room_directory", // instrumentation 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 { root: IHierarchyRoom; roomSet: Set; hierarchy: RoomHierarchy; parents: Set; selectedMap?: Map>; - onViewRoomClick(roomId: string, autoJoin: boolean, roomType?: RoomType): void; + onViewRoomClick(roomId: string, roomType?: RoomType): void; + onJoinRoomClick(roomId: string): void; onToggleClick?(parentId: string, childId: string): void; } @@ -373,6 +400,7 @@ export const HierarchyLevel = ({ parents, selectedMap, onViewRoomClick, + onJoinRoomClick, onToggleClick, }: IHierarchyLevelProps) => { const cli = useContext(MatrixClientContext); @@ -400,9 +428,8 @@ export const HierarchyLevel = ({ room={room} suggested={hierarchy.isSuggested(root.room_id, room.room_id)} selected={selectedMap?.get(root.room_id)?.has(room.room_id)} - onViewRoomClick={(autoJoin, roomType) => { - onViewRoomClick(room.room_id, autoJoin, roomType); - }} + onViewRoomClick={() => onViewRoomClick(room.room_id, room.room_type as RoomType)} + onJoinRoomClick={() => onJoinRoomClick(room.room_id)} hasPermissions={hasPermissions} onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined} /> @@ -420,9 +447,8 @@ export const HierarchyLevel = ({ }).length} suggested={hierarchy.isSuggested(root.room_id, space.room_id)} selected={selectedMap?.get(root.room_id)?.has(space.room_id)} - onViewRoomClick={(autoJoin, roomType) => { - onViewRoomClick(space.room_id, autoJoin, roomType); - }} + onViewRoomClick={() => onViewRoomClick(space.room_id, RoomType.Space)} + onJoinRoomClick={() => onJoinRoomClick(space.room_id)} hasPermissions={hasPermissions} onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined} > @@ -433,6 +459,7 @@ export const HierarchyLevel = ({ parents={newParents} selectedMap={selectedMap} onViewRoomClick={onViewRoomClick} + onJoinRoomClick={onJoinRoomClick} onToggleClick={onToggleClick} /> @@ -696,9 +723,8 @@ const SpaceHierarchy = ({ parents={new Set()} selectedMap={selected} onToggleClick={hasPermissions ? onToggleClick : undefined} - onViewRoomClick={(roomId, autoJoin, roomType) => { - showRoom(cli, hierarchy, roomId, autoJoin, roomType); - }} + onViewRoomClick={(roomId, roomType) => showRoom(cli, hierarchy, roomId, roomType)} + onJoinRoomClick={(roomId) => joinRoom(cli, hierarchy, roomId)} /> ; } else if (!hierarchy.canLoadMore) { diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 2cdf0a7051..25128dd4f0 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -55,7 +55,7 @@ import { showSpaceInvite, showSpaceSettings, } from "../../utils/space"; -import SpaceHierarchy, { showRoom } from "./SpaceHierarchy"; +import SpaceHierarchy, { joinRoom, showRoom } from "./SpaceHierarchy"; import MemberAvatar from "../views/avatars/MemberAvatar"; import SpaceStore from "../../stores/SpaceStore"; import FacePile from "../views/elements/FacePile"; @@ -508,7 +508,7 @@ const SpaceLanding = ({ space }: { space: Room }) => { ) } - + ; }; diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index f285222f7b..5955c44bc3 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -17,6 +17,7 @@ limitations under the License. import React, { ComponentProps } from 'react'; import { Room } from 'matrix-js-sdk/src/models/room'; import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials'; +import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import classNames from "classnames"; import BaseAvatar from './BaseAvatar'; @@ -83,8 +84,7 @@ export default class RoomAvatar extends React.Component { }; } - // TODO: type when js-sdk has types - private onRoomStateEvents = (ev: any) => { + private onRoomStateEvents = (ev: MatrixEvent) => { if (!this.props.room || ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'm.room.avatar' diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0596f61b44..1fffc04696 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2930,6 +2930,7 @@ "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.|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", "Joined": "Joined", "This room is suggested as a good one to join": "This room is suggested as a good one to join", diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index 1a44b4fb32..edcb0aeff3 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -308,7 +308,7 @@ class RoomViewStore extends Store { } } - private getInvitingUserId(roomId: string): string { + private static getInvitingUserId(roomId: string): string { const cli = MatrixClientPeg.get(); const room = cli.getRoom(roomId); if (room && room.getMyMembership() === "invite") { @@ -318,12 +318,7 @@ class RoomViewStore extends Store { } } - private joinRoomError(payload: ActionPayload) { - this.setState({ - joining: false, - joinError: payload.err, - }); - const err = payload.err; + public showJoinRoomError(err: Error | MatrixError, roomId: string) { let msg = err.message ? err.message : JSON.stringify(err); logger.log("Failed to join room:", msg); @@ -335,7 +330,7 @@ class RoomViewStore extends Store { { _t("Please contact your homeserver administrator.") } ; } 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 if (invitingUserId) { // 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 { }); } + private joinRoomError(payload: ActionPayload) { + this.setState({ + joining: false, + joinError: payload.err, + }); + this.showJoinRoomError(payload.err, this.state.roomId); + } + public reset() { this.state = Object.assign({}, INITIAL_STATE); } diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts index ba3fb08c9e..b9ea93d7fc 100644 --- a/src/utils/RoomUpgrade.ts +++ b/src/utils/RoomUpgrade.ts @@ -25,6 +25,7 @@ import SpaceStore from "../stores/SpaceStore"; import Spinner from "../components/views/elements/Spinner"; import { logger } from "matrix-js-sdk/src/logger"; +import { MatrixClient } from "matrix-js-sdk/src/client"; interface IProgress { roomUpgraded: boolean; @@ -35,6 +36,23 @@ interface IProgress { updateSpacesTotal: number; } +export async function awaitRoomDownSync(cli: MatrixClient, roomId: string): Promise { + const room = cli.getRoom(roomId); + if (room) return room; // already have the room + + return new Promise(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( room: Room, targetVersion: string, @@ -93,24 +111,7 @@ export async function upgradeRoom( progressCallback?.(progress); if (awaitRoom || inviteUsers) { - await new Promise(resolve => { - // 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); - }); - + await awaitRoomDownSync(room.client, newRoomId); progress.roomSynced = true; progressCallback?.(progress); }