Merge pull request #5435 from matrix-org/travis/window-widgets
Support arbitrary widgets sticking to the screen + sending stickers
This commit is contained in:
commit
054560b6a7
6 changed files with 46 additions and 61 deletions
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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}
|
||||||
/>;
|
/>;
|
||||||
|
|
|
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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}
|
|
||||||
/>);
|
/>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue