Merge pull request #21 from vector-im/bwindels/backfill-fixes

More backfill fixes
This commit is contained in:
Bruno Windels 2020-08-17 16:06:24 +00:00 committed by GitHub
commit cf689e7643
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 80 additions and 15 deletions

View file

@ -1,5 +1,6 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -42,13 +43,16 @@ export class TimelineViewModel {
this._tiles = new TilesCollection(timeline.entries, tilesCreator({room, ownUserId}));
}
// doesn't fill gaps, only loads stored entries/tiles
loadAtTop() {
/**
* @return {bool} startReached if the start of the timeline was reached
*/
async loadAtTop() {
const firstTile = this._tiles.getFirst();
if (firstTile.shape === "gap") {
return firstTile.fill();
} else {
return this._timeline.loadAtTop(50);
await this._timeline.loadAtTop(50);
return false;
}
}

View file

@ -0,0 +1,23 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
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 {MessageTile} from "./MessageTile.js";
export class EncryptedEventTile extends MessageTile {
get text() {
return this.i18n`**Encrypted message**`;
}
}

View file

@ -41,6 +41,8 @@ export class GapTile extends SimpleTile {
this.emitChange("isLoading");
}
}
// edgeReached will have been updated by fillGap
return this._entry.edgeReached;
}
updateEntry(entry, params) {
@ -60,14 +62,6 @@ export class GapTile extends SimpleTile {
return this._loading;
}
get isUp() {
return this._entry.direction.isBackward;
}
get isDown() {
return this._entry.direction.isForward;
}
get error() {
if (this._error) {
const dir = this._entry.prev_batch ? "previous" : "next";

View file

@ -20,6 +20,7 @@ import {ImageTile} from "./tiles/ImageTile.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";
export function tilesCreator({room, ownUserId}) {
return function tilesCreator(entry, emitUpdate) {
@ -49,6 +50,8 @@ export function tilesCreator({room, ownUserId}) {
return new RoomNameTile(options);
case "m.room.member":
return new RoomMemberTile(options);
case "m.room.encrypted":
return new EncryptedEventTile(options);
default:
// unknown type not rendered
return null;

View file

@ -83,6 +83,9 @@ export class Room extends EventEmitter {
/** @public */
async fillGap(fragmentEntry, amount) {
if (fragmentEntry.edgeReached) {
return;
}
const response = await this._hsApi.messages(this._roomId, {
from: fragmentEntry.token,
dir: fragmentEntry.direction.asApiString(),

View file

@ -60,7 +60,7 @@ export class FragmentBoundaryEntry extends BaseEntry {
}
get isGap() {
return !!this.token;
return !!this.token && !this.edgeReached;
}
get token() {
@ -79,6 +79,25 @@ export class FragmentBoundaryEntry extends BaseEntry {
}
}
get edgeReached() {
if (this.started) {
return this.fragment.startReached;
} else {
return this.fragment.endReached;
}
}
set edgeReached(reached) {
if (this.started) {
this.fragment.startReached = reached;
} else {
this.fragment.endReached = reached;
}
}
get linkedFragmentId() {
if (this.started) {
return this.fragment.previousId;

View file

@ -178,6 +178,14 @@ export class GapWriter {
if (fragmentEntry.token !== start) {
throw new Error("start is not equal to prev_batch or next_batch");
}
// begin (or end) of timeline reached
if (chunk.length === 0) {
fragmentEntry.edgeReached = true;
await txn.timelineFragments.update(fragmentEntry.fragment);
return {entries: [fragmentEntry], fragments: []};
}
// find last event in fragment so we get the eventIndex to begin creating keys at
let lastKey = await this._findFragmentEdgeEventKey(fragmentEntry, txn);
// find out if any event in chunk is already present using findFirstOrLastOccurringEventId

View file

@ -102,12 +102,14 @@ export class ListView {
}
this._subscription = this._list.subscribe(this);
this._childInstances = [];
const fragment = document.createDocumentFragment();
for (let item of this._list) {
const child = this._childCreator(item);
this._childInstances.push(child);
const childDomNode = child.mount(this._mountArgs);
this._root.appendChild(childDomNode);
fragment.appendChild(childDomNode);
}
this._root.appendChild(fragment);
}
onAdd(idx, value) {

View file

@ -41,11 +41,17 @@ export class TimelineList extends ListView {
}
async _loadAtTopWhile(predicate) {
if (this._topLoadingPromise) {
return;
}
try {
while (predicate()) {
// fill, not enough content to fill timeline
this._topLoadingPromise = this._viewModel.loadAtTop();
await this._topLoadingPromise;
const startReached = await this._topLoadingPromise;
if (startReached) {
break;
}
}
}
catch (err) {
@ -86,9 +92,12 @@ export class TimelineList extends ListView {
super.unmount();
}
loadList() {
async loadList() {
super.loadList();
const root = this.root();
// yield so the browser can render the list
// and we can measure the content below
await Promise.resolve();
const {scrollHeight, clientHeight} = root;
if (scrollHeight > clientHeight) {
root.scrollTop = root.scrollHeight;