From 2483f1dc90af743b8a90f1613ca09865c6d50493 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 8 Oct 2021 12:20:43 +0100 Subject: [PATCH] Add progress bar to restricted room upgrade dialog --- .../dialogs/_RoomUpgradeWarningDialog.scss | 18 +++- .../dialogs/RoomUpgradeWarningDialog.tsx | 53 ++++++++-- .../views/settings/JoinRuleSettings.tsx | 96 ++++++++++++------- src/i18n/strings/en_EN.json | 10 +- src/utils/RoomUpgrade.ts | 81 ++++++++++++---- 5 files changed, 188 insertions(+), 70 deletions(-) diff --git a/res/css/views/dialogs/_RoomUpgradeWarningDialog.scss b/res/css/views/dialogs/_RoomUpgradeWarningDialog.scss index 5b9978eba00..941c8cb8079 100644 --- a/res/css/views/dialogs/_RoomUpgradeWarningDialog.scss +++ b/res/css/views/dialogs/_RoomUpgradeWarningDialog.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +17,22 @@ limitations under the License. .mx_RoomUpgradeWarningDialog { max-width: 38vw; width: 38vw; + + .mx_RoomUpgradeWarningDialog_progress { + .mx_ProgressBar { + height: 8px; + width: 100%; + + @mixin ProgressBarBorderRadius 8px; + } + + .mx_RoomUpgradeWarningDialog_progressText { + margin-top: 8px; + font-size: $font-15px; + line-height: $font-24px; + color: $primary-content; + } + } } .mx_RoomUpgradeWarningDialog .mx_SettingsFlag { diff --git a/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx b/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx index a90f417959b..35d27afe252 100644 --- a/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx +++ b/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx @@ -28,15 +28,25 @@ import { IDialogProps } from "./IDialogProps"; import BugReportDialog from './BugReportDialog'; import BaseDialog from "./BaseDialog"; import DialogButtons from "../elements/DialogButtons"; +import ProgressBar from "../elements/ProgressBar"; + +export interface IFinishedOpts { + continue: boolean; + invite: boolean; +} interface IProps extends IDialogProps { roomId: string; targetVersion: string; description?: ReactNode; + doUpgrade?(opts: IFinishedOpts, fn: (progressText: string, progress: number, total: number) => void): Promise; } interface IState { inviteUsersToNewRoom: boolean; + progressText?: string; + progress?: number; + total?: number; } @replaceableComponent("views.dialogs.RoomUpgradeWarningDialog") @@ -50,15 +60,30 @@ export default class RoomUpgradeWarningDialog extends React.Component { + this.setState({ progressText, progress, total }); + }; + private onContinue = () => { - this.props.onFinished({ continue: true, invite: this.isPrivate && this.state.inviteUsersToNewRoom }); + const opts = { + continue: true, + invite: this.isPrivate && this.state.inviteUsersToNewRoom, + }; + + if (this.props.doUpgrade) { + this.props.doUpgrade(opts, this.onProgressCallback).then(() => { + this.props.onFinished(opts); + }); + } else { + this.props.onFinished(opts); + } }; private onCancel = () => { @@ -118,6 +143,23 @@ export default class RoomUpgradeWarningDialog extends React.Component + +
+ { this.state.progressText } +
+ ; + } else { + footer = ; + } + return ( { inviteToggle } - + { footer } ); } diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index 76596103f57..2d622683e00 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -27,8 +27,7 @@ import SpaceStore from "../../../stores/SpaceStore"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import Modal from "../../../Modal"; import ManageRestrictedJoinRuleDialog from "../dialogs/ManageRestrictedJoinRuleDialog"; -import RoomUpgradeWarningDialog from "../dialogs/RoomUpgradeWarningDialog"; -import QuestionDialog from "../dialogs/QuestionDialog"; +import RoomUpgradeWarningDialog, { IFinishedOpts } from "../dialogs/RoomUpgradeWarningDialog"; import { upgradeRoom } from "../../../utils/RoomUpgrade"; import { arrayHasDiff } from "../../../utils/arrays"; import { useLocalEcho } from "../../../hooks/useLocalEcho"; @@ -210,47 +209,70 @@ const JoinRuleSettings = ({ room, promptUpgrade, onError, beforeChange, closeSet // Block this action on a room upgrade otherwise it'd make their room unjoinable const targetVersion = preferredRestrictionVersion; - const modal = Modal.createTrackedDialog('Restricted join rule upgrade', '', RoomUpgradeWarningDialog, { - roomId: room.roomId, - targetVersion, - description: _t("This upgrade will allow members of selected spaces " + - "access to this room without an invite."), - }); - - const [resp] = await modal.finished; - if (!resp?.continue) return; - + let warning: JSX.Element; const userId = cli.getUserId(); const unableToUpdateSomeParents = Array.from(SpaceStore.instance.getKnownParents(room.roomId)) .some(roomId => !cli.getRoom(roomId)?.currentState.maySendStateEvent(EventType.SpaceChild, userId)); if (unableToUpdateSomeParents) { - const modal = Modal.createTrackedDialog<[boolean]>('Parent relink warning', '', QuestionDialog, { - title: _t("Before you upgrade"), - description: ( -
{ _t("This room is in some spaces you’re not an admin of. " + - "In those spaces, the old room will still be shown, " + - "but people will be prompted to join the new one.") }
- ), - hasCancelButton: true, - button: _t("Upgrade anyway"), - danger: true, - }); - - const [shouldUpgrade] = await modal.finished; - if (!shouldUpgrade) return; + warning = + { _t("This room is in some spaces you’re not an admin of. " + + "In those spaces, the old room will still be shown, " + + "but people will be prompted to join the new one.") } + ; } - const roomId = await upgradeRoom(room, targetVersion, resp.invite, true, true, true); - closeSettingsFn(); - // switch to the new room in the background - dis.dispatch({ - action: "view_room", - room_id: roomId, - }); - // open new settings on this tab - dis.dispatch({ - action: "open_room_settings", - initial_tab_id: ROOM_SECURITY_TAB, + Modal.createTrackedDialog('Restricted join rule upgrade', '', RoomUpgradeWarningDialog, { + roomId: room.roomId, + targetVersion, + description: <> + { _t("This upgrade will allow members of selected spaces " + + "access to this room without an invite.") } + { warning } + , + doUpgrade: async ( + opts: IFinishedOpts, + fn: (progressText: string, progress: number, total: number) => void, + ): Promise => { + const roomId = await upgradeRoom( + room, + targetVersion, + opts.invite, + true, + true, + true, + progress => { + const total = 2 + progress.updateSpacesTotal + progress.inviteUsersTotal; + if (!progress.roomUpgraded) { + fn(_t("Upgrading room"), 0, total); + } else if (!progress.roomSynced) { + fn(_t("Loading new room"), 1, total); + } else if (progress.inviteUsersProgress < progress.inviteUsersTotal) { + fn(_t("Sending invites... (%(progress)s out of %(count)s)", { + progress: progress.inviteUsersProgress, + count: progress.inviteUsersTotal, + }), 2 + progress.inviteUsersProgress, total); + } else if (progress.updateSpacesProgress < progress.updateSpacesTotal) { + fn(_t("Updating spaces... (%(progress)s out of %(count)s)", { + progress: progress.updateSpacesProgress, + count: progress.updateSpacesTotal, + }), 2 + progress.inviteUsersProgress + progress.updateSpacesProgress, total); + } + }, + ); + closeSettingsFn(); + + // switch to the new room in the background + dis.dispatch({ + action: "view_room", + room_id: roomId, + }); + + // open new settings on this tab + dis.dispatch({ + action: "open_room_settings", + initial_tab_id: ROOM_SECURITY_TAB, + }); + }, }); return; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bdc86a9e083..b5a67ebe6af 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1166,10 +1166,14 @@ "Anyone in can find and join. You can select other spaces too.": "Anyone in can find and join. You can select other spaces too.", "Anyone in a space can find and join. You can select multiple spaces.": "Anyone in a space can find and join. You can select multiple spaces.", "Space members": "Space members", - "This upgrade will allow members of selected spaces access to this room without an invite.": "This upgrade will allow members of selected spaces access to this room without an invite.", - "Before you upgrade": "Before you upgrade", "This room is in some spaces you’re not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.": "This room is in some spaces you’re not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.", - "Upgrade anyway": "Upgrade anyway", + "This upgrade will allow members of selected spaces access to this room without an invite.": "This upgrade will allow members of selected spaces access to this room without an invite.", + "Upgrading room": "Upgrading room", + "Loading new room": "Loading new room", + "Sending invites... (%(progress)s out of %(count)s)|other": "Sending invites... (%(progress)s out of %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Sending invite...", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Updating spaces... (%(progress)s out of %(count)s)", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Updating space...", "Message layout": "Message layout", "IRC": "IRC", "Modern": "Modern", diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts index 366f49d892d..7e5115f53ea 100644 --- a/src/utils/RoomUpgrade.ts +++ b/src/utils/RoomUpgrade.ts @@ -18,12 +18,21 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { inviteUsersToRoom } from "../RoomInvite"; -import Modal from "../Modal"; +import Modal, { IHandle } from "../Modal"; import { _t } from "../languageHandler"; import ErrorDialog from "../components/views/dialogs/ErrorDialog"; import SpaceStore from "../stores/SpaceStore"; import Spinner from "../components/views/elements/Spinner"; +interface IProgress { + roomUpgraded: boolean; + roomSynced?: boolean; + inviteUsersProgress?: number; + inviteUsersTotal: number; + updateSpacesProgress?: number; + updateSpacesTotal: number; +} + export async function upgradeRoom( room: Room, targetVersion: string, @@ -31,9 +40,38 @@ export async function upgradeRoom( handleError = true, updateSpaces = true, awaitRoom = false, + progressCallback?: (progress: IProgress) => void, ): Promise { const cli = room.client; - const spinnerModal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); + let spinnerModal: IHandle; + if (!progressCallback) { + spinnerModal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); + } + + let toInvite: string[]; + if (inviteUsers) { + toInvite = [ + ...room.getMembersWithMembership("join"), + ...room.getMembersWithMembership("invite"), + ].map(m => m.userId).filter(m => m !== cli.getUserId()); + } + + let parentsToRelink: Room[]; + if (updateSpaces) { + parentsToRelink = Array.from(SpaceStore.instance.getKnownParents(room.roomId)) + .map(roomId => cli.getRoom(roomId)) + .filter(parent => parent?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId())); + } + + const progress: IProgress = { + roomUpgraded: false, + roomSynced: (awaitRoom || inviteUsers) ? false : undefined, + inviteUsersProgress: inviteUsers ? 0 : undefined, + inviteUsersTotal: toInvite.length, + updateSpacesProgress: updateSpaces ? 0 : undefined, + updateSpacesTotal: parentsToRelink.length, + }; + progressCallback?.(progress); let newRoomId: string; try { @@ -49,6 +87,9 @@ export async function upgradeRoom( throw e; } + progress.roomUpgraded = true; + progressCallback?.(progress); + if (awaitRoom || inviteUsers) { await new Promise(resolve => { // already have the room @@ -67,33 +108,31 @@ export async function upgradeRoom( }; cli.on("Room", checkForRoomFn); }); - } - if (inviteUsers) { - const toInvite = [ - ...room.getMembersWithMembership("join"), - ...room.getMembersWithMembership("invite"), - ].map(m => m.userId).filter(m => m !== cli.getUserId()); + progress.roomSynced = true; + progressCallback?.(progress); + } - if (toInvite.length > 0) { - // Errors are handled internally to this function - await inviteUsersToRoom(newRoomId, toInvite); - } + if (toInvite.length > 0) { + // Errors are handled internally to this function + await inviteUsersToRoom(newRoomId, toInvite, () => { + progress.inviteUsersProgress++; + progressCallback?.(progress); + }); } - if (updateSpaces) { - const parents = SpaceStore.instance.getKnownParents(room.roomId); + if (parentsToRelink.length > 0) { try { - for (const parentId of parents) { - const parent = cli.getRoom(parentId); - if (!parent?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId())) continue; - + for (const parent of parentsToRelink) { const currentEv = parent.currentState.getStateEvents(EventType.SpaceChild, room.roomId); - await cli.sendStateEvent(parentId, EventType.SpaceChild, { + await cli.sendStateEvent(parent.roomId, EventType.SpaceChild, { ...(currentEv?.getContent() || {}), // copy existing attributes like suggested via: [cli.getDomain()], }, newRoomId); - await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, room.roomId); + await cli.sendStateEvent(parent.roomId, EventType.SpaceChild, {}, room.roomId); + + progress.updateSpacesProgress++; + progressCallback?.(progress); } } catch (e) { // These errors are not critical to the room upgrade itself @@ -101,6 +140,6 @@ export async function upgradeRoom( } } - spinnerModal.close(); + spinnerModal?.close(); return newRoomId; }