Merge pull request #715 from vector-im/bwindels/rename-viewclassfortile

Some timeline refactoring and also make reply tiles of correct custom view class
This commit is contained in:
Bruno Windels 2022-04-08 15:19:39 +02:00 committed by GitHub
commit 4cbd149c25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 56 additions and 32 deletions

View file

@ -311,7 +311,7 @@ export function tests() {
} }
const entries = new ObservableArray([{n: 5}, {n: 10}]); const entries = new ObservableArray([{n: 5}, {n: 10}]);
const tileOptions = { const tileOptions = {
tileClassForEntry: entry => UpdateOnSiblingTile, tileClassForEntry: () => UpdateOnSiblingTile,
}; };
const tiles = new TilesCollection(entries, tileOptions); const tiles = new TilesCollection(entries, tileOptions);
let receivedAdd = false; let receivedAdd = false;
@ -337,7 +337,7 @@ export function tests() {
} }
const entries = new ObservableArray([{n: 5}, {n: 10}, {n: 15}]); const entries = new ObservableArray([{n: 5}, {n: 10}, {n: 15}]);
const tileOptions = { const tileOptions = {
tileClassForEntry: entry => UpdateOnSiblingTile, tileClassForEntry: () => UpdateOnSiblingTile,
}; };
const tiles = new TilesCollection(entries, tileOptions); const tiles = new TilesCollection(entries, tileOptions);
const events = []; const events = [];

View file

@ -44,7 +44,7 @@ export {MissingAttachmentTile} from "./domain/session/room/timeline/tiles/Missin
export {SimpleTile} from "./domain/session/room/timeline/tiles/SimpleTile.js"; export {SimpleTile} from "./domain/session/room/timeline/tiles/SimpleTile.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 {viewClassForTile} from "./platform/web/ui/session/room/common";
export type {TileViewConstructor, ViewClassForEntryFn} from "./platform/web/ui/session/room/TimelineView"; export type {TileViewConstructor, ViewClassForEntryFn} from "./platform/web/ui/session/room/TimelineView";
// export timeline tile views // export timeline tile views
export {AnnouncementView} from "./platform/web/ui/session/room/timeline/AnnouncementView.js"; export {AnnouncementView} from "./platform/web/ui/session/room/timeline/AnnouncementView.js";

View file

