diff --git a/src/lib.ts b/src/lib.ts index a0ada84f..2634b0c0 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -27,6 +27,8 @@ export {RoomViewModel} from "./domain/session/room/RoomViewModel.js"; export {RoomView} from "./platform/web/ui/session/room/RoomView.js"; export {TimelineViewModel} from "./domain/session/room/timeline/TimelineViewModel.js"; export {TimelineView} from "./platform/web/ui/session/room/TimelineView"; +export {viewClassForEntry} from "./platform/web/ui/session/room/common"; +export type {TileViewConstructor, ViewClassForEntryFn} from "./platform/web/ui/session/room/TimelineView"; export {Navigation} from "./domain/navigation/Navigation.js"; export {ComposerViewModel} from "./domain/session/room/ComposerViewModel.js"; export {MessageComposer} from "./platform/web/ui/session/room/MessageComposer.js"; diff --git a/src/platform/web/ui/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js index c172766a..2190f1f1 100644 --- a/src/platform/web/ui/session/room/RoomView.js +++ b/src/platform/web/ui/session/room/RoomView.js @@ -23,6 +23,7 @@ import {TimelineLoadingView} from "./TimelineLoadingView.js"; import {MessageComposer} from "./MessageComposer.js"; import {RoomArchivedView} from "./RoomArchivedView.js"; import {AvatarView} from "../../AvatarView.js"; +import {viewClassForEntry} from "./common"; export class RoomView extends TemplateView { constructor(options) { @@ -54,7 +55,7 @@ export class RoomView extends TemplateView { t.div({className: "RoomView_error"}, vm => vm.error), t.mapView(vm => vm.timelineViewModel, timelineViewModel => { return timelineViewModel ? - new TimelineView(timelineViewModel) : + new TimelineView(timelineViewModel, viewClassForEntry) : new TimelineLoadingView(vm); // vm is just needed for i18n }), t.view(bottomView), diff --git a/src/platform/web/ui/session/room/TimelineView.ts b/src/platform/web/ui/session/room/TimelineView.ts index 936b8c7c..91ff59d5 100644 --- a/src/platform/web/ui/session/room/TimelineView.ts +++ b/src/platform/web/ui/session/room/TimelineView.ts @@ -14,9 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import type {TileView} from "./common"; -import {viewClassForEntry} from "./common"; import {ListView} from "../../general/ListView"; +import type {IView} from "../../general/types"; import {TemplateView, Builder} from "../../general/TemplateView"; import {IObservableValue} from "../../general/BaseUpdateView"; import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js"; @@ -25,6 +24,12 @@ import {RedactedView} from "./timeline/RedactedView.js"; import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js"; import {BaseObservableList as ObservableList} from "../../../../../observable/list/BaseObservableList"; +export interface TileView extends IView { + readonly value: SimpleTile; +} +export type TileViewConstructor = new (tile: SimpleTile) => TileView; +export type ViewClassForEntryFn = (tile: SimpleTile) => TileViewConstructor; + //import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js"; export interface TimelineViewModel extends IObservableValue { showJumpDown: boolean; @@ -55,13 +60,17 @@ export class TimelineView extends TemplateView { private tilesView?: TilesListView; private resizeObserver?: ResizeObserver; + constructor(vm: TimelineViewModel, private readonly viewClassForEntry: ViewClassForEntryFn) { + super(vm); + } + render(t: Builder, vm: TimelineViewModel) { // assume this view will be mounted in the parent DOM straight away requestAnimationFrame(() => { // do initial scroll positioning this.restoreScrollPosition(); }); - this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition()); + this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition(), this.viewClassForEntry); const root = t.div({className: "Timeline"}, [ t.div({ className: "Timeline_scroller bottom-aligned-scroll", @@ -174,16 +183,14 @@ class TilesListView extends ListView { private onChanged: () => void; - constructor(tiles: ObservableList, onChanged: () => void) { + constructor(tiles: ObservableList, onChanged: () => void, private readonly viewClassForEntry: ViewClassForEntryFn) { const options = { list: tiles, onItemClick: (tileView, evt) => tileView.onClick(evt), }; super(options, entry => { const View = viewClassForEntry(entry); - if (View) { - return new View(entry); - } + return new View(entry); }); this.onChanged = onChanged; } @@ -195,7 +202,7 @@ class TilesListView extends ListView { onUpdate(index: number, value: SimpleTile, param: any) { if (param === "shape") { - const ExpectedClass = viewClassForEntry(value); + const ExpectedClass = this.viewClassForEntry(value); const child = this.getChildInstanceByIndex(index); if (!ExpectedClass || !(child instanceof ExpectedClass)) { // shape was updated, so we need to recreate the tile view, diff --git a/src/platform/web/ui/session/room/common.ts b/src/platform/web/ui/session/room/common.ts index 5048211a..87997cc4 100644 --- a/src/platform/web/ui/session/room/common.ts +++ b/src/platform/web/ui/session/room/common.ts @@ -24,14 +24,10 @@ import {AnnouncementView} from "./timeline/AnnouncementView.js"; import {RedactedView} from "./timeline/RedactedView.js"; import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js"; import {GapView} from "./timeline/GapView.js"; +import type {TileViewConstructor, ViewClassForEntryFn} from "./TimelineView"; -export type TileView = GapView | AnnouncementView | TextMessageView | - ImageView | VideoView | FileView | LocationView | MissingAttachmentView | RedactedView; - -// TODO: this is what works for a ctor but doesn't actually check we constrain the returned ctors to the types above -type TileViewConstructor = (this: TileView, SimpleTile) => void; -export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | undefined { - switch (entry.shape) { +export function viewClassForEntry(vm: SimpleTile): TileViewConstructor { + switch (vm.shape) { case "gap": return GapView; case "announcement": @@ -51,5 +47,7 @@ export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | unde return MissingAttachmentView; case "redacted": return RedactedView; + default: + throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tilesCreator function in the view model`); } }