Files
ThreadNet-Web/src/components/views/rooms/Stickerpicker.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

358 lines
14 KiB
TypeScript
Raw Normal View History

2018-01-16 18:14:32 +00:00
/*
2024-09-09 14:57:16 +01:00
Copyright 2018-2024 New Vector Ltd.
2018-01-16 18:14:32 +00:00
2024-09-09 14:57:16 +01:00
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
2018-01-16 18:14:32 +00:00
*/
2018-01-16 18:14:32 +00:00
import React from "react";
import { Room, ClientEvent } from "matrix-js-sdk/src/matrix";
2021-10-22 17:23:32 -05:00
import { logger } from "matrix-js-sdk/src/logger";
import { IWidget } from "matrix-widget-api";
2021-10-22 17:23:32 -05:00
import { _t, _td, TranslationKey } from "../../../languageHandler";
2018-01-16 18:14:32 +00:00
import AppTile from "../elements/AppTile";
2021-06-29 13:11:58 +01:00
import { MatrixClientPeg } from "../../../MatrixClientPeg";
2020-05-13 20:41:41 -06:00
import dis from "../../../dispatcher/dispatcher";
2018-04-02 10:18:35 +01:00
import AccessibleButton from "../elements/AccessibleButton";
import WidgetUtils, { UserWidget } from "../../../utils/WidgetUtils";
import PersistedElement from "../elements/PersistedElement";
2021-06-29 13:11:58 +01:00
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import ContextMenu, { ChevronFace } from "../../structures/ContextMenu";
2021-06-29 13:11:58 +01:00
import { WidgetType } from "../../../widgets/WidgetType";
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
2021-08-25 14:26:21 +01:00
import { ActionPayload } from "../../../dispatcher/payloads";
import ScalarAuthClient from "../../../ScalarAuthClient";
2021-08-25 14:38:47 +01:00
import GenericElementContextMenu from "../context_menus/GenericElementContextMenu";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
2018-02-27 13:40:21 +00:00
2019-04-01 20:34:33 -06:00
// This should be below the dialog level (4000), but above the rest of the UI (1000-2000).
// We sit in a context menu, so this should be given to the context menu.
const STICKERPICKER_Z_INDEX = 3500;
2018-07-03 18:25:33 +01:00
// Key to store the widget's AppTile under in PersistedElement
const PERSISTED_ELEMENT_KEY = "stickerPicker";
2021-08-25 14:26:21 +01:00
interface IProps {
room: Room;
threadId?: string | null;
isStickerPickerOpen: boolean;
menuPosition?: any;
setStickerPickerOpen: (isStickerPickerOpen: boolean) => void;
2021-08-25 14:26:21 +01:00
}
interface IState {
imError: string | null;
stickerpickerWidget: UserWidget | null;
widgetId: string | null;
2021-08-25 14:26:21 +01:00
}
export default class Stickerpicker extends React.PureComponent<IProps, IState> {
2023-02-13 11:39:16 +00:00
public static defaultProps: Partial<IProps> = {
threadId: null,
};
public static currentWidget?: UserWidget;
private dispatcherRef?: string;
2021-08-25 14:26:21 +01:00
private prevSentVisibility?: boolean;
2021-08-25 14:26:21 +01:00
private popoverWidth = 300;
private popoverHeight = 300;
// This is loaded by _acquireScalarClient on an as-needed basis.
private scalarClient: ScalarAuthClient | null = null;
2021-08-25 14:26:21 +01:00
public constructor(props: IProps) {
2018-01-16 18:14:32 +00:00
super(props);
this.state = {
imError: null,
stickerpickerWidget: null,
widgetId: null,
};
}
private async acquireScalarClient(): Promise<void | undefined | null | ScalarAuthClient> {
if (this.scalarClient) return Promise.resolve(this.scalarClient);
// TODO: Pick the right manager for the widget
if (IntegrationManagers.sharedInstance().hasManager()) {
this.scalarClient = IntegrationManagers.sharedInstance().getPrimaryManager()?.getScalarClient() ?? null;
return this.scalarClient
?.connect()
.then(() => {
this.forceUpdate();
return this.scalarClient;
})
.catch((e) => {
this.imError(_td("integration_manager|error_connecting_heading"), e);
});
} else {
IntegrationManagers.sharedInstance().openNoManagerDialog();
}
}
2021-08-25 14:26:21 +01:00
private removeStickerpickerWidgets = async (): Promise<void> => {
const scalarClient = await this.acquireScalarClient();
logger.log("Removing Stickerpicker widgets");
if (this.state.widgetId) {
if (scalarClient) {
scalarClient
.disableWidgetAssets(WidgetType.STICKERPICKER, this.state.widgetId)
.then(() => {
logger.log("Assets disabled");
})
.catch(() => {
2021-10-15 16:30:53 +02:00
logger.error("Failed to disable assets");
});
} else {
2021-10-15 16:30:53 +02:00
logger.error("Cannot disable assets: no scalar client");
}
} else {
2021-10-15 16:31:29 +02:00
logger.warn("No widget ID specified, not disabling assets");
}
2018-03-29 21:16:35 +01:00
this.props.setStickerPickerOpen(false);
WidgetUtils.removeStickerpickerWidgets(this.props.room.client)
2018-06-26 11:52:21 +01:00
.then(() => {
2018-04-02 20:25:24 +01:00
this.forceUpdate();
2018-03-29 21:16:35 +01:00
})
.catch((e) => {
2021-10-15 16:30:53 +02:00
logger.error("Failed to remove sticker picker widget", e);
2018-03-29 21:16:35 +01:00
});
2021-08-25 14:26:21 +01:00
};
2018-02-05 11:49:44 +00:00
2021-08-25 14:26:21 +01:00
public componentDidMount(): void {
// Close the sticker picker when the window resizes
2021-08-25 14:26:21 +01:00
window.addEventListener("resize", this.onResize);
this.dispatcherRef = dis.register(this.onAction);
// Track updates to widget state in account data
MatrixClientPeg.safeGet().on(ClientEvent.AccountData, this.updateWidget);
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
// Initialise widget state from current account data
2021-08-25 14:26:21 +01:00
this.updateWidget();
2018-02-07 09:23:00 +00:00
}
2018-01-17 00:04:06 +00:00
2021-08-25 14:26:21 +01:00
public componentWillUnmount(): void {
const client = MatrixClientPeg.get();
if (client) client.removeListener(ClientEvent.AccountData, this.updateWidget);
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
2021-08-25 14:26:21 +01:00
window.removeEventListener("resize", this.onResize);
2018-03-29 17:35:21 +01:00
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
}
2018-02-07 09:23:00 +00:00
}
public componentDidUpdate(): void {
this.sendVisibilityToWidget(this.props.isStickerPickerOpen);
}
private imError(errorMsg: TranslationKey, e: Error): void {
2021-10-15 16:30:53 +02:00
logger.error(errorMsg, e);
this.setState({
imError: _t(errorMsg),
});
this.props.setStickerPickerOpen(false);
}
2021-08-25 14:26:21 +01:00
private updateWidget = (): void => {
const stickerpickerWidget = WidgetUtils.getStickerpickerWidgets(this.props.room.client)[0];
if (!stickerpickerWidget) {
Stickerpicker.currentWidget = undefined;
2021-06-29 13:11:58 +01:00
this.setState({ stickerpickerWidget: null, widgetId: null });
return;
}
const currentWidget = Stickerpicker.currentWidget;
const currentUrl = currentWidget?.content?.url ?? null;
const newUrl = stickerpickerWidget?.content?.url ?? null;
if (newUrl !== currentUrl) {
// Destroy the existing frame so a new one can be created
PersistedElement.destroyElement(PERSISTED_ELEMENT_KEY);
}
Stickerpicker.currentWidget = stickerpickerWidget;
this.setState({
stickerpickerWidget,
widgetId: stickerpickerWidget ? stickerpickerWidget.id : null,
});
2021-08-25 14:26:21 +01:00
};
private onAction = (payload: ActionPayload): void => {
switch (payload.action) {
case "user_widget_updated":
this.forceUpdate();
break;
case "stickerpicker_close":
this.props.setStickerPickerOpen(false);
break;
case "show_left_panel":
case "hide_left_panel":
this.props.setStickerPickerOpen(false);
break;
2018-02-07 09:23:00 +00:00
}
2021-08-25 14:26:21 +01:00
};
2018-02-07 09:23:00 +00:00
private onRightPanelStoreUpdate = (): void => {
this.props.setStickerPickerOpen(false);
};
2021-08-25 14:26:21 +01:00
private defaultStickerpickerContent(): JSX.Element {
return (
2021-08-25 14:26:21 +01:00
<AccessibleButton onClick={this.launchManageIntegrations} className="mx_Stickers_contentPlaceholder">
<p>{_t("stickers|empty")}</p>
<p className="mx_Stickers_addLink">{_t("stickers|empty_add_prompt")}</p>
<img src={require("../../../../res/img/stickerpack-placeholder.png")} alt="" />
</AccessibleButton>
);
}
2021-08-25 14:26:21 +01:00
private errorStickerpickerContent(): JSX.Element {
return (
2021-08-25 14:26:21 +01:00
<div style={{ textAlign: "center" }} className="error">
<p> {this.state.imError} </p>
</div>
);
}
2021-08-25 14:26:21 +01:00
private sendVisibilityToWidget(visible: boolean): void {
if (!this.state.stickerpickerWidget) return;
const messaging = WidgetMessagingStore.instance.getMessagingForUid(
WidgetUtils.calcWidgetUid(this.state.stickerpickerWidget.id),
);
2021-08-25 14:26:21 +01:00
if (messaging && visible !== this.prevSentVisibility) {
2020-09-30 16:12:00 -06:00
messaging.updateVisibility(visible).catch((err) => {
2021-10-15 16:30:53 +02:00
logger.error("Error updating widget visibility: ", err);
2020-09-30 16:12:00 -06:00
});
2021-08-25 14:26:21 +01:00
this.prevSentVisibility = visible;
}
}
2021-08-25 14:26:21 +01:00
public getStickerpickerContent(): JSX.Element {
2021-07-13 16:04:50 +01:00
// Handle integration manager errors
2021-08-25 14:26:21 +01:00
if (this.state.imError) {
return this.errorStickerpickerContent();
}
2018-01-16 18:14:32 +00:00
// Stickers
2018-02-25 22:10:38 +00:00
// TODO - Add support for Stickerpickers from multiple app stores.
// Render content from multiple stickerpack sources, each within their
// own iframe, within the stickerpicker UI element.
const stickerpickerWidget = this.state.stickerpickerWidget;
let stickersContent: JSX.Element | undefined;
2018-01-16 18:14:32 +00:00
// Use a separate ReactDOM tree to render the AppTile separately so that it persists and does
// not unmount when we (a) close the sticker picker (b) switch rooms. It's properties are still
// updated.
2018-01-16 18:14:32 +00:00
// Load stickerpack content
if (!!stickerpickerWidget?.content?.url) {
2018-02-07 09:23:00 +00:00
// Set default name
stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("common|stickerpack");
2018-02-07 14:44:01 +00:00
// FIXME: could this use the same code as other apps?
const stickerApp: IWidget = {
id: stickerpickerWidget.id,
url: stickerpickerWidget.content.url,
name: stickerpickerWidget.content.name,
type: stickerpickerWidget.content.type,
data: stickerpickerWidget.content.data,
creatorUserId: stickerpickerWidget.content.creatorUserId || stickerpickerWidget.sender,
};
2018-01-16 23:25:07 +00:00
stickersContent = (
2018-03-29 22:22:57 +01:00
<div className="mx_Stickers_content_container">
2018-01-16 23:25:07 +00:00
<div
id="stickersContent"
2018-01-17 15:49:36 +00:00
className="mx_Stickers_content"
2018-01-16 23:25:07 +00:00
style={{
border: "none",
2018-02-07 14:44:01 +00:00
height: this.popoverHeight,
2018-01-16 23:25:07 +00:00
width: this.popoverWidth,
}}
>
2021-04-27 17:23:27 +02:00
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} zIndex={STICKERPICKER_Z_INDEX}>
<AppTile
app={stickerApp}
room={this.props.room}
threadId={this.props.threadId}
2021-04-27 17:23:27 +02:00
fullWidth={true}
userId={MatrixClientPeg.safeGet().credentials.userId!}
creatorUserId={
stickerpickerWidget.sender || MatrixClientPeg.safeGet().credentials.userId!
}
2021-04-27 17:23:27 +02:00
waitForIframeLoad={true}
showMenubar={true}
2021-08-25 14:26:21 +01:00
onEditClick={this.launchManageIntegrations}
onDeleteClick={this.removeStickerpickerWidgets}
2021-04-27 17:23:27 +02:00
showTitle={false}
showPopout={false}
handleMinimisePointerEvents={true}
userWidget={true}
showLayoutButtons={false}
2021-04-27 17:23:27 +02:00
/>
</PersistedElement>
2018-01-16 23:25:07 +00:00
</div>
</div>
);
2018-01-16 18:14:32 +00:00
} else {
2018-02-25 22:10:38 +00:00
// Default content to show if stickerpicker widget not added
2021-08-25 14:26:21 +01:00
stickersContent = this.defaultStickerpickerContent();
2018-01-16 18:14:32 +00:00
}
return stickersContent;
2018-01-16 18:14:32 +00:00
}
/**
* Called when the window is resized
*/
2021-08-25 14:26:21 +01:00
private onResize = (): void => {
if (this.props.isStickerPickerOpen) {
this.props.setStickerPickerOpen(false);
}
2021-08-25 14:26:21 +01:00
};
2018-01-16 18:14:32 +00:00
2018-01-22 17:00:50 +01:00
/**
* The stickers picker was hidden
*/
2021-08-25 14:26:21 +01:00
private onFinished = (): void => {
if (this.props.isStickerPickerOpen) {
this.props.setStickerPickerOpen(false);
}
2021-08-25 14:26:21 +01:00
};
2018-01-16 21:28:15 +00:00
2018-01-22 17:00:50 +01:00
/**
* Launch the integration manager on the stickers integration page
2018-01-22 17:00:50 +01:00
*/
2021-08-25 14:26:21 +01:00
private launchManageIntegrations = (): void => {
2022-05-06 12:46:26 -06:00
// noinspection JSIgnoredPromiseFromCall
IntegrationManagers.sharedInstance()
?.getPrimaryManager()
?.open(this.props.room, `type_${WidgetType.STICKERPICKER.preferred}`, this.state.widgetId ?? undefined);
2021-06-07 09:22:47 +01:00
};
2018-01-17 00:04:06 +00:00
public render(): React.ReactNode {
if (!this.props.isStickerPickerOpen) return null;
2019-11-28 20:38:58 +00:00
return (
<ContextMenu
chevronFace={ChevronFace.Bottom}
menuWidth={this.popoverWidth}
menuHeight={this.popoverHeight}
onFinished={this.onFinished}
menuPaddingTop={0}
menuPaddingLeft={0}
menuPaddingRight={0}
zIndex={STICKERPICKER_Z_INDEX}
mountAsChild={true}
{...this.props.menuPosition}
>
<GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} />
</ContextMenu>
);
2018-01-16 18:14:32 +00:00
}
}