diff --git a/index.html b/index.html index facaa4d3..477d7c05 100644 --- a/index.html +++ b/index.html @@ -45,6 +45,10 @@ flex: 1; overflow-y: scroll; } + + .RoomView_error { + color: red; + }
diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 17427305..0e5f9b1c 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -16,8 +16,9 @@ export default class RoomViewModel extends EventEmitter { try { this._timeline = await this._room.openTimeline(); this._timelineVM = new TimelineViewModel(this._timeline); - this.emit("change", "timelineEntries"); + this.emit("change", "timelineViewModel"); } catch (err) { + console.error(`room.openTimeline(): ${err.message}:\n${err.stack}`); this._timelineError = err; this.emit("change", "error"); } diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index 3c80fc98..0b1dab8e 100644 --- a/src/domain/session/room/timeline/TilesCollection.js +++ b/src/domain/session/room/timeline/TilesCollection.js @@ -22,8 +22,9 @@ export default class TilesCollection extends BaseObservableList { for (let entry of this._entries) { if (!currentTile || !currentTile.tryIncludeEntry(entry)) { currentTile = this._tileCreator(entry); - // if (currentTile) here? - this._tiles.push(currentTile); + if (currentTile) { + this._tiles.push(currentTile); + } } } let prevTile = null; @@ -145,4 +146,8 @@ export default class TilesCollection extends BaseObservableList { [Symbol.iterator]() { return this._tiles.values(); } + + get length() { + return this._tiles.length; + } } diff --git a/src/domain/session/room/timeline/tiles/TextTile.js b/src/domain/session/room/timeline/tiles/TextTile.js index 08c330f5..03485ea0 100644 --- a/src/domain/session/room/timeline/tiles/TextTile.js +++ b/src/domain/session/room/timeline/tiles/TextTile.js @@ -4,10 +4,11 @@ export default class TextTile extends MessageTile { get label() { const content = this._getContent(); const body = content && content.body; - if (this._entry.type() === "m.emote") { - return `* ${this._entry.event.sender} ${body}`; + const sender = this._entry.event.sender; + if (this._entry.type === "m.emote") { + return `* ${sender} ${body}`; } else { - return body; + return `${sender}: ${body}`; } } } diff --git a/src/main.js b/src/main.js index 4f840c4b..f0787673 100644 --- a/src/main.js +++ b/src/main.js @@ -88,6 +88,6 @@ export default async function main(label, button, container) { label.innerText = "sync stopped"; }); } catch(err) { - console.error(err); + console.error(`${err.message}:\n${err.stack}`); } } diff --git a/src/matrix/room/timeline/Timeline.js b/src/matrix/room/timeline/Timeline.js index da40eb53..505138f9 100644 --- a/src/matrix/room/timeline/Timeline.js +++ b/src/matrix/room/timeline/Timeline.js @@ -20,7 +20,7 @@ export default class Timeline { /** @package */ async load() { - const entries = this._timelineReader.readFromEnd(100); + const entries = await this._timelineReader.readFromEnd(100); this._entriesList.setManySorted(entries); } diff --git a/src/matrix/room/timeline/entries/BaseEntry.js b/src/matrix/room/timeline/entries/BaseEntry.js index 37695ddf..3ef00862 100644 --- a/src/matrix/room/timeline/entries/BaseEntry.js +++ b/src/matrix/room/timeline/entries/BaseEntry.js @@ -1,4 +1,5 @@ //entries can be sorted, first by fragment, then by entry index. +import EventKey from "../EventKey.js"; export default class BaseEntry { constructor(fragmentIdComparer) { @@ -21,4 +22,8 @@ export default class BaseEntry { return this._fragmentIdComparer.compare(this.fragmentId, otherEntry.fragmentId); } } + + asEventKey() { + return new EventKey(this.fragmentId, this.entryIndex); + } } diff --git a/src/matrix/room/timeline/persistence/SyncWriter.js b/src/matrix/room/timeline/persistence/SyncWriter.js index faf0d6e6..2abbcf13 100644 --- a/src/matrix/room/timeline/persistence/SyncWriter.js +++ b/src/matrix/room/timeline/persistence/SyncWriter.js @@ -8,6 +8,7 @@ export default class SyncWriter { this._roomId = roomId; this._storage = storage; this._fragmentIdComparer = fragmentIdComparer; + this._lastLiveKey = null; } async load(txn) { @@ -98,7 +99,6 @@ export default class SyncWriter { // right thing to do? if the txn fails, not sure we'll continue anyways ... // only advance the key once the transaction has succeeded txn.complete().then(() => { - console.log("txn complete, setting key"); this._lastLiveKey = currentKey; }) diff --git a/src/matrix/room/timeline/persistence/TimelineReader.js b/src/matrix/room/timeline/persistence/TimelineReader.js index 8f2e5a4d..dd57ca39 100644 --- a/src/matrix/room/timeline/persistence/TimelineReader.js +++ b/src/matrix/room/timeline/persistence/TimelineReader.js @@ -32,9 +32,9 @@ export default class TimelineReader { while (entries.length < amount && eventKey) { let eventsWithinFragment; if (direction.isForward) { - eventsWithinFragment = timelineStore.eventsAfter(eventKey, amount); + eventsWithinFragment = await timelineStore.eventsAfter(this._roomId, eventKey, amount); } else { - eventsWithinFragment = timelineStore.eventsBefore(eventKey, amount); + eventsWithinFragment = await timelineStore.eventsBefore(this._roomId, eventKey, amount); } const eventEntries = eventsWithinFragment.map(e => new EventEntry(e, this._fragmentIdComparer)); entries = directionalConcat(entries, eventEntries, direction); @@ -52,7 +52,7 @@ export default class TimelineReader { this._fragmentIdComparer.add(nextFragment); const nextFragmentEntry = new FragmentBoundaryEntry(nextFragment, direction.isForward, this._fragmentIdComparer); directionalAppend(entries, nextFragmentEntry, direction); - eventKey = new EventKey(nextFragmentEntry.fragmentId, nextFragmentEntry.eventIndex); + eventKey = nextFragmentEntry.asEventKey(); } else { eventKey = null; } @@ -71,8 +71,8 @@ export default class TimelineReader { } this._fragmentIdComparer.add(liveFragment); const liveFragmentEntry = FragmentBoundaryEntry.end(liveFragment, this._fragmentIdComparer); - const eventKey = new EventKey(liveFragmentEntry.fragmentId, liveFragmentEntry.eventIndex); - const entries = this._readFrom(eventKey, Direction.Backward, amount, txn); + const eventKey = liveFragmentEntry.asEventKey(); + const entries = await this._readFrom(eventKey, Direction.Backward, amount, txn); entries.unshift(liveFragmentEntry); return entries; } diff --git a/src/matrix/storage/idb/create.js b/src/matrix/storage/idb/create.js index 6ee6af24..ce8b6aca 100644 --- a/src/matrix/storage/idb/create.js +++ b/src/matrix/storage/idb/create.js @@ -15,7 +15,7 @@ function createStores(db) { db.createObjectStore("timelineFragments", {keyPath: ["roomId", "id"]}); const timelineEvents = db.createObjectStore("timelineEvents", {keyPath: ["roomId", "fragmentId", "eventIndex"]}); timelineEvents.createIndex("byEventId", [ - "event.room_id", + "roomId", "event.event_id" ], {unique: true}); diff --git a/src/matrix/sync.js b/src/matrix/sync.js index 11c6ad4c..37425090 100644 --- a/src/matrix/sync.js +++ b/src/matrix/sync.js @@ -94,7 +94,7 @@ export default class Sync extends EventEmitter { await Promise.all(promises); } } catch(err) { - console.warn("aborting syncTxn because of error", err.stack); + console.warn("aborting syncTxn because of error"); // avoid corrupting state by only // storing the sync up till the point // the exception occurred diff --git a/src/observable/list/BaseObservableList.js b/src/observable/list/BaseObservableList.js index a1c38e91..97e50b20 100644 --- a/src/observable/list/BaseObservableList.js +++ b/src/observable/list/BaseObservableList.js @@ -34,4 +34,11 @@ export default class BaseObservableList extends BaseObservableCollection { } } + [Symbol.iterator]() { + throw new Error("unimplemented"); + } + + get length() { + throw new Error("unimplemented"); + } } diff --git a/src/ui/web/RoomView.js b/src/ui/web/RoomView.js index fd18ba6f..d26cf5df 100644 --- a/src/ui/web/RoomView.js +++ b/src/ui/web/RoomView.js @@ -14,11 +14,14 @@ export default class RoomView { mount() { this._viewModel.on("change", this._onViewModelUpdate); this._nameLabel = html.h2(null, this._viewModel.name); + this._errorLabel = html.div({className: "RoomView_error"}); + this._timelineList = new ListView({}, entry => new TimelineTile(entry)); this._timelineList.mount(); this._root = html.div({className: "RoomView"}, [ this._nameLabel, + this._errorLabel, this._timelineList.root() ]); @@ -40,6 +43,8 @@ export default class RoomView { } else if (prop === "timelineViewModel") { this._timelineList.update({list: this._viewModel.timelineViewModel.tiles}); + } else if (prop === "error") { + this._errorLabel.innerText = this._viewModel.error; } } diff --git a/src/ui/web/TimelineTile.js b/src/ui/web/TimelineTile.js index a8d7e481..bf03d5f4 100644 --- a/src/ui/web/TimelineTile.js +++ b/src/ui/web/TimelineTile.js @@ -23,7 +23,7 @@ export default class TimelineTile { function renderTile(tile) { switch (tile.shape) { case "message": - return html.li(null, [html.strong(tile.sender), `: ${tile.label}`]); + return html.li(null, tile.label); case "gap": { const button = html.button(null, (tile.isUp ? "🠝" : "🠟") + " fill gap"); const handler = () => { diff --git a/src/ui/web/html.js b/src/ui/web/html.js index 754e3ebe..ebf57091 100644 --- a/src/ui/web/html.js +++ b/src/ui/web/html.js @@ -51,3 +51,4 @@ export function main(... params) { return el("main", ... params); } export function article(... params) { return el("article", ... params); } export function aside(... params) { return el("aside", ... params); } export function pre(... params) { return el("pre", ... params); } +export function button(... params) { return el("button", ... params); }