Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 7cae566

Browse files
authored
Merge pull request #5975 from matrix-org/t3chguy/fix/16891
Add retry mechanism and progress bar to add existing to space dialog
2 parents 4bc5990 + 886959f commit 7cae566

File tree

5 files changed

+181
-68
lines changed

5 files changed

+181
-68
lines changed

res/css/views/dialogs/_AddExistingToSpaceDialog.scss

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ limitations under the License.
101101

102102
.mx_BaseAvatar {
103103
display: inline-flex;
104-
margin: 5px 16px 5px 5px;
104+
margin: auto 16px auto 5px;
105105
vertical-align: middle;
106106
}
107107

@@ -160,40 +160,87 @@ limitations under the License.
160160
}
161161
}
162162

163-
.mx_AddExistingToSpaceDialog_errorText {
164-
font-weight: $font-semi-bold;
165-
font-size: $font-12px;
166-
line-height: $font-15px;
167-
color: $notice-primary-color;
168-
margin-bottom: 28px;
169-
}
170-
171163
.mx_AddExistingToSpace {
172164
display: contents;
173165
}
174166

175167
.mx_AddExistingToSpaceDialog_footer {
176168
display: flex;
177-
margin-top: 32px;
169+
margin-top: 20px;
178170

179171
> span {
180172
flex-grow: 1;
181-
font-size: $font-14px;
173+
font-size: $font-12px;
182174
line-height: $font-15px;
183-
font-weight: $font-semi-bold;
175+
color: $secondary-fg-color;
176+
177+
.mx_ProgressBar {
178+
height: 8px;
179+
width: 100%;
184180

185-
.mx_AccessibleButton {
186-
font-size: inherit;
187-
display: inline-block;
181+
@mixin ProgressBarBorderRadius 8px;
182+
}
183+
184+
.mx_AddExistingToSpaceDialog_progressText {
185+
margin-top: 8px;
186+
font-size: $font-15px;
187+
line-height: $font-24px;
188+
color: $primary-fg-color;
188189
}
189190

190191
> * {
191192
vertical-align: middle;
192193
}
193194
}
194195

196+
.mx_AddExistingToSpaceDialog_error {
197+
padding-left: 12px;
198+
199+
> img {
200+
align-self: center;
201+
}
202+
203+
.mx_AddExistingToSpaceDialog_errorHeading {
204+
font-weight: $font-semi-bold;
205+
font-size: $font-15px;
206+
line-height: $font-18px;
207+
color: $notice-primary-color;
208+
}
209+
210+
.mx_AddExistingToSpaceDialog_errorCaption {
211+
margin-top: 4px;
212+
font-size: $font-12px;
213+
line-height: $font-15px;
214+
color: $primary-fg-color;
215+
}
216+
}
217+
195218
.mx_AccessibleButton {
196219
display: inline-block;
220+
align-self: center;
221+
}
222+
223+
.mx_AccessibleButton_kind_primary {
224+
padding: 8px 36px;
225+
}
226+
227+
.mx_AddExistingToSpaceDialog_retryButton {
228+
margin-left: 12px;
229+
padding-left: 24px;
230+
position: relative;
231+
232+
&::before {
233+
content: '';
234+
position: absolute;
235+
background-color: $primary-fg-color;
236+
mask-repeat: no-repeat;
237+
mask-position: center;
238+
mask-size: contain;
239+
mask-image: url('$(res)/img/element-icons/retry.svg');
240+
width: 18px;
241+
height: 18px;
242+
left: 0;
243+
}
197244
}
198245

199246
.mx_AccessibleButton_kind_link {

res/css/views/elements/_ProgressBar.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ progress.mx_ProgressBar {
2121
appearance: none;
2222
border: none;
2323

24-
@mixin ProgressBarBorderRadius "6px";
24+
@mixin ProgressBarBorderRadius 6px;
2525
@mixin ProgressBarColour $progressbar-fg-color;
2626
@mixin ProgressBarBgColour $progressbar-bg-color;
2727
::-webkit-progress-value {

src/components/structures/SpaceRoomView.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import {useStateToggle} from "../../hooks/useStateToggle";
5252
import SpaceStore from "../../stores/SpaceStore";
5353
import FacePile from "../views/elements/FacePile";
5454
import {AddExistingToSpace} from "../views/dialogs/AddExistingToSpaceDialog";
55-
import {allSettled} from "../../utils/promise";
55+
import {sleep} from "../../utils/promise";
5656
import {calculateRoomVia} from "../../utils/permalinks/Permalinks";
5757

5858
interface IProps {
@@ -389,15 +389,24 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
389389
let buttonLabel = _t("Skip for now");
390390
if (selectedToAdd.size > 0) {
391391
onClick = async () => {
392-
// TODO rate limiting
393392
setBusy(true);
394-
try {
395-
await allSettled(Array.from(selectedToAdd).map((room) =>
396-
SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room))));
397-
onFinished(true);
398-
} catch (e) {
399-
console.error("Failed to add rooms to space", e);
400-
setError(_t("Failed to add rooms to space"));
393+
394+
for (const room of selectedToAdd) {
395+
const via = calculateRoomVia(room);
396+
try {
397+
await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => {
398+
if (e.errcode === "M_LIMIT_EXCEEDED") {
399+
await sleep(e.data.retry_after_ms);
400+
return SpaceStore.instance.addRoomToSpace(space, room.roomId, via); // retry
401+
}
402+
403+
throw e;
404+
});
405+
} catch (e) {
406+
console.error("Failed to add rooms to space", e);
407+
setError(_t("Failed to add rooms to space"));
408+
break;
409+
}
401410
}
402411
setBusy(false);
403412
};

src/components/views/dialogs/AddExistingToSpaceDialog.tsx

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ import RoomAvatar from "../avatars/RoomAvatar";
2929
import {getDisplayAliasForRoom} from "../../../Rooms";
3030
import AccessibleButton from "../elements/AccessibleButton";
3131
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
32-
import {allSettled} from "../../../utils/promise";
32+
import {sleep} from "../../../utils/promise";
3333
import DMRoomMap from "../../../utils/DMRoomMap";
3434
import {calculateRoomVia} from "../../../utils/permalinks/Permalinks";
3535
import StyledCheckbox from "../elements/StyledCheckbox";
3636
import MatrixClientContext from "../../../contexts/MatrixClientContext";
3737
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
38+
import ProgressBar from "../elements/ProgressBar";
3839

3940
interface IProps extends IDialogProps {
4041
matrixClient: MatrixClient;
@@ -46,7 +47,11 @@ const Entry = ({ room, checked, onChange }) => {
4647
return <label className="mx_AddExistingToSpace_entry">
4748
<RoomAvatar room={room} height={32} width={32} />
4849
<span className="mx_AddExistingToSpace_entry_name">{ room.name }</span>
49-
<StyledCheckbox onChange={(e) => onChange(e.target.checked)} checked={checked} />
50+
<StyledCheckbox
51+
onChange={onChange ? (e) => onChange(e.target.checked) : null}
52+
checked={checked}
53+
disabled={!onChange}
54+
/>
5055
</label>;
5156
};
5257

@@ -104,9 +109,9 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
104109
key={room.roomId}
105110
room={room}
106111
checked={selected.has(room)}
107-
onChange={(checked) => {
112+
onChange={onChange ? (checked) => {
108113
onChange(checked, room);
109-
}}
114+
} : null}
110115
/>;
111116
}) }
112117
</div>
@@ -120,9 +125,9 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
120125
key={space.roomId}
121126
room={space}
122127
checked={selected.has(space)}
123-
onChange={(checked) => {
128+
onChange={onChange ? (checked) => {
124129
onChange(checked, space);
125-
}}
130+
} : null}
126131
/>;
127132
}) }
128133
</div>
@@ -136,9 +141,9 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
136141
key={room.roomId}
137142
room={room}
138143
checked={selected.has(room)}
139-
onChange={(checked) => {
144+
onChange={onChange ? (checked) => {
140145
onChange(checked, room);
141-
}}
146+
} : null}
142147
/>;
143148
}) }
144149
</div>
@@ -156,8 +161,8 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
156161
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
157162
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
158163

159-
const [busy, setBusy] = useState(false);
160-
const [error, setError] = useState("");
164+
const [progress, setProgress] = useState<number>(null);
165+
const [error, setError] = useState<Error>(null);
161166

162167
let spaceOptionSection;
163168
if (existingSubspaces.length > 0) {
@@ -197,57 +202,106 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
197202
</div>
198203
</React.Fragment>;
199204

205+
const addRooms = async () => {
206+
setError(null);
207+
setProgress(0);
208+
209+
let error;
210+
211+
for (const room of selectedToAdd) {
212+
const via = calculateRoomVia(room);
213+
try {
214+
await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => {
215+
if (e.errcode === "M_LIMIT_EXCEEDED") {
216+
await sleep(e.data.retry_after_ms);
217+
return SpaceStore.instance.addRoomToSpace(space, room.roomId, via); // retry
218+
}
219+
220+
throw e;
221+
});
222+
setProgress(i => i + 1);
223+
} catch (e) {
224+
console.error("Failed to add rooms to space", e);
225+
setError(error = e);
226+
break;
227+
}
228+
}
229+
230+
if (!error) {
231+
onFinished(true);
232+
}
233+
};
234+
235+
const busy = progress !== null;
236+
237+
let footer;
238+
if (error) {
239+
footer = <>
240+
<img
241+
src={require("../../../../res/img/element-icons/warning-badge.svg")}
242+
height="24"
243+
width="24"
244+
alt=""
245+
/>
246+
247+
<span className="mx_AddExistingToSpaceDialog_error">
248+
<div className="mx_AddExistingToSpaceDialog_errorHeading">{ _t("Not all selected were added") }</div>
249+
<div className="mx_AddExistingToSpaceDialog_errorCaption">{ _t("Try again") }</div>
250+
</span>
251+
252+
<AccessibleButton className="mx_AddExistingToSpaceDialog_retryButton" onClick={addRooms}>
253+
{ _t("Retry") }
254+
</AccessibleButton>
255+
</>;
256+
} else if (busy) {
257+
footer = <span>
258+
<ProgressBar value={progress} max={selectedToAdd.size} />
259+
<div className="mx_AddExistingToSpaceDialog_progressText">
260+
{ _t("Adding rooms... (%(progress)s out of %(count)s)", {
261+
count: selectedToAdd.size,
262+
progress,
263+
}) }
264+
</div>
265+
</span>;
266+
} else {
267+
footer = <>
268+
<span>
269+
<div>{ _t("Want to add a new room instead?") }</div>
270+
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
271+
{ _t("Create a new room") }
272+
</AccessibleButton>
273+
</span>
274+
275+
<AccessibleButton kind="primary" disabled={selectedToAdd.size < 1} onClick={addRooms}>
276+
{ _t("Add") }
277+
</AccessibleButton>
278+
</>;
279+
}
280+
200281
return <BaseDialog
201282
title={title}
202283
className="mx_AddExistingToSpaceDialog"
203284
contentId="mx_AddExistingToSpace"
204285
onFinished={onFinished}
205286
fixedWidth={false}
206287
>
207-
{ error && <div className="mx_AddExistingToSpaceDialog_errorText">{ error }</div> }
208-
209288
<MatrixClientContext.Provider value={cli}>
210289
<AddExistingToSpace
211290
space={space}
212291
selected={selectedToAdd}
213-
onChange={(checked, room) => {
292+
onChange={!busy && !error ? (checked, room) => {
214293
if (checked) {
215294
selectedToAdd.add(room);
216295
} else {
217296
selectedToAdd.delete(room);
218297
}
219298
setSelectedToAdd(new Set(selectedToAdd));
220-
}}
299+
} : null}
221300
/>
222301
</MatrixClientContext.Provider>
223302

