Hook thread panel to homeserver API (#7352)

pull/21833/head
Germain 2021-12-22 14:08:05 +00:00 committed by GitHub
parent b4755f38b9
commit cd04799cb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 103 additions and 72 deletions

View File

@ -14,10 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set'; import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set';
import { Room } from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
import { RelationType } from 'matrix-js-sdk/src/@types/event';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import {
Filter,
IFilterDefinition,
UNSTABLE_FILTER_RELATION_SENDERS,
UNSTABLE_FILTER_RELATION_TYPES,
} from 'matrix-js-sdk/src/filter';
import BaseCard from "../views/right_panel/BaseCard"; import BaseCard from "../views/right_panel/BaseCard";
import ResizeNotifier from '../../utils/ResizeNotifier'; import ResizeNotifier from '../../utils/ResizeNotifier';
@ -28,10 +35,65 @@ import ContextMenu, { ChevronFace, MenuItemRadio, useContextMenu } from './Conte
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
import TimelinePanel from './TimelinePanel'; import TimelinePanel from './TimelinePanel';
import { Layout } from '../../settings/enums/Layout'; import { Layout } from '../../settings/enums/Layout';
import { useEventEmitter } from '../../hooks/useEventEmitter';
import { TileShape } from '../views/rooms/EventTile'; import { TileShape } from '../views/rooms/EventTile';
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
async function getThreadTimelineSet(
client: MatrixClient,
room: Room,
filterType = ThreadFilterType.All,
): Promise<EventTimelineSet> {
const myUserId = client.getUserId();
const filter = new Filter(myUserId);
const definition: IFilterDefinition = {
"room": {
"timeline": {
[UNSTABLE_FILTER_RELATION_TYPES.name]: [RelationType.Thread],
},
},
};
if (filterType === ThreadFilterType.My) {
definition.room.timeline[UNSTABLE_FILTER_RELATION_SENDERS.name] = [myUserId];
}
filter.setDefinition(definition);
let timelineSet;
try {
const filterId = await client.getOrCreateFilter(
`THREAD_PANEL_${room.roomId}_${filterType}`,
filter,
);
filter.filterId = filterId;
timelineSet = room.getOrCreateFilteredTimelineSet(
filter,
{ prepopulateTimeline: false },
);
timelineSet.resetLiveTimeline();
await client.paginateEventTimeline(
timelineSet.getLiveTimeline(),
{ backwards: true, limit: 20 },
);
} catch (e) {
// Filter creation fails if HomeServer does not support the new relation
// filter fields. We fallback to the threads that have been discovered in
// the main timeline
timelineSet = new EventTimelineSet(room, {});
for (const [, thread] of room.threads) {
const isOwnEvent = thread.rootEvent.getSender() === client.getUserId();
if (filterType !== ThreadFilterType.My || isOwnEvent) {
timelineSet.getLiveTimeline().addEvent(thread.rootEvent);
}
}
}
return timelineSet;
}
interface IProps { interface IProps {
roomId: string; roomId: string;
onClose: () => void; onClose: () => void;
@ -50,44 +112,6 @@ type ThreadPanelHeaderOption = {
key: ThreadFilterType; key: ThreadFilterType;
}; };
const useFilteredThreadsTimelinePanel = ({
threads,
room,
filterOption,
userId,
updateTimeline,
}: {
threads: Map<string, Thread>;
room: Room;
userId: string;
filterOption: ThreadFilterType;
updateTimeline: () => void;
}) => {
const timelineSet = useMemo(() => new EventTimelineSet(null, {
timelineSupport: true,
unstableClientRelationAggregation: true,
pendingEvents: false,
}), []);
const buildThreadList = useCallback(function(timelineSet: EventTimelineSet) {
timelineSet.resetLiveTimeline("");
Array.from(threads)
.forEach(([, thread]) => {
if (filterOption !== ThreadFilterType.My || thread.hasCurrentUserParticipated) {
timelineSet.addLiveEvent(thread.rootEvent);
}
});
updateTimeline();
}, [filterOption, threads, updateTimeline]);
useEffect(() => { buildThreadList(timelineSet); }, [timelineSet, buildThreadList]);
useEventEmitter(room, ThreadEvent.Update, () => { buildThreadList(timelineSet); });
useEventEmitter(room, ThreadEvent.New, () => { buildThreadList(timelineSet); });
return timelineSet;
};
export const ThreadPanelHeaderFilterOptionItem = ({ export const ThreadPanelHeaderFilterOptionItem = ({
label, label,
description, description,
@ -185,19 +209,24 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
const [filterOption, setFilterOption] = useState<ThreadFilterType>(ThreadFilterType.All); const [filterOption, setFilterOption] = useState<ThreadFilterType>(ThreadFilterType.All);
const ref = useRef<TimelinePanel>(); const ref = useRef<TimelinePanel>();
const filteredTimelineSet = useFilteredThreadsTimelinePanel({ const [timelineSet, setTimelineSet] = useState<EventTimelineSet | null>(null);
threads: room.threads, const timelineSetPromise = useMemo(
room, async () => {
filterOption, const timelineSet = getThreadTimelineSet(mxClient, room, filterOption);
userId: mxClient.getUserId(), return timelineSet;
updateTimeline: () => ref.current?.refreshTimeline(), },
}); [mxClient, room, filterOption],
);
useEffect(() => {
timelineSetPromise
.then(timelineSet => { setTimelineSet(timelineSet); })
.catch(() => setTimelineSet(null));
}, [timelineSetPromise]);
return ( return (
<RoomContext.Provider value={{ <RoomContext.Provider value={{
...roomContext, ...roomContext,
timelineRenderingType: TimelineRenderingType.ThreadsList, timelineRenderingType: TimelineRenderingType.ThreadsList,
liveTimeline: filteredTimelineSet.getLiveTimeline(),
showHiddenEventsInTimeline: true, showHiddenEventsInTimeline: true,
}}> }}>
<BaseCard <BaseCard
@ -206,29 +235,31 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
onClose={onClose} onClose={onClose}
withoutScrollContainer={true} withoutScrollContainer={true}
> >
<TimelinePanel { timelineSet && (
ref={ref} <TimelinePanel
showReadReceipts={false} // No RR support in thread's MVP ref={ref}
manageReadReceipts={false} // No RR support in thread's MVP showReadReceipts={false} // No RR support in thread's MVP
manageReadMarkers={false} // No RM support in thread's MVP manageReadReceipts={false} // No RR support in thread's MVP
sendReadReceiptOnLoad={false} // No RR support in thread's MVP manageReadMarkers={false} // No RM support in thread's MVP
timelineSet={filteredTimelineSet} sendReadReceiptOnLoad={false} // No RR support in thread's MVP
showUrlPreview={true} timelineSet={timelineSet}
empty={<EmptyThread showUrlPreview={true}
filterOption={filterOption} empty={<EmptyThread
showAllThreadsCallback={() => setFilterOption(ThreadFilterType.All)} filterOption={filterOption}
/>} showAllThreadsCallback={() => setFilterOption(ThreadFilterType.All)}
alwaysShowTimestamps={true} />}
layout={Layout.Group} alwaysShowTimestamps={true}
hideThreadedMessages={false} layout={Layout.Group}
hidden={false} hideThreadedMessages={false}
showReactions={true} hidden={false}
className="mx_RoomView_messagePanel mx_GroupLayout" showReactions={false}
membersLoaded={true} className="mx_RoomView_messagePanel mx_GroupLayout"
permalinkCreator={permalinkCreator} membersLoaded={true}
tileShape={TileShape.ThreadPanel} permalinkCreator={permalinkCreator}
disableGrouping={true} tileShape={TileShape.ThreadPanel}
/> disableGrouping={true}
/>
) }
</BaseCard> </BaseCard>
</RoomContext.Provider> </RoomContext.Provider>
); );