diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index f2bd7db0f3..85d3013a58 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -46,17 +46,20 @@ import UserInfo from "../views/right_panel/UserInfo"; import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo"; import FilePanel from "./FilePanel"; import ThreadView from "./ThreadView"; +import ThreadPanel from "./ThreadPanel"; import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; import { throttle } from 'lodash'; import SpaceStore from "../../stores/SpaceStore"; +import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; interface IProps { room?: Room; // if showing panels for a given room, this is set groupId?: string; // if showing panels for a given group, this is set user?: User; // used if we know the user ahead of opening the panel resizeNotifier: ResizeNotifier; + permalinkCreator?: RoomPermalinkCreator; } interface IState { @@ -315,7 +318,15 @@ export default class RightPanel extends React.Component { roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} - mxEvent={this.state.event} />; + mxEvent={this.state.event} + permalinkCreator={this.props.permalinkCreator} />; + break; + + case RightPanelPhases.ThreadPanel: + panel = ; break; case RightPanelPhases.RoomSummary: diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 474b99262d..baf557ee18 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2052,7 +2052,10 @@ export default class RoomView extends React.Component { const showRightPanel = this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel - ? + ? : null; const timelineClasses = classNames("mx_RoomView_timeline", { diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx new file mode 100644 index 0000000000..c99246ccda --- /dev/null +++ b/src/components/structures/ThreadPanel.tsx @@ -0,0 +1,106 @@ +/* +Copyright 2016 OpenMarket Ltd +Copyright 2019 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 React from 'react'; +import { MatrixEvent, Room } from 'matrix-js-sdk/src'; + +import BaseCard from "../views/right_panel/BaseCard"; +import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; +import { replaceableComponent } from "../../utils/replaceableComponent"; +import { MatrixClientPeg } from '../../MatrixClientPeg'; + +import ResizeNotifier from '../../utils/ResizeNotifier'; +import EventTile from '../views/rooms/EventTile'; +import { Thread } from '../../../../matrix-js-sdk/src/models/thread'; + +interface IProps { + roomId: string; + onClose: () => void; + resizeNotifier: ResizeNotifier; +} + +interface IState { + threads?: Thread[]; +} + +/* + * Component which shows the filtered file using a TimelinePanel + */ +@replaceableComponent("structures.ThreadView") +class ThreadView extends React.Component { + private room: Room; + + constructor(props: IProps) { + super(props); + this.room = MatrixClientPeg.get().getRoom(this.props.roomId); + } + + public componentDidMount(): void { + this.room.on("Thread.update", this.onThreadEventReceived); + this.room.on("Thread.ready", this.onThreadEventReceived); + this.updateThreads(() => { + this.state.threads.forEach(thread => { + if (!thread.ready) { + thread.fetchReplyChain(); + } + }); + }); + } + + public componentWillUnmount(): void { + this.room.removeListener("Thread.update", this.onThreadEventReceived); + this.room.removeListener("Thread.ready", this.onThreadEventReceived); + } + + public onThreadEventReceived = () => this.updateThreads(); + + public updateThreads = (callback?: () => void): void => { + this.setState({ + threads: this.room.getThreads(), + }, callback); + }; + + public renderEventTile(event: MatrixEvent): JSX.Element { + return ; + } + + public render() { + return ( + + { + this.state?.threads.map((thread: Thread) => { + if (thread.ready) { + return this.renderEventTile(thread.rootEvent); + } + }) + } + + ); + } +} + +export default ThreadView; diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 8b196cb4f0..0f9d499884 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -26,12 +26,14 @@ import { MatrixClientPeg } from '../../MatrixClientPeg'; import ResizeNotifier from '../../utils/ResizeNotifier'; import EventTile from '../views/rooms/EventTile'; import MessageComposer from '../views/rooms/MessageComposer'; +import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; interface IProps { roomId: string; onClose: () => void; resizeNotifier: ResizeNotifier; mxEvent: MatrixEvent; + permalinkCreator?: RoomPermalinkCreator; } interface IState { @@ -44,9 +46,18 @@ interface IState { class ThreadView extends React.Component { state = {}; - public componentDidMount(): void {} + public componentDidMount(): void { + // this.props.mxEvent.getThread().on("Thread.update", this.updateThread); + this.props.mxEvent.getThread().once("Thread.ready", this.updateThread); + } - public componentWillUnmount(): void {} + public componentWillUnmount(): void { + this.props.mxEvent.getThread().removeListener("Thread.update", this.updateThread); + } + + updateThread = () => { + this.forceUpdate(); + }; public renderEventTile(event: MatrixEvent): JSX.Element { return { } public render() { - const client = MatrixClientPeg.get(); - const room = client.getRoom(this.props.roomId); - const thread = room.getThread(this.props.mxEvent.getId()); + const thread = this.props.mxEvent.getThread(); + const room = MatrixClientPeg.get().getRoom(this.props.roomId); return ( { room={room} resizeNotifier={this.props.resizeNotifier} replyToEvent={this.props.mxEvent} - permalinkCreator={null} + permalinkCreator={this.props.permalinkCreator} /> ); diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 047448d925..eb3d7499f4 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -220,6 +220,13 @@ const onRoomFilesClick = () => { }); }; +const onRoomThreadsClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.ThreadPanel, + }); +}; + const onRoomSettingsClick = () => { defaultDispatcher.dispatch({ action: "open_room_settings" }); }; @@ -273,6 +280,9 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ca55f45619..ec28be664b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1790,6 +1790,7 @@ "%(count)s people|other": "%(count)s people", "%(count)s people|one": "%(count)s person", "Show files": "Show files", + "Show threads": "Show threads", "Share room": "Share room", "Room settings": "Room settings", "Trusted": "Trusted", diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 8f41b17e47..96a585b676 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -40,6 +40,7 @@ export enum RightPanelPhases { // Thread stuff ThreadView = "ThreadView", + ThreadPanel = "ThreadPanel", } // These are the phases that are safe to persist (the ones that don't require additional