Extract Extensions into their own right panel tab (#12844)
* Extract useIsVideoRoom hook Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Move useWidgets hook to WidgetUtils Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Extract Extensions into their own right panel tab Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove unused components & classes Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
fae5bf1612
commit
b55653ddf0
25 changed files with 820 additions and 475 deletions
|
@ -41,26 +41,11 @@ interface IProps {
|
|||
onKeyDown?(ev: KeyboardEvent): void;
|
||||
cardState?: any;
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
// Ref for the 'close' button the the card
|
||||
// Ref for the 'close' button the card
|
||||
closeButtonRef?: Ref<HTMLButtonElement>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface IGroupProps {
|
||||
className?: string;
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const Group: React.FC<IGroupProps> = ({ className, title, children }) => {
|
||||
return (
|
||||
<div className={classNames("mx_BaseCard_Group", className)}>
|
||||
<h2>{title}</h2>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
|
||||
(
|
||||
{
|
||||
|
|
214
src/components/views/right_panel/ExtensionsCard.tsx
Normal file
214
src/components/views/right_panel/ExtensionsCard.tsx
Normal file
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
Copyright 2024 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, { 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);
|
||||
}
|
||||
};
|
||||
|
||||
// The button is in the header to keep it outside the scrollable region
|
||||
const header = (
|
||||
<Button size="sm" onClick={onManageIntegrations} kind="secondary" Icon={PlusIcon}>
|
||||
{_t("right_panel|add_integrations")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
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={header} className="mx_ExtensionsCard" onClose={onClose} hideHeaderButtons>
|
||||
{body}
|
||||
</BaseCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtensionsCard;
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React, { useRef } from "react";
|
||||
import { NavBar, NavItem } from "@vector-im/compound-web";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
|
@ -24,17 +25,27 @@ import PosthogTrackers from "../../../PosthogTrackers";
|
|||
import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||
import dispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIComponent, UIFeature } from "../../../settings/UIFeature";
|
||||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||
import { useIsVideoRoom } from "../../../utils/video-rooms";
|
||||
|
||||
function shouldShowTabsForPhase(phase?: RightPanelPhases): boolean {
|
||||
const tabs = [RightPanelPhases.RoomSummary, RightPanelPhases.RoomMemberList, RightPanelPhases.ThreadPanel];
|
||||
const tabs = [
|
||||
RightPanelPhases.RoomSummary,
|
||||
RightPanelPhases.RoomMemberList,
|
||||
RightPanelPhases.ThreadPanel,
|
||||
RightPanelPhases.Extensions,
|
||||
];
|
||||
return !!phase && tabs.includes(phase);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
room?: Room;
|
||||
phase: RightPanelPhases;
|
||||
};
|
||||
|
||||
export const RightPanelTabs: React.FC<Props> = ({ phase }): JSX.Element | null => {
|
||||
export const RightPanelTabs: React.FC<Props> = ({ phase, room }): JSX.Element | null => {
|
||||
const threadsTabRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
useDispatcher(dispatcher, (payload) => {
|
||||
|
@ -45,6 +56,8 @@ export const RightPanelTabs: React.FC<Props> = ({ phase }): JSX.Element | null =
|
|||
}
|
||||
});
|
||||
|
||||
const isVideoRoom = useIsVideoRoom(room);
|
||||
|
||||
if (!shouldShowTabsForPhase(phase)) return null;
|
||||
|
||||
return (
|
||||
|
@ -81,6 +94,20 @@ export const RightPanelTabs: React.FC<Props> = ({ phase }): JSX.Element | null =
|
|||
>
|
||||
{_t("common|threads")}
|
||||
</NavItem>
|
||||
{SettingsStore.getValue(UIFeature.Widgets) &&
|
||||
!isVideoRoom &&
|
||||
shouldShowComponent(UIComponent.AddIntegrations) && (
|
||||
<NavItem
|
||||
aria-controls="thread-panel"
|
||||
id="extensions-panel-tab"
|
||||
onClick={() => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.Extensions }, true);
|
||||
}}
|
||||
active={phase === RightPanelPhases.Extensions}
|
||||
>
|
||||
{_t("common|extensions")}
|
||||
</NavItem>
|
||||
)}
|
||||
</NavBar>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,16 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
SyntheticEvent,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import React, { ChangeEvent, SyntheticEvent, useContext, useEffect, useRef, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
MenuItem,
|
||||
|
@ -55,35 +46,23 @@ import { EventType, JoinRule, Room, RoomStateEvent } from "matrix-js-sdk/src/mat
|
|||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
||||
import BaseCard, { Group } from "./BaseCard";
|
||||
import BaseCard from "./BaseCard";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import Modal from "../../../Modal";
|
||||
import ShareDialog from "../dialogs/ShareDialog";
|
||||
import { useEventEmitter, useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import WidgetUtils from "../../../utils/WidgetUtils";
|
||||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import WidgetAvatar from "../avatars/WidgetAvatar";
|
||||
import WidgetStore, { IApp } from "../../../stores/WidgetStore";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import { UIComponent, UIFeature } from "../../../settings/UIFeature";
|
||||
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import { usePinnedEvents } from "./PinnedMessagesCard";
|
||||
import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import RoomName from "../elements/RoomName";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
import ExportDialog from "../dialogs/ExportDialog";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||
import { PollHistoryDialog } from "../dialogs/PollHistoryDialog";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
|
@ -111,182 +90,6 @@ interface IProps {
|
|||
focusRoomSearch?: boolean;
|
||||
}
|
||||
|
||||
interface IAppsSectionProps {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
export const useWidgets = (room: Room): IApp[] => {
|
||||
const [apps, setApps] = useState<IApp[]>(() => WidgetStore.instance.getApps(room.roomId));
|
||||
|
||||
const updateApps = useCallback(() => {
|
||||
// Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings
|
||||
setApps([...WidgetStore.instance.getApps(room.roomId)]);
|
||||
}, [room]);
|
||||
|
||||
useEffect(updateApps, [room, updateApps]);
|
||||
useEventEmitter(WidgetStore.instance, room.roomId, updateApps);
|
||||
useEventEmitter(WidgetLayoutStore.instance, WidgetLayoutStore.emissionForRoom(room), updateApps);
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
interface IAppRowProps {
|
||||
app: IApp;
|
||||
room: Room;
|
||||
}
|
||||
|
||||
const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
|
||||
const name = WidgetUtils.getWidgetName(app);
|
||||
const dataTitle = WidgetUtils.getWidgetDataTitle(app);
|
||||
const subtitle = dataTitle && " - " + dataTitle;
|
||||
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);
|
||||
const toggleMaximised = isMaximised
|
||||
? () => {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Right);
|
||||
}
|
||||
: () => {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Center);
|
||||
};
|
||||
|
||||
const maximiseTitle = isMaximised ? _t("action|close") : _t("action|maximise");
|
||||
|
||||
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_RoomSummaryCard_Button", {
|
||||
mx_RoomSummaryCard_Button_pinned: isPinned,
|
||||
mx_RoomSummaryCard_Button_maximised: isMaximised,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classes} ref={handle}>
|
||||
<AccessibleButton
|
||||
className="mx_RoomSummaryCard_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="20px" />
|
||||
<span>{name}</span>
|
||||
{subtitle}
|
||||
</AccessibleButton>
|
||||
|
||||
{canModifyWidget && (
|
||||
<ContextMenuTooltipButton
|
||||
className="mx_RoomSummaryCard_app_options"
|
||||
isExpanded={menuDisplayed}
|
||||
onClick={openMenu}
|
||||
title={_t("common|options")}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AccessibleButton
|
||||
className="mx_RoomSummaryCard_app_pinToggle"
|
||||
onClick={togglePin}
|
||||
title={pinTitle}
|
||||
disabled={cannotPin}
|
||||
/>
|
||||
<AccessibleButton
|
||||
className="mx_RoomSummaryCard_app_maximiseToggle"
|
||||
onClick={toggleMaximised}
|
||||
title={maximiseTitle}
|
||||
/>
|
||||
|
||||
{contextMenu}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
|
||||
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 copyLayoutBtn: JSX.Element | null = null;
|
||||
if (realApps.length > 0 && WidgetLayoutStore.instance.canCopyLayoutToRoom(room)) {
|
||||
copyLayoutBtn = (
|
||||
<AccessibleButton kind="link" onClick={() => WidgetLayoutStore.instance.copyLayoutToRoom(room)}>
|
||||
{_t("widget|set_room_layout")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Group className="mx_RoomSummaryCard_appsGroup" title={_t("right_panel|widgets_section")}>
|
||||
{realApps.map((app) => (
|
||||
<AppRow key={app.id} app={app} room={room} />
|
||||
))}
|
||||
{copyLayoutBtn}
|
||||
<AccessibleButton kind="link" onClick={onManageIntegrations}>
|
||||
{realApps.length > 0 ? _t("right_panel|edit_integrations") : _t("right_panel|add_integrations")}
|
||||
</AccessibleButton>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
const onRoomFilesClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true);
|
||||
};
|
||||
|
@ -622,10 +425,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||
onSelect={onLeaveRoomClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{SettingsStore.getValue(UIFeature.Widgets) &&
|
||||
!isVideoRoom &&
|
||||
shouldShowComponent(UIComponent.AddIntegrations) && <AppsSection room={room} />}
|
||||
</BaseCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -19,10 +19,9 @@ import { Room } from "matrix-js-sdk/src/matrix";
|
|||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import BaseCard from "./BaseCard";
|
||||
import WidgetUtils from "../../../utils/WidgetUtils";
|
||||
import WidgetUtils, { useWidgets } from "../../../utils/WidgetUtils";
|
||||
import AppTile from "../elements/AppTile";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { useWidgets } from "./RoomSummaryCard";
|
||||
import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
|
||||
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
|
|
|
@ -54,7 +54,7 @@ import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHa
|
|||
import { useFeatureEnabled, useSettingValue } from "../../../hooks/useSettings";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { useEventEmitterState, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { useWidgets } from "../right_panel/RoomSummaryCard";
|
||||
import { useWidgets } from "../../../utils/WidgetUtils";
|
||||
import { WidgetType } from "../../../widgets/WidgetType";
|
||||
import { useCall, useLayout } from "../../../hooks/useCall";
|
||||
import { getJoinedNonFunctionalMembers } from "../../../utils/room/getJoinedNonFunctionalMembers";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue