|
| 1 | +/* |
| 2 | +Copyright 2020 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 | +import * as React from 'react'; |
| 18 | +import BaseDialog from './BaseDialog'; |
| 19 | +import { _t } from '../../../languageHandler'; |
| 20 | +import AccessibleButton from "../elements/AccessibleButton"; |
| 21 | +import { |
| 22 | + ClientWidgetApi, |
| 23 | + IModalWidgetCloseRequest, |
| 24 | + IModalWidgetOpenRequestData, |
| 25 | + IModalWidgetReturnData, |
| 26 | + ModalButtonKind, |
| 27 | + Widget, |
| 28 | + WidgetApiFromWidgetAction, |
| 29 | +} from "matrix-widget-api"; |
| 30 | +import {StopGapWidgetDriver} from "../../../stores/widgets/StopGapWidgetDriver"; |
| 31 | +import {MatrixClientPeg} from "../../../MatrixClientPeg"; |
| 32 | +import RoomViewStore from "../../../stores/RoomViewStore"; |
| 33 | +import {OwnProfileStore} from "../../../stores/OwnProfileStore"; |
| 34 | + |
| 35 | +interface IProps { |
| 36 | + widgetDefinition: IModalWidgetOpenRequestData; |
| 37 | + sourceWidgetId: string; |
| 38 | + onFinished(success: boolean, data?: IModalWidgetReturnData): void; |
| 39 | +} |
| 40 | + |
| 41 | +interface IState { |
| 42 | + messaging?: ClientWidgetApi; |
| 43 | +} |
| 44 | + |
| 45 | +const MAX_BUTTONS = 3; |
| 46 | + |
| 47 | +export default class ModalWidgetDialog extends React.PureComponent<IProps, IState> { |
| 48 | + private readonly widget: Widget; |
| 49 | + private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef(); |
| 50 | + |
| 51 | + state: IState = {}; |
| 52 | + |
| 53 | + constructor(props) { |
| 54 | + super(props); |
| 55 | + |
| 56 | + this.widget = new Widget({ |
| 57 | + ...this.props.widgetDefinition, |
| 58 | + creatorUserId: MatrixClientPeg.get().getUserId(), |
| 59 | + id: `modal_${this.props.sourceWidgetId}`, |
| 60 | + }); |
| 61 | + } |
| 62 | + |
| 63 | + public componentDidMount() { |
| 64 | + const driver = new StopGapWidgetDriver( []); |
| 65 | + const messaging = new ClientWidgetApi(this.widget, this.appFrame.current, driver); |
| 66 | + this.setState({messaging}); |
| 67 | + } |
| 68 | + |
| 69 | + public componentWillUnmount() { |
| 70 | + this.state.messaging.off("ready", this.onReady); |
| 71 | + this.state.messaging.off(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose); |
| 72 | + this.state.messaging.stop(); |
| 73 | + } |
| 74 | + |
| 75 | + private onReady = () => { |
| 76 | + this.state.messaging.sendWidgetConfig(this.props.widgetDefinition); |
| 77 | + }; |
| 78 | + |
| 79 | + private onLoad = () => { |
| 80 | + this.state.messaging.once("ready", this.onReady); |
| 81 | + this.state.messaging.on(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose); |
| 82 | + }; |
| 83 | + |
| 84 | + private onWidgetClose = (ev: CustomEvent<IModalWidgetCloseRequest>) => { |
| 85 | + this.props.onFinished(true, ev.detail.data); |
| 86 | + } |
| 87 | + |
| 88 | + public render() { |
| 89 | + const templated = this.widget.getCompleteUrl({ |
| 90 | + currentRoomId: RoomViewStore.getRoomId(), |
| 91 | + currentUserId: MatrixClientPeg.get().getUserId(), |
| 92 | + userDisplayName: OwnProfileStore.instance.displayName, |
| 93 | + userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(), |
| 94 | + }); |
| 95 | + |
| 96 | + const parsed = new URL(templated); |
| 97 | + |
| 98 | + // Add in some legacy support sprinkles (for non-popout widgets) |
| 99 | + // TODO: Replace these with proper widget params |
| 100 | + // See https://github.com/matrix-org/matrix-doc/pull/1958/files#r405714833 |
| 101 | + parsed.searchParams.set('widgetId', this.widget.id); |
| 102 | + parsed.searchParams.set('parentUrl', window.location.href.split('#', 2)[0]); |
| 103 | + |
| 104 | + // Replace the encoded dollar signs back to dollar signs. They have no special meaning |
| 105 | + // in HTTP, but URL parsers encode them anyways. |
| 106 | + const widgetUrl = parsed.toString().replace(/%24/g, '$'); |
| 107 | + |
| 108 | + let buttons; |
| 109 | + if (this.props.widgetDefinition.buttons) { |
| 110 | + // show first button rightmost for a more natural specification |
| 111 | + buttons = this.props.widgetDefinition.buttons.slice(0, MAX_BUTTONS).reverse().map(def => { |
| 112 | + let kind = "secondary"; |
| 113 | + switch (def.kind) { |
| 114 | + case ModalButtonKind.Primary: |
| 115 | + kind = "primary"; |
| 116 | + break; |
| 117 | + case ModalButtonKind.Secondary: |
| 118 | + kind = "primary_outline"; |
| 119 | + break |
| 120 | + case ModalButtonKind.Danger: |
| 121 | + kind = "danger"; |
| 122 | + break; |
| 123 | + } |
| 124 | + |
| 125 | + const onClick = () => { |
| 126 | + this.state.messaging.notifyModalWidgetButtonClicked(def.id); |
| 127 | + }; |
| 128 | + |
| 129 | + return <AccessibleButton key={def.id} kind={kind} onClick={onClick}> |
| 130 | + { def.label } |
| 131 | + </AccessibleButton>; |
| 132 | + }); |
| 133 | + } |
| 134 | + |
| 135 | + return <BaseDialog |
| 136 | + title={this.props.widgetDefinition.name || _t("Modal Widget")} |
| 137 | + className="mx_ModalWidgetDialog" |
| 138 | + contentId="mx_Dialog_content" |
| 139 | + onFinished={this.props.onFinished} |
| 140 | + > |
| 141 | + <div className="mx_ModalWidgetDialog_warning"> |
| 142 | + <img |
| 143 | + src={require("../../../../res/img/element-icons/warning-badge.svg")} |
| 144 | + height="16" |
| 145 | + width="16" |
| 146 | + alt="" |
| 147 | + /> |
| 148 | + {_t("Data on this screen is shared with %(widgetDomain)s", { |
| 149 | + widgetDomain: parsed.hostname, |
| 150 | + })} |
| 151 | + </div> |
| 152 | + <div> |
| 153 | + <iframe |
| 154 | + ref={this.appFrame} |
| 155 | + sandbox="allow-forms allow-scripts allow-same-origin" |
| 156 | + src={widgetUrl} |
| 157 | + onLoad={this.onLoad} |
| 158 | + /> |
| 159 | + </div> |
| 160 | + <div className="mx_ModalWidgetDialog_buttons"> |
| 161 | + { buttons } |
| 162 | + </div> |
| 163 | + </BaseDialog>; |
| 164 | + } |
| 165 | +} |
0 commit comments