Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
f76ddbc718
8 changed files with 198 additions and 74 deletions
|
@ -31,7 +31,6 @@ limitations under the License.
|
||||||
top: 14px;
|
top: 14px;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
|
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import URL from 'url';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher';
|
||||||
import IntegrationManager from './IntegrationManager';
|
import IntegrationManager from './IntegrationManager';
|
||||||
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
||||||
|
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
||||||
|
|
||||||
const WIDGET_API_VERSION = '0.0.1'; // Current API version
|
const WIDGET_API_VERSION = '0.0.1'; // Current API version
|
||||||
const SUPPORTED_WIDGET_API_VERSIONS = [
|
const SUPPORTED_WIDGET_API_VERSIONS = [
|
||||||
|
@ -155,6 +156,14 @@ export default class FromWidgetPostMessageApi {
|
||||||
const integType = (data && data.integType) ? data.integType : null;
|
const integType = (data && data.integType) ? data.integType : null;
|
||||||
const integId = (data && data.integId) ? data.integId : null;
|
const integId = (data && data.integId) ? data.integId : null;
|
||||||
IntegrationManager.open(integType, integId);
|
IntegrationManager.open(integType, integId);
|
||||||
|
} else if (action === 'set_always_on_screen') {
|
||||||
|
// This is a new message: there is no reason to support the deprecated widgetData here
|
||||||
|
const data = event.data.data;
|
||||||
|
const val = data.value;
|
||||||
|
|
||||||
|
if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
|
||||||
|
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
||||||
|
}
|
||||||
} 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'});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/**
|
/**
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -33,6 +34,7 @@ import AppWarning from './AppWarning';
|
||||||
import MessageSpinner from './MessageSpinner';
|
import MessageSpinner from './MessageSpinner';
|
||||||
import WidgetUtils from '../../../utils/WidgetUtils';
|
import WidgetUtils from '../../../utils/WidgetUtils';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
|
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
||||||
|
|
||||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||||
const ENABLE_REACT_PERF = false;
|
const ENABLE_REACT_PERF = false;
|
||||||
|
@ -40,9 +42,13 @@ const ENABLE_REACT_PERF = false;
|
||||||
export default class AppTile extends React.Component {
|
export default class AppTile extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
// The key used for PersistedElement
|
||||||
|
this._persistKey = 'widget_' + this.props.id;
|
||||||
|
|
||||||
this.state = this._getNewState(props);
|
this.state = this._getNewState(props);
|
||||||
|
|
||||||
this._onWidgetAction = this._onWidgetAction.bind(this);
|
this._onAction = this._onAction.bind(this);
|
||||||
this._onMessage = this._onMessage.bind(this);
|
this._onMessage = this._onMessage.bind(this);
|
||||||
this._onLoaded = this._onLoaded.bind(this);
|
this._onLoaded = this._onLoaded.bind(this);
|
||||||
this._onEditClick = this._onEditClick.bind(this);
|
this._onEditClick = this._onEditClick.bind(this);
|
||||||
|
@ -50,7 +56,6 @@ export default class AppTile extends React.Component {
|
||||||
this._onSnapshotClick = this._onSnapshotClick.bind(this);
|
this._onSnapshotClick = this._onSnapshotClick.bind(this);
|
||||||
this.onClickMenuBar = this.onClickMenuBar.bind(this);
|
this.onClickMenuBar = this.onClickMenuBar.bind(this);
|
||||||
this._onMinimiseClick = this._onMinimiseClick.bind(this);
|
this._onMinimiseClick = this._onMinimiseClick.bind(this);
|
||||||
this._onInitialLoad = this._onInitialLoad.bind(this);
|
|
||||||
this._grantWidgetPermission = this._grantWidgetPermission.bind(this);
|
this._grantWidgetPermission = this._grantWidgetPermission.bind(this);
|
||||||
this._revokeWidgetPermission = this._revokeWidgetPermission.bind(this);
|
this._revokeWidgetPermission = this._revokeWidgetPermission.bind(this);
|
||||||
this._onPopoutWidgetClick = this._onPopoutWidgetClick.bind(this);
|
this._onPopoutWidgetClick = this._onPopoutWidgetClick.bind(this);
|
||||||
|
@ -66,9 +71,12 @@ export default class AppTile extends React.Component {
|
||||||
_getNewState(newProps) {
|
_getNewState(newProps) {
|
||||||
const widgetPermissionId = [newProps.room.roomId, encodeURIComponent(newProps.url)].join('_');
|
const widgetPermissionId = [newProps.room.roomId, encodeURIComponent(newProps.url)].join('_');
|
||||||
const hasPermissionToLoad = localStorage.getItem(widgetPermissionId);
|
const hasPermissionToLoad = localStorage.getItem(widgetPermissionId);
|
||||||
|
|
||||||
|
const PersistedElement = sdk.getComponent("elements.PersistedElement");
|
||||||
return {
|
return {
|
||||||
initialising: true, // True while we are mangling the widget URL
|
initialising: true, // True while we are mangling the widget URL
|
||||||
loading: this.props.waitForIframeLoad, // True while the iframe content is loading
|
// True while the iframe content is loading
|
||||||
|
loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey),
|
||||||
widgetUrl: this._addWurlParams(newProps.url),
|
widgetUrl: this._addWurlParams(newProps.url),
|
||||||
widgetPermissionId: widgetPermissionId,
|
widgetPermissionId: widgetPermissionId,
|
||||||
// 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
|
||||||
|
@ -77,9 +85,6 @@ export default class AppTile extends React.Component {
|
||||||
error: null,
|
error: null,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
widgetPageTitle: newProps.widgetPageTitle,
|
widgetPageTitle: newProps.widgetPageTitle,
|
||||||
allowedCapabilities: (this.props.whitelistCapabilities && this.props.whitelistCapabilities.length > 0) ?
|
|
||||||
this.props.whitelistCapabilities : [],
|
|
||||||
requestedCapabilities: [],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +94,7 @@ export default class AppTile extends React.Component {
|
||||||
* @return {Boolean} True if capability supported
|
* @return {Boolean} True if capability supported
|
||||||
*/
|
*/
|
||||||
_hasCapability(capability) {
|
_hasCapability(capability) {
|
||||||
return this.state.allowedCapabilities.some((c) => {return c === capability;});
|
return ActiveWidgetStore.widgetHasCapability(this.props.id, capability);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,30 +147,24 @@ export default class AppTile extends React.Component {
|
||||||
window.addEventListener('message', this._onMessage, false);
|
window.addEventListener('message', this._onMessage, false);
|
||||||
|
|
||||||
// Widget action listeners
|
// Widget action listeners
|
||||||
this.dispatcherRef = dis.register(this._onWidgetAction);
|
this.dispatcherRef = dis.register(this._onAction);
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
// Allow parents to access widget messaging
|
|
||||||
if (this.props.collectWidgetMessaging) {
|
|
||||||
this.props.collectWidgetMessaging(this.widgetMessaging);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
// Widget action listeners
|
// Widget action listeners
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
|
|
||||||
// Widget postMessage listeners
|
|
||||||
try {
|
|
||||||
if (this.widgetMessaging) {
|
|
||||||
this.widgetMessaging.stop();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to stop listening for widgetMessaging events', e.message);
|
|
||||||
}
|
|
||||||
// Jitsi listener
|
// Jitsi listener
|
||||||
window.removeEventListener('message', this._onMessage);
|
window.removeEventListener('message', this._onMessage);
|
||||||
|
|
||||||
|
// if it's not remaining on screen, get rid of the PersistedElement container
|
||||||
|
if (!ActiveWidgetStore.getWidgetPersistence(this.props.id)) {
|
||||||
|
// FIXME: ActiveWidgetStore should probably worry about this?
|
||||||
|
const PersistedElement = sdk.getComponent("elements.PersistedElement");
|
||||||
|
PersistedElement.destroyElement(this._persistKey);
|
||||||
|
ActiveWidgetStore.delWidgetMessaging(this.props.id);
|
||||||
|
ActiveWidgetStore.delWidgetCapabilities(this.props.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -286,7 +285,7 @@ export default class AppTile extends React.Component {
|
||||||
|
|
||||||
_onSnapshotClick(e) {
|
_onSnapshotClick(e) {
|
||||||
console.warn("Requesting widget snapshot");
|
console.warn("Requesting widget snapshot");
|
||||||
this.widgetMessaging.getScreenshot()
|
ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Failed to get screenshot", err);
|
console.error("Failed to get screenshot", err);
|
||||||
})
|
})
|
||||||
|
@ -341,19 +340,19 @@ export default class AppTile extends React.Component {
|
||||||
* Called when widget iframe has finished loading
|
* Called when widget iframe has finished loading
|
||||||
*/
|
*/
|
||||||
_onLoaded() {
|
_onLoaded() {
|
||||||
if (!this.widgetMessaging) {
|
if (!ActiveWidgetStore.getWidgetMessaging(this.props.id)) {
|
||||||
this._onInitialLoad();
|
this._setupWidgetMessaging();
|
||||||
}
|
}
|
||||||
this.setState({loading: false});
|
this.setState({loading: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_setupWidgetMessaging() {
|
||||||
* Called on initial load of the widget iframe
|
// FIXME: There's probably no reason to do this here: it should probably be done entirely
|
||||||
*/
|
// in ActiveWidgetStore.
|
||||||
_onInitialLoad() {
|
const widgetMessaging = new WidgetMessaging(this.props.id, this.props.url, this.refs.appFrame.contentWindow);
|
||||||
this.widgetMessaging = new WidgetMessaging(this.props.id, this.props.url, this.refs.appFrame.contentWindow);
|
ActiveWidgetStore.setWidgetMessaging(this.props.id, widgetMessaging);
|
||||||
this.widgetMessaging.getCapabilities().then((requestedCapabilities) => {
|
widgetMessaging.getCapabilities().then((requestedCapabilities) => {
|
||||||
console.log(`Widget ${this.props.id} requested capabilities:`, requestedCapabilities);
|
console.log(`Widget ${this.props.id} requested capabilities: ` + requestedCapabilities);
|
||||||
requestedCapabilities = requestedCapabilities || [];
|
requestedCapabilities = requestedCapabilities || [];
|
||||||
|
|
||||||
// Allow whitelisted capabilities
|
// Allow whitelisted capabilities
|
||||||
|
@ -365,16 +364,15 @@ export default class AppTile extends React.Component {
|
||||||
}, this.props.whitelistCapabilities);
|
}, this.props.whitelistCapabilities);
|
||||||
|
|
||||||
if (requestedWhitelistCapabilies.length > 0 ) {
|
if (requestedWhitelistCapabilies.length > 0 ) {
|
||||||
console.warn(`Widget ${this.props.id} allowing requested, whitelisted properties:`,
|
console.warn(`Widget ${this.props.id} allowing requested, whitelisted properties: ` +
|
||||||
requestedWhitelistCapabilies);
|
requestedWhitelistCapabilies,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO -- Add UI to warn about and optionally allow requested capabilities
|
// TODO -- Add UI to warn about and optionally allow requested capabilities
|
||||||
this.setState({
|
|
||||||
requestedCapabilities,
|
ActiveWidgetStore.setWidgetCapabilities(this.props.id, requestedWhitelistCapabilies);
|
||||||
allowedCapabilities: this.state.allowedCapabilities.concat(requestedWhitelistCapabilies),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.props.onCapabilityRequest) {
|
if (this.props.onCapabilityRequest) {
|
||||||
this.props.onCapabilityRequest(requestedCapabilities);
|
this.props.onCapabilityRequest(requestedCapabilities);
|
||||||
|
@ -384,7 +382,7 @@ export default class AppTile extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onWidgetAction(payload) {
|
_onAction(payload) {
|
||||||
if (payload.widgetId === this.props.id) {
|
if (payload.widgetId === this.props.id) {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'm.sticker':
|
case 'm.sticker':
|
||||||
|
@ -562,6 +560,15 @@ export default class AppTile extends React.Component {
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
// if the widget would be allowed to remian on screen, we must put it in
|
||||||
|
// a PersistedElement from the get-go, otherwise the iframe will be
|
||||||
|
// re-mounted later when we do.
|
||||||
|
if (this.props.whitelistCapabilities.includes('m.always_on_screen')) {
|
||||||
|
const PersistedElement = sdk.getComponent("elements.PersistedElement");
|
||||||
|
appTileBody = <PersistedElement persistKey={this._persistKey}>
|
||||||
|
{appTileBody}
|
||||||
|
</PersistedElement>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||||
|
|
|
@ -22,8 +22,12 @@ const PropTypes = require('prop-types');
|
||||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||||
// pass in a custom control as the actual body.
|
// pass in a custom control as the actual body.
|
||||||
|
|
||||||
|
function getContainer(containerId) {
|
||||||
|
return document.getElementById(containerId);
|
||||||
|
}
|
||||||
|
|
||||||
function getOrCreateContainer(containerId) {
|
function getOrCreateContainer(containerId) {
|
||||||
let container = document.getElementById(containerId);
|
let container = getContainer(containerId);
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
container = document.createElement("div");
|
container = document.createElement("div");
|
||||||
|
@ -60,6 +64,24 @@ export default class PersistedElement extends React.Component {
|
||||||
this.collectChild = this.collectChild.bind(this);
|
this.collectChild = this.collectChild.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the DOM elements created when a PersistedElement with the given
|
||||||
|
* persistKey was mounted. The DOM elements will be re-added if another
|
||||||
|
* PeristedElement is mounted in the future.
|
||||||
|
*
|
||||||
|
* @param {string} persistKey Key used to uniquely identify this PersistedElement
|
||||||
|
*/
|
||||||
|
static destroyElement(persistKey) {
|
||||||
|
const container = getContainer('mx_persistedElement_' + persistKey);
|
||||||
|
if (container) {
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static isMounted(persistKey) {
|
||||||
|
return Boolean(getContainer('mx_persistedElement_' + persistKey));
|
||||||
|
}
|
||||||
|
|
||||||
collectChildContainer(ref) {
|
collectChildContainer(ref) {
|
||||||
this.childContainer = ref;
|
this.childContainer = ref;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -214,8 +215,14 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", this.props.room.room_id);
|
const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", this.props.room.room_id);
|
||||||
|
|
||||||
const apps = this.state.apps.map(
|
const apps = this.state.apps.map((app, index, arr) => {
|
||||||
(app, index, arr) => {
|
const capWhitelist = enableScreenshots ? ["m.capability.screenshot"] : [];
|
||||||
|
|
||||||
|
// Obviously anyone that can add a widget can claim it's a jitsi widget,
|
||||||
|
// so this doesn't really offer much over the set of domains we load
|
||||||
|
// widgets from at all, but it probably makes sense for sanity.
|
||||||
|
if (app.type == 'jitsi') capWhitelist.push("m.always_on_screen");
|
||||||
|
|
||||||
return (<AppTile
|
return (<AppTile
|
||||||
key={app.id}
|
key={app.id}
|
||||||
id={app.id}
|
id={app.id}
|
||||||
|
@ -229,7 +236,7 @@ module.exports = React.createClass({
|
||||||
creatorUserId={app.creatorUserId}
|
creatorUserId={app.creatorUserId}
|
||||||
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
|
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
|
||||||
waitForIframeLoad={app.waitForIframeLoad}
|
waitForIframeLoad={app.waitForIframeLoad}
|
||||||
whitelistCapabilities={enableScreenshots ? ["m.capability.screenshot"] : []}
|
whitelistCapabilities={capWhitelist}
|
||||||
/>);
|
/>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -702,7 +702,6 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
<div className="mx_EventTile_msgOption">
|
<div className="mx_EventTile_msgOption">
|
||||||
{ readAvatars }
|
{ readAvatars }
|
||||||
</div>
|
</div>
|
||||||
{ avatar }
|
|
||||||
{ sender }
|
{ sender }
|
||||||
<div className="mx_EventTile_line">
|
<div className="mx_EventTile_line">
|
||||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||||
|
@ -719,6 +718,12 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
{ keyRequestInfo }
|
{ keyRequestInfo }
|
||||||
{ editButton }
|
{ editButton }
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
// The avatar goes after the event tile as it's absolutly positioned to be over the
|
||||||
|
// event tile line, so needs to be later in the DOM so it appears on top (this avoids
|
||||||
|
// the need for further z-indexing chaos)
|
||||||
|
}
|
||||||
|
{ avatar }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ScalarAuthClient from '../../../ScalarAuthClient';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import WidgetUtils from '../../../utils/WidgetUtils';
|
import WidgetUtils from '../../../utils/WidgetUtils';
|
||||||
|
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
||||||
|
|
||||||
const widgetType = 'm.stickerpicker';
|
const widgetType = 'm.stickerpicker';
|
||||||
|
|
||||||
|
@ -43,8 +44,6 @@ export default class Stickerpicker extends React.Component {
|
||||||
this._onResize = this._onResize.bind(this);
|
this._onResize = this._onResize.bind(this);
|
||||||
this._onFinished = this._onFinished.bind(this);
|
this._onFinished = this._onFinished.bind(this);
|
||||||
|
|
||||||
this._collectWidgetMessaging = this._collectWidgetMessaging.bind(this);
|
|
||||||
|
|
||||||
this.popoverWidth = 300;
|
this.popoverWidth = 300;
|
||||||
this.popoverHeight = 300;
|
this.popoverHeight = 300;
|
||||||
|
|
||||||
|
@ -166,17 +165,10 @@ export default class Stickerpicker extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_collectWidgetMessaging(widgetMessaging) {
|
|
||||||
this._appWidgetMessaging = widgetMessaging;
|
|
||||||
|
|
||||||
// Do this now instead of in componentDidMount because we might not have had the
|
|
||||||
// reference to widgetMessaging when mounting
|
|
||||||
this._sendVisibilityToWidget(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendVisibilityToWidget(visible) {
|
_sendVisibilityToWidget(visible) {
|
||||||
if (this._appWidgetMessaging && visible !== this._prevSentVisibility) {
|
const widgetMessaging = ActiveWidgetStore.getWidgetMessaging(this.state.stickerpickerWidget.id);
|
||||||
this._appWidgetMessaging.sendVisibility(visible);
|
if (widgetMessaging && visible !== this._prevSentVisibility) {
|
||||||
|
widgetMessaging.sendVisibility(visible);
|
||||||
this._prevSentVisibility = visible;
|
this._prevSentVisibility = visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,7 +209,6 @@ export default class Stickerpicker extends React.Component {
|
||||||
>
|
>
|
||||||
<PersistedElement persistKey="stickerPicker" style={{zIndex: STICKERPICKER_Z_INDEX}}>
|
<PersistedElement persistKey="stickerPicker" style={{zIndex: STICKERPICKER_Z_INDEX}}>
|
||||||
<AppTile
|
<AppTile
|
||||||
collectWidgetMessaging={this._collectWidgetMessaging}
|
|
||||||
id={stickerpickerWidget.id}
|
id={stickerpickerWidget.id}
|
||||||
url={stickerpickerWidget.content.url}
|
url={stickerpickerWidget.content.url}
|
||||||
name={stickerpickerWidget.content.name}
|
name={stickerpickerWidget.content.name}
|
||||||
|
|
84
src/stores/ActiveWidgetStore.js
Normal file
84
src/stores/ActiveWidgetStore.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores information about the widgets active in the app right now:
|
||||||
|
* * What widget is set to remain always-on-screen, if any
|
||||||
|
* Only one widget may be 'always on screen' at any one time.
|
||||||
|
* * Negotiated capabilities for active apps
|
||||||
|
*/
|
||||||
|
class ActiveWidgetStore {
|
||||||
|
constructor() {
|
||||||
|
this._persistentWidgetId = null;
|
||||||
|
|
||||||
|
// A list of negotiated capabilities for each widget, by ID
|
||||||
|
// {
|
||||||
|
// widgetId: [caps...],
|
||||||
|
// }
|
||||||
|
this._capsByWidgetId = {};
|
||||||
|
|
||||||
|
// A WidgetMessaging instance for each widget ID
|
||||||
|
this._widgetMessagingByWidgetId = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
setWidgetPersistence(widgetId, val) {
|
||||||
|
if (this._persistentWidgetId === widgetId && !val) {
|
||||||
|
this._persistentWidgetId = null;
|
||||||
|
} else if (this._persistentWidgetId !== widgetId && val) {
|
||||||
|
this._persistentWidgetId = widgetId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidgetPersistence(widgetId) {
|
||||||
|
return this._persistentWidgetId === widgetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWidgetCapabilities(widgetId, caps) {
|
||||||
|
this._capsByWidgetId[widgetId] = caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetHasCapability(widgetId, cap) {
|
||||||
|
return this._capsByWidgetId[widgetId] && this._capsByWidgetId[widgetId].includes(cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
delWidgetCapabilities(widgetId) {
|
||||||
|
delete this._capsByWidgetId[widgetId];
|
||||||
|
}
|
||||||
|
|
||||||
|
setWidgetMessaging(widgetId, wm) {
|
||||||
|
this._widgetMessagingByWidgetId[widgetId] = wm;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidgetMessaging(widgetId) {
|
||||||
|
return this._widgetMessagingByWidgetId[widgetId];
|
||||||
|
}
|
||||||
|
|
||||||
|
delWidgetMessaging(widgetId) {
|
||||||
|
if (this._widgetMessagingByWidgetId[widgetId]) {
|
||||||
|
try {
|
||||||
|
this._widgetMessagingByWidgetId[widgetId].stop();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to stop listening for widgetMessaging events', e.message);
|
||||||
|
}
|
||||||
|
delete this._widgetMessagingByWidgetId[widgetId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (global.singletonActiveWidgetStore === undefined) {
|
||||||
|
global.singletonActiveWidgetStore = new ActiveWidgetStore();
|
||||||
|
}
|
||||||
|
export default global.singletonActiveWidgetStore;
|
Loading…
Reference in a new issue