@ -21,6 +21,11 @@ import {TemplateView} from "../general/TemplateView";
import {StaticView} from "../general/StaticView.js"; import {StaticView} from "../general/StaticView.js";
export class RoomGridView extends TemplateView { export class RoomGridView extends TemplateView {
constructor(vm, viewClassForTile) {
super(vm);
this._viewClassForTile = viewClassForTile;
}
render(t, vm) { render(t, vm) {
const children = []; const children = [];
for (let i = 0; i < (vm.height * vm.width); i+=1) { for (let i = 0; i < (vm.height * vm.width); i+=1) {
@ -39,7 +44,7 @@ export class RoomGridView extends TemplateView {
} else if (roomVM.kind === "invite") { } else if (roomVM.kind === "invite") {
return new InviteView(roomVM); return new InviteView(roomVM);
} else { } else {
return new RoomView(roomVM); return new RoomView(roomVM, this._viewClassForTile);
} }
} else { } else {
return new StaticView(t => t.div({className: "room-placeholder"}, [ return new StaticView(t => t.div({className: "room-placeholder"}, [

View file

@ -28,6 +28,7 @@ import {RoomGridView} from "./RoomGridView.js";
import {SettingsView} from "./settings/SettingsView.js"; import {SettingsView} from "./settings/SettingsView.js";
import {CreateRoomView} from "./CreateRoomView.js"; import {CreateRoomView} from "./CreateRoomView.js";
import {RightPanelView} from "./rightpanel/RightPanelView.js"; import {RightPanelView} from "./rightpanel/RightPanelView.js";
import {viewClassForTile} from "./room/common";
export class SessionView extends TemplateView { export class SessionView extends TemplateView {
render(t, vm) { render(t, vm) {
@ -42,7 +43,7 @@ export class SessionView extends TemplateView {
t.view(new LeftPanelView(vm.leftPanelViewModel)), t.view(new LeftPanelView(vm.leftPanelViewModel)),
t.mapView(vm => vm.activeMiddleViewModel, () => { t.mapView(vm => vm.activeMiddleViewModel, () => {
if (vm.roomGridViewModel) { if (vm.roomGridViewModel) {
return new RoomGridView(vm.roomGridViewModel); return new RoomGridView(vm.roomGridViewModel, viewClassForTile);
} else if (vm.settingsViewModel) { } else if (vm.settingsViewModel) {
return new SettingsView(vm.settingsViewModel); return new SettingsView(vm.settingsViewModel);
} else if (vm.createRoomViewModel) { } else if (vm.createRoomViewModel) {
@ -51,7 +52,7 @@ export class SessionView extends TemplateView {
if (vm.currentRoomViewModel.kind === "invite") { if (vm.currentRoomViewModel.kind === "invite") {
return new InviteView(vm.currentRoomViewModel); return new InviteView(vm.currentRoomViewModel);
} else if (vm.currentRoomViewModel.kind === "room") { } else if (vm.currentRoomViewModel.kind === "room") {
return new RoomView(vm.currentRoomViewModel); return new RoomView(vm.currentRoomViewModel, viewClassForTile);
} else if (vm.currentRoomViewModel.kind === "roomBeingCreated") { } else if (vm.currentRoomViewModel.kind === "roomBeingCreated") {
return new RoomBeingCreatedView(vm.currentRoomViewModel); return new RoomBeingCreatedView(vm.currentRoomViewModel);
} else { } else {

View file

@ -17,11 +17,11 @@ limitations under the License.
import {TemplateView} from "../../general/TemplateView"; import {TemplateView} from "../../general/TemplateView";
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 "./common"
export class MessageComposer extends TemplateView { export class MessageComposer extends TemplateView {
constructor(viewModel) { constructor(viewModel, viewClassForTile) {
super(viewModel); super(viewModel);
this._viewClassForTile = viewClassForTile;
this._input = null; this._input = null;
this._attachmentPopup = null; this._attachmentPopup = null;
this._focusInput = null; this._focusInput = null;
@ -45,8 +45,8 @@ export class MessageComposer extends TemplateView {
this._focusInput = () => this._input.focus(); this._focusInput = () => this._input.focus();
this.value.on("focus", this._focusInput); this.value.on("focus", this._focusInput);
const replyPreview = t.map(vm => vm.replyViewModel, (rvm, t) => { const replyPreview = t.map(vm => vm.replyViewModel, (rvm, t) => {
const View = rvm && viewClassForEntry(rvm); const TileView = rvm && this._viewClassForTile(rvm);
if (!View) { return null; } if (!TileView) { return null; }
return t.div({ return t.div({
className: "MessageComposer_replyPreview" className: "MessageComposer_replyPreview"
}, [ }, [
@ -55,8 +55,8 @@ export class MessageComposer extends TemplateView {
className: "cancel", className: "cancel",
onClick: () => this._clearReplyingTo() onClick: () => this._clearReplyingTo()
}, "Close"), }, "Close"),
t.view(new View(rvm, { interactive: false }, "div")) t.view(new TileView(rvm, this._viewClassForTile, { interactive: false }, "div"))
]) ]);
}); });
const input = t.div({className: "MessageComposer_input"}, [ const input = t.div({className: "MessageComposer_input"}, [
this._input, this._input,

View file

@ -23,18 +23,18 @@ 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(vm, viewClassForTile) {
super(options); super(vm);
this._viewClassForTile = viewClassForTile;
this._optionsPopup = null; this._optionsPopup = null;
} }
render(t, vm) { render(t, vm) {
let bottomView; let bottomView;
if (vm.composerViewModel.kind === "composer") { if (vm.composerViewModel.kind === "composer") {
bottomView = new MessageComposer(vm.composerViewModel); bottomView = new MessageComposer(vm.composerViewModel, this._viewClassForTile);
} else if (vm.composerViewModel.kind === "archived") { } else if (vm.composerViewModel.kind === "archived") {
bottomView = new RoomArchivedView(vm.composerViewModel); bottomView = new RoomArchivedView(vm.composerViewModel);
} }
@ -55,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, viewClassForEntry) : new TimelineView(timelineViewModel, this._viewClassForTile) :
new TimelineLoadingView(vm); // vm is just needed for i18n new TimelineLoadingView(vm); // vm is just needed for i18n
}), }),
t.view(bottomView), t.view(bottomView),

View file

@ -28,7 +28,11 @@ export interface TileView extends IView {
readonly value: SimpleTile; readonly value: SimpleTile;
onClick(event: UIEvent); onClick(event: UIEvent);
} }
export type TileViewConstructor = new (tile: SimpleTile) => TileView; export type TileViewConstructor = new (
tile: SimpleTile,
viewClassForTile: ViewClassForEntryFn,
renderFlags?: { reply?: boolean, interactive?: boolean }
) => TileView;
export type ViewClassForEntryFn = (tile: SimpleTile) => TileViewConstructor; export type ViewClassForEntryFn = (tile: SimpleTile) => TileViewConstructor;
//import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js"; //import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js";
@ -61,7 +65,7 @@ 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) { constructor(vm: TimelineViewModel, private readonly viewClassForTile: ViewClassForEntryFn) {
super(vm); super(vm);
} }
@ -71,7 +75,7 @@ export class TimelineView extends TemplateView<TimelineViewModel> {
// do initial scroll positioning // do initial scroll positioning
this.restoreScrollPosition(); this.restoreScrollPosition();
}); });
this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition(), this.viewClassForEntry); this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition(), this.viewClassForTile);
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",
@ -184,13 +188,13 @@ class TilesListView extends ListView<SimpleTile, TileView> {
private onChanged: () => void; private onChanged: () => void;
constructor(tiles: ObservableList<SimpleTile>, onChanged: () => void, private readonly viewClassForEntry: ViewClassForEntryFn) { constructor(tiles: ObservableList<SimpleTile>, onChanged: () => void, private readonly viewClassForTile: ViewClassForEntryFn) {
super({ super({
list: tiles, list: tiles,
onItemClick: (tileView, evt) => tileView.onClick(evt), onItemClick: (tileView, evt) => tileView.onClick(evt),
}, entry => { }, tile => {
const View = viewClassForEntry(entry); const TileView = viewClassForTile(tile);
return new View(entry); return new TileView(tile, viewClassForTile);
}); });
this.onChanged = onChanged; this.onChanged = onChanged;
} }
@ -202,7 +206,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 = this.viewClassForEntry(value); const ExpectedClass = this.viewClassForTile(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,

View file

@ -26,7 +26,7 @@ import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/Simp
import {GapView} from "./timeline/GapView.js"; import {GapView} from "./timeline/GapView.js";
import type {TileViewConstructor, ViewClassForEntryFn} from "./TimelineView"; import type {TileViewConstructor, ViewClassForEntryFn} from "./TimelineView";
export function viewClassForEntry(vm: SimpleTile): TileViewConstructor { export function viewClassForTile(vm: SimpleTile): TileViewConstructor {
switch (vm.shape) { switch (vm.shape) {
case "gap": case "gap":
return GapView; return GapView;

View file

@ -17,6 +17,11 @@ limitations under the License.
import {TemplateView} from "../../../general/TemplateView"; import {TemplateView} from "../../../general/TemplateView";
export class AnnouncementView extends TemplateView { export class AnnouncementView extends TemplateView {
// ignore other arguments
constructor(vm) {
super(vm);
}
render(t) { render(t) {
return t.li({className: "AnnouncementView"}, t.div(vm => vm.announcement)); return t.li({className: "AnnouncementView"}, t.div(vm => vm.announcement));
} }

View file

@ -24,10 +24,11 @@ import {Menu} from "../../../general/Menu.js";
import {ReactionsView} from "./ReactionsView.js"; import {ReactionsView} from "./ReactionsView.js";
export class BaseMessageView extends TemplateView { export class BaseMessageView extends TemplateView {
constructor(value, renderFlags, tagName = "li") { constructor(value, viewClassForTile, renderFlags, tagName = "li") {
super(value); super(value);
this._menuPopup = null; this._menuPopup = null;
this._tagName = tagName; this._tagName = tagName;
this._viewClassForTile = viewClassForTile;
// TODO An enum could be nice to make code easier to read at call sites. // TODO An enum could be nice to make code easier to read at call sites.
this._renderFlags = renderFlags; this._renderFlags = renderFlags;
} }

View file

@ -18,6 +18,11 @@ import {TemplateView} from "../../../general/TemplateView";
import {spinner} from "../../../common.js"; import {spinner} from "../../../common.js";
export class GapView extends TemplateView { export class GapView extends TemplateView {
// ignore other argument
constructor(vm) {
super(vm);
}
render(t) { render(t) {
const className = { const className = {
GapView: true, GapView: true,

View file

@ -16,15 +16,18 @@ limitations under the License.
import {renderStaticAvatar} from "../../../avatar"; import {renderStaticAvatar} from "../../../avatar";
import {TemplateView} from "../../../general/TemplateView"; import {TemplateView} from "../../../general/TemplateView";
import {viewClassForEntry} from "../common";
export class ReplyPreviewView extends TemplateView { export class ReplyPreviewView extends TemplateView {
constructor(vm, viewClassForTile) {
super(vm);
this._viewClassForTile = viewClassForTile;
}
render(t, vm) { render(t, vm) {
const viewClass = viewClassForEntry(vm); const TileView = this._viewClassForTile(vm);
if (!viewClass) { if (!TileView) {
throw new Error(`Shape ${vm.shape} is unrecognized.`) throw new Error(`Shape ${vm.shape} is unrecognized.`)
} }
const view = new viewClass(vm, { reply: true, interactive: false }); const view = new TileView(vm, this._viewClassForTile, { reply: true, interactive: false });
return t.div( return t.div(
{ className: "ReplyPreviewView" }, { className: "ReplyPreviewView" },
t.blockquote([ t.blockquote([

View file

@ -35,7 +35,7 @@ export class TextMessageView extends BaseMessageView {
return new ReplyPreviewError(); return new ReplyPreviewError();
} }
else if (replyTile) { else if (replyTile) {
return new ReplyPreviewView(replyTile); return new ReplyPreviewView(replyTile, this._viewClassForTile);
} }
else { else {
return null; return null;