Convert AppTile to TS

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-09-16 20:05:57 +02:00
parent 03ce568a5d
commit e1445f71bd
No known key found for this signature in database
GPG key ID: 55C211A1226CB17D
5 changed files with 149 additions and 147 deletions

View file

@ -76,7 +76,6 @@ const LeftPanelWidget: React.FC = () => {
<AppTile <AppTile
app={app} app={app}
fullWidth fullWidth
show
showMenubar={false} showMenubar={false}
userWidget userWidget
userId={cli.getUserId()} userId={cli.getUserId()}

View file

@ -19,7 +19,6 @@ limitations under the License.
import url from 'url'; import url from 'url';
import React, { createRef } from 'react'; import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import AccessibleButton from './AccessibleButton'; import AccessibleButton from './AccessibleButton';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -39,33 +38,96 @@ import { MatrixCapabilities } from "matrix-widget-api";
import RoomWidgetContextMenu from "../context_menus/WidgetContextMenu"; import RoomWidgetContextMenu from "../context_menus/WidgetContextMenu";
import WidgetAvatar from "../avatars/WidgetAvatar"; import WidgetAvatar from "../avatars/WidgetAvatar";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { Room } from "matrix-js-sdk/src/models/room";
import { IApp } from "../../../stores/WidgetStore";
interface IProps {
app: IApp;
// If room is not specified then it is an account level widget
// which bypasses permission prompts as it was added explicitly by that user
room: Room;
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
fullWidth?: boolean;
// Optional. If set, renders a smaller view of the widget
miniMode?: boolean;
// UserId of the current user
userId: string;
// UserId of the entity that added / modified the widget
creatorUserId: string;
waitForIframeLoad: boolean;
showMenubar?: boolean;
// Optional onEditClickHandler (overrides default behaviour)
onEditClick?: () => void;
// Optional onDeleteClickHandler (overrides default behaviour)
onDeleteClick?: () => void;
// Optionally hide the tile title
showTitle?: boolean;
// Optionally handle minimise button pointer events (default false)
handleMinimisePointerEvents?: boolean;
// Optionally hide the popout widget icon
showPopout?: boolean;
// Is this an instance of a user widget
userWidget: boolean;
// sets the pointer-events property on the iframe
pointerEvents?: string;
widgetPageTitle?: string;
}
interface IState {
initialising: boolean; // True while we are mangling the widget URL
// True while the iframe content is loading
loading: boolean;
// Assume that widget has permission to load if we are the user who
// added it to the room, or if explicitly granted by the user
hasPermissionToLoad: boolean;
error: Error;
menuDisplayed: boolean;
widgetPageTitle: string;
}
@replaceableComponent("views.elements.AppTile") @replaceableComponent("views.elements.AppTile")
export default class AppTile extends React.Component { export default class AppTile extends React.Component<IProps, IState> {
constructor(props) { displayName = 'AppTile';
public static defaultProps: Partial<IProps> = {
waitForIframeLoad: true,
showMenubar: true,
showTitle: true,
showPopout: true,
handleMinimisePointerEvents: false,
userWidget: false,
miniMode: false,
};
private contextMenuButton = createRef<any>();
private iframe: HTMLIFrameElement; // ref to the iframe (callback style)
private allowedWidgetsWatchRef: string;
private persistKey: string;
private sgWidget: StopGapWidget;
private dispatcherRef: string;
constructor(props: IProps) {
super(props); super(props);
// The key used for PersistedElement // The key used for PersistedElement
this._persistKey = getPersistKey(this.props.app.id); this.persistKey = getPersistKey(this.props.app.id);
try { try {
this._sgWidget = new StopGapWidget(this.props); this.sgWidget = new StopGapWidget(this.props);
this._sgWidget.on("preparing", this._onWidgetPrepared); this.sgWidget.on("preparing", this.onWidgetPrepared);
this._sgWidget.on("ready", this._onWidgetReady); this.sgWidget.on("ready", this.onWidgetReady);
} catch (e) { } catch (e) {
console.log("Failed to construct widget", e); console.log("Failed to construct widget", e);
this._sgWidget = null; this.sgWidget = null;
} }
this.iframe = null; // ref to the iframe (callback style)
this.state = this._getNewState(props); this.state = this.getNewState(props);
this._contextMenuButton = createRef();
this._allowedWidgetsWatchRef = SettingsStore.watchSetting("allowedWidgets", null, this.onAllowedWidgetsChange); this.allowedWidgetsWatchRef = SettingsStore.watchSetting("allowedWidgets", null, this.onAllowedWidgetsChange);
} }
// This is a function to make the impact of calling SettingsStore slightly less // This is a function to make the impact of calling SettingsStore slightly less
hasPermissionToLoad = (props) => { private hasPermissionToLoad = (props: IProps): boolean => {
if (this._usingLocalWidget()) return true; if (this.usingLocalWidget()) return true;
if (!props.room) return true; // user widgets always have permissions if (!props.room) return true; // user widgets always have permissions
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", props.room.roomId); const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", props.room.roomId);
@ -81,34 +143,34 @@ export default class AppTile extends React.Component {
* @param {Object} newProps The new properties of the component * @param {Object} newProps The new properties of the component
* @return {Object} Updated component state to be set with setState * @return {Object} Updated component state to be set with setState
*/ */
_getNewState(newProps) { private getNewState(newProps: IProps): IState {
return { return {
initialising: true, // True while we are mangling the widget URL initialising: true, // True while we are mangling the widget URL
// True while the iframe content is loading // True while the iframe content is loading
loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey), loading: this.props.waitForIframeLoad,
// Assume that widget has permission to load if we are the user who // Assume that widget has permission to load if we are the user who
// added it to the room, or if explicitly granted by the user // added it to the room, or if explicitly granted by the user
hasPermissionToLoad: this.hasPermissionToLoad(newProps), hasPermissionToLoad: this.hasPermissionToLoad(newProps),
error: null, error: null,
widgetPageTitle: newProps.widgetPageTitle,
menuDisplayed: false, menuDisplayed: false,
widgetPageTitle: this.props.widgetPageTitle,
}; };
} }
onAllowedWidgetsChange = () => { private onAllowedWidgetsChange = (): void => {
const hasPermissionToLoad = this.hasPermissionToLoad(this.props); const hasPermissionToLoad = this.hasPermissionToLoad(this.props);
if (this.state.hasPermissionToLoad && !hasPermissionToLoad) { if (this.state.hasPermissionToLoad && !hasPermissionToLoad) {
// Force the widget to be non-persistent (able to be deleted/forgotten) // Force the widget to be non-persistent (able to be deleted/forgotten)
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id); ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this._persistKey); PersistedElement.destroyElement(this.persistKey);
if (this._sgWidget) this._sgWidget.stop(); if (this.sgWidget) this.sgWidget.stop();
} }
this.setState({ hasPermissionToLoad }); this.setState({ hasPermissionToLoad });
}; };
isMixedContent() { private isMixedContent(): boolean {
const parentContentProtocol = window.location.protocol; const parentContentProtocol = window.location.protocol;
const u = url.parse(this.props.app.url); const u = url.parse(this.props.app.url);
const childContentProtocol = u.protocol; const childContentProtocol = u.protocol;
@ -120,77 +182,72 @@ export default class AppTile extends React.Component {
return false; return false;
} }
componentDidMount() { public componentDidMount(): void {
// Only fetch IM token on mount if we're showing and have permission to load // Only fetch IM token on mount if we're showing and have permission to load
if (this._sgWidget && this.state.hasPermissionToLoad) { if (this.sgWidget && this.state.hasPermissionToLoad) {
this._startWidget(); this.startWidget();
} }
// Widget action listeners // Widget action listeners
this.dispatcherRef = dis.register(this._onAction); this.dispatcherRef = dis.register(this.onAction);
} }
componentWillUnmount() { public componentWillUnmount(): void {
// Widget action listeners // Widget action listeners
if (this.dispatcherRef) dis.unregister(this.dispatcherRef); if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
// if it's not remaining on screen, get rid of the PersistedElement container // if it's not remaining on screen, get rid of the PersistedElement container
if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) { if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) {
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id); ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this._persistKey); PersistedElement.destroyElement(this.persistKey);
} }
if (this._sgWidget) { if (this.sgWidget) {
this._sgWidget.stop(); this.sgWidget.stop();
} }
SettingsStore.unwatchSetting(this._allowedWidgetsWatchRef); SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef);
} }
_resetWidget(newProps) { private resetWidget(newProps: IProps): void {
if (this._sgWidget) { if (this.sgWidget) {
this._sgWidget.stop(); this.sgWidget.stop();
} }
try { try {
this._sgWidget = new StopGapWidget(newProps); this.sgWidget = new StopGapWidget(newProps);
this._sgWidget.on("preparing", this._onWidgetPrepared); this.sgWidget.on("preparing", this.onWidgetPrepared);
this._sgWidget.on("ready", this._onWidgetReady); this.sgWidget.on("ready", this.onWidgetReady);
this._startWidget(); this.startWidget();
} catch (e) { } catch (e) {
console.log("Failed to construct widget", e); console.log("Failed to construct widget", e);
this._sgWidget = null; this.sgWidget = null;
} }
} }
_startWidget() { private startWidget(): void {
this._sgWidget.prepare().then(() => { this.sgWidget.prepare().then(() => {
this.setState({ initialising: false }); this.setState({ initialising: false });
}); });
} }
_iframeRefChange = (ref) => { private iframeRefChange = (ref: HTMLIFrameElement): void => {
this.iframe = ref; this.iframe = ref;
if (ref) { if (ref) {
if (this._sgWidget) this._sgWidget.start(ref); if (this.sgWidget) this.sgWidget.start(ref);
} else { } else {
this._resetWidget(this.props); this.resetWidget(this.props);
} }
}; };
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase // eslint-disable-next-line @typescript-eslint/naming-convention
public UNSAFE_componentWillReceiveProps(nextProps: IProps): void { // eslint-disable-line camelcase
if (nextProps.app.url !== this.props.app.url) { if (nextProps.app.url !== this.props.app.url) {
this._getNewState(nextProps); this.getNewState(nextProps);
if (this.state.hasPermissionToLoad) { if (this.state.hasPermissionToLoad) {
this._resetWidget(nextProps); this.resetWidget(nextProps);
} }
} }
if (nextProps.widgetPageTitle !== this.props.widgetPageTitle) {
this.setState({
widgetPageTitle: nextProps.widgetPageTitle,
});
}
} }
/** /**
@ -198,7 +255,7 @@ export default class AppTile extends React.Component {
* @private * @private
* @returns {Promise<*>} Resolves when the widget is terminated, or timeout passed. * @returns {Promise<*>} Resolves when the widget is terminated, or timeout passed.
*/ */
async _endWidgetActions() { // widget migration dev note: async to maintain signature private async endWidgetActions(): Promise<void> { // widget migration dev note: async to maintain signature
// HACK: This is a really dirty way to ensure that Jitsi cleans up // HACK: This is a really dirty way to ensure that Jitsi cleans up
// its hold on the webcam. Without this, the widget holds a media // its hold on the webcam. Without this, the widget holds a media
// stream open, even after death. See https://github.com/vector-im/element-web/issues/7351 // stream open, even after death. See https://github.com/vector-im/element-web/issues/7351
@ -217,27 +274,27 @@ export default class AppTile extends React.Component {
} }
// Delete the widget from the persisted store for good measure. // Delete the widget from the persisted store for good measure.
PersistedElement.destroyElement(this._persistKey); PersistedElement.destroyElement(this.persistKey);
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id); ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
if (this._sgWidget) this._sgWidget.stop({ forceDestroy: true }); if (this.sgWidget) this.sgWidget.stop({ forceDestroy: true });
} }
_onWidgetPrepared = () => { private onWidgetPrepared = (): void => {
this.setState({ loading: false }); this.setState({ loading: false });
}; };
_onWidgetReady = () => { private onWidgetReady = (): void => {
if (WidgetType.JITSI.matches(this.props.app.type)) { if (WidgetType.JITSI.matches(this.props.app.type)) {
this._sgWidget.widgetApi.transport.send(ElementWidgetActions.ClientReady, {}); this.sgWidget.widgetApi.transport.send(ElementWidgetActions.ClientReady, {});
} }
}; };
_onAction = payload => { private onAction = (payload): void => {
if (payload.widgetId === this.props.app.id) { if (payload.widgetId === this.props.app.id) {
switch (payload.action) { switch (payload.action) {
case 'm.sticker': case 'm.sticker':
if (this._sgWidget.widgetApi.hasCapability(MatrixCapabilities.StickerSending)) { if (this.sgWidget.widgetApi.hasCapability(MatrixCapabilities.StickerSending)) {
dis.dispatch({ action: 'post_sticker_message', data: payload.data }); dis.dispatch({ action: 'post_sticker_message', data: payload.data });
dis.dispatch({ action: 'stickerpicker_close' }); dis.dispatch({ action: 'stickerpicker_close' });
} else { } else {
@ -248,7 +305,7 @@ export default class AppTile extends React.Component {
} }
}; };
_grantWidgetPermission = () => { private grantWidgetPermission = (): void => {
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
console.info("Granting permission for widget to load: " + this.props.app.eventId); console.info("Granting permission for widget to load: " + this.props.app.eventId);
const current = SettingsStore.getValue("allowedWidgets", roomId); const current = SettingsStore.getValue("allowedWidgets", roomId);
@ -258,14 +315,14 @@ export default class AppTile extends React.Component {
this.setState({ hasPermissionToLoad: true }); this.setState({ hasPermissionToLoad: true });
// Fetch a token for the integration manager, now that we're allowed to // Fetch a token for the integration manager, now that we're allowed to
this._startWidget(); this.startWidget();
}).catch(err => { }).catch(err => {
console.error(err); console.error(err);
// We don't really need to do anything about this - the user will just hit the button again. // We don't really need to do anything about this - the user will just hit the button again.
}); });
}; };
formatAppTileName() { private formatAppTileName(): string {
let appTileName = "No name"; let appTileName = "No name";
if (this.props.app.name && this.props.app.name.trim()) { if (this.props.app.name && this.props.app.name.trim()) {
appTileName = this.props.app.name.trim(); appTileName = this.props.app.name.trim();
@ -278,11 +335,11 @@ export default class AppTile extends React.Component {
* actual widget URL * actual widget URL
* @returns {bool} true If using a local version of the widget * @returns {bool} true If using a local version of the widget
*/ */
_usingLocalWidget() { private usingLocalWidget(): boolean {
return WidgetType.JITSI.matches(this.props.app.type); return WidgetType.JITSI.matches(this.props.app.type);
} }
_getTileTitle() { private getTileTitle(): JSX.Element {
const name = this.formatAppTileName(); const name = this.formatAppTileName();
const titleSpacer = <span>&nbsp;-&nbsp;</span>; const titleSpacer = <span>&nbsp;-&nbsp;</span>;
let title = ''; let title = '';
@ -300,32 +357,32 @@ export default class AppTile extends React.Component {
} }
// TODO replace with full screen interactions // TODO replace with full screen interactions
_onPopoutWidgetClick = () => { private onPopoutWidgetClick = (): void => {
// Ensure Jitsi conferences are closed on pop-out, to not confuse the user to join them // Ensure Jitsi conferences are closed on pop-out, to not confuse the user to join them
// twice from the same computer, which Jitsi can have problems with (audio echo/gain-loop). // twice from the same computer, which Jitsi can have problems with (audio echo/gain-loop).
if (WidgetType.JITSI.matches(this.props.app.type)) { if (WidgetType.JITSI.matches(this.props.app.type)) {
this._endWidgetActions().then(() => { this.endWidgetActions().then(() => {
if (this.iframe) { if (this.iframe) {
// Reload iframe // Reload iframe
this.iframe.src = this._sgWidget.embedUrl; this.iframe.src = this.sgWidget.embedUrl;
} }
}); });
} }
// Using Object.assign workaround as the following opens in a new window instead of a new tab. // Using Object.assign workaround as the following opens in a new window instead of a new tab.
// window.open(this._getPopoutUrl(), '_blank', 'noopener=yes'); // window.open(this._getPopoutUrl(), '_blank', 'noopener=yes');
Object.assign(document.createElement('a'), Object.assign(document.createElement('a'),
{ target: '_blank', href: this._sgWidget.popoutUrl, rel: 'noreferrer noopener' }).click(); { target: '_blank', href: this.sgWidget.popoutUrl, rel: 'noreferrer noopener' }).click();
}; };
_onContextMenuClick = () => { private onContextMenuClick = (): void => {
this.setState({ menuDisplayed: true }); this.setState({ menuDisplayed: true });
}; };
_closeContextMenu = () => { private closeContextMenu = (): void => {
this.setState({ menuDisplayed: false }); this.setState({ menuDisplayed: false });
}; };
render() { public render(): JSX.Element {
let appTileBody; let appTileBody;
// Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin // Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
@ -351,7 +408,7 @@ export default class AppTile extends React.Component {
<Spinner message={_t("Loading...")} /> <Spinner message={_t("Loading...")} />
</div> </div>
); );
if (this._sgWidget === null) { if (this.sgWidget === null) {
appTileBody = ( appTileBody = (
<div className={appTileBodyClass} style={appTileBodyStyles}> <div className={appTileBodyClass} style={appTileBodyStyles}>
<AppWarning errorMsg={_t("Error loading Widget")} /> <AppWarning errorMsg={_t("Error loading Widget")} />
@ -365,9 +422,9 @@ export default class AppTile extends React.Component {
<AppPermission <AppPermission
roomId={this.props.room.roomId} roomId={this.props.room.roomId}
creatorUserId={this.props.creatorUserId} creatorUserId={this.props.creatorUserId}
url={this._sgWidget.embedUrl} url={this.sgWidget.embedUrl}
isRoomEncrypted={isEncrypted} isRoomEncrypted={isEncrypted}
onPermissionGranted={this._grantWidgetPermission} onPermissionGranted={this.grantWidgetPermission}
/> />
</div> </div>
); );
@ -390,8 +447,8 @@ export default class AppTile extends React.Component {
{ this.state.loading && loadingElement } { this.state.loading && loadingElement }
<iframe <iframe
allow={iframeFeatures} allow={iframeFeatures}
ref={this._iframeRefChange} ref={this.iframeRefChange}
src={this._sgWidget.embedUrl} src={this.sgWidget.embedUrl}
allowFullScreen={true} allowFullScreen={true}
sandbox={sandboxFlags} sandbox={sandboxFlags}
/> />
@ -407,7 +464,7 @@ export default class AppTile extends React.Component {
// Also wrap the PersistedElement in a div to fix the height, otherwise // Also wrap the PersistedElement in a div to fix the height, otherwise
// AppTile's border is in the wrong place // AppTile's border is in the wrong place
appTileBody = <div className="mx_AppTile_persistedWrapper"> appTileBody = <div className="mx_AppTile_persistedWrapper">
<PersistedElement persistKey={this._persistKey}> <PersistedElement persistKey={this.persistKey}>
{ appTileBody } { appTileBody }
</PersistedElement> </PersistedElement>
</div>; </div>;
@ -429,9 +486,9 @@ export default class AppTile extends React.Component {
if (this.state.menuDisplayed) { if (this.state.menuDisplayed) {
contextMenu = ( contextMenu = (
<RoomWidgetContextMenu <RoomWidgetContextMenu
{...aboveLeftOf(this._contextMenuButton.current.getBoundingClientRect(), null)} {...aboveLeftOf(this.contextMenuButton.current.getBoundingClientRect(), null)}
app={this.props.app} app={this.props.app}
onFinished={this._closeContextMenu} onFinished={this.closeContextMenu}
showUnpin={!this.props.userWidget} showUnpin={!this.props.userWidget}
userWidget={this.props.userWidget} userWidget={this.props.userWidget}
onEditClick={this.props.onEditClick} onEditClick={this.props.onEditClick}
@ -444,21 +501,21 @@ export default class AppTile extends React.Component {
<div className={appTileClasses} id={this.props.app.id}> <div className={appTileClasses} id={this.props.app.id}>
{ this.props.showMenubar && { this.props.showMenubar &&
<div className="mx_AppTileMenuBar"> <div className="mx_AppTileMenuBar">
<span className="mx_AppTileMenuBarTitle" style={{ pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false) }}> <span className="mx_AppTileMenuBarTitle" style={{ pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : "none") }}>
{ this.props.showTitle && this._getTileTitle() } { this.props.showTitle && this.getTileTitle() }
</span> </span>
<span className="mx_AppTileMenuBarWidgets"> <span className="mx_AppTileMenuBarWidgets">
{ this.props.showPopout && <AccessibleButton { this.props.showPopout && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout" className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
title={_t('Popout widget')} title={_t('Popout widget')}
onClick={this._onPopoutWidgetClick} onClick={this.onPopoutWidgetClick}
/> } /> }
<ContextMenuButton <ContextMenuButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu" className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
label={_t("Options")} label={_t("Options")}
isExpanded={this.state.menuDisplayed} isExpanded={this.state.menuDisplayed}
inputRef={this._contextMenuButton} inputRef={this.contextMenuButton}
onClick={this._onContextMenuClick} onClick={this.onContextMenuClick}
/> />
</span> </span>
</div> } </div> }
@ -469,49 +526,3 @@ export default class AppTile extends React.Component {
</React.Fragment>; </React.Fragment>;
} }
} }
AppTile.displayName = 'AppTile';
AppTile.propTypes = {
app: PropTypes.object.isRequired,
// If room is not specified then it is an account level widget
// which bypasses permission prompts as it was added explicitly by that user
room: PropTypes.object,
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
fullWidth: PropTypes.bool,
// Optional. If set, renders a smaller view of the widget
miniMode: PropTypes.bool,
// UserId of the current user
userId: PropTypes.string.isRequired,
// UserId of the entity that added / modified the widget
creatorUserId: PropTypes.string,
waitForIframeLoad: PropTypes.bool,
showMenubar: PropTypes.bool,
// Optional onEditClickHandler (overrides default behaviour)
onEditClick: PropTypes.func,
// Optional onDeleteClickHandler (overrides default behaviour)
onDeleteClick: PropTypes.func,
// Optional onMinimiseClickHandler
onMinimiseClick: PropTypes.func,
// Optionally hide the tile title
showTitle: PropTypes.bool,
// Optionally handle minimise button pointer events (default false)
handleMinimisePointerEvents: PropTypes.bool,
// Optionally hide the popout widget icon
showPopout: PropTypes.bool,
// Is this an instance of a user widget
userWidget: PropTypes.bool,
// sets the pointer-events property on the iframe
pointerEvents: PropTypes.string,
};
AppTile.defaultProps = {
waitForIframeLoad: true,
showMenubar: true,
showTitle: true,
showPopout: true,
handleMinimisePointerEvents: false,
userWidget: false,
miniMode: false,
};

View file

@ -97,7 +97,6 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
<AppTile <AppTile
app={app} app={app}
fullWidth fullWidth
show
showMenubar={false} showMenubar={false}
room={room} room={room}
userId={cli.getUserId()} userId={cli.getUserId()}

View file

@ -32,6 +32,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ActionPayload } from '../../../dispatcher/payloads'; import { ActionPayload } from '../../../dispatcher/payloads';
import ScalarAuthClient from '../../../ScalarAuthClient'; import ScalarAuthClient from '../../../ScalarAuthClient';
import GenericElementContextMenu from "../context_menus/GenericElementContextMenu"; import GenericElementContextMenu from "../context_menus/GenericElementContextMenu";
import { IApp } from "../../../stores/WidgetStore";
// This should be below the dialog level (4000), but above the rest of the UI (1000-2000). // 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. // We sit in a context menu, so this should be given to the context menu.
@ -256,12 +257,16 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack"); stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack");
// FIXME: could this use the same code as other apps? // FIXME: could this use the same code as other apps?
const stickerApp = { const stickerApp: IApp = {
id: stickerpickerWidget.id, id: stickerpickerWidget.id,
url: stickerpickerWidget.content.url, url: stickerpickerWidget.content.url,
name: stickerpickerWidget.content.name, name: stickerpickerWidget.content.name,
type: stickerpickerWidget.content.type, type: stickerpickerWidget.content.type,
data: stickerpickerWidget.content.data, data: stickerpickerWidget.content.data,
roomId: stickerpickerWidget.content.roomId,
eventId: stickerpickerWidget.content.eventId,
avatar_url: stickerpickerWidget.content.avatar_url,
creatorUserId: stickerpickerWidget.content.creatorUserId,
}; };
stickersContent = ( stickersContent = (
@ -287,9 +292,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
onEditClick={this.launchManageIntegrations} onEditClick={this.launchManageIntegrations}
onDeleteClick={this.removeStickerpickerWidgets} onDeleteClick={this.removeStickerpickerWidgets}
showTitle={false} showTitle={false}
showCancel={false}
showPopout={false} showPopout={false}
onMinimiseClick={this.onHideStickersClick}
handleMinimisePointerEvents={true} handleMinimisePointerEvents={true}
userWidget={true} userWidget={true}
/> />
@ -345,16 +348,6 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
}); });
}; };
/**
* Trigger hiding of the sticker picker overlay
* @param {Event} ev Event that triggered the function call
*/
private onHideStickersClick = (ev: React.MouseEvent): void => {
if (this.props.showStickers) {
this.props.setShowStickers(false);
}
};
/** /**
* Called when the window is resized * Called when the window is resized
*/ */

View file

@ -67,7 +67,7 @@ interface IAppTileProps {
userId: string; userId: string;
creatorUserId: string; creatorUserId: string;
waitForIframeLoad: boolean; waitForIframeLoad: boolean;
whitelistCapabilities: string[]; whitelistCapabilities?: string[];
userWidget: boolean; userWidget: boolean;
} }