224303
<div className="mx_AddExistingToSpaceDialog_footer">
225-
<span>
226-
<div>{ _t("Don't want to add an existing room?") }</div>
227-
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
228-
{ _t("Create a new room") }
229-
</AccessibleButton>
230-
</span>
231-
232-
<AccessibleButton
233-
kind="primary"
234-
disabled={busy || selectedToAdd.size < 1}
235-
onClick={async () => {
236-
// TODO rate limiting
237-
setBusy(true);
238-
try {
239-
await allSettled(Array.from(selectedToAdd).map((room) =>
240-
SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room))));
241-
onFinished(true);
242-
} catch (e) {
243-
console.error("Failed to add rooms to space", e);
244-
setError(_t("Failed to add rooms to space"));
245-
}
246-
setBusy(false);
247-
}}
248-
>
249-
{ busy ? _t("Adding...") : _t("Add") }
250-
</AccessibleButton>
304+
{ footer }
251305
</div>
252306
</BaseDialog>;
253307
};

src/i18n/strings/en_EN.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,10 +2034,11 @@
20342034
"Direct Messages": "Direct Messages",
20352035
"Space selection": "Space selection",
20362036
"Add existing rooms": "Add existing rooms",
2037-
"Don't want to add an existing room?": "Don't want to add an existing room?",
2037+
"Not all selected were added": "Not all selected were added",
2038+
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
2039+
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
2040+
"Want to add a new room instead?": "Want to add a new room instead?",
20382041
"Create a new room": "Create a new room",
2039-
"Failed to add rooms to space": "Failed to add rooms to space",
2040-
"Adding...": "Adding...",
20412042
"Matrix ID": "Matrix ID",
20422043
"Matrix Room ID": "Matrix Room ID",
20432044
"email address": "email address",
@@ -2675,6 +2676,8 @@
26752676
"Failed to create initial space rooms": "Failed to create initial space rooms",
26762677
"Skip for now": "Skip for now",
26772678
"Creating rooms...": "Creating rooms...",
2679+
"Failed to add rooms to space": "Failed to add rooms to space",
2680+
"Adding...": "Adding...",
26782681
"What do you want to organise?": "What do you want to organise?",
26792682
"Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.",
26802683
"Share %(name)s": "Share %(name)s",

0 commit comments

Comments
 (0)