convert TimelineView to typescript

This commit is contained in:
Bruno Windels 2021-09-06 17:51:32 +02:00
parent 632d29795a
commit ad4ec5f04c
4 changed files with 51 additions and 31 deletions

View file

@ -24,7 +24,7 @@ interface IOptions<T, V> {
onItemClick?: (childView: V, evt: UIEvent) => void, onItemClick?: (childView: V, evt: UIEvent) => void,
className?: string, className?: string,
tagName?: string, tagName?: string,
parentProvidesUpdates: boolean parentProvidesUpdates?: boolean
} }
type SubscriptionHandle = () => undefined; type SubscriptionHandle = () => undefined;
@ -122,7 +122,7 @@ export class ListView<T, V extends UIView> implements UIView {
this._childInstances = undefined; this._childInstances = undefined;
} }
private loadList() { protected loadList() {
if (!this._list) { if (!this._list) {
return; return;
} }
@ -184,4 +184,8 @@ export class ListView<T, V extends UIView> implements UIView {
protected onBeforeListChanged() {} protected onBeforeListChanged() {}
protected onListChanged() {} protected onListChanged() {}
protected getChildInstanceByIndex(idx: number): V | undefined {
return this._childInstances?.[idx];
}
} }

View file

@ -17,7 +17,7 @@ limitations under the License.
import {TemplateView} from "../../general/TemplateView.js"; import {TemplateView} from "../../general/TemplateView.js";
import {Popup} from "../../general/Popup.js"; import {Popup} from "../../general/Popup.js";
import {Menu} from "../../general/Menu.js"; import {Menu} from "../../general/Menu.js";
import {viewClassForEntry} from "./TimelineView.js" import {viewClassForEntry} from "./TimelineView"
export class MessageComposer extends TemplateView { export class MessageComposer extends TemplateView {
constructor(viewModel) { constructor(viewModel) {

View file

@ -18,7 +18,7 @@ limitations under the License.
import {TemplateView} from "../../general/TemplateView.js"; import {TemplateView} from "../../general/TemplateView.js";
import {Popup} from "../../general/Popup.js"; import {Popup} from "../../general/Popup.js";
import {Menu} from "../../general/Menu.js"; import {Menu} from "../../general/Menu.js";
import {TimelineView} from "./TimelineView.js"; import {TimelineView} from "./TimelineView";
import {TimelineLoadingView} from "./TimelineLoadingView.js"; import {TimelineLoadingView} from "./TimelineLoadingView.js";
import {MessageComposer} from "./MessageComposer.js"; import {MessageComposer} from "./MessageComposer.js";
import {RoomArchivedView} from "./RoomArchivedView.js"; import {RoomArchivedView} from "./RoomArchivedView.js";

View file

@ -23,8 +23,14 @@ import {FileView} from "./timeline/FileView.js";
import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js"; import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js";
import {AnnouncementView} from "./timeline/AnnouncementView.js"; import {AnnouncementView} from "./timeline/AnnouncementView.js";
import {RedactedView} from "./timeline/RedactedView.js"; import {RedactedView} from "./timeline/RedactedView.js";
import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js";
import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js";
export function viewClassForEntry(entry) { type TileView = GapView | AnnouncementView | TextMessageView |
ImageView | VideoView | FileView | MissingAttachmentView | RedactedView;
type TileViewConstructor = (this: TileView, SimpleTile) => void;
export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | undefined {
switch (entry.shape) { switch (entry.shape) {
case "gap": return GapView; case "gap": return GapView;
case "announcement": return AnnouncementView; case "announcement": return AnnouncementView;
@ -40,13 +46,18 @@ export function viewClassForEntry(entry) {
} }
} }
export class TimelineView extends ListView { export class TimelineView extends ListView<SimpleTile, TileView> {
constructor(viewModel) {
private _atBottom: boolean;
private _topLoadingPromise?: Promise<boolean>;
private _viewModel: TimelineViewModel;
constructor(viewModel: TimelineViewModel) {
const options = { const options = {
className: "Timeline bottom-aligned-scroll", className: "Timeline bottom-aligned-scroll",
list: viewModel.tiles, list: viewModel.tiles,
onItemClick: (tileView, evt) => tileView.onClick(evt), onItemClick: (tileView, evt) => tileView.onClick(evt),
} };
super(options, entry => { super(options, entry => {
const View = viewClassForEntry(entry); const View = viewClassForEntry(entry);
if (View) { if (View) {
@ -54,12 +65,19 @@ export class TimelineView extends ListView {
} }
}); });
this._atBottom = false; this._atBottom = false;
this._onScroll = this._onScroll.bind(this); this._topLoadingPromise = undefined;
this._topLoadingPromise = null;
this._viewModel = viewModel; this._viewModel = viewModel;
} }
async _loadAtTopWhile(predicate) { override handleEvent(evt: Event) {
if (evt.type === "scroll") {
this._handleScroll(evt);
} else {
super.handleEvent(evt);
}
}
async _loadAtTopWhile(predicate: () => boolean) {
if (this._topLoadingPromise) { if (this._topLoadingPromise) {
return; return;
} }
@ -78,11 +96,11 @@ export class TimelineView extends ListView {
//ignore error, as it is handled in the VM //ignore error, as it is handled in the VM
} }
finally { finally {
this._topLoadingPromise = null; this._topLoadingPromise = undefined;
} }
} }
async _onScroll() { async _handleScroll(evt: Event) {
const PAGINATE_OFFSET = 100; const PAGINATE_OFFSET = 100;
const root = this.root(); const root = this.root();
if (root.scrollTop < PAGINATE_OFFSET && !this._topLoadingPromise && this._viewModel) { if (root.scrollTop < PAGINATE_OFFSET && !this._topLoadingPromise && this._viewModel) {
@ -102,18 +120,18 @@ export class TimelineView extends ListView {
} }
} }
mount() { override mount() {
const root = super.mount(); const root = super.mount();
root.addEventListener("scroll", this._onScroll); root.addEventListener("scroll", this);
return root; return root;
} }
unmount() { override unmount() {
this.root().removeEventListener("scroll", this._onScroll); this.root().removeEventListener("scroll", this);
super.unmount(); super.unmount();
} }
async loadList() { override async loadList() {
super.loadList(); super.loadList();
const root = this.root(); const root = this.root();
// yield so the browser can render the list // yield so the browser can render the list
@ -130,7 +148,7 @@ export class TimelineView extends ListView {
}); });
} }
onBeforeListChanged() { override onBeforeListChanged() {
const fromBottom = this._distanceFromBottom(); const fromBottom = this._distanceFromBottom();
this._atBottom = fromBottom < 1; this._atBottom = fromBottom < 1;
} }
@ -140,25 +158,23 @@ export class TimelineView extends ListView {
return root.scrollHeight - root.scrollTop - root.clientHeight; return root.scrollHeight - root.scrollTop - root.clientHeight;
} }
onListChanged() { override onListChanged() {
const root = this.root(); const root = this.root();
if (this._atBottom) { if (this._atBottom) {
root.scrollTop = root.scrollHeight; root.scrollTop = root.scrollHeight;
} }
} }
onUpdate(index, value, param) { override onUpdate(index: number, value: SimpleTile, param: any) {
if (param === "shape") { if (param === "shape") {
if (this._childInstances) { const ExpectedClass = viewClassForEntry(value);
const ExpectedClass = viewClassForEntry(value); const child = this.getChildInstanceByIndex(index);
const child = this._childInstances[index]; if (!ExpectedClass || !(child instanceof ExpectedClass)) {
if (!ExpectedClass || !(child instanceof ExpectedClass)) { // shape was updated, so we need to recreate the tile view,
// shape was updated, so we need to recreate the tile view, // the shape parameter is set in EncryptedEventTile.updateEntry
// the shape parameter is set in EncryptedEventTile.updateEntry // (and perhaps elsewhere by the time you read this)
// (and perhaps elsewhere by the time you read this) super.recreateItem(index, value);
super.recreateItem(index, value); return;
return;
}
} }
} }
super.onUpdate(index, value, param); super.onUpdate(index, value, param);