This repository was archived by the owner on Oct 22, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathExtensionsCard.tsx
More file actions
202 lines (177 loc) · 7.07 KB
/
ExtensionsCard.tsx
File metadata and controls
202 lines (177 loc) · 7.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*
Copyright 2024 New Vector Ltd.
Copyright 2024 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React, { useEffect, useMemo, useState } from "react";
import { Room } from "matrix-js-sdk/src/matrix";
import classNames from "classnames";
import { Button, Link, Separator, Text } from "@vector-im/compound-web";
import { Icon as PlusIcon } from "@vector-im/compound-design-tokens/icons/plus.svg";
import { Icon as ExtensionsIcon } from "@vector-im/compound-design-tokens/icons/extensions.svg";
import BaseCard from "./BaseCard";
import WidgetUtils, { useWidgets } from "../../../utils/WidgetUtils";
import { _t } from "../../../languageHandler";
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
import UIStore from "../../../stores/UIStore";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { IApp } from "../../../stores/WidgetStore";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import AccessibleButton from "../elements/AccessibleButton";
import WidgetAvatar from "../avatars/WidgetAvatar";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import EmptyState from "./EmptyState";
interface Props {
room: Room;
onClose(): void;
}
interface IAppRowProps {
app: IApp;
room: Room;
}
const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
const name = WidgetUtils.getWidgetName(app);
const [canModifyWidget, setCanModifyWidget] = useState<boolean>();
useEffect(() => {
setCanModifyWidget(WidgetUtils.canUserModifyWidgets(room.client, room.roomId));
}, [room.client, room.roomId]);
const onOpenWidgetClick = (): void => {
RightPanelStore.instance.pushCard({
phase: RightPanelPhases.Widget,
state: { widgetId: app.id },
});
};
const isPinned = WidgetLayoutStore.instance.isInContainer(room, app, Container.Top);
const togglePin = isPinned
? () => {
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Right);
}
: () => {
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Top);
};
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
let contextMenu;
if (menuDisplayed) {
const rect = handle.current?.getBoundingClientRect();
const rightMargin = rect?.right ?? 0;
const topMargin = rect?.top ?? 0;
contextMenu = (
<WidgetContextMenu
chevronFace={ChevronFace.None}
right={UIStore.instance.windowWidth - rightMargin}
bottom={UIStore.instance.windowHeight - topMargin}
onFinished={closeMenu}
app={app}
/>
);
}
const cannotPin = !isPinned && !WidgetLayoutStore.instance.canAddToContainer(room, Container.Top);
let pinTitle: string;
if (cannotPin) {
pinTitle = _t("right_panel|pinned_messages|limits", { count: MAX_PINNED });
} else {
pinTitle = isPinned ? _t("action|unpin") : _t("action|pin");
}
const isMaximised = WidgetLayoutStore.instance.isInContainer(room, app, Container.Center);
let openTitle = "";
if (isPinned) {
openTitle = _t("widget|unpin_to_view_right_panel");
} else if (isMaximised) {
openTitle = _t("widget|close_to_view_right_panel");
}
const classes = classNames("mx_BaseCard_Button mx_ExtensionsCard_Button", {
mx_ExtensionsCard_Button_pinned: isPinned,
});
return (
<div className={classes} ref={handle}>
<AccessibleButton
className="mx_ExtensionsCard_icon_app"
onClick={onOpenWidgetClick}
// only show a tooltip if the widget is pinned
title={!(isPinned || isMaximised) ? undefined : openTitle}
disabled={isPinned || isMaximised}
>
<WidgetAvatar app={app} size="24px" />
<Text size="md" weight="medium" className="mx_lineClamp">
{name}
</Text>
</AccessibleButton>
{canModifyWidget && (
<ContextMenuTooltipButton
className="mx_ExtensionsCard_app_options"
isExpanded={menuDisplayed}
onClick={openMenu}
title={_t("common|options")}
/>
)}
<AccessibleButton
className="mx_ExtensionsCard_app_pinToggle"
onClick={togglePin}
title={pinTitle}
disabled={cannotPin}
/>
{contextMenu}
</div>
);
};
/**
* A right panel card displaying a list of widgets in the room and allowing the user to manage them.
* @param room the room to manage widgets for
* @param onClose callback when the card is closed
*/
const ExtensionsCard: React.FC<Props> = ({ room, onClose }) => {
const apps = useWidgets(room);
// Filter out virtual widgets
const realApps = useMemo(() => apps.filter((app) => app.eventId !== undefined), [apps]);
const onManageIntegrations = (): void => {
const managers = IntegrationManagers.sharedInstance();
if (!managers.hasManager()) {
managers.openNoManagerDialog();
} else {
// noinspection JSIgnoredPromiseFromCall
managers.getPrimaryManager()?.open(room);
}
};
let body: JSX.Element;
if (realApps.length < 1) {
body = (
<EmptyState
Icon={ExtensionsIcon}
title={_t("right_panel|extensions_empty_title")}
description={_t("right_panel|extensions_empty_description", {
addIntegrations: _t("right_panel|add_integrations"),
})}
/>
);
} else {
let copyLayoutBtn: JSX.Element | null = null;
if (WidgetLayoutStore.instance.canCopyLayoutToRoom(room)) {
copyLayoutBtn = (
<Link onClick={() => WidgetLayoutStore.instance.copyLayoutToRoom(room)}>
{_t("widget|set_room_layout")}
</Link>
);
}
body = (
<>
<Separator />
{realApps.map((app) => (
<AppRow key={app.id} app={app} room={room} />
))}
{copyLayoutBtn}
</>
);
}
return (
<BaseCard header={_t("right_panel|extensions_button")} className="mx_ExtensionsCard" onClose={onClose}>
<Button size="sm" onClick={onManageIntegrations} kind="secondary" Icon={PlusIcon}>
{_t("right_panel|add_integrations")}
</Button>
{body}
</BaseCard>
);
};
export default ExtensionsCard;