Files
ThreadNet-Web/src/components/views/dialogs/ModalWidgetDialog.tsx
T

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

212 lines
8.4 KiB
TypeScript
Raw Normal View History

2020-09-25 14:08:27 +01:00
/*
2024-09-09 14:57:16 +01:00
Copyright 2024 New Vector Ltd.
2021-05-12 14:10:02 -06:00
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
2020-09-25 14:08:27 +01: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.
2020-09-25 14:08:27 +01:00
*/
import * as React from "react";
2020-10-19 20:39:43 +01:00
import {
ClientWidgetApi,
IModalWidgetCloseRequest,
IModalWidgetOpenRequestData,
IModalWidgetReturnData,
2020-11-09 21:14:20 -07:00
ISetModalButtonEnabledActionRequest,
IWidgetApiAcknowledgeResponseData,
IWidgetApiErrorResponseData,
BuiltInModalButtonID,
ModalButtonID,
2020-10-19 20:39:43 +01:00
ModalButtonKind,
Widget,
WidgetApiFromWidgetAction,
2020-11-19 11:24:17 -07:00
WidgetKind,
2020-10-19 20:39:43 +01:00
} from "matrix-widget-api";
2021-10-22 17:23:32 -05:00
import BaseDialog from "./BaseDialog";
import { _t, getUserLanguage } from "../../../languageHandler";
import AccessibleButton, { AccessibleButtonKind } from "../elements/AccessibleButton";
2021-06-29 13:11:58 +01:00
import { StopGapWidgetDriver } from "../../../stores/widgets/StopGapWidgetDriver";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
2020-11-09 21:14:20 -07:00
import { arrayFastClone } from "../../../utils/arrays";
import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
2021-06-29 13:11:58 +01:00
import { ELEMENT_CLIENT_ID } from "../../../identifiers";
2021-05-12 14:10:02 -06:00
import SettingsStore from "../../../settings/SettingsStore";
2020-09-25 14:08:27 +01:00
interface IProps {
2020-10-19 20:39:43 +01:00
widgetDefinition: IModalWidgetOpenRequestData;
2021-02-02 14:22:28 +00:00
widgetRoomId?: string;
2020-09-25 14:08:27 +01:00
sourceWidgetId: string;
onFinished(success: true, data: IModalWidgetReturnData): void;
onFinished(success?: false, data?: void): void;
2020-09-25 14:08:27 +01:00
}
interface IState {
2020-10-19 20:39:43 +01:00
messaging?: ClientWidgetApi;
2020-11-09 21:14:20 -07:00
disabledButtonIds: ModalButtonID[];
2020-09-25 14:08:27 +01:00
}
const MAX_BUTTONS = 3;
export default class ModalWidgetDialog extends React.PureComponent<IProps, IState> {
2020-10-19 20:39:43 +01:00
private readonly widget: Widget;
2020-11-09 21:14:20 -07:00
private readonly possibleButtons: ModalButtonID[];
2020-09-25 14:08:27 +01:00
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
public state: IState = {
disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter((b) => b.disabled).map((b) => b.id),
2020-11-09 21:14:20 -07:00
};
2020-09-25 14:08:27 +01:00
2023-02-13 11:39:16 +00:00
public constructor(props: IProps) {
2020-10-19 20:39:43 +01:00
super(props);
this.widget = new ElementWidget({
2020-10-19 20:39:43 +01:00
...this.props.widgetDefinition,
creatorUserId: MatrixClientPeg.safeGet().getSafeUserId(),
2020-10-19 20:39:43 +01:00
id: `modal_${this.props.sourceWidgetId}`,
});
2020-11-09 21:14:20 -07:00
this.possibleButtons = (this.props.widgetDefinition.buttons || []).map((b) => b.id);
2020-09-25 14:08:27 +01:00
}
public componentDidMount(): void {
2022-09-16 11:12:27 -04:00
const driver = new StopGapWidgetDriver([], this.widget, WidgetKind.Modal, false);
const messaging = new ClientWidgetApi(this.widget, this.appFrame.current!, driver);
2021-06-29 13:11:58 +01:00
this.setState({ messaging });
2020-09-25 14:08:27 +01:00
}
public componentWillUnmount(): void {
if (!this.state.messaging) return;
2020-10-19 20:39:43 +01:00
this.state.messaging.off("ready", this.onReady);
this.state.messaging.off(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose);
2020-09-25 14:08:27 +01:00
this.state.messaging.stop();
}
private onReady = (): void => {
this.state.messaging?.sendWidgetConfig(this.props.widgetDefinition);
2020-09-25 14:08:27 +01:00
};
private onLoad = (): void => {
if (!this.state.messaging) return;
2020-10-19 20:39:43 +01:00
this.state.messaging.once("ready", this.onReady);
this.state.messaging.on(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose);
2020-11-09 21:14:20 -07:00
this.state.messaging.on(`action:${WidgetApiFromWidgetAction.SetModalButtonEnabled}`, this.onButtonEnableToggle);
2020-10-19 20:39:43 +01:00
};
private onWidgetClose = (ev: CustomEvent<IModalWidgetCloseRequest>): void => {
2020-10-19 20:39:43 +01:00
this.props.onFinished(true, ev.detail.data);
2021-06-29 13:11:58 +01:00
};
2020-09-25 14:08:27 +01:00
private onButtonEnableToggle = (ev: CustomEvent<ISetModalButtonEnabledActionRequest>): void => {
2020-11-09 21:14:20 -07:00
ev.preventDefault();
const isClose = ev.detail.data.button === BuiltInModalButtonID.Close;
if (isClose || !this.possibleButtons.includes(ev.detail.data.button)) {
return this.state.messaging?.transport.reply(ev.detail, {
2021-06-29 13:11:58 +01:00
error: { message: "Invalid button" },
2020-11-09 21:14:20 -07:00
} as IWidgetApiErrorResponseData);
}
let buttonIds: ModalButtonID[];
if (ev.detail.data.enabled) {
buttonIds = arrayFastClone(this.state.disabledButtonIds).filter((i) => i !== ev.detail.data.button);
} else {
// use a set to swap the operation to avoid memory leaky arrays.
const tempSet = new Set(this.state.disabledButtonIds);
tempSet.add(ev.detail.data.button);
buttonIds = Array.from(tempSet);
}
2021-06-29 13:11:58 +01:00
this.setState({ disabledButtonIds: buttonIds });
this.state.messaging?.transport.reply(ev.detail, {} as IWidgetApiAcknowledgeResponseData);
2020-11-09 21:14:20 -07:00
};
public render(): React.ReactNode {
2020-10-19 20:39:43 +01:00
const templated = this.widget.getCompleteUrl({
2021-02-02 14:22:28 +00:00
widgetRoomId: this.props.widgetRoomId,
currentUserId: MatrixClientPeg.safeGet().getSafeUserId(),
userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
2021-05-12 14:10:02 -06:00
clientId: ELEMENT_CLIENT_ID,
clientTheme: SettingsStore.getValue("theme"),
clientLanguage: getUserLanguage(),
baseUrl: MatrixClientPeg.safeGet().baseUrl,
2020-10-19 20:39:43 +01:00
});
2020-09-25 14:08:27 +01:00
2020-10-19 20:39:43 +01:00
const parsed = new URL(templated);
// Add in some legacy support sprinkles (for non-popout widgets)
2020-09-28 10:50:31 +01:00
// TODO: Replace these with proper widget params
// See https://github.com/matrix-org/matrix-doc/pull/1958/files#r405714833
2020-10-19 20:39:43 +01:00
parsed.searchParams.set("widgetId", this.widget.id);
parsed.searchParams.set("parentUrl", window.location.href.split("#", 2)[0]);
// Replace the encoded dollar signs back to dollar signs. They have no special meaning
// in HTTP, but URL parsers encode them anyways.
const widgetUrl = parsed.toString().replace(/%24/g, "$");
2020-09-25 14:08:27 +01:00
let buttons;
if (this.props.widgetDefinition.buttons) {
// show first button rightmost for a more natural specification
buttons = this.props.widgetDefinition.buttons
.slice(0, MAX_BUTTONS)
.reverse()
.map((def) => {
let kind: AccessibleButtonKind = "secondary";
2020-09-25 14:08:27 +01:00
switch (def.kind) {
2020-10-19 20:39:43 +01:00
case ModalButtonKind.Primary:
2020-09-25 14:08:27 +01:00
kind = "primary";
break;
2020-10-19 20:39:43 +01:00
case ModalButtonKind.Secondary:
2020-09-25 14:08:27 +01:00
kind = "primary_outline";
2021-06-29 13:11:58 +01:00
break;
2020-10-19 20:39:43 +01:00
case ModalButtonKind.Danger:
2020-09-25 14:08:27 +01:00
kind = "danger";
break;
}
const onClick = (): void => {
this.state.messaging?.notifyModalWidgetButtonClicked(def.id);
2020-09-25 14:08:27 +01:00
};
const isDisabled = this.state.disabledButtonIds.includes(def.id);
return (
<AccessibleButton key={def.id} kind={kind} onClick={onClick} disabled={isDisabled}>
2020-09-25 14:08:27 +01:00
{def.label}
</AccessibleButton>
);
});
}
return (
<BaseDialog
title={this.props.widgetDefinition.name || _t("widget|modal_title_default")}
2020-09-25 14:08:27 +01:00
className="mx_ModalWidgetDialog"
contentId="mx_Dialog_content"
onFinished={this.props.onFinished}
>
<div className="mx_ModalWidgetDialog_warning">
<img
src={require("../../../../res/img/element-icons/warning-badge.svg").default}
height="16"
width="16"
alt=""
/>
{_t("widget|modal_data_warning", {
widgetDomain: parsed.hostname,
})}
</div>
2020-09-25 14:08:27 +01:00
<div>
<iframe
title={this.widget.name ?? undefined}
2020-09-25 14:08:27 +01:00
ref={this.appFrame}
2020-10-22 22:09:16 +01:00
sandbox="allow-forms allow-scripts allow-same-origin"
src={widgetUrl}
2020-09-25 14:08:27 +01:00
onLoad={this.onLoad}
/>
</div>
<div className="mx_ModalWidgetDialog_buttons">{buttons}</div>
2020-09-25 14:08:27 +01:00
</BaseDialog>
);
}
}