forked from mystiq/hydrogen-web
allow to inject custom tile view creator fn into timeline view
This commit is contained in:
parent
88482292e1
commit
6aa79cf6e2
4 changed files with 24 additions and 16 deletions
|
@ -27,6 +27,8 @@ export {RoomViewModel} from "./domain/session/room/RoomViewModel.js";
|
||||||
export {RoomView} from "./platform/web/ui/session/room/RoomView.js";
|
export {RoomView} from "./platform/web/ui/session/room/RoomView.js";
|
||||||
export {TimelineViewModel} from "./domain/session/room/timeline/TimelineViewModel.js";
|
export {TimelineViewModel} from "./domain/session/room/timeline/TimelineViewModel.js";
|
||||||
export {TimelineView} from "./platform/web/ui/session/room/TimelineView";
|
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 {Navigation} from "./domain/navigation/Navigation.js";
|
||||||
export {ComposerViewModel} from "./domain/session/room/ComposerViewModel.js";
|
export {ComposerViewModel} from "./domain/session/room/ComposerViewModel.js";
|
||||||
export {MessageComposer} from "./platform/web/ui/session/room/MessageComposer.js";
|
export {MessageComposer} from "./platform/web/ui/session/room/MessageComposer.js";
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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";
|
||||||
import {AvatarView} from "../../AvatarView.js";
|
import {AvatarView} from "../../AvatarView.js";
|
||||||
|
import {viewClassForEntry} from "./common";
|
||||||
|
|
||||||
export class RoomView extends TemplateView {
|
export class RoomView extends TemplateView {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
@ -54,7 +55,7 @@ export class RoomView extends TemplateView {
|
||||||
t.div({className: "RoomView_error"}, vm => vm.error),
|
t.div({className: "RoomView_error"}, vm => vm.error),
|
||||||
t.mapView(vm => vm.timelineViewModel, timelineViewModel => {
|
t.mapView(vm => vm.timelineViewModel, timelineViewModel => {
|
||||||
return timelineViewModel ?
|
return timelineViewModel ?
|
||||||
new TimelineView(timelineViewModel) :
|
new TimelineView(timelineViewModel, viewClassForEntry) :
|
||||||
new TimelineLoadingView(vm); // vm is just needed for i18n
|
new TimelineLoadingView(vm); // vm is just needed for i18n
|
||||||
}),
|
}),
|
||||||
t.view(bottomView),
|
t.view(bottomView),
|
||||||
|
|
|
@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {TileView} from "./common";
|
|
||||||
import {viewClassForEntry} from "./common";
|
|
||||||
import {ListView} from "../../general/ListView";
|
import {ListView} from "../../general/ListView";
|
||||||
|
import type {IView} from "../../general/types";
|
||||||
import {TemplateView, Builder} from "../../general/TemplateView";
|
import {TemplateView, Builder} from "../../general/TemplateView";
|
||||||
import {IObservableValue} from "../../general/BaseUpdateView";
|
import {IObservableValue} from "../../general/BaseUpdateView";
|
||||||
import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js";
|
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 {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js";
|
||||||
import {BaseObservableList as ObservableList} from "../../../../../observable/list/BaseObservableList";
|
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";
|
//import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js";
|
||||||
export interface TimelineViewModel extends IObservableValue {
|
export interface TimelineViewModel extends IObservableValue {
|
||||||
showJumpDown: boolean;
|
showJumpDown: boolean;
|
||||||
|
@ -55,13 +60,17 @@ export class TimelineView extends TemplateView<TimelineViewModel> {
|
||||||
private tilesView?: TilesListView;
|
private tilesView?: TilesListView;
|
||||||
private resizeObserver?: ResizeObserver;
|
private resizeObserver?: ResizeObserver;
|
||||||
|
|
||||||
|
constructor(vm: TimelineViewModel, private readonly viewClassForEntry: ViewClassForEntryFn) {
|
||||||
|
super(vm);
|
||||||
|
}
|
||||||
|
|
||||||
render(t: Builder<TimelineViewModel>, vm: TimelineViewModel) {
|
render(t: Builder<TimelineViewModel>, vm: TimelineViewModel) {
|
||||||
// assume this view will be mounted in the parent DOM straight away
|
// assume this view will be mounted in the parent DOM straight away
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
// do initial scroll positioning
|
// do initial scroll positioning
|
||||||
this.restoreScrollPosition();
|
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"}, [
|
const root = t.div({className: "Timeline"}, [
|
||||||
t.div({
|
t.div({
|
||||||
className: "Timeline_scroller bottom-aligned-scroll",
|
className: "Timeline_scroller bottom-aligned-scroll",
|
||||||
|
@ -174,16 +183,14 @@ class TilesListView extends ListView<SimpleTile, TileView> {
|
||||||
|
|
||||||
private onChanged: () => void;
|
private onChanged: () => void;
|
||||||
|
|
||||||
constructor(tiles: ObservableList<SimpleTile>, onChanged: () => void) {
|
constructor(tiles: ObservableList<SimpleTile>, onChanged: () => void, private readonly viewClassForEntry: ViewClassForEntryFn) {
|
||||||
const options = {
|
const options = {
|
||||||
list: tiles,
|
list: 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) {
|
|
||||||
return new View(entry);
|
return new View(entry);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.onChanged = onChanged;
|
this.onChanged = onChanged;
|
||||||
}
|
}
|
||||||
|
@ -195,7 +202,7 @@ class TilesListView extends ListView<SimpleTile, TileView> {
|
||||||
|
|
||||||
onUpdate(index: number, value: SimpleTile, param: any) {
|
onUpdate(index: number, value: SimpleTile, param: any) {
|
||||||
if (param === "shape") {
|
if (param === "shape") {
|
||||||
const ExpectedClass = viewClassForEntry(value);
|
const ExpectedClass = this.viewClassForEntry(value);
|
||||||
const child = this.getChildInstanceByIndex(index);
|
const child = this.getChildInstanceByIndex(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,
|
||||||
|
|
|
@ -24,14 +24,10 @@ 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 {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js";
|
||||||
import {GapView} from "./timeline/GapView.js";
|
import {GapView} from "./timeline/GapView.js";
|
||||||
|
import type {TileViewConstructor, ViewClassForEntryFn} from "./TimelineView";
|
||||||
|
|
||||||
export type TileView = GapView | AnnouncementView | TextMessageView |
|
export function viewClassForEntry(vm: SimpleTile): TileViewConstructor {
|
||||||
ImageView | VideoView | FileView | LocationView | MissingAttachmentView | RedactedView;
|
switch (vm.shape) {
|
||||||
|
|
||||||
// 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) {
|
|
||||||
case "gap":
|
case "gap":
|
||||||
return GapView;
|
return GapView;
|
||||||
case "announcement":
|
case "announcement":
|
||||||
|
@ -51,5 +47,7 @@ export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | unde
|
||||||
return MissingAttachmentView;
|
return MissingAttachmentView;
|
||||||
case "redacted":
|
case "redacted":
|
||||||
return RedactedView;
|
return RedactedView;
|
||||||
|
default:
|
||||||
|
throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tilesCreator function in the view model`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue