Merge pull request #5435 from matrix-org/travis/window-widgets

Support arbitrary widgets sticking to the screen + sending stickers
This commit is contained in:
Travis Ralston 2020-11-23 08:32:46 -07:00 committed by GitHub
commit 054560b6a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 46 additions and 61 deletions

View file

@ -23,7 +23,6 @@ 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';
import * as sdk from '../../../index';
import AppPermission from './AppPermission'; import AppPermission from './AppPermission';
import AppWarning from './AppWarning'; import AppWarning from './AppWarning';
import Spinner from './Spinner'; import Spinner from './Spinner';
@ -375,11 +374,11 @@ export default class AppTile extends React.Component {
/> />
</div> </div>
); );
// if the widget would be allowed to remain on screen, we must put it in
// a PersistedElement from the get-go, otherwise the iframe will be // all widgets can theoretically be allowed to remain on screen, so we wrap
// re-mounted later when we do. // them all in a PersistedElement from the get-go. If we wait, the iframe will
if (this.props.whitelistCapabilities.includes('m.always_on_screen')) { // be re-mounted later, which means the widget has to start over, which is bad.
const PersistedElement = sdk.getComponent("elements.PersistedElement");
// 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">
@ -389,7 +388,6 @@ export default class AppTile extends React.Component {
</div>; </div>;
} }
} }
}
let appTileClasses; let appTileClasses;
if (this.props.miniMode) { if (this.props.miniMode) {
@ -474,10 +472,6 @@ AppTile.propTypes = {
handleMinimisePointerEvents: PropTypes.bool, handleMinimisePointerEvents: PropTypes.bool,
// Optionally hide the popout widget icon // Optionally hide the popout widget icon
showPopout: PropTypes.bool, showPopout: PropTypes.bool,
// Widget capabilities to allow by default (without user confirmation)
// NOTE -- Use with caution. This is intended to aid better integration / UX
// basic widget capabilities, e.g. injecting sticker message events.
whitelistCapabilities: PropTypes.array,
// Is this an instance of a user widget // Is this an instance of a user widget
userWidget: PropTypes.bool, userWidget: PropTypes.bool,
}; };
@ -488,7 +482,6 @@ AppTile.defaultProps = {
showTitle: true, showTitle: true,
showPopout: true, showPopout: true,
handleMinimisePointerEvents: false, handleMinimisePointerEvents: false,
whitelistCapabilities: [],
userWidget: false, userWidget: false,
miniMode: false, miniMode: false,
}; };

View file

@ -71,7 +71,6 @@ export default class PersistentApp extends React.Component {
appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(), appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(),
persistentWidgetInRoomId, appEvent.getId(), persistentWidgetInRoomId, appEvent.getId(),
); );
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, persistentWidgetInRoomId);
const AppTile = sdk.getComponent('elements.AppTile'); const AppTile = sdk.getComponent('elements.AppTile');
return <AppTile return <AppTile
key={app.id} key={app.id}
@ -82,7 +81,6 @@ export default class PersistentApp extends React.Component {
creatorUserId={app.creatorUserId} creatorUserId={app.creatorUserId}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad} waitForIframeLoad={app.waitForIframeLoad}
whitelistCapabilities={capWhitelist}
miniMode={true} miniMode={true}
showMenubar={false} showMenubar={false}
/>; />;

View file

@ -103,7 +103,6 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
creatorUserId={app.creatorUserId} creatorUserId={app.creatorUserId}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad} waitForIframeLoad={app.waitForIframeLoad}
whitelistCapabilities={WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, room.roomId)}
/> />
</BaseCard>; </BaseCard>;
}; };

View file

@ -210,8 +210,6 @@ export default class AppsDrawer extends React.Component {
if (!this.props.showApps) return <div />; if (!this.props.showApps) return <div />;
const apps = this.state.apps.map((app, index, arr) => { const apps = this.state.apps.map((app, index, arr) => {
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId);
return (<AppTile return (<AppTile
key={app.id} key={app.id}
app={app} app={app}
@ -221,7 +219,6 @@ export default class AppsDrawer extends React.Component {
creatorUserId={app.creatorUserId} creatorUserId={app.creatorUserId}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad} waitForIframeLoad={app.waitForIframeLoad}
whitelistCapabilities={capWhitelist}
/>); />);
}); });

View file

