Initial Modal Widget work tweaks MSC2790
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
342f1d5b43
commit
44bc8fc67e
10 changed files with 294 additions and 243 deletions
|
@ -74,6 +74,7 @@
|
||||||
@import "./views/dialogs/_InviteDialog.scss";
|
@import "./views/dialogs/_InviteDialog.scss";
|
||||||
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
|
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
|
||||||
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
|
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
|
||||||
|
@import "./views/dialogs/_ModalWidgetDialog.scss";
|
||||||
@import "./views/dialogs/_NewSessionReviewDialog.scss";
|
@import "./views/dialogs/_NewSessionReviewDialog.scss";
|
||||||
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
||||||
@import "./views/dialogs/_RoomSettingsDialogBridges.scss";
|
@import "./views/dialogs/_RoomSettingsDialogBridges.scss";
|
||||||
|
|
23
res/css/views/dialogs/_ModalWidgetDialog.scss
Normal file
23
res/css/views/dialogs/_ModalWidgetDialog.scss
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_ModalWidgetDialog {
|
||||||
|
.mx_ModalWidgetDialog_buttons {
|
||||||
|
.mx_AccessibleButton + .mx_AccessibleButton {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,8 +26,7 @@ import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import {Capability, KnownWidgetActions} from "./widgets/WidgetApi";
|
import {Capability, KnownWidgetActions} from "./widgets/WidgetApi";
|
||||||
import {objectClone} from "./utils/objects";
|
import {objectClone} from "./utils/objects";
|
||||||
import {Action} from "./dispatcher/actions";
|
import {ModalWidgetStore} from "./stores/ModalWidgetStore";
|
||||||
import {TempWidgetStore} from "./stores/TempWidgetStore";
|
|
||||||
|
|
||||||
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
||||||
const SUPPORTED_WIDGET_API_VERSIONS = [
|
const SUPPORTED_WIDGET_API_VERSIONS = [
|
||||||
|
@ -220,12 +219,16 @@ export default class FromWidgetPostMessageApi {
|
||||||
if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.AlwaysOnScreen)) {
|
if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.AlwaysOnScreen)) {
|
||||||
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
||||||
}
|
}
|
||||||
} else if (action === 'get_openid'
|
} else if (action === 'get_openid' || action === KnownWidgetActions.CloseModalWidget) {
|
||||||
|| action === KnownWidgetActions.CloseWidget) {
|
|
||||||
// Handled by caller
|
// Handled by caller
|
||||||
} else if (action === KnownWidgetActions.OpenTempWidget) {
|
|
||||||
TempWidgetStore.instance.openTempWidget(event.data.data, widgetId);
|
|
||||||
this.sendResponse(event, {}); // ack
|
this.sendResponse(event, {}); // ack
|
||||||
|
} else if (action === KnownWidgetActions.OpenModalWidget) {
|
||||||
|
if (ModalWidgetStore.instance.canOpenModalWidget()) {
|
||||||
|
ModalWidgetStore.instance.openModalWidget(event.data.data, widgetId);
|
||||||
|
this.sendResponse(event, {}); // ack
|
||||||
|
} else {
|
||||||
|
this.sendError(event, {message: 'Unable to open modal at this time'}); // nak
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Widget postMessage event unhandled');
|
console.warn('Widget postMessage event unhandled');
|
||||||
this.sendError(event, {message: 'The postMessage was unhandled'});
|
this.sendError(event, {message: 'The postMessage was unhandled'});
|
||||||
|
|
|
@ -38,7 +38,7 @@ export interface IModal<T extends any[]> {
|
||||||
close(...args: T): void;
|
close(...args: T): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IHandle<T extends any[]> {
|
export interface IHandle<T extends any[]> {
|
||||||
finished: Promise<T>;
|
finished: Promise<T>;
|
||||||
close(...args: T): void;
|
close(...args: T): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,33 +147,33 @@ export default class WidgetMessaging {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendThemeInfo(themeInfo: any) {
|
|
||||||
return this.messageToWidget({
|
|
||||||
api: OUTBOUND_API_NAME,
|
|
||||||
action: KnownWidgetActions.UpdateThemeInfo,
|
|
||||||
data: themeInfo,
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error("Failed to send theme info: ", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendWidgetConfig(widgetConfig: any) {
|
sendWidgetConfig(widgetConfig: any) {
|
||||||
return this.messageToWidget({
|
return this.messageToWidget({
|
||||||
api: OUTBOUND_API_NAME,
|
api: OUTBOUND_API_NAME,
|
||||||
action: KnownWidgetActions.SendWidgetConfig,
|
action: KnownWidgetActions.GetWidgetConfig,
|
||||||
data: widgetConfig,
|
data: widgetConfig,
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("Failed to send widget info: ", error);
|
console.error("Failed to send widget info: ", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTempCloseInfo(info: any) {
|
sendModalButtonClicked(id: string) {
|
||||||
return this.messageToWidget({
|
return this.messageToWidget({
|
||||||
api: OUTBOUND_API_NAME,
|
api: OUTBOUND_API_NAME,
|
||||||
action: KnownWidgetActions.ClosedWidgetResponse,
|
action: KnownWidgetActions.ButtonClicked,
|
||||||
|
data: {id},
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error("Failed to send modal widget button clicked: ", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendModalCloseInfo(info: any) {
|
||||||
|
return this.messageToWidget({
|
||||||
|
api: OUTBOUND_API_NAME,
|
||||||
|
action: KnownWidgetActions.CloseModalWidget,
|
||||||
data: info,
|
data: info,
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("Failed to send temp widget close info: ", error);
|
console.error("Failed to send modal widget close info: ", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
138
src/components/views/dialogs/ModalWidgetDialog.tsx
Normal file
138
src/components/views/dialogs/ModalWidgetDialog.tsx
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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 * as React from 'react';
|
||||||
|
import BaseDialog from './BaseDialog';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import WidgetMessaging from "../../../WidgetMessaging";
|
||||||
|
import {ButtonKind, IButton, KnownWidgetActions} from "../../../widgets/WidgetApi";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
|
||||||
|
interface IModalWidget {
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
data: any;
|
||||||
|
waitForIframeLoad?: boolean;
|
||||||
|
buttons?: IButton[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
widgetDefinition: IModalWidget;
|
||||||
|
sourceWidgetId: string;
|
||||||
|
onFinished(success: boolean, data?: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
messaging?: WidgetMessaging;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_BUTTONS = 3;
|
||||||
|
|
||||||
|
export default class ModalWidgetDialog extends React.PureComponent<IProps, IState> {
|
||||||
|
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
|
||||||
|
|
||||||
|
state: IState = {};
|
||||||
|
|
||||||
|
private getWidgetId() {
|
||||||
|
return `modal_${this.props.sourceWidgetId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
// TODO: Don't violate every principle of widget creation
|
||||||
|
const messaging = new WidgetMessaging(
|
||||||
|
this.getWidgetId(),
|
||||||
|
this.props.widgetDefinition.url,
|
||||||
|
this.props.widgetDefinition.url, // TODO templating and such
|
||||||
|
true,
|
||||||
|
this.appFrame.current.contentWindow,
|
||||||
|
);
|
||||||
|
this.setState({messaging});
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
this.state.messaging.fromWidget.removeListener(KnownWidgetActions.CloseModalWidget, this.onWidgetClose);
|
||||||
|
this.state.messaging.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onLoad = () => {
|
||||||
|
this.state.messaging.getCapabilities().then(caps => {
|
||||||
|
console.log("Requested capabilities: ", caps);
|
||||||
|
this.state.messaging.sendWidgetConfig(this.props.widgetDefinition.data);
|
||||||
|
});
|
||||||
|
this.state.messaging.fromWidget.addListener(KnownWidgetActions.CloseModalWidget, this.onWidgetClose);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onWidgetClose = (req) => {
|
||||||
|
this.props.onFinished(true, req.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
// TODO: Don't violate every single security principle
|
||||||
|
|
||||||
|
const widgetUrl = this.props.widgetDefinition.url
|
||||||
|
+ `?widgetId=${this.getWidgetId()}&parentUrl=${encodeURIComponent(window.location.href)}`;
|
||||||
|
|
||||||
|
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 = "secondary";
|
||||||
|
switch (def.kind) {
|
||||||
|
case ButtonKind.Primary:
|
||||||
|
kind = "primary";
|
||||||
|
break;
|
||||||
|
case ButtonKind.Secondary:
|
||||||
|
kind = "primary_outline";
|
||||||
|
break
|
||||||
|
case ButtonKind.Danger:
|
||||||
|
kind = "danger";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
this.state.messaging.sendModalButtonClicked(def.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <AccessibleButton key={def.id} kind={kind} onClick={onClick}>
|
||||||
|
{ def.label }
|
||||||
|
</AccessibleButton>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BaseDialog
|
||||||
|
title={this.props.widgetDefinition.name || _t("Modal Widget")}
|
||||||
|
className="mx_ModalWidgetDialog"
|
||||||
|
contentId="mx_Dialog_content"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
hasCancel={false}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<iframe
|
||||||
|
ref={this.appFrame}
|
||||||
|
sandbox="allow-forms allow-scripts"
|
||||||
|
width={700} // TODO
|
||||||
|
height={450} // TODO
|
||||||
|
src={widgetUrl}
|
||||||
|
onLoad={this.onLoad}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx_ModalWidgetDialog_buttons" style={{float: "right"}}>
|
||||||
|
{ buttons }
|
||||||
|
</div>
|
||||||
|
</BaseDialog>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,155 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2020 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 * as React from 'react';
|
|
||||||
import BaseDialog from './BaseDialog';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import WidgetMessaging from "../../../WidgetMessaging";
|
|
||||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
|
||||||
import Field from "../elements/Field";
|
|
||||||
import { KnownWidgetActions } from "../../../widgets/WidgetApi";
|
|
||||||
import ActiveWidgetStore from "../../../stores/ActiveWidgetStore";
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
messaging?: WidgetMessaging;
|
|
||||||
|
|
||||||
androidMode: boolean;
|
|
||||||
darkTheme: boolean;
|
|
||||||
accentColor: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
|
||||||
widgetDefinition: {url: string, data: any};
|
|
||||||
sourceWidgetId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make a better dialog
|
|
||||||
|
|
||||||
export default class TempWidgetDialog extends React.PureComponent<IProps, IState> {
|
|
||||||
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
androidMode: false,
|
|
||||||
darkTheme: false,
|
|
||||||
accentColor: "#03b381",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidMount() {
|
|
||||||
// TODO: Don't violate every principle of widget creation
|
|
||||||
const messaging = new WidgetMessaging(
|
|
||||||
"TEMP_ID",
|
|
||||||
this.props.widgetDefinition.url,
|
|
||||||
this.props.widgetDefinition.url,
|
|
||||||
false,
|
|
||||||
this.appFrame.current.contentWindow,
|
|
||||||
);
|
|
||||||
this.setState({messaging});
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillUnmount() {
|
|
||||||
this.state.messaging.fromWidget.removeListener(KnownWidgetActions.CloseWidget, this.onWidgetClose);
|
|
||||||
this.state.messaging.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onLoad = () => {
|
|
||||||
this.state.messaging.getCapabilities().then(caps => {
|
|
||||||
console.log("Requested capabilities: ", caps);
|
|
||||||
this.sendTheme();
|
|
||||||
this.state.messaging.sendWidgetConfig(this.props.widgetDefinition.data);
|
|
||||||
});
|
|
||||||
this.state.messaging.fromWidget.addListener(KnownWidgetActions.CloseWidget, this.onWidgetClose);
|
|
||||||
};
|
|
||||||
|
|
||||||
private sendTheme() {
|
|
||||||
if (!this.state.messaging) return;
|
|
||||||
this.state.messaging.sendThemeInfo({
|
|
||||||
clientName: this.state.androidMode ? "element-android" : "element-web",
|
|
||||||
isDark: this.state.darkTheme,
|
|
||||||
accentColor: this.state.accentColor,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static sendExitData(sourceWidgetId: string, success: boolean, data?: any) {
|
|
||||||
const sourceMessaging = ActiveWidgetStore.getWidgetMessaging(sourceWidgetId);
|
|
||||||
if (!sourceMessaging) {
|
|
||||||
console.error("No source widget messaging for temp widget");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sourceMessaging.sendTempCloseInfo({success, ...data});
|
|
||||||
}
|
|
||||||
|
|
||||||
private onWidgetClose = (req) => {
|
|
||||||
this.props.onFinished(true);
|
|
||||||
TempWidgetDialog.sendExitData(this.props.sourceWidgetId, true, req.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onClientToggleChanged = (androidMode) => {
|
|
||||||
this.setState({androidMode}, () => this.sendTheme());
|
|
||||||
};
|
|
||||||
|
|
||||||
private onDarkThemeChanged = (darkTheme) => {
|
|
||||||
this.setState({darkTheme}, () => this.sendTheme());
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAccentColorChanged = (ev) => {
|
|
||||||
this.setState({accentColor: ev.target.value}, () => this.sendTheme());
|
|
||||||
};
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
// TODO: Don't violate every single security principle
|
|
||||||
|
|
||||||
const widgetUrl = this.props.widgetDefinition.url
|
|
||||||
+ "?widgetId=TEMP_ID&parentUrl=" + encodeURIComponent(window.location.href);
|
|
||||||
|
|
||||||
return <BaseDialog
|
|
||||||
title={_t("Widget Proof of Concept Dashboard")}
|
|
||||||
className='mx_TempWidgetDialog'
|
|
||||||
contentId='mx_Dialog_content'
|
|
||||||
onFinished={this.props.onFinished}
|
|
||||||
hasCancel={false}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<LabelledToggleSwitch
|
|
||||||
label={ _t("Look like Android")}
|
|
||||||
onChange={this.onClientToggleChanged}
|
|
||||||
value={this.state.androidMode}
|
|
||||||
/>
|
|
||||||
<LabelledToggleSwitch
|
|
||||||
label={ _t("Look like dark theme")}
|
|
||||||
onChange={this.onDarkThemeChanged}
|
|
||||||
value={this.state.darkTheme}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
value={this.state.accentColor}
|
|
||||||
label={_t('Accent Colour')}
|
|
||||||
onChange={this.onAccentColorChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<iframe
|
|
||||||
ref={this.appFrame}
|
|
||||||
width={700} height={450}
|
|
||||||
src={widgetUrl}
|
|
||||||
onLoad={this.onLoad}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>;
|
|
||||||
}
|
|
||||||
}
|
|
86
src/stores/ModalWidgetStore.ts
Normal file
86
src/stores/ModalWidgetStore.ts
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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 { AsyncStoreWithClient } from "./AsyncStoreWithClient";
|
||||||
|
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||||
|
import { ActionPayload } from "../dispatcher/payloads";
|
||||||
|
import Modal, {IHandle, IModal} from "../Modal";
|
||||||
|
import ModalWidgetDialog from "../components/views/dialogs/ModalWidgetDialog";
|
||||||
|
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
modal?: IModal<any>;
|
||||||
|
openedFromId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
|
||||||
|
private static internalInstance = new ModalWidgetStore();
|
||||||
|
private modalInstance: IHandle<void[]> = null;
|
||||||
|
private openSourceWidgetId: string = null;
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
super(defaultDispatcher, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get instance(): ModalWidgetStore {
|
||||||
|
return ModalWidgetStore.internalInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async onAction(payload: ActionPayload): Promise<any> {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public canOpenModalWidget = () => {
|
||||||
|
return !this.modalInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
public openModalWidget = (requestData: any, sourceWidgetId: string) => {
|
||||||
|
if (this.modalInstance) return;
|
||||||
|
this.openSourceWidgetId = sourceWidgetId;
|
||||||
|
this.modalInstance = Modal.createTrackedDialog('Modal Widget', '', ModalWidgetDialog, {
|
||||||
|
widgetDefinition: {...requestData},
|
||||||
|
sourceWidgetId: sourceWidgetId,
|
||||||
|
onFinished: (success: boolean, data?: any) => {
|
||||||
|
if (!success) {
|
||||||
|
this.closeModalWidget(sourceWidgetId, {
|
||||||
|
"m.exited": true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.closeModalWidget(sourceWidgetId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openSourceWidgetId = null;
|
||||||
|
this.modalInstance = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public closeModalWidget = (sourceWidgetId: string, data?: any) => {
|
||||||
|
if (!this.modalInstance) return;
|
||||||
|
if (this.openSourceWidgetId === sourceWidgetId) {
|
||||||
|
this.openSourceWidgetId = null;
|
||||||
|
this.modalInstance.close();
|
||||||
|
this.modalInstance = null;
|
||||||
|
|
||||||
|
const sourceMessaging = ActiveWidgetStore.getWidgetMessaging(sourceWidgetId);
|
||||||
|
if (!sourceMessaging) {
|
||||||
|
console.error("No source widget messaging for modal widget");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sourceMessaging.sendModalCloseInfo(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2020 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 { AsyncStoreWithClient } from "./AsyncStoreWithClient";
|
|
||||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
|
||||||
import { ActionPayload } from "../dispatcher/payloads";
|
|
||||||
import Modal, { IModal } from "../Modal";
|
|
||||||
import TempWidgetDialog from "../components/views/dialogs/TempWidgetDialog";
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
modal?: IModal<any>;
|
|
||||||
openedFromId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TempWidgetStore extends AsyncStoreWithClient<IState> {
|
|
||||||
private static internalInstance = new TempWidgetStore();
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
super(defaultDispatcher, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get instance(): TempWidgetStore {
|
|
||||||
return TempWidgetStore.internalInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async onAction(payload: ActionPayload): Promise<any> {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
public openTempWidget(requestData: any, sourceWidgetId: string) {
|
|
||||||
Modal.createTrackedDialog('Temp Widget', '', TempWidgetDialog, {
|
|
||||||
widgetDefinition: {...requestData},
|
|
||||||
sourceWidgetId: sourceWidgetId,
|
|
||||||
onFinished: (success) => {
|
|
||||||
if (!success) {
|
|
||||||
TempWidgetDialog.sendExitData(sourceWidgetId, false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,6 +25,7 @@ export enum Capability {
|
||||||
Screenshot = "m.capability.screenshot",
|
Screenshot = "m.capability.screenshot",
|
||||||
Sticker = "m.sticker",
|
Sticker = "m.sticker",
|
||||||
AlwaysOnScreen = "m.always_on_screen",
|
AlwaysOnScreen = "m.always_on_screen",
|
||||||
|
Modals = "m.modals",
|
||||||
ReceiveTerminate = "im.vector.receive_terminate",
|
ReceiveTerminate = "im.vector.receive_terminate",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,12 +40,10 @@ export enum KnownWidgetActions {
|
||||||
SetAlwaysOnScreen = "set_always_on_screen",
|
SetAlwaysOnScreen = "set_always_on_screen",
|
||||||
ClientReady = "im.vector.ready",
|
ClientReady = "im.vector.ready",
|
||||||
Terminate = "im.vector.terminate",
|
Terminate = "im.vector.terminate",
|
||||||
|
OpenModalWidget = "open_modal",
|
||||||
OpenTempWidget = "io.element.start_temp",
|
CloseModalWidget = "close_modal",
|
||||||
UpdateThemeInfo = "io.element.theme_info",
|
GetWidgetConfig = "widget_config",
|
||||||
SendWidgetConfig = "io.element.widget_config",
|
ButtonClicked = "button_clicked",
|
||||||
CloseWidget = "io.element.exit",
|
|
||||||
ClosedWidgetResponse = "io.element.exit_response",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WidgetAction = KnownWidgetActions | string;
|
export type WidgetAction = KnownWidgetActions | string;
|
||||||
|
@ -78,6 +77,18 @@ export interface OpenIDCredentials {
|
||||||
expiresIn: number;
|
expiresIn: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ButtonKind {
|
||||||
|
Primary = "m.primary",
|
||||||
|
Secondary = "m.secondary",
|
||||||
|
Danger = "m.danger",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IButton {
|
||||||
|
id: "m.close" | string;
|
||||||
|
label: string;
|
||||||
|
kind: ButtonKind;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles Element <--> Widget interactions for embedded/standalone widgets.
|
* Handles Element <--> Widget interactions for embedded/standalone widgets.
|
||||||
*
|
*
|
||||||
|
@ -140,9 +151,7 @@ export class WidgetApi extends EventEmitter {
|
||||||
// Save OpenID credentials
|
// Save OpenID credentials
|
||||||
this.setOpenIDCredentials(<ToWidgetRequest>payload);
|
this.setOpenIDCredentials(<ToWidgetRequest>payload);
|
||||||
this.replyToRequest(<ToWidgetRequest>payload, {});
|
this.replyToRequest(<ToWidgetRequest>payload, {});
|
||||||
} else if (payload.action === KnownWidgetActions.UpdateThemeInfo
|
} else if (payload.action === KnownWidgetActions.GetWidgetConfig) {
|
||||||
|| payload.action === KnownWidgetActions.SendWidgetConfig
|
|
||||||
|| payload.action === KnownWidgetActions.ClosedWidgetResponse) {
|
|
||||||
// Finalization needs to be async, so postpone with a promise
|
// Finalization needs to be async, so postpone with a promise
|
||||||
let finalizePromise = Promise.resolve();
|
let finalizePromise = Promise.resolve();
|
||||||
const wait = (promise) => {
|
const wait = (promise) => {
|
||||||
|
@ -236,16 +245,16 @@ export class WidgetApi extends EventEmitter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeWidget(exitData: any): Promise<any> {
|
public closeModalWidget(exitData: any): Promise<any> {
|
||||||
return new Promise<any>(resolve => {
|
return new Promise<any>(resolve => {
|
||||||
this.callAction(KnownWidgetActions.CloseWidget, exitData, null);
|
this.callAction(KnownWidgetActions.CloseModalWidget, exitData, null);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public openTempWidget(url: string, data: any): Promise<any> {
|
public openModalWidget(url: string, name: string, buttons: IButton[], data: any): Promise<any> {
|
||||||
return new Promise<any>(resolve => {
|
return new Promise<any>(resolve => {
|
||||||
this.callAction(KnownWidgetActions.OpenTempWidget, {url, data}, null);
|
this.callAction(KnownWidgetActions.OpenModalWidget, {url, name, buttons, data}, null);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue