diff --git a/src/ui/web/general/TemplateView.js b/src/ui/web/general/TemplateView.js index 22bb7e43..31043e54 100644 --- a/src/ui/web/general/TemplateView.js +++ b/src/ui/web/general/TemplateView.js @@ -35,6 +35,10 @@ export class TemplateView { this._boundUpdateFromValue = null; } + get value() { + return this._value; + } + _subscribe() { this._boundUpdateFromValue = this._updateFromValue.bind(this); @@ -94,8 +98,10 @@ export class TemplateView { unmount() { this._detach(); this._unsubscribe(); - for (const v of this._subViews) { - v.unmount(); + if (this._subViews) { + for (const v of this._subViews) { + v.unmount(); + } } } diff --git a/src/ui/web/general/html.js b/src/ui/web/general/html.js index 9bea640f..24f34ff4 100644 --- a/src/ui/web/general/html.js +++ b/src/ui/web/general/html.js @@ -70,7 +70,7 @@ export function text(str) { export const TAG_NAMES = [ "a", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", "strong", "em", "span", "img", "section", "main", "article", "aside", - "pre", "button", "time", "input", "textarea"]; + "pre", "button", "time", "input", "textarea", "svg", "circle"]; export const tag = {}; diff --git a/src/ui/web/login/LoginView.js b/src/ui/web/login/LoginView.js index ca8ea945..6ba6459c 100644 --- a/src/ui/web/login/LoginView.js +++ b/src/ui/web/login/LoginView.js @@ -2,15 +2,11 @@ import {TemplateView} from "../general/TemplateView.js"; import {brawlGithubLink} from "./common.js"; export class LoginView extends TemplateView { - constructor(vm) { - super(vm, true); - } - render(t, vm) { - const disabled = vm => vm.loading; const username = t.input({type: "text", placeholder: vm.usernamePlaceholder, disabled}); const password = t.input({type: "password", placeholder: vm.passwordPlaceholder, disabled}); const homeserver = t.input({type: "text", placeholder: vm.hsPlaceholder, value: vm.defaultHomeServer, disabled}); + const disabled = vm => !!vm.loadViewModel; return t.div({className: "LoginView form"}, [ t.h1(["Log in to your homeserver"]), t.if(vm => vm.error, t.createTemplate(t => t.div({className: "error"}, vm => vm.error))), @@ -28,10 +24,17 @@ export class LoginView extends TemplateView { } } -function renderLoadProgress(t) { - return t.div({className: "loadProgress"}, [ - t.div({className: "spinner"}), - t.p(vm => vm.loadLabel), - t.if(vm => vm.loading, t.createTemplate(t => t.button({onClick: vm => vm.cancel()}, "Cancel login"))) - ]); +function spinner(t, extraClasses = undefined) { + return t.svg({className: Object.assign({"spinner": true}, extraClasses), viewBox:"0 0 100% 100%"}, + t.circle({cx:"50%", cy:"50%", r:"45%", pathLength:"100"}) + ); +} + +class SessionLoadView extends TemplateView { + render(t) { + return t.div([ + spinner(t, {hidden: vm => !vm.loading}), + t.p(vm => vm.loadLabel) + ]); + } } diff --git a/src/ui/web/login/SessionPickerView.js b/src/ui/web/login/SessionPickerView.js index 5380efbe..176c015e 100644 --- a/src/ui/web/login/SessionPickerView.js +++ b/src/ui/web/login/SessionPickerView.js @@ -29,28 +29,28 @@ function selectFileAsText(mimeType) { class SessionPickerItemView extends TemplateView { _onDeleteClick() { if (confirm("Are you sure?")) { - this.viewModel.delete(); + this.value.delete(); } } - render(t) { + render(t, vm) { const deleteButton = t.button({ disabled: vm => vm.isDeleting, onClick: this._onDeleteClick.bind(this), }, "Delete"); const clearButton = t.button({ disabled: vm => vm.isClearing, - onClick: () => this.viewModel.clear(), + onClick: () => vm.clear(), }, "Clear"); const exportButton = t.button({ disabled: vm => vm.isClearing, - onClick: () => this.viewModel.export(), + onClick: () => vm.export(), }, "Export"); const downloadExport = t.if(vm => vm.exportDataUrl, t.createTemplate((t, vm) => { return t.a({ href: vm.exportDataUrl, - download: `brawl-session-${this.viewModel.id}.json`, - onClick: () => setTimeout(() => this.viewModel.clearExport(), 100), + download: `brawl-session-${vm.id}.json`, + onClick: () => setTimeout(() => vm.clearExport(), 100), }, "Download"); })); @@ -68,32 +68,24 @@ class SessionPickerItemView extends TemplateView { } export class SessionPickerView extends TemplateView { - mount() { - this._sessionList = new ListView({ - list: this.viewModel.sessions, + render(t, vm) { + const sessionList = new ListView({ + list: vm.sessions, onItemClick: (item, event) => { if (event.target.closest(".userId")) { - this.viewModel.pick(item.viewModel.id); + vm.pick(item.value.id); } }, }, sessionInfo => { return new SessionPickerItemView(sessionInfo); }); - return super.mount(); - } - render(t) { return t.div({className: "SessionPickerView"}, [ t.h1(["Pick a session"]), - this._sessionList.mount(), - t.p(t.button({onClick: () => this.viewModel.cancel()}, ["Log in to a new session instead"])), - t.p(t.button({onClick: async () => this.viewModel.import(await selectFileAsText("application/json"))}, "Import")), + t.view(sessionList), + t.p(t.button({onClick: () => vm.cancel()}, ["Log in to a new session instead"])), + t.p(t.button({onClick: async () => vm.import(await selectFileAsText("application/json"))}, "Import")), t.p(brawlGithubLink(t)) ]); } - - unmount() { - super.unmount(); - this._sessionList.unmount(); - } } diff --git a/src/ui/web/session/RoomTile.js b/src/ui/web/session/RoomTile.js index 38210003..c7f3edd9 100644 --- a/src/ui/web/session/RoomTile.js +++ b/src/ui/web/session/RoomTile.js @@ -10,6 +10,6 @@ export class RoomTile extends TemplateView { // called from ListView clicked() { - this.viewModel.open(); + this.value.open(); } } diff --git a/src/ui/web/session/SyncStatusBar.js b/src/ui/web/session/SyncStatusBar.js index 79fbdefe..fa17cc56 100644 --- a/src/ui/web/session/SyncStatusBar.js +++ b/src/ui/web/session/SyncStatusBar.js @@ -1,10 +1,6 @@ import {TemplateView} from "../general/TemplateView.js"; export class SyncStatusBar extends TemplateView { - constructor(vm) { - super(vm, true); - } - render(t, vm) { return t.div({className: { "SyncStatusBar": true, diff --git a/src/ui/web/session/room/MessageComposer.js b/src/ui/web/session/room/MessageComposer.js index 664b246c..13211965 100644 --- a/src/ui/web/session/room/MessageComposer.js +++ b/src/ui/web/session/room/MessageComposer.js @@ -16,7 +16,7 @@ export class MessageComposer extends TemplateView { _onKeyDown(event) { if (event.key === "Enter") { - if (this.viewModel.sendMessage(this._input.value)) { + if (this.value.sendMessage(this._input.value)) { this._input.value = ""; } } diff --git a/src/ui/web/session/room/RoomView.js b/src/ui/web/session/room/RoomView.js index d3d0d849..116de8fd 100644 --- a/src/ui/web/session/room/RoomView.js +++ b/src/ui/web/session/room/RoomView.js @@ -4,7 +4,7 @@ import {MessageComposer} from "./MessageComposer.js"; export class RoomView extends TemplateView { constructor(viewModel) { - super(viewModel, true); + super(viewModel); this._timelineList = null; } @@ -26,7 +26,7 @@ export class RoomView extends TemplateView { } mount() { - this._composer = new MessageComposer(this.viewModel); + this._composer = new MessageComposer(this.value); this._timelineList = new TimelineList(); return super.mount(); } @@ -40,7 +40,7 @@ export class RoomView extends TemplateView { update(value, prop) { super.update(value, prop); if (prop === "timelineViewModel") { - this._timelineList.update({viewModel: this.viewModel.timelineViewModel}); + this._timelineList.update({viewModel: this.value.timelineViewModel}); } } } diff --git a/src/ui/web/session/room/timeline/GapView.js b/src/ui/web/session/room/timeline/GapView.js index 637d3ce3..5687ce58 100644 --- a/src/ui/web/session/room/timeline/GapView.js +++ b/src/ui/web/session/room/timeline/GapView.js @@ -9,7 +9,7 @@ export class GapView extends TemplateView { const label = (vm.isUp ? "🠝" : "🠟") + " fill gap"; //no binding return t.li({className}, [ t.button({ - onClick: () => this.viewModel.fill(), + onClick: () => vm.fill(), disabled: vm => vm.isLoading }, label), t.if(vm => vm.error, t.createTemplate(t => t.strong(vm => vm.error)))