Hook thread panel to homeserver API (#7352)
parent
b4755f38b9
commit
cd04799cb4
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue