diff --git a/src/domain/session/room/RoomBeingCreatedViewModel.js b/src/domain/session/room/RoomBeingCreatedViewModel.js index e9cb3616..f15838df 100644 --- a/src/domain/session/room/RoomBeingCreatedViewModel.js +++ b/src/domain/session/room/RoomBeingCreatedViewModel.js @@ -34,21 +34,15 @@ export class RoomBeingCreatedViewModel extends ViewModel { get name() { return this._roomBeingCreated.name; } get id() { return this._roomBeingCreated.id; } get isEncrypted() { return this._roomBeingCreated.isEncrypted; } - - get avatarLetter() { - return avatarInitials(this.name); - } - - get avatarColorNumber() { - return getIdentifierColorNumber(this._roomBeingCreated.avatarColorId); - } + get error() { return this._roomBeingCreated.error?.message; } + get avatarLetter() { return avatarInitials(this.name); } + get avatarColorNumber() { return getIdentifierColorNumber(this._roomBeingCreated.avatarColorId); } + get avatarTitle() { return this.name; } avatarUrl(size) { - return getAvatarHttpUrl(this._roomBeingCreated.avatarUrl, size, this.platform, this._mediaRepository); - } - - get avatarTitle() { - return this.name; + // allow blob url which doesn't need mxc => http resolution + return this._roomBeingCreated.avatarBlobUrl ?? + getAvatarHttpUrl(this._roomBeingCreated.avatarUrl, size, this.platform, this._mediaRepository); } focus() {} @@ -57,6 +51,12 @@ export class RoomBeingCreatedViewModel extends ViewModel { this.emitChange(); } + cancel() { + this._roomBeingCreated.cancel(); + // navigate away from the room + this.navigation.applyPath(this.navigation.path.until("session")); + } + dispose() { super.dispose(); this._roomBeingCreated.off("change", this._onRoomChange); diff --git a/src/matrix/room/create.ts b/src/matrix/room/create.ts index 1127c7ac..6a1fc947 100644 --- a/src/matrix/room/create.ts +++ b/src/matrix/room/create.ts @@ -92,6 +92,7 @@ export class RoomBeingCreated extends EventEmitter<{change: never}> { public readonly isEncrypted: boolean; private _calculatedName: string; private _error?: Error; + private _isCancelled = false; constructor( public readonly id: string, @@ -189,17 +190,25 @@ export class RoomBeingCreated extends EventEmitter<{change: never}> { this.emit("change"); } - get avatarUrl(): string | undefined { return this.profiles?.[0].avatarUrl; } get avatarColorId(): string { return this.options.invites?.[0] ?? this._roomId ?? this.id; } + get avatarUrl(): string | undefined { return this.profiles?.[0]?.avatarUrl; } get avatarBlobUrl(): string | undefined { return this.options.avatar?.blob?.url; } get roomId(): string | undefined { return this._roomId; } get name() { return this._calculatedName; } get isBeingCreated(): boolean { return true; } get error(): Error | undefined { return this._error; } - cancel() { - // TODO: remove from collection somehow - } + cancel() { + if (!this._isCancelled) { + this.dispose(); + this._isCancelled = true; + this.emitChange("isCancelled"); + } + } + // called from Session when updateCallback is invoked to remove it from the collection + get isCancelled() { return this._isCancelled; } + + /** @internal */ dispose() { if (this.options.avatar) { this.options.avatar.blob.dispose(); diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 449de0c6..87d82485 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -1095,10 +1095,17 @@ button.RoomDetailsView_row::after { gap: 12px; } -.CreateRoomView { - padding: 0 12px; - justify-self: center; +.CreateRoomView, .RoomBeingCreated_error { max-width: 400px; +} + +.RoomBeingCreated_error { + margin-top: 48px; +} + +.centered-column { + padding: 0 12px; + align-self: center; width: 100%; box-sizing: border-box; } diff --git a/src/platform/web/ui/session/CreateRoomView.js b/src/platform/web/ui/session/CreateRoomView.js index 6bdc32ca..c447710f 100644 --- a/src/platform/web/ui/session/CreateRoomView.js +++ b/src/platform/web/ui/session/CreateRoomView.js @@ -21,70 +21,72 @@ import {StaticView} from "../general/StaticView"; export class CreateRoomView extends TemplateView { render(t, vm) { - return t.main({className: "CreateRoomView middle"}, [ - t.h2("Create room"), - //t.div({className: "RoomView_error"}, vm => vm.error), - t.form({className: "CreateRoomView_detailsForm form", onChange: evt => this.onFormChange(evt), onSubmit: evt => this.onSubmit(evt)}, [ - t.div({className: "vertical-layout"}, [ - t.button({type: "button", className: "CreateRoomView_selectAvatar", onClick: () => vm.selectAvatar()}, - t.mapView(vm => vm.hasAvatar, hasAvatar => { - if (hasAvatar) { - return new AvatarView(vm, 64); - } else { - return new StaticView(undefined, t => { - return t.div({className: "CreateRoomView_selectAvatarPlaceholder"}) - }); - } - }) - ), - t.div({className: "stretch form-row text"}, [ - t.label({for: "name"}, vm.i18n`Room name`), + return t.main({className: "middle"}, + t.div({className: "CreateRoomView centered-column"}, [ + t.h2("Create room"), + //t.div({className: "RoomView_error"}, vm => vm.error), + t.form({className: "CreateRoomView_detailsForm form", onChange: evt => this.onFormChange(evt), onSubmit: evt => this.onSubmit(evt)}, [ + t.div({className: "vertical-layout"}, [ + t.button({type: "button", className: "CreateRoomView_selectAvatar", onClick: () => vm.selectAvatar()}, + t.mapView(vm => vm.hasAvatar, hasAvatar => { + if (hasAvatar) { + return new AvatarView(vm, 64); + } else { + return new StaticView(undefined, t => { + return t.div({className: "CreateRoomView_selectAvatarPlaceholder"}) + }); + } + }) + ), + t.div({className: "stretch form-row text"}, [ + t.label({for: "name"}, vm.i18n`Room name`), + t.input({ + onInput: evt => vm.setName(evt.target.value), + type: "text", name: "name", id: "name", + placeholder: vm.i18n`Enter a room name` + }, vm => vm.name), + ]), + ]), + t.div({className: "form-row text"}, [ + t.label({for: "topic"}, vm.i18n`Topic (optional)`), + t.textarea({ + onInput: evt => vm.setTopic(evt.target.value), + name: "topic", id: "topic", + placeholder: vm.i18n`Topic` + }), + ]), + t.div({className: "form-group"}, [ + t.div({className: "form-row check"}, [ + t.input({type: "radio", name: "isPublic", id: "isPrivate", value: "false", checked: !vm.isPublic}), + t.label({for: "isPrivate"}, vm.i18n`Private room, only upon invitation.`) + ]), + t.div({className: "form-row check"}, [ + t.input({type: "radio", name: "isPublic", id: "isPublic", value: "true", checked: vm.isPublic}), + t.label({for: "isPublic"}, vm.i18n`Public room, anyone can join`) + ]), + ]), + t.div({className: {"form-row check": true, hidden: vm => vm.isPublic}}, [ + t.input({type: "checkbox", name: "isEncrypted", id: "isEncrypted", checked: vm.isEncrypted}), + t.label({for: "isEncrypted"}, vm.i18n`Enable end-to-end encryption`) + ]), + t.div({className: {"form-row text": true, hidden: vm => !vm.isPublic}}, [ + t.label({for: "roomAlias"}, vm.i18n`Room alias`), t.input({ - onInput: evt => vm.setName(evt.target.value), - type: "text", name: "name", id: "name", - placeholder: vm.i18n`Enter a room name` - }, vm => vm.name), + onInput: evt => vm.setRoomAlias(evt.target.value), + type: "text", name: "roomAlias", id: "roomAlias", + placeholder: vm.i18n`Room alias + `}), ]), - ]), - t.div({className: "form-row text"}, [ - t.label({for: "topic"}, vm.i18n`Topic (optional)`), - t.textarea({ - onInput: evt => vm.setTopic(evt.target.value), - name: "topic", id: "topic", - placeholder: vm.i18n`Topic` - }), - ]), - t.div({className: "form-group"}, [ - t.div({className: "form-row check"}, [ - t.input({type: "radio", name: "isPublic", id: "isPrivate", value: "false", checked: !vm.isPublic}), - t.label({for: "isPrivate"}, vm.i18n`Private room, only upon invitation.`) + t.div({className: "button-row"}, [ + t.button({ + className: "button-action primary", + type: "submit", + disabled: vm => !vm.canCreate + }, vm.i18n`Create room`), ]), - t.div({className: "form-row check"}, [ - t.input({type: "radio", name: "isPublic", id: "isPublic", value: "true", checked: vm.isPublic}), - t.label({for: "isPublic"}, vm.i18n`Public room, anyone can join`) - ]), - ]), - t.div({className: {"form-row check": true, hidden: vm => vm.isPublic}}, [ - t.input({type: "checkbox", name: "isEncrypted", id: "isEncrypted", checked: vm.isEncrypted}), - t.label({for: "isEncrypted"}, vm.i18n`Enable end-to-end encryption`) - ]), - t.div({className: {"form-row text": true, hidden: vm => !vm.isPublic}}, [ - t.label({for: "roomAlias"}, vm.i18n`Room alias`), - t.input({ - onInput: evt => vm.setRoomAlias(evt.target.value), - type: "text", name: "roomAlias", id: "roomAlias", - placeholder: vm.i18n`Room alias - `}), - ]), - t.div({className: "button-row"}, [ - t.button({ - className: "button-action primary", - type: "submit", - disabled: vm => !vm.canCreate - }, vm.i18n`Create room`), - ]), + ]) ]) - ]); + ); } onFormChange(evt) { diff --git a/src/platform/web/ui/session/room/RoomBeingCreatedView.js b/src/platform/web/ui/session/room/RoomBeingCreatedView.js index df26a43d..1da44d34 100644 --- a/src/platform/web/ui/session/room/RoomBeingCreatedView.js +++ b/src/platform/web/ui/session/room/RoomBeingCreatedView.js @@ -16,10 +16,43 @@ limitations under the License. */ import {TemplateView} from "../../general/TemplateView"; +import {LoadingView} from "../../general/LoadingView"; +import {AvatarView} from "../../AvatarView"; import {renderStaticAvatar} from "../../avatar.js"; export class RoomBeingCreatedView extends TemplateView { render(t, vm) { - return t.h1({className: "middle"}, ["creating room", vm => vm.name]); + return t.main({className: "RoomView middle"}, [ + t.div({className: "RoomHeader middle-header"}, [ + t.a({className: "button-utility close-middle", href: vm.closeUrl, title: vm.i18n`Close room`}), + t.view(new AvatarView(vm, 32)), + t.div({className: "room-description"}, [ + t.h2(vm => vm.name), + ]) + ]), + t.div({className: "RoomView_body"}, [ + t.mapView(vm => vm.error, error => { + if (error) { + return new ErrorView(vm); + } else { + return new LoadingView(vm.i18n`Setting up the room…`); + } + }) + ]) + ]); + } +} + +class ErrorView extends TemplateView { + render(t,vm) { + return t.div({className: "RoomBeingCreated_error centered-column"}, [ + t.h3(vm.i18n`Could not create the room, something went wrong:`), + t.div({className: "RoomView_error form-group"}, vm.error), + t.div({className: "button-row"}, + t.button({ + className: "button-action primary destructive", + onClick: () => vm.cancel() + }, vm.i18n`Cancel`)) + ]); } }