From 1b6d9db7cd5a43fc547f33bea60746a7b03ea331 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 7 Dec 2021 12:48:37 +0000 Subject: [PATCH] Get placeholder->room logic working Feels somewhat hacky, but it mostly works with the caveats: - Room names / avatars bleed between rooms when there are updates. - Clicking on a room doesn't immediately highlight it on the list --- .../session/leftpanel/LeftPanelViewModel.js | 20 +++++++++++++++---- .../session/leftpanel/RoomTileViewModel.js | 4 ++++ src/matrix/Sync3.ts | 2 +- src/observable/list/BaseMappedList.ts | 17 +++++++++++++--- src/platform/web/ui/general/LazyListView.ts | 13 +++++++++--- .../web/ui/session/leftpanel/LeftPanelView.js | 7 +++++++ 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/domain/session/leftpanel/LeftPanelViewModel.js b/src/domain/session/leftpanel/LeftPanelViewModel.js index 04d8c2a5..86040da8 100644 --- a/src/domain/session/leftpanel/LeftPanelViewModel.js +++ b/src/domain/session/leftpanel/LeftPanelViewModel.js @@ -33,8 +33,7 @@ export class LeftPanelViewModel extends ViewModel { this._sync = sync; const sync3List = new Sync3ObservableList(sync, rooms); const list = new ConcatList(invites.sortValues((a,b) => a.compare(b)), sync3List); - this._tileViewModelsMap = this._mapTileViewModels(list); - this._tileViewModels = this._tileViewModelsMap; + this._tileViewModels = this._mapTileViewModels(list); this._currentTileVM = null; this._setupNavigation(); this._closeUrl = this.urlCreator.urlForSegment("session"); @@ -106,8 +105,21 @@ export class LeftPanelViewModel extends ViewModel { this._currentTileVM?.close(); this._currentTileVM = null; if (roomId) { - this._currentTileVM = this._tileViewModelsMap.get(roomId); - this._currentTileVM?.open(); + // find the vm for the room. Previously we used a map to do this but sync3 only gives + // us a list. We could've re-mapped things in the observable pipeline but we don't need + // these values to be kept up-to-date when a O(n) search on click isn't particularly + // expensive. + let targetVM; + for ( let vm of this._tileViewModels ) { + if (vm.id === roomId) { + targetVM = vm; + break; + } + } + if (targetVM) { + this._currentTileVM = targetVM; + this._currentTileVM?.open(); + } } } diff --git a/src/domain/session/leftpanel/RoomTileViewModel.js b/src/domain/session/leftpanel/RoomTileViewModel.js index f35619e2..79313512 100644 --- a/src/domain/session/leftpanel/RoomTileViewModel.js +++ b/src/domain/session/leftpanel/RoomTileViewModel.js @@ -34,6 +34,10 @@ export class RoomTileViewModel extends BaseTileViewModel { return this._url; } + get id() { + return this._room.id; + } + compare(other) { return this._compareFn(this._room.id, other._room.id); diff --git a/src/matrix/Sync3.ts b/src/matrix/Sync3.ts index ea108c29..c1ae4d6d 100644 --- a/src/matrix/Sync3.ts +++ b/src/matrix/Sync3.ts @@ -129,7 +129,7 @@ export class Sync3 { this.status = new ObservableValue(SyncStatus.Stopped); this.error = null; // Hydrogen only has 1 list currently (no DM section) so we only need 1 range - this.ranges = [[0, 4]]; + this.ranges = [[0, 49]]; this.roomIndexToRoomId = {}; this.roomIdToRoomIndex = {}; this.totalRooms = 0; diff --git a/src/observable/list/BaseMappedList.ts b/src/observable/list/BaseMappedList.ts index 6794d1a7..a758efe5 100644 --- a/src/observable/list/BaseMappedList.ts +++ b/src/observable/list/BaseMappedList.ts @@ -19,7 +19,7 @@ import { BaseObservableList } from "./BaseObservableList"; import { findAndUpdateInArray } from "./common"; export type Mapper = (value: F) => T -export type Updater = (mappedValue: T, params: any, value: F) => void; +export type Updater = (mappedValue: T, params: any, value: F) => any; export class BaseMappedList extends BaseObservableList { protected _sourceList: BaseObservableList; @@ -56,10 +56,21 @@ export function runAdd(list: BaseMappedList, index: number, ma } export function runUpdate(list: BaseMappedList, index: number, value: F, params: any): void { - const mappedValue = list._mappedValues![index]; + let mappedValue = list._mappedValues![index]; if (list._updater) { - list._updater(mappedValue, params, value); + // allow updates to completely remap the underlying data type + // TODO: do we need to unsubscribe from anything here? + let newMappedValue = list._updater(mappedValue, params, value); + if (newMappedValue) { + if (!params) { + params = {}; + } + params.oldValue = mappedValue; + mappedValue = newMappedValue; + list._mappedValues![index] = mappedValue; + } } + // pass the new updated value down the chain list.emitUpdate(index, mappedValue, params); } diff --git a/src/platform/web/ui/general/LazyListView.ts b/src/platform/web/ui/general/LazyListView.ts index c1e42bbe..c08edaf2 100644 --- a/src/platform/web/ui/general/LazyListView.ts +++ b/src/platform/web/ui/general/LazyListView.ts @@ -23,6 +23,7 @@ import { IView } from "./types"; export interface IOptions extends IParentOptions { itemHeight: number; overflowItems?: number; + shouldRecreateItem?: (value: any, oldValue: any) => boolean; onRangeVisible?: (range: ListRange) => void; } @@ -33,15 +34,17 @@ export class LazyListView extends ListView { private overflowItems: number; private scrollContainer?: HTMLElement; private onRangeVisible?: (range: ListRange) => void; + private shouldRecreateItem?: (value: any, oldValue: any) => boolean; constructor( - { itemHeight, onRangeVisible, overflowItems = 20, ...options }: IOptions, + { itemHeight, onRangeVisible, shouldRecreateItem, overflowItems = 20, ...options }: IOptions, childCreator: (value: T) => V ) { super(options, childCreator); this.itemHeight = itemHeight; this.overflowItems = overflowItems; - this.onRangeVisible = onRangeVisible; // function(ItemRange) + this.onRangeVisible = onRangeVisible; + this.shouldRecreateItem = shouldRecreateItem; } handleEvent(e: Event) { @@ -190,7 +193,11 @@ export class LazyListView extends ListView { onUpdate(i: number, value: T, params: any) { if (this.renderRange!.containsIndex(i)) { - this.updateChild(this.renderRange!.toLocalIndex(i), value, params); + if (this.shouldRecreateItem && this.shouldRecreateItem(value, params?.oldValue)) { + super.recreateItem(this.renderRange!.toLocalIndex(i), value); + } else { + this.updateChild(this.renderRange!.toLocalIndex(i), value, params); + } } } diff --git a/src/platform/web/ui/session/leftpanel/LeftPanelView.js b/src/platform/web/ui/session/leftpanel/LeftPanelView.js index fcf5c5e8..855d9d12 100644 --- a/src/platform/web/ui/session/leftpanel/LeftPanelView.js +++ b/src/platform/web/ui/session/leftpanel/LeftPanelView.js @@ -19,6 +19,8 @@ import {TemplateView} from "../../general/TemplateView"; import {RoomTileView} from "./RoomTileView.js"; import {InviteTileView} from "./InviteTileView.js"; import { PlaceholderRoomTileView } from "./PlaceholderRoomTileView"; +import { PlaceholderRoomTileViewModel } from "../../../../../domain/session/leftpanel/PlaceholderRoomTileViewModel"; +import { RoomTileViewModel } from "../../../../../domain/session/leftpanel/RoomTileViewModel"; class FilterField extends TemplateView { render(t, options) { @@ -67,6 +69,11 @@ export class LeftPanelView extends TemplateView { onRangeVisible: (range) => { vm.loadRoomRange(range); }, + shouldRecreateItem: (value, oldValue) => { + const isOldRoom = oldValue instanceof RoomTileViewModel; + const isNewRoom = value instanceof RoomTileViewModel; + return isOldRoom != isNewRoom; + } }, tileVM => { if (tileVM.kind === "invite") {