@ -280,7 +280,6 @@ export default class Stickerpicker extends React.Component {
showPopout={false} showPopout={false}
onMinimiseClick={this._onHideStickersClick} onMinimiseClick={this._onHideStickersClick}
handleMinimisePointerEvents={true} handleMinimisePointerEvents={true}
whitelistCapabilities={['m.sticker', 'visibility']}
userWidget={true} userWidget={true}
/> />
</PersistedElement> </PersistedElement>

View file

@ -75,8 +75,8 @@ interface IAppTileProps {
// TODO: Don't use this because it's wrong // TODO: Don't use this because it's wrong
class ElementWidget extends Widget { class ElementWidget extends Widget {
constructor(w) { constructor(private rawDefinition: IWidget) {
super(w); super(rawDefinition);
} }
public get templateUrl(): string { public get templateUrl(): string {
@ -137,12 +137,7 @@ class ElementWidget extends Widget {
public getCompleteUrl(params: ITemplateParams, asPopout=false): string { public getCompleteUrl(params: ITemplateParams, asPopout=false): string {
return runTemplate(asPopout ? this.popoutTemplateUrl : this.templateUrl, { return runTemplate(asPopout ? this.popoutTemplateUrl : this.templateUrl, {
// we need to supply a whole widget to the template, but don't have ...this.rawDefinition,
// easy access to the definition the superclass is using, so be sad
// and gutwrench it.
// This isn't a problem when the widget architecture is fixed and this
// subclass gets deleted.
...super['definition'], // XXX: Private member access
data: this.rawData, data: this.rawData,
}, params); }, params);
} }
@ -351,18 +346,39 @@ export class StopGapWidget extends EventEmitter {
MatrixClientPeg.get().on('event', this.onEvent); MatrixClientPeg.get().on('event', this.onEvent);
MatrixClientPeg.get().on('Event.decrypted', this.onEventDecrypted); MatrixClientPeg.get().on('Event.decrypted', this.onEventDecrypted);
if (WidgetType.JITSI.matches(this.mockWidget.type)) { this.messaging.on(`action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`,
this.messaging.on("action:set_always_on_screen",
(ev: CustomEvent<IStickyActionRequest>) => { (ev: CustomEvent<IStickyActionRequest>) => {
if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) { if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) {
if (WidgetType.JITSI.matches(this.mockWidget.type)) {
CountlyAnalytics.instance.trackJoinCall(this.appTileProps.room.roomId, true, true); CountlyAnalytics.instance.trackJoinCall(this.appTileProps.room.roomId, true, true);
}
ActiveWidgetStore.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value); ActiveWidgetStore.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value);
ev.preventDefault(); ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack
} }
}, },
); );
} else if (WidgetType.STICKERPICKER.matches(this.mockWidget.type)) {
// TODO: Replace this event listener with appropriate driver functionality once the API
// establishes a sane way to send events back and forth.
this.messaging.on(`action:${WidgetApiFromWidgetAction.SendSticker}`,
(ev: CustomEvent<IStickerActionRequest>) => {
if (this.messaging.hasCapability(MatrixCapabilities.StickerSending)) {
// Acknowledge first
ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
// Send the sticker
defaultDispatcher.dispatch({
action: 'm.sticker',
data: ev.detail.data,
widgetId: this.mockWidget.id,
});
}
},
);
if (WidgetType.STICKERPICKER.matches(this.mockWidget.type)) {
this.messaging.on(`action:${ElementWidgetActions.OpenIntegrationManager}`, this.messaging.on(`action:${ElementWidgetActions.OpenIntegrationManager}`,
(ev: CustomEvent<IWidgetApiRequest>) => { (ev: CustomEvent<IWidgetApiRequest>) => {
// Acknowledge first // Acknowledge first
@ -394,23 +410,6 @@ export class StopGapWidget extends EventEmitter {
} }
}, },
); );
// TODO: Replace this event listener with appropriate driver functionality once the API
// establishes a sane way to send events back and forth.
this.messaging.on(`action:${WidgetApiFromWidgetAction.SendSticker}`,
(ev: CustomEvent<IStickerActionRequest>) => {
// Acknowledge first
ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
// Send the sticker
defaultDispatcher.dispatch({
action: 'm.sticker',
data: ev.detail.data,
widgetId: this.mockWidget.id,
});
},
);
} }
} }