Merge branch 'poljar/seshat-filepanel' into develop
This commit is contained in:
commit
cbfde4d78f
5 changed files with 384 additions and 38 deletions
|
@ -19,9 +19,10 @@ import React from 'react';
|
|||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Matrix from 'matrix-js-sdk';
|
||||
import {Filter} from 'matrix-js-sdk';
|
||||
import * as sdk from '../../index';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
/*
|
||||
|
@ -29,6 +30,9 @@ import { _t } from '../../languageHandler';
|
|||
*/
|
||||
const FilePanel = createReactClass({
|
||||
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: {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
|
@ -40,42 +44,147 @@ const FilePanel = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.updateTimelineSet(this.props.roomId);
|
||||
onRoomTimeline(ev, room, toStartOfTimeline, removed, data) {
|
||||
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 room = client.getRoom(roomId);
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
|
||||
this.noRoom = !room;
|
||||
|
||||
if (room) {
|
||||
const filter = new Matrix.Filter(client.credentials.userId);
|
||||
filter.setDefinition(
|
||||
{
|
||||
"room": {
|
||||
"timeline": {
|
||||
"contains_url": true,
|
||||
"types": [
|
||||
"m.room.message",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
let timelineSet;
|
||||
|
||||
// FIXME: we shouldn't be doing this every time we change room - see comment above.
|
||||
client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter).then(
|
||||
(filterId)=>{
|
||||
filter.filterId = filterId;
|
||||
const timelineSet = room.getOrCreateFilteredTimelineSet(filter);
|
||||
this.setState({ timelineSet: timelineSet });
|
||||
},
|
||||
(error)=>{
|
||||
console.error("Failed to get or create file panel filter", error);
|
||||
},
|
||||
);
|
||||
try {
|
||||
timelineSet = await this.fetchFileEventsServer(room);
|
||||
|
||||
// If this room is encrypted the file panel won't be populated
|
||||
// correctly since the defined filter doesn't support encrypted
|
||||
// events and the server can't check if encrypted events contain
|
||||
// URLs.
|
||||
//
|
||||
// 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 {
|
||||
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
|
||||
}
|
||||
|
@ -111,6 +220,7 @@ const FilePanel = createReactClass({
|
|||
manageReadMarkers={false}
|
||||
timelineSet={this.state.timelineSet}
|
||||
showUrlPreview = {false}
|
||||
onPaginationRequest={this.onPaginationRequest}
|
||||
tileShape="file_grid"
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
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.
|
||||
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
|
||||
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.
|
||||
onMessageListFillRequest: function(backwards) {
|
||||
if (!this._shouldPaginate()) return Promise.resolve(false);
|
||||
|
@ -360,7 +372,7 @@ const TimelinePanel = createReactClass({
|
|||
debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards);
|
||||
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; }
|
||||
|
||||
debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue