forked from mystiq/hydrogen-web
convert TimelineView to typescript
This commit is contained in:
parent
632d29795a
commit
ad4ec5f04c
4 changed files with 51 additions and 31 deletions
|
@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
Loading…
Reference in a new issue