allow injecting the tilesCreator from the Root/Session/RoomViewModel
this changes the API slightly to be more future-proof, as we'll expose it in the SDK now. The function now returns a SimpleTile constructor, rather than an instance. This allows us to test if an entry would render in the timeline without creating a tile, which is something we might want in the matrix layer later on. The function is now called tileClassForEntry, analogue to what we do in TimelineView.
This commit is contained in:
parent
220f35ae03
commit
5445db2a42
17 changed files with 174 additions and 133 deletions
|
@ -18,17 +18,17 @@ limitations under the License.
|
||||||
import {TimelineViewModel} from "./timeline/TimelineViewModel.js";
|
import {TimelineViewModel} from "./timeline/TimelineViewModel.js";
|
||||||
import {ComposerViewModel} from "./ComposerViewModel.js"
|
import {ComposerViewModel} from "./ComposerViewModel.js"
|
||||||
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
|
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
|
||||||
import {tilesCreator} from "./timeline/tilesCreator.js";
|
|
||||||
import {ViewModel} from "../../ViewModel";
|
import {ViewModel} from "../../ViewModel";
|
||||||
import {imageToInfo} from "../common.js";
|
import {imageToInfo} from "../common.js";
|
||||||
|
|
||||||
export class RoomViewModel extends ViewModel {
|
export class RoomViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
const {room} = options;
|
const {room, tileClassForEntry} = options;
|
||||||
this._room = room;
|
this._room = room;
|
||||||
this._timelineVM = null;
|
this._timelineVM = null;
|
||||||
this._tilesCreator = null;
|
this._tileClassForEntry = tileClassForEntry;
|
||||||
|
this._tileOptions = undefined;
|
||||||
this._onRoomChange = this._onRoomChange.bind(this);
|
this._onRoomChange = this._onRoomChange.bind(this);
|
||||||
this._timelineError = null;
|
this._timelineError = null;
|
||||||
this._sendError = null;
|
this._sendError = null;
|
||||||
|
@ -46,12 +46,13 @@ export class RoomViewModel extends ViewModel {
|
||||||
this._room.on("change", this._onRoomChange);
|
this._room.on("change", this._onRoomChange);
|
||||||
try {
|
try {
|
||||||
const timeline = await this._room.openTimeline();
|
const timeline = await this._room.openTimeline();
|
||||||
this._tilesCreator = tilesCreator(this.childOptions({
|
this._tileOptions = this.childOptions({
|
||||||
roomVM: this,
|
roomVM: this,
|
||||||
timeline,
|
timeline,
|
||||||
}));
|
tileClassForEntry: this._tileClassForEntry,
|
||||||
|
});
|
||||||
this._timelineVM = this.track(new TimelineViewModel(this.childOptions({
|
this._timelineVM = this.track(new TimelineViewModel(this.childOptions({
|
||||||
tilesCreator: this._tilesCreator,
|
tileOptions: this._tileOptions,
|
||||||
timeline,
|
timeline,
|
||||||
})));
|
})));
|
||||||
this.emitChange("timelineViewModel");
|
this.emitChange("timelineViewModel");
|
||||||
|
@ -161,7 +162,12 @@ export class RoomViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
_createTile(entry) {
|
_createTile(entry) {
|
||||||
return this._tilesCreator(entry);
|
if (this._tileOptions) {
|
||||||
|
const Tile = this._tileOptions.tileClassForEntry(entry);
|
||||||
|
if (Tile) {
|
||||||
|
return new Tile(entry, this._tileOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _sendMessage(message, replyingTo) {
|
async _sendMessage(message, replyingTo) {
|
||||||
|
|
|
@ -222,7 +222,7 @@ export function tests() {
|
||||||
};
|
};
|
||||||
const tiles = new MappedList(timeline.entries, entry => {
|
const tiles = new MappedList(timeline.entries, entry => {
|
||||||
if (entry.eventType === "m.room.message") {
|
if (entry.eventType === "m.room.message") {
|
||||||
return new BaseMessageTile({entry, roomVM: {room}, timeline, platform: {logger}});
|
return new BaseMessageTile(entry, {roomVM: {room}, timeline, platform: {logger}});
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, (tile, params, entry) => tile?.updateEntry(entry, params, function () {}));
|
}, (tile, params, entry) => tile?.updateEntry(entry, params, function () {}));
|
||||||
|
|
|
@ -18,20 +18,27 @@ import {BaseObservableList} from "../../../../observable/list/BaseObservableList
|
||||||
import {sortedIndex} from "../../../../utils/sortedIndex";
|
import {sortedIndex} from "../../../../utils/sortedIndex";
|
||||||
|
|
||||||
// maps 1..n entries to 0..1 tile. Entries are what is stored in the timeline, either an event or fragmentboundary
|
// maps 1..n entries to 0..1 tile. Entries are what is stored in the timeline, either an event or fragmentboundary
|
||||||
// for now, tileCreator should be stable in whether it returns a tile or not.
|
// for now, tileClassForEntry should be stable in whether it returns a tile or not.
|
||||||
// e.g. the decision to create a tile or not should be based on properties
|
// e.g. the decision to create a tile or not should be based on properties
|
||||||
// not updated later on (e.g. event type)
|
// not updated later on (e.g. event type)
|
||||||
// also see big comment in onUpdate
|
// also see big comment in onUpdate
|
||||||
export class TilesCollection extends BaseObservableList {
|
export class TilesCollection extends BaseObservableList {
|
||||||
constructor(entries, tileCreator) {
|
constructor(entries, tileOptions) {
|
||||||
super();
|
super();
|
||||||
this._entries = entries;
|
this._entries = entries;
|
||||||
this._tiles = null;
|
this._tiles = null;
|
||||||
this._entrySubscription = null;
|
this._entrySubscription = null;
|
||||||
this._tileCreator = tileCreator;
|
this._tileOptions = tileOptions;
|
||||||
this._emitSpontanousUpdate = this._emitSpontanousUpdate.bind(this);
|
this._emitSpontanousUpdate = this._emitSpontanousUpdate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_createTile(entry) {
|
||||||
|
const Tile = this._tileOptions.tileClassForEntry(entry);
|
||||||
|
if (Tile) {
|
||||||
|
return new Tile(entry, this._tileOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_emitSpontanousUpdate(tile, params) {
|
_emitSpontanousUpdate(tile, params) {
|
||||||
const entry = tile.lowerEntry;
|
const entry = tile.lowerEntry;
|
||||||
const tileIdx = this._findTileIdx(entry);
|
const tileIdx = this._findTileIdx(entry);
|
||||||
|
@ -48,7 +55,7 @@ export class TilesCollection extends BaseObservableList {
|
||||||
let currentTile = null;
|
let currentTile = null;
|
||||||
for (let entry of this._entries) {
|
for (let entry of this._entries) {
|
||||||
if (!currentTile || !currentTile.tryIncludeEntry(entry)) {
|
if (!currentTile || !currentTile.tryIncludeEntry(entry)) {
|
||||||
currentTile = this._tileCreator(entry);
|
currentTile = this._createTile(entry);
|
||||||
if (currentTile) {
|
if (currentTile) {
|
||||||
this._tiles.push(currentTile);
|
this._tiles.push(currentTile);
|
||||||
}
|
}
|
||||||
|
@ -121,7 +128,7 @@ export class TilesCollection extends BaseObservableList {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTile = this._tileCreator(entry);
|
const newTile = this._createTile(entry);
|
||||||
if (newTile) {
|
if (newTile) {
|
||||||
if (prevTile) {
|
if (prevTile) {
|
||||||
prevTile.updateNextSibling(newTile);
|
prevTile.updateNextSibling(newTile);
|
||||||
|
@ -150,9 +157,9 @@ export class TilesCollection extends BaseObservableList {
|
||||||
const tileIdx = this._findTileIdx(entry);
|
const tileIdx = this._findTileIdx(entry);
|
||||||
const tile = this._findTileAtIdx(entry, tileIdx);
|
const tile = this._findTileAtIdx(entry, tileIdx);
|
||||||
if (tile) {
|
if (tile) {
|
||||||
const action = tile.updateEntry(entry, params, this._tileCreator);
|
const action = tile.updateEntry(entry, params);
|
||||||
if (action.shouldReplace) {
|
if (action.shouldReplace) {
|
||||||
const newTile = this._tileCreator(entry);
|
const newTile = this._createTile(entry);
|
||||||
if (newTile) {
|
if (newTile) {
|
||||||
this._replaceTile(tileIdx, tile, newTile, action.updateParams);
|
this._replaceTile(tileIdx, tile, newTile, action.updateParams);
|
||||||
newTile.setUpdateEmit(this._emitSpontanousUpdate);
|
newTile.setUpdateEmit(this._emitSpontanousUpdate);
|
||||||
|
@ -303,7 +310,10 @@ export function tests() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const entries = new ObservableArray([{n: 5}, {n: 10}]);
|
const entries = new ObservableArray([{n: 5}, {n: 10}]);
|
||||||
const tiles = new TilesCollection(entries, entry => new UpdateOnSiblingTile(entry));
|
const tileOptions = {
|
||||||
|
tileClassForEntry: entry => UpdateOnSiblingTile,
|
||||||
|
};
|
||||||
|
const tiles = new TilesCollection(entries, tileOptions);
|
||||||
let receivedAdd = false;
|
let receivedAdd = false;
|
||||||
tiles.subscribe({
|
tiles.subscribe({
|
||||||
onAdd(idx, tile) {
|
onAdd(idx, tile) {
|
||||||
|
@ -326,7 +336,10 @@ export function tests() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const entries = new ObservableArray([{n: 5}, {n: 10}, {n: 15}]);
|
const entries = new ObservableArray([{n: 5}, {n: 10}, {n: 15}]);
|
||||||
const tiles = new TilesCollection(entries, entry => new UpdateOnSiblingTile(entry));
|
const tileOptions = {
|
||||||
|
tileClassForEntry: entry => UpdateOnSiblingTile,
|
||||||
|
};
|
||||||
|
const tiles = new TilesCollection(entries, tileOptions);
|
||||||
const events = [];
|
const events = [];
|
||||||
tiles.subscribe({
|
tiles.subscribe({
|
||||||
onUpdate(idx, tile) {
|
onUpdate(idx, tile) {
|
||||||
|
|
|
@ -37,9 +37,9 @@ import {ViewModel} from "../../../ViewModel";
|
||||||
export class TimelineViewModel extends ViewModel {
|
export class TimelineViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
const {timeline, tilesCreator} = options;
|
const {timeline, tileOptions} = options;
|
||||||
this._timeline = this.track(timeline);
|
this._timeline = this.track(timeline);
|
||||||
this._tiles = new TilesCollection(timeline.entries, tilesCreator);
|
this._tiles = new TilesCollection(timeline.entries, tileOptions);
|
||||||
this._startTile = null;
|
this._startTile = null;
|
||||||
this._endTile = null;
|
this._endTile = null;
|
||||||
this._topLoadingPromise = null;
|
this._topLoadingPromise = null;
|
||||||
|
|
|
@ -21,8 +21,8 @@ const MAX_HEIGHT = 300;
|
||||||
const MAX_WIDTH = 400;
|
const MAX_WIDTH = 400;
|
||||||
|
|
||||||
export class BaseMediaTile extends BaseMessageTile {
|
export class BaseMediaTile extends BaseMessageTile {
|
||||||
constructor(options) {
|
constructor(entry, options) {
|
||||||
super(options);
|
super(entry, options);
|
||||||
this._decryptedThumbnail = null;
|
this._decryptedThumbnail = null;
|
||||||
this._decryptedFile = null;
|
this._decryptedFile = null;
|
||||||
this._isVisible = false;
|
this._isVisible = false;
|
||||||
|
|
|
@ -19,8 +19,8 @@ import {ReactionsViewModel} from "../ReactionsViewModel.js";
|
||||||
import {getIdentifierColorNumber, avatarInitials, getAvatarHttpUrl} from "../../../../avatar";
|
import {getIdentifierColorNumber, avatarInitials, getAvatarHttpUrl} from "../../../../avatar";
|
||||||
|
|
||||||
export class BaseMessageTile extends SimpleTile {
|
export class BaseMessageTile extends SimpleTile {
|
||||||
constructor(options) {
|
constructor(entry, options) {
|
||||||
super(options);
|
super(entry, options);
|
||||||
this._date = this._entry.timestamp ? new Date(this._entry.timestamp) : null;
|
this._date = this._entry.timestamp ? new Date(this._entry.timestamp) : null;
|
||||||
this._isContinuation = false;
|
this._isContinuation = false;
|
||||||
this._reactions = null;
|
this._reactions = null;
|
||||||
|
@ -28,7 +28,7 @@ export class BaseMessageTile extends SimpleTile {
|
||||||
if (this._entry.annotations || this._entry.pendingAnnotations) {
|
if (this._entry.annotations || this._entry.pendingAnnotations) {
|
||||||
this._updateReactions();
|
this._updateReactions();
|
||||||
}
|
}
|
||||||
this._updateReplyTileIfNeeded(options.tilesCreator, undefined);
|
this._updateReplyTileIfNeeded(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyVisible() {
|
notifyVisible() {
|
||||||
|
@ -122,23 +122,27 @@ export class BaseMessageTile extends SimpleTile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEntry(entry, param, tilesCreator) {
|
updateEntry(entry, param) {
|
||||||
const action = super.updateEntry(entry, param, tilesCreator);
|
const action = super.updateEntry(entry, param);
|
||||||
if (action.shouldUpdate) {
|
if (action.shouldUpdate) {
|
||||||
this._updateReactions();
|
this._updateReactions();
|
||||||
}
|
}
|
||||||
this._updateReplyTileIfNeeded(tilesCreator, param);
|
this._updateReplyTileIfNeeded(param);
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateReplyTileIfNeeded(tilesCreator, param) {
|
_updateReplyTileIfNeeded(param) {
|
||||||
const replyEntry = this._entry.contextEntry;
|
const replyEntry = this._entry.contextEntry;
|
||||||
if (replyEntry) {
|
if (replyEntry) {
|
||||||
// this is an update to contextEntry used for replyPreview
|
// this is an update to contextEntry used for replyPreview
|
||||||
const action = this._replyTile?.updateEntry(replyEntry, param, tilesCreator);
|
const action = this._replyTile?.updateEntry(replyEntry, param);
|
||||||
if (action?.shouldReplace || !this._replyTile) {
|
if (action?.shouldReplace || !this._replyTile) {
|
||||||
this.disposeTracked(this._replyTile);
|
this.disposeTracked(this._replyTile);
|
||||||
this._replyTile = tilesCreator(replyEntry);
|
const tileClassForEntry = this._options.tileClassForEntry;
|
||||||
|
const ReplyTile = tileClassForEntry(replyEntry);
|
||||||
|
if (ReplyTile) {
|
||||||
|
this._replyTile = new ReplyTile(replyEntry, this._options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(action?.shouldUpdate) {
|
if(action?.shouldUpdate) {
|
||||||
this._replyTile?.emitChange();
|
this._replyTile?.emitChange();
|
||||||
|
|
|
@ -21,8 +21,8 @@ import {createEnum} from "../../../../../utils/enum";
|
||||||
export const BodyFormat = createEnum("Plain", "Html");
|
export const BodyFormat = createEnum("Plain", "Html");
|
||||||
|
|
||||||
export class BaseTextTile extends BaseMessageTile {
|
export class BaseTextTile extends BaseMessageTile {
|
||||||
constructor(options) {
|
constructor(entry, options) {
|
||||||
super(options);
|
super(entry, options);
|
||||||
this._messageBody = null;
|
this._messageBody = null;
|
||||||
this._format = null
|
this._format = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ import {BaseTextTile} from "./BaseTextTile.js";
|
||||||
import {UpdateAction} from "../UpdateAction.js";
|
import {UpdateAction} from "../UpdateAction.js";
|
||||||
|
|
||||||
export class EncryptedEventTile extends BaseTextTile {
|
export class EncryptedEventTile extends BaseTextTile {
|
||||||
updateEntry(entry, params, tilesCreator) {
|
updateEntry(entry, params) {
|
||||||
const parentResult = super.updateEntry(entry, params, tilesCreator);
|
const parentResult = super.updateEntry(entry, params);
|
||||||
// event got decrypted, recreate the tile and replace this one with it
|
// event got decrypted, recreate the tile and replace this one with it
|
||||||
if (entry.eventType !== "m.room.encrypted") {
|
if (entry.eventType !== "m.room.encrypted") {
|
||||||
// the "shape" parameter trigger tile recreation in TimelineView
|
// the "shape" parameter trigger tile recreation in TimelineView
|
||||||
|
|
|
@ -20,8 +20,8 @@ import {formatSize} from "../../../../../utils/formatSize";
|
||||||
import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js";
|
import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js";
|
||||||
|
|
||||||
export class FileTile extends BaseMessageTile {
|
export class FileTile extends BaseMessageTile {
|
||||||
constructor(options) {
|
constructor(entry, options) {
|
||||||
super(options);
|
super(entry, options);
|
||||||
this._downloadError = null;
|
this._downloadError = null;
|
||||||
this._downloading = false;
|
this._downloading = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ import {SimpleTile} from "./SimpleTile.js";
|
||||||
import {UpdateAction} from "../UpdateAction.js";
|
import {UpdateAction} from "../UpdateAction.js";
|
||||||
|
|
||||||
export class GapTile extends SimpleTile {
|
export class GapTile extends SimpleTile {
|
||||||
constructor(options) {
|
constructor(entry, options) {
|
||||||
super(options);
|
super(entry, options);
|
||||||
this._loading = false;
|
this._loading = false;
|
||||||
this._error = null;
|
this._error = null;
|
||||||
this._isAtTop = true;
|
this._isAtTop = true;
|
||||||
|
@ -81,8 +81,8 @@ export class GapTile extends SimpleTile {
|
||||||
this._siblingChanged = true;
|
this._siblingChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEntry(entry, params, tilesCreator) {
|
updateEntry(entry, params) {
|
||||||
super.updateEntry(entry, params, tilesCreator);
|
super.updateEntry(entry, params);
|
||||||
if (!entry.isGap) {
|
if (!entry.isGap) {
|
||||||
return UpdateAction.Remove();
|
return UpdateAction.Remove();
|
||||||
} else {
|
} else {
|
||||||
|
@ -125,7 +125,7 @@ export function tests() {
|
||||||
tile.updateEntry(newEntry);
|
tile.updateEntry(newEntry);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const tile = new GapTile({entry: new FragmentBoundaryEntry(fragment, true), roomVM: {room}});
|
const tile = new GapTile(new FragmentBoundaryEntry(fragment, true), {roomVM: {room}});
|
||||||
await tile.fill();
|
await tile.fill();
|
||||||
await tile.fill();
|
await tile.fill();
|
||||||
await tile.fill();
|
await tile.fill();
|
||||||
|
|
|
@ -18,8 +18,8 @@ limitations under the License.
|
||||||
import {BaseMediaTile} from "./BaseMediaTile.js";
|
import {BaseMediaTile} from "./BaseMediaTile.js";
|
||||||
|
|
||||||
export class ImageTile extends BaseMediaTile {
|
export class ImageTile extends BaseMediaTile {
|
||||||
constructor(options) {
|
constructor(entry, options) {
|
||||||
super(options);
|
super(entry, options);
|
||||||
this._lightboxUrl = this.urlCreator.urlForSegments([
|
this._lightboxUrl = this.urlCreator.urlForSegments([
|
||||||
// ensure the right room is active if in grid view
|
// ensure the right room is active if in grid view
|
||||||
this.navigation.segment("room", this._room.id),
|
this.navigation.segment("room", this._room.id),
|
||||||
|
|
|
@ -66,23 +66,25 @@ export class RoomMemberTile extends SimpleTile {
|
||||||
export function tests() {
|
export function tests() {
|
||||||
return {
|
return {
|
||||||
"user removes display name": (assert) => {
|
"user removes display name": (assert) => {
|
||||||
const tile = new RoomMemberTile({
|
const tile = new RoomMemberTile(
|
||||||
entry: {
|
{
|
||||||
prevContent: {displayname: "foo", membership: "join"},
|
prevContent: {displayname: "foo", membership: "join"},
|
||||||
content: {membership: "join"},
|
content: {membership: "join"},
|
||||||
stateKey: "foo@bar.com",
|
stateKey: "foo@bar.com",
|
||||||
},
|
},
|
||||||
});
|
{}
|
||||||
|
);
|
||||||
assert.strictEqual(tile.announcement, "foo@bar.com removed their name (foo)");
|
assert.strictEqual(tile.announcement, "foo@bar.com removed their name (foo)");
|
||||||
},
|
},
|
||||||
"user without display name sets a new display name": (assert) => {
|
"user without display name sets a new display name": (assert) => {
|
||||||
const tile = new RoomMemberTile({
|
const tile = new RoomMemberTile(
|
||||||
entry: {
|
{
|
||||||
prevContent: {membership: "join"},
|
prevContent: {membership: "join"},
|
||||||
content: {displayname: "foo", membership: "join" },
|
content: {displayname: "foo", membership: "join" },
|
||||||
stateKey: "foo@bar.com",
|
stateKey: "foo@bar.com",
|
||||||
},
|
},
|
||||||
});
|
{}
|
||||||
|
);
|
||||||
assert.strictEqual(tile.announcement, "foo@bar.com changed their name to foo");
|
assert.strictEqual(tile.announcement, "foo@bar.com changed their name to foo");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,9 +19,9 @@ import {ViewModel} from "../../../../ViewModel";
|
||||||
import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js";
|
import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js";
|
||||||
|
|
||||||
export class SimpleTile extends ViewModel {
|
export class SimpleTile extends ViewModel {
|
||||||
constructor(options) {
|
constructor(entry, options) {
|
||||||
super(options);
|
super(options);
|
||||||
this._entry = options.entry;
|
this._entry = entry;
|
||||||
}
|
}
|
||||||
// view model props for all subclasses
|
// view model props for all subclasses
|
||||||
// hmmm, could also do instanceof ... ?
|
// hmmm, could also do instanceof ... ?
|
||||||
|
|
94
src/domain/session/room/timeline/tiles/index.ts
Normal file
94
src/domain/session/room/timeline/tiles/index.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {GapTile} from "./GapTile.js";
|
||||||
|
import {TextTile} from "./TextTile.js";
|
||||||
|
import {RedactedTile} from "./RedactedTile.js";
|
||||||
|
import {ImageTile} from "./ImageTile.js";
|
||||||
|
import {VideoTile} from "./VideoTile.js";
|
||||||
|
import {FileTile} from "./FileTile.js";
|
||||||
|
import {LocationTile} from "./LocationTile.js";
|
||||||
|
import {RoomNameTile} from "./RoomNameTile.js";
|
||||||
|
import {RoomMemberTile} from "./RoomMemberTile.js";
|
||||||
|
import {EncryptedEventTile} from "./EncryptedEventTile.js";
|
||||||
|
import {EncryptionEnabledTile} from "./EncryptionEnabledTile.js";
|
||||||
|
import {MissingAttachmentTile} from "./MissingAttachmentTile.js";
|
||||||
|
|
||||||
|
import type {SimpleTile} from "./SimpleTile.js";
|
||||||
|
import type {Room} from "../../../../../matrix/room/Room";
|
||||||
|
import type {Timeline} from "../../../../../matrix/room/timeline/Timeline";
|
||||||
|
import type {FragmentBoundaryEntry} from "../../../../../matrix/room/timeline/entries/FragmentBoundaryEntry";
|
||||||
|
import type {EventEntry} from "../../../../../matrix/room/timeline/entries/EventEntry";
|
||||||
|
import type {PendingEventEntry} from "../../../../../matrix/room/timeline/entries/PendingEventEntry";
|
||||||
|
import type {Options as ViewModelOptions} from "../../../../ViewModel";
|
||||||
|
|
||||||
|
export type TimelineEntry = FragmentBoundaryEntry | EventEntry | PendingEventEntry;
|
||||||
|
export type TileClassForEntryFn = (entry: TimelineEntry) => TileConstructor | undefined;
|
||||||
|
export type Options = ViewModelOptions & {
|
||||||
|
room: Room,
|
||||||
|
timeline: Timeline
|
||||||
|
tileClassForEntry: TileClassForEntryFn;
|
||||||
|
};
|
||||||
|
export type TileConstructor = new (entry: TimelineEntry, options: Options) => SimpleTile;
|
||||||
|
|
||||||
|
export function tileClassForEntry(entry: TimelineEntry): TileConstructor | undefined {
|
||||||
|
if (entry.isGap) {
|
||||||
|
return GapTile;
|
||||||
|
} else if (entry.isPending && entry.pendingEvent.isMissingAttachments) {
|
||||||
|
return MissingAttachmentTile;
|
||||||
|
} else if (entry.eventType) {
|
||||||
|
switch (entry.eventType) {
|
||||||
|
case "m.room.message": {
|
||||||
|
if (entry.isRedacted) {
|
||||||
|
return RedactedTile;
|
||||||
|
}
|
||||||
|
const content = entry.content;
|
||||||
|
const msgtype = content && content.msgtype;
|
||||||
|
switch (msgtype) {
|
||||||
|
case "m.text":
|
||||||
|
case "m.notice":
|
||||||
|
case "m.emote":
|
||||||
|
return TextTile;
|
||||||
|
case "m.image":
|
||||||
|
return ImageTile;
|
||||||
|
case "m.video":
|
||||||
|
return VideoTile;
|
||||||
|
case "m.file":
|
||||||
|
return FileTile;
|
||||||
|
case "m.location":
|
||||||
|
return LocationTile;
|
||||||
|
default:
|
||||||
|
// unknown msgtype not rendered
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "m.room.name":
|
||||||
|
return RoomNameTile;
|
||||||
|
case "m.room.member":
|
||||||
|
return RoomMemberTile;
|
||||||
|
case "m.room.encrypted":
|
||||||
|
if (entry.isRedacted) {
|
||||||
|
return RedactedTile;
|
||||||
|
}
|
||||||
|
return EncryptedEventTile;
|
||||||
|
case "m.room.encryption":
|
||||||
|
return EncryptionEnabledTile;
|
||||||
|
default:
|
||||||
|
// unknown type not rendered
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {GapTile} from "./tiles/GapTile.js";
|
|
||||||
import {TextTile} from "./tiles/TextTile.js";
|
|
||||||
import {RedactedTile} from "./tiles/RedactedTile.js";
|
|
||||||
import {ImageTile} from "./tiles/ImageTile.js";
|
|
||||||
import {VideoTile} from "./tiles/VideoTile.js";
|
|
||||||
import {FileTile} from "./tiles/FileTile.js";
|
|
||||||
import {LocationTile} from "./tiles/LocationTile.js";
|
|
||||||
import {RoomNameTile} from "./tiles/RoomNameTile.js";
|
|
||||||
import {RoomMemberTile} from "./tiles/RoomMemberTile.js";
|
|
||||||
import {EncryptedEventTile} from "./tiles/EncryptedEventTile.js";
|
|
||||||
import {EncryptionEnabledTile} from "./tiles/EncryptionEnabledTile.js";
|
|
||||||
import {MissingAttachmentTile} from "./tiles/MissingAttachmentTile.js";
|
|
||||||
|
|
||||||
export function tilesCreator(baseOptions) {
|
|
||||||
const tilesCreator = function tilesCreator(entry, emitUpdate) {
|
|
||||||
const options = Object.assign({entry, emitUpdate, tilesCreator}, baseOptions);
|
|
||||||
if (entry.isGap) {
|
|
||||||
return new GapTile(options);
|
|
||||||
} else if (entry.isPending && entry.pendingEvent.isMissingAttachments) {
|
|
||||||
return new MissingAttachmentTile(options);
|
|
||||||
} else if (entry.eventType) {
|
|
||||||
switch (entry.eventType) {
|
|
||||||
case "m.room.message": {
|
|
||||||
if (entry.isRedacted) {
|
|
||||||
return new RedactedTile(options);
|
|
||||||
}
|
|
||||||
const content = entry.content;
|
|
||||||
const msgtype = content && content.msgtype;
|
|
||||||
switch (msgtype) {
|
|
||||||
case "m.text":
|
|
||||||
case "m.notice":
|
|
||||||
case "m.emote":
|
|
||||||
return new TextTile(options);
|
|
||||||
case "m.image":
|
|
||||||
return new ImageTile(options);
|
|
||||||
case "m.video":
|
|
||||||
return new VideoTile(options);
|
|
||||||
case "m.file":
|
|
||||||
return new FileTile(options);
|
|
||||||
case "m.location":
|
|
||||||
return new LocationTile(options);
|
|
||||||
default:
|
|
||||||
// unknown msgtype not rendered
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "m.room.name":
|
|
||||||
return new RoomNameTile(options);
|
|
||||||
case "m.room.member":
|
|
||||||
return new RoomMemberTile(options);
|
|
||||||
case "m.room.encrypted":
|
|
||||||
if (entry.isRedacted) {
|
|
||||||
return new RedactedTile(options);
|
|
||||||
}
|
|
||||||
return new EncryptedEventTile(options);
|
|
||||||
case "m.room.encryption":
|
|
||||||
return new EncryptionEnabledTile(options);
|
|
||||||
default:
|
|
||||||
// unknown type not rendered
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return tilesCreator;
|
|
||||||
}
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
// import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay";
|
// import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay";
|
||||||
import {RootViewModel} from "../../domain/RootViewModel.js";
|
import {RootViewModel} from "../../domain/RootViewModel.js";
|
||||||
import {createNavigation, createRouter} from "../../domain/navigation/index.js";
|
import {createNavigation, createRouter} from "../../domain/navigation/index.js";
|
||||||
|
import {tileClassForEntry} from "../../domain/session/room/timeline/tiles/index";
|
||||||
// Don't use a default export here, as we use multiple entries during legacy build,
|
// Don't use a default export here, as we use multiple entries during legacy build,
|
||||||
// which does not support default exports,
|
// which does not support default exports,
|
||||||
// see https://github.com/rollup/plugins/tree/master/packages/multi-entry
|
// see https://github.com/rollup/plugins/tree/master/packages/multi-entry
|
||||||
|
@ -42,6 +43,8 @@ export async function main(platform) {
|
||||||
// so we call it that in the view models
|
// so we call it that in the view models
|
||||||
urlCreator: urlRouter,
|
urlCreator: urlRouter,
|
||||||
navigation,
|
navigation,
|
||||||
|
// which tiles are supported by the timeline
|
||||||
|
tileClassForEntry
|
||||||
});
|
});
|
||||||
await vm.load();
|
await vm.load();
|
||||||
platform.createAndMountRootView(vm);
|
platform.createAndMountRootView(vm);
|
||||||
|
|
|
@ -48,6 +48,6 @@ export function viewClassForEntry(vm: SimpleTile): TileViewConstructor {
|
||||||
case "redacted":
|
case "redacted":
|
||||||
return RedactedView;
|
return RedactedView;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tilesCreator function in the view model`);
|
throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tileClassForEntry function in the view model`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue