From 943467cf67f98180552e397daa6b6fd7c0011039 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 5 Oct 2020 18:18:44 +0200 Subject: [PATCH 01/11] actually implemented filtered map --- src/observable/map/FilteredMap.js | 119 ++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/src/observable/map/FilteredMap.js b/src/observable/map/FilteredMap.js index 59d8efa1..20e5c568 100644 --- a/src/observable/map/FilteredMap.js +++ b/src/observable/map/FilteredMap.js @@ -17,56 +17,127 @@ limitations under the License. import {BaseObservableMap} from "./BaseObservableMap.js"; export class FilteredMap extends BaseObservableMap { - constructor(source, mapper, updater) { + constructor(source, filter) { super(); this._source = source; - this._mapper = mapper; - this._updater = updater; - this._mappedValues = new Map(); + this._filter = filter; + /** @type {Map} */ + this._included = null; + } + + setFilter(filter) { + this._filter = filter; + this.update(); + } + + update() { + // TODO: need to check if we have a subscriber already? If not, we really should not iterate the source? + if (this._filter) { + this._included = this._included || new Map(); + for (const [key, value] of this._source) { + this._included.set(key, this._filter(value, key)); + } + } else { + this._included = null; + } } onAdd(key, value) { - const mappedValue = this._mapper(value); - this._mappedValues.set(key, mappedValue); - this.emitAdd(key, mappedValue); + if (this._filter) { + const included = this._filter(value, key); + this._included.set(key, included); + if (!included) { + return; + } + } + this.emitAdd(key, value); } - onRemove(key, _value) { - const mappedValue = this._mappedValues.get(key); - if (this._mappedValues.delete(key)) { - this.emitRemove(key, mappedValue); + onRemove(key, value) { + if (this._filter && !this._included.get(key)) { + return; } + this.emitRemove(key, value); } onChange(key, value, params) { - const mappedValue = this._mappedValues.get(key); - if (mappedValue !== undefined) { - const newParams = this._updater(value, params); - if (newParams !== undefined) { - this.emitChange(key, mappedValue, newParams); - } + if (this._filter) { + const wasIncluded = this._included.get(key); + const isIncluded = this._filter(value, key); + this._included.set(key, isIncluded); + + if (wasIncluded && !isIncluded) { + this.emitRemove(key, value); + } else if (!wasIncluded && isIncluded) { + this.emitAdd(key, value); + } else if (!wasIncluded && !isIncluded) { + return; + } // fall through to emitChange } + this.emitChange(key, value, params); } onSubscribeFirst() { - for (let [key, value] of this._source) { - const mappedValue = this._mapper(value); - this._mappedValues.set(key, mappedValue); - } + this.update(); super.onSubscribeFirst(); } onUnsubscribeLast() { super.onUnsubscribeLast(); - this._mappedValues.clear(); + this._included = null; } onReset() { - this._mappedValues.clear(); + this.update(); this.emitReset(); } [Symbol.iterator]() { - return this._mappedValues.entries()[Symbol.iterator]; + return new FilterIterator(this._source, this._included); + } +} + +class FilterIterator { + constructor(map, _included) { + this._included = _included; + this._sourceIterator = map.entries(); + } + + next() { + // eslint-disable-next-line no-constant-condition + while (true) { + const sourceResult = this._sourceIterator.next(); + if (sourceResult.done) { + return sourceResult; + } + const key = sourceResult.value[1]; + if (this._included.get(key)) { + return sourceResult; + } + } + } +} + +import {ObservableMap} from "./ObservableMap.js"; +export function tests() { + return { + "filter preloaded list": assert => { + const source = new ObservableMap(); + source.add("one", 1); + source.add("two", 2); + source.add("three", 3); + const odds = Array.from(new FilteredMap(source, x => x % 2 !== 0)); + assert.equal(odds.length, 2); + + }, + "filter added values": assert => { + + }, + "filter removed values": assert => { + + }, + "filter changed values": assert => { + + }, } } From 84425fad5ca5e83140cd27b1466e68e5da686476 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:19:08 +0200 Subject: [PATCH 02/11] more work on FilteredMap (not done yet though) I ended up not using this, but at some point we'll need it, so commit this work --- src/observable/map/FilteredMap.js | 84 +++++++++++++++++++------------ 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/src/observable/map/FilteredMap.js b/src/observable/map/FilteredMap.js index 20e5c568..d07c3228 100644 --- a/src/observable/map/FilteredMap.js +++ b/src/observable/map/FilteredMap.js @@ -23,6 +23,7 @@ export class FilteredMap extends BaseObservableMap { this._filter = filter; /** @type {Map} */ this._included = null; + this._subscription = null; } setFilter(filter) { @@ -30,14 +31,30 @@ export class FilteredMap extends BaseObservableMap { this.update(); } + /** + * reapply the filter + */ update() { // TODO: need to check if we have a subscriber already? If not, we really should not iterate the source? if (this._filter) { + const hadFilterBefore = !!this._included; this._included = this._included || new Map(); for (const [key, value] of this._source) { - this._included.set(key, this._filter(value, key)); + const isIncluded = this._filter(value, key); + const wasIncluded = hadFilterBefore ? this._included.get(key) : true; + this._included.set(key, isIncluded); + this._emitForUpdate(wasIncluded, isIncluded, key, value); + } + } else { // no filter + // did we have a filter before? + if (this._included) { + // add any non-included items again + for (const [key, value] of this._source) { + if (!this._included.get(key)) { + this.emitAdd(key, value); + } + } } - } else { this._included = null; } } @@ -60,24 +77,28 @@ export class FilteredMap extends BaseObservableMap { this.emitRemove(key, value); } - onChange(key, value, params) { + onUpdate(key, value, params) { if (this._filter) { const wasIncluded = this._included.get(key); const isIncluded = this._filter(value, key); this._included.set(key, isIncluded); - - if (wasIncluded && !isIncluded) { - this.emitRemove(key, value); - } else if (!wasIncluded && isIncluded) { - this.emitAdd(key, value); - } else if (!wasIncluded && !isIncluded) { - return; - } // fall through to emitChange + this._emitForUpdate(wasIncluded, isIncluded, key, value, params); + } + this.emitUpdate(key, value, params); + } + + _emitForUpdate(wasIncluded, isIncluded, key, value, params = null) { + if (wasIncluded && !isIncluded) { + this.emitRemove(key, value); + } else if (!wasIncluded && isIncluded) { + this.emitAdd(key, value); + } else if (wasIncluded && isIncluded) { + this.emitUpdate(key, value, params); } - this.emitChange(key, value, params); } onSubscribeFirst() { + this._subscription = this._source.subscribe(this); this.update(); super.onSubscribeFirst(); } @@ -85,6 +106,7 @@ export class FilteredMap extends BaseObservableMap { onUnsubscribeLast() { super.onUnsubscribeLast(); this._included = null; + this._subscription = this._subscription(); } onReset() { @@ -118,26 +140,26 @@ class FilterIterator { } } -import {ObservableMap} from "./ObservableMap.js"; -export function tests() { - return { - "filter preloaded list": assert => { - const source = new ObservableMap(); - source.add("one", 1); - source.add("two", 2); - source.add("three", 3); - const odds = Array.from(new FilteredMap(source, x => x % 2 !== 0)); - assert.equal(odds.length, 2); +// import {ObservableMap} from "./ObservableMap.js"; +// export function tests() { +// return { +// "filter preloaded list": assert => { +// const source = new ObservableMap(); +// source.add("one", 1); +// source.add("two", 2); +// source.add("three", 3); +// const odds = Array.from(new FilteredMap(source, x => x % 2 !== 0)); +// assert.equal(odds.length, 2); - }, - "filter added values": assert => { +// }, +// "filter added values": assert => { - }, - "filter removed values": assert => { +// }, +// "filter removed values": assert => { - }, - "filter changed values": assert => { +// }, +// "filter changed values": assert => { - }, - } -} +// }, +// } +// } From 7def542e2157f91ff36f632992af3e05eabda718 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:19:47 +0200 Subject: [PATCH 03/11] fix test --- src/matrix/Session.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 3c2f0e8c..708efd29 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -426,7 +426,7 @@ export function tests() { function createStorageMock(session, pendingEvents = []) { return { readTxn() { - return Promise.resolve({ + return { session: { get(key) { return Promise.resolve(session[key]); @@ -442,7 +442,7 @@ export function tests() { return Promise.resolve([]); } } - }); + }; }, storeNames: {} }; From 9a3734e5ba6d109208f69377ef3216565a9b6e86 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:20:03 +0200 Subject: [PATCH 04/11] this is a NOP, but in the future might not be --- src/observable/map/MappedMap.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/observable/map/MappedMap.js b/src/observable/map/MappedMap.js index 7b7d1583..28b7d1e8 100644 --- a/src/observable/map/MappedMap.js +++ b/src/observable/map/MappedMap.js @@ -67,6 +67,7 @@ export class MappedMap extends BaseObservableMap { } onUnsubscribeLast() { + super.onUnsubscribeLast(); this._subscription = this._subscription(); this._mappedValues.clear(); } From e3fdd3a4fd8e37660cfd89644cf2cc4521220df7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:20:28 +0200 Subject: [PATCH 05/11] ApplyMap, your observable map collection for applying side-effects --- src/observable/map/ApplyMap.js | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/observable/map/ApplyMap.js diff --git a/src/observable/map/ApplyMap.js b/src/observable/map/ApplyMap.js new file mode 100644 index 00000000..2a2dc19c --- /dev/null +++ b/src/observable/map/ApplyMap.js @@ -0,0 +1,81 @@ +/* +Copyright 2020 Bruno Windels + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {BaseObservableMap} from "./BaseObservableMap.js"; + +export class ApplyMap extends BaseObservableMap { + constructor(source, apply) { + super(); + this._source = source; + this._apply = apply; + this._subscription = null; + } + + setApply(apply) { + this._apply = apply; + if (apply) { + this.applyOnce(this._apply); + } + } + + applyOnce(apply) { + for (const [key, value] of this._source) { + apply(key, value); + } + } + + onAdd(key, value) { + if (this._apply) { + this._apply(key, value); + } + this.emitAdd(key, value); + } + + onRemove(key, value) { + this.emitRemove(key, value); + } + + onUpdate(key, value, params) { + if (this._apply) { + this._apply(key, value, params); + } + this.emitUpdate(key, value, params); + } + + onSubscribeFirst() { + this._subscription = this._source.subscribe(this); + if (this._apply) { + this.applyOnce(this._apply); + } + super.onSubscribeFirst(); + } + + onUnsubscribeLast() { + super.onUnsubscribeLast(); + this._subscription = this._subscription(); + } + + onReset() { + if (this._apply) { + this.applyOnce(this._apply); + } + this.emitReset(); + } + + [Symbol.iterator]() { + return this._source[Symbol.iterator](); + } +} From c532cb5aeaeee50166aeb69d75794c2a9bc32af3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:21:11 +0200 Subject: [PATCH 06/11] add hidden flag to room tile vm also move to leftpanel dir --- .../{roomlist => leftpanel}/RoomTileViewModel.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) rename src/domain/session/{roomlist => leftpanel}/RoomTileViewModel.js (93%) diff --git a/src/domain/session/roomlist/RoomTileViewModel.js b/src/domain/session/leftpanel/RoomTileViewModel.js similarity index 93% rename from src/domain/session/roomlist/RoomTileViewModel.js rename to src/domain/session/leftpanel/RoomTileViewModel.js index 76cab067..86f832c4 100644 --- a/src/domain/session/roomlist/RoomTileViewModel.js +++ b/src/domain/session/leftpanel/RoomTileViewModel.js @@ -1,5 +1,6 @@ /* Copyright 2020 Bruno Windels +Copyright 2020 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. @@ -29,6 +30,18 @@ export class RoomTileViewModel extends ViewModel { this._emitOpen = emitOpen; this._isOpen = false; this._wasUnreadWhenOpening = false; + this._hidden = false; + } + + get hidden() { + return this._hidden; + } + + set hidden(value) { + if (value !== this._hidden) { + this._hidden = value; + this.emitChange("hidden"); + } } // called by parent for now (later should integrate with router) From c8125595a7fd82c17004df1badaa4b285c535edf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:21:42 +0200 Subject: [PATCH 07/11] render hidden flag --- .../RoomTileView.js} | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) rename src/ui/web/session/{RoomTile.js => leftpanel/RoomTileView.js} (58%) diff --git a/src/ui/web/session/RoomTile.js b/src/ui/web/session/leftpanel/RoomTileView.js similarity index 58% rename from src/ui/web/session/RoomTile.js rename to src/ui/web/session/leftpanel/RoomTileView.js index 0486bfe2..31c49b66 100644 --- a/src/ui/web/session/RoomTile.js +++ b/src/ui/web/session/leftpanel/RoomTileView.js @@ -1,5 +1,6 @@ /* Copyright 2020 Bruno Windels +Copyright 2020 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. @@ -14,16 +15,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {TemplateView} from "../general/TemplateView.js"; -import {renderAvatar} from "../common.js"; +import {TemplateView} from "../../general/TemplateView.js"; +import {renderAvatar} from "../../common.js"; -export class RoomTile extends TemplateView { +export class RoomTileView extends TemplateView { render(t, vm) { - return t.li({"className": {"active": vm => vm.isOpen}}, [ + const classes = { + "active": vm => vm.isOpen, + "hidden": vm => vm.hidden + }; + return t.li({"className": classes}, [ renderAvatar(t, vm, 32), t.div({className: "description"}, [ t.div({className: {"name": true, unread: vm => vm.isUnread}}, vm => vm.name), - t.div({className: {"badge": true, highlighted: vm => vm.isHighlighted, hidden: vm => !vm.badgeCount}}, vm => vm.badgeCount), + t.div({ + className: { + "badge": true, + highlighted: vm => vm.isHighlighted, + hidden: vm => !vm.badgeCount + } + }, vm => vm.badgeCount), ]) ]); } From cac3daca72d972b1557d61b28e8716cc6ba28699 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:22:06 +0200 Subject: [PATCH 08/11] add LeftPanelView(Model) with room filtering --- src/domain/session/SessionViewModel.js | 19 +++--- .../session/leftpanel/LeftPanelViewModel.js | 58 +++++++++++++++++++ src/domain/session/leftpanel/RoomFilter.js | 26 +++++++++ src/ui/web/css/left-panel.css | 27 +++++++-- src/ui/web/session/leftpanel/LeftPanelView.js | 49 ++++++++++++++++ 5 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 src/domain/session/leftpanel/LeftPanelViewModel.js create mode 100644 src/domain/session/leftpanel/RoomFilter.js create mode 100644 src/ui/web/session/leftpanel/LeftPanelView.js diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 0a77d2a0..f7d28fed 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -1,5 +1,6 @@ /* Copyright 2020 Bruno Windels +Copyright 2020 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. @@ -14,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {RoomTileViewModel} from "./roomlist/RoomTileViewModel.js"; +import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js"; import {RoomViewModel} from "./room/RoomViewModel.js"; import {SessionStatusViewModel} from "./SessionStatusViewModel.js"; import {ViewModel} from "../ViewModel.js"; @@ -29,22 +30,22 @@ export class SessionViewModel extends ViewModel { reconnector: sessionContainer.reconnector, session: sessionContainer.session, }))); + this._leftPanelViewModel = new LeftPanelViewModel(this.childOptions({ + rooms: this._session.rooms, + openRoom: this._openRoom.bind(this) + })); this._currentRoomTileViewModel = null; this._currentRoomViewModel = null; - const roomTileVMs = this._session.rooms.mapValues((room, emitChange) => { - return new RoomTileViewModel({ - room, - emitChange, - emitOpen: this._openRoom.bind(this) - }); - }); - this._roomList = roomTileVMs.sortValues((a, b) => a.compare(b)); } start() { this._sessionStatusViewModel.start(); } + get leftPanelViewModel() { + return this._leftPanelViewModel; + } + get sessionStatusViewModel() { return this._sessionStatusViewModel; } diff --git a/src/domain/session/leftpanel/LeftPanelViewModel.js b/src/domain/session/leftpanel/LeftPanelViewModel.js new file mode 100644 index 00000000..50864b4c --- /dev/null +++ b/src/domain/session/leftpanel/LeftPanelViewModel.js @@ -0,0 +1,58 @@ +/* +Copyright 2020 Bruno Windels +Copyright 2020 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {ViewModel} from "../../ViewModel.js"; +import {RoomTileViewModel} from "./RoomTileViewModel.js"; +import {RoomFilter} from "./RoomFilter.js"; +import {ApplyMap} from "../../../observable/map/ApplyMap.js"; + +export class LeftPanelViewModel extends ViewModel { + constructor(options) { + super(options); + const {rooms, openRoom} = options; + const roomTileVMs = rooms.mapValues((room, emitChange) => { + return new RoomTileViewModel({ + room, + emitChange, + emitOpen: openRoom + }); + }); + this._roomListFilterMap = new ApplyMap(roomTileVMs); + this._roomList = this._roomListFilterMap.sortValues((a, b) => a.compare(b)); + } + + get roomList() { + return this._roomList; + } + + clearFilter() { + this._roomListFilterMap.setApply(null); + this._roomListFilterMap.applyOnce((roomId, vm) => vm.hidden = false); + } + + setFilter(query) { + query = query.trim(); + if (query.length === 0) { + this.clearFilter(); + } else { + const filter = new RoomFilter(query); + this._roomListFilterMap.setApply((roomId, vm) => { + vm.hidden = !filter.matches(vm); + }); + } + } +} diff --git a/src/domain/session/leftpanel/RoomFilter.js b/src/domain/session/leftpanel/RoomFilter.js new file mode 100644 index 00000000..cdbe2cb2 --- /dev/null +++ b/src/domain/session/leftpanel/RoomFilter.js @@ -0,0 +1,26 @@ +/* +Copyright 2020 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export class RoomFilter { + constructor(query) { + this._parts = query.split(" ").map(s => s.toLowerCase().trim()); + } + + matches(roomTileVM) { + const name = roomTileVM.name.toLowerCase(); + return this._parts.every(p => name.includes(p)); + } +} diff --git a/src/ui/web/css/left-panel.css b/src/ui/web/css/left-panel.css index cde12111..fa935e10 100644 --- a/src/ui/web/css/left-panel.css +++ b/src/ui/web/css/left-panel.css @@ -14,10 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ - .LeftPanel { - overflow-y: auto; - overscroll-behavior: contain; + display: flex; + flex-direction: column; +} + +.LeftPanel .filter { + flex: 0; + display: flex; +} + +.LeftPanel .filter input { + display: block; + flex: 1; + box-sizing: border-box; } .LeftPanel ul { @@ -26,19 +36,24 @@ limitations under the License. margin: 0; } -.LeftPanel li { +.RoomList { + overflow-y: auto; + overscroll-behavior: contain; +} + +.RoomList li { display: flex; align-items: center; } -.LeftPanel div.description { +.RoomList .description { margin: 0; flex: 1 1 0; min-width: 0; display: flex; } -.LeftPanel .description > .name { +.RoomList .description > .name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; diff --git a/src/ui/web/session/leftpanel/LeftPanelView.js b/src/ui/web/session/leftpanel/LeftPanelView.js new file mode 100644 index 00000000..6688b537 --- /dev/null +++ b/src/ui/web/session/leftpanel/LeftPanelView.js @@ -0,0 +1,49 @@ +/* +Copyright 2020 Bruno Windels + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {ListView} from "../../general/ListView.js"; +import {TemplateView} from "../../general/TemplateView.js"; +import {RoomTileView} from "./RoomTileView.js"; + +export class LeftPanelView extends TemplateView { + render(t, vm) { + const filterInput = t.input({ + type: "text", + placeholder: vm.i18n`Filter rooms…`, + "aria-label": vm.i18n`Filter rooms by name`, + autocomplete: true, + name: "room-filter", + onInput: event => vm.setFilter(event.target.value), + }); + return t.div({className: "LeftPanel"}, [ + t.div({className: "filter"}, [ + filterInput, + t.button({onClick: () => { + filterInput.value = ""; + vm.clearFilter(); + }}, vm.i18n`Clear`) + ]), + t.view(new ListView( + { + className: "RoomList", + list: vm.roomList, + onItemClick: (roomTile, event) => roomTile.clicked(event) + }, + roomTileVM => new RoomTileView(roomTileVM) + )) + ]); + } +} From 2fe6f4b769019073f8a3b735a0614d192bd7df3b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:23:17 +0200 Subject: [PATCH 09/11] clean up SessionView by making it a TemplateView --- src/ui/web/session/SessionView.js | 77 ++++++++----------------------- 1 file changed, 18 insertions(+), 59 deletions(-) diff --git a/src/ui/web/session/SessionView.js b/src/ui/web/session/SessionView.js index 73e6bf98..a66fcd4b 100644 --- a/src/ui/web/session/SessionView.js +++ b/src/ui/web/session/SessionView.js @@ -14,70 +14,29 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ListView} from "../general/ListView.js"; -import {RoomTile} from "./RoomTile.js"; +import {LeftPanelView} from "./leftpanel/LeftPanelView.js"; import {RoomView} from "./room/RoomView.js"; -import {SwitchView} from "../general/SwitchView.js"; +import {TemplateView} from "../general/TemplateView.js"; import {RoomPlaceholderView} from "./RoomPlaceholderView.js"; import {SessionStatusView} from "./SessionStatusView.js"; -import {tag} from "../general/html.js"; -export class SessionView { - constructor(viewModel) { - this._viewModel = viewModel; - this._middleSwitcher = null; - this._roomList = null; - this._currentRoom = null; - this._root = null; - this._onViewModelChange = this._onViewModelChange.bind(this); - } - - root() { - return this._root; - } - - mount() { - this._viewModel.on("change", this._onViewModelChange); - this._sessionStatusBar = new SessionStatusView(this._viewModel.sessionStatusViewModel); - this._roomList = new ListView( - { - className: "RoomList", - list: this._viewModel.roomList, - onItemClick: (roomTile, event) => roomTile.clicked(event) - }, - (room) => new RoomTile(room) - ); - this._middleSwitcher = new SwitchView(new RoomPlaceholderView()); - - this._root = tag.div({className: "SessionView"}, [ - this._sessionStatusBar.mount(), - tag.div({className: "main"}, [ - tag.div({className: "LeftPanel"}, this._roomList.mount()), - this._middleSwitcher.mount() +export class SessionView extends TemplateView { + render(t, vm) { + return t.div({ + className: "SessionView", + "room-shown": vm => !!vm.currentRoom + }, [ + t.view(new SessionStatusView(vm.sessionStatusViewModel)), + t.div({className: "main"}, [ + t.view(new LeftPanelView(vm.leftPanelViewModel)), + t.mapView(vm => vm.currentRoom, currentRoom => { + if (currentRoom) { + return new RoomView(currentRoom); + } else { + return new RoomPlaceholderView(); + } + }) ]) ]); - - return this._root; } - - unmount() { - this._roomList.unmount(); - this._middleSwitcher.unmount(); - this._viewModel.off("change", this._onViewModelChange); - } - - _onViewModelChange(prop) { - if (prop === "currentRoom") { - if (this._viewModel.currentRoom) { - this._root.classList.add("room-shown"); - this._middleSwitcher.switch(new RoomView(this._viewModel.currentRoom)); - } else { - this._root.classList.remove("room-shown"); - this._middleSwitcher.switch(new RoomPlaceholderView()); - } - } - } - - // changing viewModel not supported for now - update() {} } From ddbe3305c8cf4d409a6b500455032419c95d744d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:34:13 +0200 Subject: [PATCH 10/11] fix filter field height --- src/ui/web/css/left-panel.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/web/css/left-panel.css b/src/ui/web/css/left-panel.css index fa935e10..6833341d 100644 --- a/src/ui/web/css/left-panel.css +++ b/src/ui/web/css/left-panel.css @@ -20,7 +20,6 @@ limitations under the License. } .LeftPanel .filter { - flex: 0; display: flex; } @@ -37,6 +36,7 @@ limitations under the License. } .RoomList { + flex: 1 0 0; overflow-y: auto; overscroll-behavior: contain; } From 0d6fe32f309285637311468e53c7026820229011 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 6 Oct 2020 12:43:31 +0200 Subject: [PATCH 11/11] clear filter on esc --- src/ui/web/session/leftpanel/LeftPanelView.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ui/web/session/leftpanel/LeftPanelView.js b/src/ui/web/session/leftpanel/LeftPanelView.js index 6688b537..f262e954 100644 --- a/src/ui/web/session/leftpanel/LeftPanelView.js +++ b/src/ui/web/session/leftpanel/LeftPanelView.js @@ -27,6 +27,12 @@ export class LeftPanelView extends TemplateView { autocomplete: true, name: "room-filter", onInput: event => vm.setFilter(event.target.value), + onKeydown: event => { + if (event.key === "Escape" || event.key === "Esc") { + filterInput.value = ""; + vm.clearFilter(); + } + } }); return t.div({className: "LeftPanel"}, [ t.div({className: "filter"}, [