mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 05:55:00 +08:00
Merge branch 'poljar/seshat-filepanel' into develop
This commit is contained in:
commit
cbfde4d78f
@ -592,8 +592,11 @@ async function startMatrixClient(startSyncing=true) {
|
|||||||
Mjolnir.sharedInstance().start();
|
Mjolnir.sharedInstance().start();
|
||||||
|
|
||||||
if (startSyncing) {
|
if (startSyncing) {
|
||||||
await MatrixClientPeg.start();
|
// The client might want to populate some views with events from the
|
||||||
|
// index (e.g. the FilePanel), therefore initialize the event index
|
||||||
|
// before the client.
|
||||||
await EventIndexPeg.init();
|
await EventIndexPeg.init();
|
||||||
|
await MatrixClientPeg.start();
|
||||||
} else {
|
} else {
|
||||||
console.warn("Caller requested only auxiliary services be started");
|
console.warn("Caller requested only auxiliary services be started");
|
||||||
await MatrixClientPeg.assign();
|
await MatrixClientPeg.assign();
|
||||||
|
@ -19,9 +19,10 @@ import React from 'react';
|
|||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
import {Filter} from 'matrix-js-sdk';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
|
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -29,6 +30,9 @@ import { _t } from '../../languageHandler';
|
|||||||
*/
|
*/
|
||||||
const FilePanel = createReactClass({
|
const FilePanel = createReactClass({
|
||||||
displayName: 'FilePanel',
|
displayName: 'FilePanel',
|
||||||
|
// This is used to track if a decrypted event was a live event and should be
|
||||||
|
// added to the timeline.
|
||||||
|
decryptingEvents: new Set(),
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
@ -40,42 +44,147 @@ const FilePanel = createReactClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
onRoomTimeline(ev, room, toStartOfTimeline, removed, data) {
|
||||||
this.updateTimelineSet(this.props.roomId);
|
if (room.roomId !== this.props.roomId) return;
|
||||||
|
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
|
||||||
|
|
||||||
|
if (ev.isBeingDecrypted()) {
|
||||||
|
this.decryptingEvents.add(ev.getId());
|
||||||
|
} else {
|
||||||
|
this.addEncryptedLiveEvent(ev);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTimelineSet: function(roomId) {
|
onEventDecrypted(ev, err) {
|
||||||
|
if (ev.getRoomId() !== this.props.roomId) return;
|
||||||
|
const eventId = ev.getId();
|
||||||
|
|
||||||
|
if (!this.decryptingEvents.delete(eventId)) return;
|
||||||
|
if (err) return;
|
||||||
|
|
||||||
|
this.addEncryptedLiveEvent(ev);
|
||||||
|
},
|
||||||
|
|
||||||
|
addEncryptedLiveEvent(ev, toStartOfTimeline) {
|
||||||
|
if (!this.state.timelineSet) return;
|
||||||
|
|
||||||
|
const timeline = this.state.timelineSet.getLiveTimeline();
|
||||||
|
if (ev.getType() !== "m.room.message") return;
|
||||||
|
if (["m.file", "m.image", "m.video", "m.audio"].indexOf(ev.getContent().msgtype) == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) {
|
||||||
|
this.state.timelineSet.addEventToTimeline(ev, timeline, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
await this.updateTimelineSet(this.props.roomId);
|
||||||
|
|
||||||
|
if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return;
|
||||||
|
|
||||||
|
// The timelineSets filter makes sure that encrypted events that contain
|
||||||
|
// URLs never get added to the timeline, even if they are live events.
|
||||||
|
// These methods are here to manually listen for such events and add
|
||||||
|
// them despite the filter's best efforts.
|
||||||
|
//
|
||||||
|
// We do this only for encrypted rooms and if an event index exists,
|
||||||
|
// this could be made more general in the future or the filter logic
|
||||||
|
// could be fixed.
|
||||||
|
if (EventIndexPeg.get() !== null) {
|
||||||
|
client.on('Room.timeline', this.onRoomTimeline.bind(this));
|
||||||
|
client.on('Event.decrypted', this.onEventDecrypted.bind(this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
if (client === null) return;
|
||||||
|
|
||||||
|
if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return;
|
||||||
|
|
||||||
|
if (EventIndexPeg.get() !== null) {
|
||||||
|
client.removeListener('Room.timeline', this.onRoomTimeline.bind(this));
|
||||||
|
client.removeListener('Event.decrypted', this.onEventDecrypted.bind(this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchFileEventsServer(room) {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
const filter = new Filter(client.credentials.userId);
|
||||||
|
filter.setDefinition(
|
||||||
|
{
|
||||||
|
"room": {
|
||||||
|
"timeline": {
|
||||||
|
"contains_url": true,
|
||||||
|
"types": [
|
||||||
|
"m.room.message",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter);
|
||||||
|
filter.filterId = filterId;
|
||||||
|
const timelineSet = room.getOrCreateFilteredTimelineSet(filter);
|
||||||
|
|
||||||
|
return timelineSet;
|
||||||
|
},
|
||||||
|
|
||||||
|
onPaginationRequest(timelineWindow, direction, limit) {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
const eventIndex = EventIndexPeg.get();
|
||||||
|
const roomId = this.props.roomId;
|
||||||
|
|
||||||
|
const room = client.getRoom(roomId);
|
||||||
|
|
||||||
|
// We override the pagination request for encrypted rooms so that we ask
|
||||||
|
// the event index to fulfill the pagination request. Asking the server
|
||||||
|
// to paginate won't ever work since the server can't correctly filter
|
||||||
|
// out events containing URLs
|
||||||
|
if (client.isRoomEncrypted(roomId) && eventIndex !== null) {
|
||||||
|
return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit);
|
||||||
|
} else {
|
||||||
|
return timelineWindow.paginate(direction, limit);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateTimelineSet(roomId: string) {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
|
const eventIndex = EventIndexPeg.get();
|
||||||
|
|
||||||
this.noRoom = !room;
|
this.noRoom = !room;
|
||||||
|
|
||||||
if (room) {
|
if (room) {
|
||||||
const filter = new Matrix.Filter(client.credentials.userId);
|
let timelineSet;
|
||||||
filter.setDefinition(
|
|
||||||
{
|
|
||||||
"room": {
|
|
||||||
"timeline": {
|
|
||||||
"contains_url": true,
|
|
||||||
"types": [
|
|
||||||
"m.room.message",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: we shouldn't be doing this every time we change room - see comment above.
|
try {
|
||||||
client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter).then(
|
timelineSet = await this.fetchFileEventsServer(room);
|
||||||
(filterId)=>{
|
|
||||||
filter.filterId = filterId;
|
// If this room is encrypted the file panel won't be populated
|
||||||
const timelineSet = room.getOrCreateFilteredTimelineSet(filter);
|
// correctly since the defined filter doesn't support encrypted
|
||||||
this.setState({ timelineSet: timelineSet });
|
// events and the server can't check if encrypted events contain
|
||||||
},
|
// URLs.
|
||||||
(error)=>{
|
//
|
||||||
console.error("Failed to get or create file panel filter", error);
|
// This is where our event index comes into place, we ask the
|
||||||
},
|
// event index to populate the timelineSet for us. This call
|
||||||
);
|
// will add 10 events to the live timeline of the set. More can
|
||||||
|
// be requested using pagination.
|
||||||
|
if (client.isRoomEncrypted(roomId) && eventIndex !== null) {
|
||||||
|
const timeline = timelineSet.getLiveTimeline();
|
||||||
|
await eventIndex.populateFileTimeline(timelineSet, timeline, room, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ timelineSet: timelineSet });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get or create file panel filter", error);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
|
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
|
||||||
}
|
}
|
||||||
@ -111,6 +220,7 @@ const FilePanel = createReactClass({
|
|||||||
manageReadMarkers={false}
|
manageReadMarkers={false}
|
||||||
timelineSet={this.state.timelineSet}
|
timelineSet={this.state.timelineSet}
|
||||||
showUrlPreview = {false}
|
showUrlPreview = {false}
|
||||||
|
onPaginationRequest={this.onPaginationRequest}
|
||||||
tileShape="file_grid"
|
tileShape="file_grid"
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
empty={_t('There are no visible files in this room')}
|
empty={_t('There are no visible files in this room')}
|
||||||
|
@ -94,6 +94,10 @@ const TimelinePanel = createReactClass({
|
|||||||
// callback which is called when the read-up-to mark is updated.
|
// callback which is called when the read-up-to mark is updated.
|
||||||
onReadMarkerUpdated: PropTypes.func,
|
onReadMarkerUpdated: PropTypes.func,
|
||||||
|
|
||||||
|
// callback which is called when we wish to paginate the timeline
|
||||||
|
// window.
|
||||||
|
onPaginationRequest: PropTypes.func,
|
||||||
|
|
||||||
// maximum number of events to show in a timeline
|
// maximum number of events to show in a timeline
|
||||||
timelineCap: PropTypes.number,
|
timelineCap: PropTypes.number,
|
||||||
|
|
||||||
@ -338,6 +342,14 @@ const TimelinePanel = createReactClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onPaginationRequest(timelineWindow, direction, size) {
|
||||||
|
if (this.props.onPaginationRequest) {
|
||||||
|
return this.props.onPaginationRequest(timelineWindow, direction, size);
|
||||||
|
} else {
|
||||||
|
return timelineWindow.paginate(direction, size);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// set off a pagination request.
|
// set off a pagination request.
|
||||||
onMessageListFillRequest: function(backwards) {
|
onMessageListFillRequest: function(backwards) {
|
||||||
if (!this._shouldPaginate()) return Promise.resolve(false);
|
if (!this._shouldPaginate()) return Promise.resolve(false);
|
||||||
@ -360,7 +372,7 @@ const TimelinePanel = createReactClass({
|
|||||||
debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards);
|
debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards);
|
||||||
this.setState({[paginatingKey]: true});
|
this.setState({[paginatingKey]: true});
|
||||||
|
|
||||||
return this._timelineWindow.paginate(dir, PAGINATE_SIZE).then((r) => {
|
return this.onPaginationRequest(this._timelineWindow, dir, PAGINATE_SIZE).then((r) => {
|
||||||
if (this.unmounted) { return; }
|
if (this.unmounted) { return; }
|
||||||
|
|
||||||
debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);
|
debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);
|
||||||
|
@ -62,11 +62,18 @@ export interface SearchArgs {
|
|||||||
room_id: ?string;
|
room_id: ?string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HistoricEvent {
|
export interface EventAndProfile {
|
||||||
event: MatrixEvent;
|
event: MatrixEvent;
|
||||||
profile: MatrixProfile;
|
profile: MatrixProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LoadArgs {
|
||||||
|
roomId: string;
|
||||||
|
limit: number;
|
||||||
|
fromEvent: string;
|
||||||
|
direction: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for classes that provide platform-specific event indexing.
|
* Base class for classes that provide platform-specific event indexing.
|
||||||
*
|
*
|
||||||
@ -145,7 +152,7 @@ export default class BaseEventIndexManager {
|
|||||||
*
|
*
|
||||||
* This is used to add a batch of events to the index.
|
* This is used to add a batch of events to the index.
|
||||||
*
|
*
|
||||||
* @param {[HistoricEvent]} events The list of events and profiles that
|
* @param {[EventAndProfile]} events The list of events and profiles that
|
||||||
* should be added to the event index.
|
* should be added to the event index.
|
||||||
* @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that
|
* @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that
|
||||||
* should be stored in the index which should be used to continue crawling
|
* should be stored in the index which should be used to continue crawling
|
||||||
@ -158,7 +165,7 @@ export default class BaseEventIndexManager {
|
|||||||
* were already added to the index, false otherwise.
|
* were already added to the index, false otherwise.
|
||||||
*/
|
*/
|
||||||
async addHistoricEvents(
|
async addHistoricEvents(
|
||||||
events: [HistoricEvent],
|
events: [EventAndProfile],
|
||||||
checkpoint: CrawlerCheckpoint | null,
|
checkpoint: CrawlerCheckpoint | null,
|
||||||
oldCheckpoint: CrawlerCheckpoint | null,
|
oldCheckpoint: CrawlerCheckpoint | null,
|
||||||
): Promise<bool> {
|
): Promise<bool> {
|
||||||
@ -201,6 +208,26 @@ export default class BaseEventIndexManager {
|
|||||||
throw new Error("Unimplemented");
|
throw new Error("Unimplemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Load events that contain an mxc URL to a file from the index.
|
||||||
|
*
|
||||||
|
* @param {object} args Arguments object for the method.
|
||||||
|
* @param {string} args.roomId The ID of the room for which the events
|
||||||
|
* should be loaded.
|
||||||
|
* @param {number} args.limit The maximum number of events to return.
|
||||||
|
* @param {string} args.fromEvent An event id of a previous event returned
|
||||||
|
* by this method. Passing this means that we are going to continue loading
|
||||||
|
* events from this point in the history.
|
||||||
|
* @param {string} args.direction The direction to which we should continue
|
||||||
|
* loading events from. This is used only if fromEvent is used as well.
|
||||||
|
*
|
||||||
|
* @return {Promise<[EventAndProfile]>} A promise that will resolve to an
|
||||||
|
* array of Matrix events that contain mxc URLs accompanied with the
|
||||||
|
* historic profile of the sender.
|
||||||
|
*/
|
||||||
|
async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> {
|
||||||
|
throw new Error("Unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* close our event index.
|
* close our event index.
|
||||||
*
|
*
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import PlatformPeg from "../PlatformPeg";
|
import PlatformPeg from "../PlatformPeg";
|
||||||
import {MatrixClientPeg} from "../MatrixClientPeg";
|
import {MatrixClientPeg} from "../MatrixClientPeg";
|
||||||
|
import {EventTimeline, RoomMember} from 'matrix-js-sdk';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Event indexing class that wraps the platform specific event indexing.
|
* Event indexing class that wraps the platform specific event indexing.
|
||||||
@ -170,7 +171,9 @@ export default class EventIndex {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const e = ev.toJSON().decrypted;
|
const jsonEvent = ev.toJSON();
|
||||||
|
const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent;
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
displayname: ev.sender.rawDisplayName,
|
displayname: ev.sender.rawDisplayName,
|
||||||
avatar_url: ev.sender.getMxcAvatarUrl(),
|
avatar_url: ev.sender.getMxcAvatarUrl(),
|
||||||
@ -305,10 +308,7 @@ export default class EventIndex {
|
|||||||
// consume.
|
// consume.
|
||||||
const events = filteredEvents.map((ev) => {
|
const events = filteredEvents.map((ev) => {
|
||||||
const jsonEvent = ev.toJSON();
|
const jsonEvent = ev.toJSON();
|
||||||
|
const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent;
|
||||||
let e;
|
|
||||||
if (ev.isEncrypted()) e = jsonEvent.decrypted;
|
|
||||||
else e = jsonEvent;
|
|
||||||
|
|
||||||
let profile = {};
|
let profile = {};
|
||||||
if (e.sender in profiles) profile = profiles[e.sender];
|
if (e.sender in profiles) profile = profiles[e.sender];
|
||||||
@ -406,4 +406,198 @@ export default class EventIndex {
|
|||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||||
return indexManager.searchEventIndex(searchArgs);
|
return indexManager.searchEventIndex(searchArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load events that contain URLs from the event index.
|
||||||
|
*
|
||||||
|
* @param {Room} room The room for which we should fetch events containing
|
||||||
|
* URLs
|
||||||
|
*
|
||||||
|
* @param {number} limit The maximum number of events to fetch.
|
||||||
|
*
|
||||||
|
* @param {string} fromEvent From which event should we continue fetching
|
||||||
|
* events from the index. This is only needed if we're continuing to fill
|
||||||
|
* the timeline, e.g. if we're paginating. This needs to be set to a event
|
||||||
|
* id of an event that was previously fetched with this function.
|
||||||
|
*
|
||||||
|
* @param {string} direction The direction in which we will continue
|
||||||
|
* fetching events. EventTimeline.BACKWARDS to continue fetching events that
|
||||||
|
* are older than the event given in fromEvent, EventTimeline.FORWARDS to
|
||||||
|
* fetch newer events.
|
||||||
|
*
|
||||||
|
* @returns {Promise<MatrixEvent[]>} Resolves to an array of events that
|
||||||
|
* contain URLs.
|
||||||
|
*/
|
||||||
|
async loadFileEvents(room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||||
|
|
||||||
|
const loadArgs = {
|
||||||
|
roomId: room.roomId,
|
||||||
|
limit: limit,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fromEvent) {
|
||||||
|
loadArgs.fromEvent = fromEvent;
|
||||||
|
loadArgs.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
let events;
|
||||||
|
|
||||||
|
// Get our events from the event index.
|
||||||
|
try {
|
||||||
|
events = await indexManager.loadFileEvents(loadArgs);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("EventIndex: Error getting file events", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventMapper = client.getEventMapper();
|
||||||
|
|
||||||
|
// Turn the events into MatrixEvent objects.
|
||||||
|
const matrixEvents = events.map(e => {
|
||||||
|
const matrixEvent = eventMapper(e.event);
|
||||||
|
|
||||||
|
const member = new RoomMember(room.roomId, matrixEvent.getSender());
|
||||||
|
|
||||||
|
// We can't really reconstruct the whole room state from our
|
||||||
|
// EventIndex to calculate the correct display name. Use the
|
||||||
|
// disambiguated form always instead.
|
||||||
|
member.name = e.profile.displayname + " (" + matrixEvent.getSender() + ")";
|
||||||
|
|
||||||
|
// This is sets the avatar URL.
|
||||||
|
const memberEvent = eventMapper(
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
membership: "join",
|
||||||
|
avatar_url: e.profile.avatar_url,
|
||||||
|
displayname: e.profile.displayname,
|
||||||
|
},
|
||||||
|
type: "m.room.member",
|
||||||
|
event_id: matrixEvent.getId() + ":eventIndex",
|
||||||
|
room_id: matrixEvent.getRoomId(),
|
||||||
|
sender: matrixEvent.getSender(),
|
||||||
|
origin_server_ts: matrixEvent.getTs(),
|
||||||
|
state_key: matrixEvent.getSender(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// We set this manually to avoid emitting RoomMember.membership and
|
||||||
|
// RoomMember.name events.
|
||||||
|
member.events.member = memberEvent;
|
||||||
|
matrixEvent.sender = member;
|
||||||
|
|
||||||
|
return matrixEvent;
|
||||||
|
});
|
||||||
|
|
||||||
|
return matrixEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill a timeline with events that contain URLs.
|
||||||
|
*
|
||||||
|
* @param {TimelineSet} timelineSet The TimelineSet the Timeline belongs to,
|
||||||
|
* used to check if we're adding duplicate events.
|
||||||
|
*
|
||||||
|
* @param {Timeline} timeline The Timeline which should be filed with
|
||||||
|
* events.
|
||||||
|
*
|
||||||
|
* @param {Room} room The room for which we should fetch events containing
|
||||||
|
* URLs
|
||||||
|
*
|
||||||
|
* @param {number} limit The maximum number of events to fetch.
|
||||||
|
*
|
||||||
|
* @param {string} fromEvent From which event should we continue fetching
|
||||||
|
* events from the index. This is only needed if we're continuing to fill
|
||||||
|
* the timeline, e.g. if we're paginating. This needs to be set to a event
|
||||||
|
* id of an event that was previously fetched with this function.
|
||||||
|
*
|
||||||
|
* @param {string} direction The direction in which we will continue
|
||||||
|
* fetching events. EventTimeline.BACKWARDS to continue fetching events that
|
||||||
|
* are older than the event given in fromEvent, EventTimeline.FORWARDS to
|
||||||
|
* fetch newer events.
|
||||||
|
*
|
||||||
|
* @returns {Promise<boolean>} Resolves to true if events were added to the
|
||||||
|
* timeline, false otherwise.
|
||||||
|
*/
|
||||||
|
async populateFileTimeline(timelineSet, timeline, room, limit = 10,
|
||||||
|
fromEvent = null, direction = EventTimeline.BACKWARDS) {
|
||||||
|
const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction);
|
||||||
|
|
||||||
|
// If this is a normal fill request, not a pagination request, we need
|
||||||
|
// to get our events in the BACKWARDS direction but populate them in the
|
||||||
|
// forwards direction.
|
||||||
|
// This needs to happen because a fill request might come with an
|
||||||
|
// exisitng timeline e.g. if you close and re-open the FilePanel.
|
||||||
|
if (fromEvent === null) {
|
||||||
|
matrixEvents.reverse();
|
||||||
|
direction = direction == EventTimeline.BACKWARDS ? EventTimeline.FORWARDS: EventTimeline.BACKWARDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the events to the timeline of the file panel.
|
||||||
|
matrixEvents.forEach(e => {
|
||||||
|
if (!timelineSet.eventIdToTimeline(e.getId())) {
|
||||||
|
timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the pagination token to the oldest event that we retrieved.
|
||||||
|
if (matrixEvents.length > 0) {
|
||||||
|
timeline.setPaginationToken(matrixEvents[matrixEvents.length - 1].getId(), EventTimeline.BACKWARDS);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
timeline.setPaginationToken("", EventTimeline.BACKWARDS);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulate a TimelineWindow pagination() request with the event index as the event source
|
||||||
|
*
|
||||||
|
* Might not fetch events from the index if the timeline already contains
|
||||||
|
* events that the window isn't showing.
|
||||||
|
*
|
||||||
|
* @param {Room} room The room for which we should fetch events containing
|
||||||
|
* URLs
|
||||||
|
*
|
||||||
|
* @param {TimelineWindow} timelineWindow The timeline window that should be
|
||||||
|
* populated with new events.
|
||||||
|
*
|
||||||
|
* @param {string} direction The direction in which we should paginate.
|
||||||
|
* EventTimeline.BACKWARDS to paginate back, EventTimeline.FORWARDS to
|
||||||
|
* paginate forwards.
|
||||||
|
*
|
||||||
|
* @param {number} limit The maximum number of events to fetch while
|
||||||
|
* paginating.
|
||||||
|
*
|
||||||
|
* @returns {Promise<boolean>} Resolves to a boolean which is true if more
|
||||||
|
* events were successfully retrieved.
|
||||||
|
*/
|
||||||
|
paginateTimelineWindow(room, timelineWindow, direction, limit) {
|
||||||
|
const tl = timelineWindow.getTimelineIndex(direction);
|
||||||
|
|
||||||
|
if (!tl) return Promise.resolve(false);
|
||||||
|
if (tl.pendingPaginate) return tl.pendingPaginate;
|
||||||
|
|
||||||
|
if (timelineWindow.extend(direction, limit)) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paginationMethod = async (timelineWindow, timeline, room, direction, limit) => {
|
||||||
|
const timelineSet = timelineWindow._timelineSet;
|
||||||
|
const token = timeline.timeline.getPaginationToken(direction);
|
||||||
|
|
||||||
|
const ret = await this.populateFileTimeline(timelineSet, timeline.timeline, room, limit, token, direction);
|
||||||
|
|
||||||
|
timeline.pendingPaginate = null;
|
||||||
|
timelineWindow.extend(direction, limit);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const paginationPromise = paginationMethod(timelineWindow, tl, room, direction, limit);
|
||||||
|
tl.pendingPaginate = paginationPromise;
|
||||||
|
|
||||||
|
return paginationPromise;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user