Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 4cbed99

Browse files
toger5jryans
andauthored
Add right panel chat timeline (#7112)
Co-authored-by: J. Ryan Stinnett <[email protected]>
1 parent f5f1f18 commit 4cbed99

File tree

14 files changed

+243
-17
lines changed

14 files changed

+243
-17
lines changed

res/css/_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@
205205
@import "./views/right_panel/_PinnedMessagesCard.scss";
206206
@import "./views/right_panel/_RoomSummaryCard.scss";
207207
@import "./views/right_panel/_ThreadPanel.scss";
208+
@import "./views/right_panel/_TimelineCard.scss";
208209
@import "./views/right_panel/_UserInfo.scss";
209210
@import "./views/right_panel/_VerificationPanel.scss";
210211
@import "./views/right_panel/_WidgetCard.scss";

res/css/structures/_RightPanel.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ $pulse-color: $alert;
144144
}
145145
}
146146

147+
.mx_RightPanel_timelineCardButton {
148+
&::before {
149+
mask-image: url('$(res)/img/element-icons/feedback.svg');
150+
mask-position: center;
151+
}
152+
}
153+
147154
@keyframes mx_RightPanel_indicator_pulse {
148155
0% {
149156
transform: scale(0.95);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2021 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
.mx_TimelineCard {
18+
.mx_TimelineCard__header {
19+
margin-left: 6px;
20+
21+
span:first-of-type {
22+
font-weight: 600;
23+
font-size: 15px;
24+
line-height: 18px;
25+
color: $secondary-content;
26+
}
27+
}
28+
29+
.mx_BaseCard_header {
30+
margin: 5px 0 9px 0;
31+
.mx_BaseCard_close {
32+
margin: 8px;
33+
right: 0;
34+
}
35+
}
36+
}

src/components/structures/RightPanel.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import SpaceStore from "../../stores/spaces/SpaceStore";
5454
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
5555
import { E2EStatus } from '../../utils/ShieldUtils';
5656
import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads';
57+
import TimelineCard from '../views/right_panel/TimelineCard';
5758

5859
interface IProps {
5960
room?: Room; // if showing panels for a given room, this is set
@@ -334,7 +335,13 @@ export default class RightPanel extends React.Component<IProps, IState> {
334335
panel = <PinnedMessagesCard room={this.props.room} onClose={this.onClose} />;
335336
}
336337
break;
337-
338+
case RightPanelPhases.Timeline:
339+
if (!SettingsStore.getValue("feature_maximised_widgets")) break;
340+
panel = <TimelineCard
341+
room={this.props.room}
342+
resizeNotifier={this.props.resizeNotifier}
343+
onClose={this.onClose} />;
344+
break;
338345
case RightPanelPhases.FilePanel:
339346
panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
340347
break;

src/components/structures/RoomView.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ import { dispatchShowThreadEvent } from '../../dispatcher/dispatch-actions/threa
9696
import { fetchInitialEvent } from "../../utils/EventUtils";
9797
import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
9898
import AppsDrawer from '../views/rooms/AppsDrawer';
99+
import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload';
100+
import { RightPanelPhases } from '../../stores/RightPanelStorePhases';
99101

100102
const DEBUG = false;
101103
let debuglog = function(msg: string) {};
@@ -327,7 +329,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
327329

328330
private onWidgetLayoutChange = () => {
329331
if (!this.state.room) return;
332+
if (WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)) {
333+
// Show chat in right panel when a widget is maximised
334+
dis.dispatch<SetRightPanelPhasePayload>({
335+
action: Action.SetRightPanelPhase,
336+
phase: RightPanelPhases.Timeline,
337+
});
338+
}
330339
this.checkWidgets(this.state.room);
340+
this.checkRightPanel(this.state.room);
331341
};
332342

333343
private checkWidgets = (room) => {
@@ -345,6 +355,22 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
345355
: MainSplitContentType.Timeline;
346356
};
347357

358+
private checkRightPanel = (room) => {
359+
// This is a hack to hide the chat. This should not be necessary once the right panel
360+
// phase is stored per room. (need to be done after check widget so that mainSplitContentType is updated)
361+
if (
362+
RightPanelStore.getSharedInstance().roomPanelPhase === RightPanelPhases.Timeline &&
363+
this.state.showRightPanel &&
364+
!WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)
365+
) {
366+
// Two timelines are shown prevent this by hiding the right panel
367+
dis.dispatch({
368+
action: Action.ToggleRightPanel,
369+
type: "room",
370+
});
371+
}
372+
};
373+
348374
private onReadReceiptsChange = () => {
349375
this.setState({
350376
showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId),
@@ -1007,6 +1033,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
10071033
this.updateE2EStatus(room);
10081034
this.updatePermissions(room);
10091035
this.checkWidgets(room);
1036+
this.checkRightPanel(room);
10101037

10111038
this.setState({
10121039
liveTimeline: room.getLiveTimeline(),
@@ -2102,6 +2129,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
21022129
}
21032130

21042131
const showRightPanel = this.state.room && this.state.showRightPanel;
2132+
21052133
const rightPanel = showRightPanel
21062134
? <RightPanel
21072135
room={this.state.room}

src/components/structures/ThreadView.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import ContentMessages from '../../ContentMessages';
4141
import UploadBar from './UploadBar';
4242
import { _t } from '../../languageHandler';
4343
import ThreadListContextMenu from '../views/context_menus/ThreadListContextMenu';
44+
import RightPanelStore from '../../stores/RightPanelStore';
45+
import SettingsStore from '../../settings/SettingsStore';
4446

4547
interface IProps {
4648
room: Room;
@@ -203,6 +205,18 @@ export default class ThreadView extends React.Component<IProps, IState> {
203205
event_id: this.state.thread?.id,
204206
};
205207

208+
let previousPhase = RightPanelStore.getSharedInstance().previousPhase;
209+
if (!SettingsStore.getValue("feature_maximised_widgets")) {
210+
previousPhase = RightPanelPhases.ThreadPanel;
211+
}
212+
// Make sure the previous Phase is always one of the two: Timeline or ThreadPanel
213+
if (![RightPanelPhases.ThreadPanel, RightPanelPhases.Timeline].includes(previousPhase)) {
214+
previousPhase = RightPanelPhases.ThreadPanel;
215+
}
216+
const previousPhaseLabels = {};
217+
previousPhaseLabels[RightPanelPhases.ThreadPanel] = _t("All threads");
218+
previousPhaseLabels[RightPanelPhases.Timeline] = _t("Chat");
219+
206220
return (
207221
<RoomContext.Provider value={{
208222
...this.context,
@@ -213,8 +227,8 @@ export default class ThreadView extends React.Component<IProps, IState> {
213227
<BaseCard
214228
className="mx_ThreadView mx_ThreadPanel"
215229
onClose={this.props.onClose}
216-
previousPhase={RightPanelPhases.ThreadPanel}
217-
previousPhaseLabel={_t("All threads")}
230+
previousPhase={previousPhase}
231+
previousPhaseLabel={previousPhaseLabels[previousPhase]}
218232
withoutScrollContainer={true}
219233
header={this.renderThreadViewHeader()}
220234
>

src/components/structures/TimelinePanel.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -476,10 +476,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
476476
};
477477

478478
private onMessageListScroll = e => {
479-
if (this.props.onScroll) {
480-
this.props.onScroll(e);
481-
}
482-
479+
this.props.onScroll?.(e);
483480
if (this.props.manageReadMarkers) {
484481
this.doManageReadMarkers();
485482
}
@@ -594,7 +591,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
594591
this.setState<null>(updatedState, () => {
595592
this.messagePanel.current.updateTimelineMinHeight();
596593
if (callRMUpdated) {
597-
this.props.onReadMarkerUpdated();
594+
this.props.onReadMarkerUpdated?.();
598595
}
599596
});
600597
});

src/components/views/context_menus/MessageContextMenu.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
4040
import { IPosition, ChevronFace } from '../../structures/ContextMenu';
4141
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
4242
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
43+
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
4344

4445
export function canCancel(eventStatus: EventStatus): boolean {
4546
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@@ -404,9 +405,12 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
404405
);
405406
const isThreadRootEvent = isThread && this.props.mxEvent?.getThread()?.rootEvent === this.props.mxEvent;
406407

408+
const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget(
409+
MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
410+
);
407411
const commonItemsList = (
408412
<IconizedContextMenuOptionList>
409-
{ isThreadRootEvent && <IconizedContextMenuOption
413+
{ (isThreadRootEvent && isMainSplitTimelineShown) && <IconizedContextMenuOption
410414
iconClassName="mx_MessageContextMenu_iconViewInRoom"
411415
label={_t("View in room")}
412416
onClick={this.viewInRoom}

src/components/views/context_menus/ThreadListContextMenu.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { copyPlaintext } from "../../../utils/strings";
2424
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
2525
import { _t } from "../../../languageHandler";
2626
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
27+
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
28+
import { MatrixClientPeg } from "../../../MatrixClientPeg";
2729

2830
interface IProps {
2931
mxEvent: MatrixEvent;
@@ -80,6 +82,9 @@ const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, on
8082
}
8183
}, [optionsPosition, onMenuToggle]);
8284

85+
const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget(
86+
MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
87+
);
8388
return <React.Fragment>
8489
<ContextMenuTooltipButton
8590
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
@@ -95,11 +100,12 @@ const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, on
95100
{...contextMenuBelow(optionsPosition)}
96101
>
97102
<IconizedContextMenuOptionList>
98-
<IconizedContextMenuOption
99-
onClick={(e) => viewInRoom(e)}
100-
label={_t("View in room")}
101-
iconClassName="mx_ThreadPanel_viewInRoom"
102-
/>
103+
{ isMainSplitTimelineShown &&
104+
<IconizedContextMenuOption
105+
onClick={(e) => viewInRoom(e)}
106+
label={_t("View in room")}
107+
iconClassName="mx_ThreadPanel_viewInRoom"
108+
/> }
103109
<IconizedContextMenuOption
104110
onClick={(e) => copyLinkToThread(e)}
105111
label={_t("Copy link to thread")}

src/components/views/elements/AppTile.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,9 +402,9 @@ export default class AppTile extends React.Component<IProps, IState> {
402402

403403
private onMaxMinWidgetClick = (): void => {
404404
const targetContainer =
405-
WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, Container.Center)
406-
? Container.Right
407-
: Container.Center;
405+
WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, Container.Center)
406+
? Container.Right
407+
: Container.Center;
408408
WidgetLayoutStore.instance.moveToContainer(this.props.room, this.props.app, targetContainer);
409409
};
410410

0 commit comments

Comments
 (0)