mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 21:24:59 +08:00
Merge pull request #2056 from matrix-org/dbkr/tiny_jitsi_follows_you_between_rooms
Implement always-on-screen capability for widgets
This commit is contained in:
commit
149a935594
@ -84,6 +84,7 @@
|
|||||||
"react-beautiful-dnd": "^4.0.1",
|
"react-beautiful-dnd": "^4.0.1",
|
||||||
"react-dom": "^15.6.0",
|
"react-dom": "^15.6.0",
|
||||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
||||||
|
"resize-observer-polyfill": "^1.5.0",
|
||||||
"slate": "0.33.4",
|
"slate": "0.33.4",
|
||||||
"slate-react": "^0.12.4",
|
"slate-react": "^0.12.4",
|
||||||
"slate-html-serializer": "^0.6.1",
|
"slate-html-serializer": "^0.6.1",
|
||||||
|
@ -54,6 +54,10 @@ limitations under the License.
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel .mx_AppTileFullWidth {
|
||||||
|
height: 132px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_LeftPanel .mx_RoomList_scrollbar {
|
.mx_LeftPanel .mx_RoomList_scrollbar {
|
||||||
order: 1;
|
order: 1;
|
||||||
|
|
||||||
|
@ -126,6 +126,12 @@ limitations under the License.
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AppTileBody_mini {
|
||||||
|
height: 132px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_AppTileBody iframe {
|
.mx_AppTileBody iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 280px;
|
height: 280px;
|
||||||
|
@ -164,6 +164,7 @@ export default class AppTile extends React.Component {
|
|||||||
PersistedElement.destroyElement(this._persistKey);
|
PersistedElement.destroyElement(this._persistKey);
|
||||||
ActiveWidgetStore.delWidgetMessaging(this.props.id);
|
ActiveWidgetStore.delWidgetMessaging(this.props.id);
|
||||||
ActiveWidgetStore.delWidgetCapabilities(this.props.id);
|
ActiveWidgetStore.delWidgetCapabilities(this.props.id);
|
||||||
|
ActiveWidgetStore.delRoomId(this.props.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,6 +344,7 @@ export default class AppTile extends React.Component {
|
|||||||
if (!ActiveWidgetStore.getWidgetMessaging(this.props.id)) {
|
if (!ActiveWidgetStore.getWidgetMessaging(this.props.id)) {
|
||||||
this._setupWidgetMessaging();
|
this._setupWidgetMessaging();
|
||||||
}
|
}
|
||||||
|
ActiveWidgetStore.setRoomId(this.props.id, this.props.room.roomId);
|
||||||
this.setState({loading: false});
|
this.setState({loading: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,6 +524,8 @@ export default class AppTile extends React.Component {
|
|||||||
// (see - https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-permissions-in-cross-origin-iframes and https://wicg.github.io/feature-policy/)
|
// (see - https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-permissions-in-cross-origin-iframes and https://wicg.github.io/feature-policy/)
|
||||||
const iframeFeatures = "microphone; camera; encrypted-media;";
|
const iframeFeatures = "microphone; camera; encrypted-media;";
|
||||||
|
|
||||||
|
const appTileBodyClass = 'mx_AppTileBody' + (this.props.miniMode ? '_mini ' : ' ');
|
||||||
|
|
||||||
if (this.props.show) {
|
if (this.props.show) {
|
||||||
const loadingElement = (
|
const loadingElement = (
|
||||||
<div className="mx_AppLoading_spinner_fadeIn">
|
<div className="mx_AppLoading_spinner_fadeIn">
|
||||||
@ -530,20 +534,20 @@ export default class AppTile extends React.Component {
|
|||||||
);
|
);
|
||||||
if (this.state.initialising) {
|
if (this.state.initialising) {
|
||||||
appTileBody = (
|
appTileBody = (
|
||||||
<div className={'mx_AppTileBody ' + (this.state.loading ? 'mx_AppLoading' : '')}>
|
<div className={appTileBodyClass + (this.state.loading ? 'mx_AppLoading' : '')}>
|
||||||
{ loadingElement }
|
{ loadingElement }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.state.hasPermissionToLoad == true) {
|
} else if (this.state.hasPermissionToLoad == true) {
|
||||||
if (this.isMixedContent()) {
|
if (this.isMixedContent()) {
|
||||||
appTileBody = (
|
appTileBody = (
|
||||||
<div className="mx_AppTileBody">
|
<div className={appTileBodyClass}>
|
||||||
<AppWarning errorMsg="Error - Mixed content" />
|
<AppWarning errorMsg="Error - Mixed content" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
appTileBody = (
|
appTileBody = (
|
||||||
<div className={'mx_AppTileBody ' + (this.state.loading ? 'mx_AppLoading' : '')}>
|
<div className={appTileBodyClass + (this.state.loading ? 'mx_AppLoading' : '')}>
|
||||||
{ this.state.loading && loadingElement }
|
{ this.state.loading && loadingElement }
|
||||||
{ /*
|
{ /*
|
||||||
The "is" attribute in the following iframe tag is needed in order to enable rendering of the
|
The "is" attribute in the following iframe tag is needed in order to enable rendering of the
|
||||||
@ -573,7 +577,7 @@ export default class AppTile extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||||
appTileBody = (
|
appTileBody = (
|
||||||
<div className="mx_AppTileBody">
|
<div className={appTileBodyClass}>
|
||||||
<AppPermission
|
<AppPermission
|
||||||
isRoomEncrypted={isRoomEncrypted}
|
isRoomEncrypted={isRoomEncrypted}
|
||||||
url={this.state.widgetUrl}
|
url={this.state.widgetUrl}
|
||||||
@ -686,6 +690,8 @@ AppTile.propTypes = {
|
|||||||
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
|
// 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.
|
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
|
||||||
fullWidth: PropTypes.bool,
|
fullWidth: PropTypes.bool,
|
||||||
|
// Optional. If set, renders a smaller view of the widget
|
||||||
|
miniMode: PropTypes.bool,
|
||||||
// UserId of the current user
|
// UserId of the current user
|
||||||
userId: PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
// UserId of the entity that added / modified the widget
|
// UserId of the entity that added / modified the widget
|
||||||
@ -738,4 +744,5 @@ AppTile.defaultProps = {
|
|||||||
handleMinimisePointerEvents: false,
|
handleMinimisePointerEvents: false,
|
||||||
whitelistCapabilities: [],
|
whitelistCapabilities: [],
|
||||||
userWidget: false,
|
userWidget: false,
|
||||||
|
miniMode: false,
|
||||||
};
|
};
|
||||||
|
@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const React = require('react');
|
import React from 'react';
|
||||||
const ReactDOM = require('react-dom');
|
import ReactDOM from 'react-dom';
|
||||||
const PropTypes = require('prop-types');
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import ResizeObserver from 'resize-observer-polyfill';
|
||||||
|
|
||||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||||
@ -62,6 +64,9 @@ export default class PersistedElement extends React.Component {
|
|||||||
super();
|
super();
|
||||||
this.collectChildContainer = this.collectChildContainer.bind(this);
|
this.collectChildContainer = this.collectChildContainer.bind(this);
|
||||||
this.collectChild = this.collectChild.bind(this);
|
this.collectChild = this.collectChild.bind(this);
|
||||||
|
this._onContainerResize = this._onContainerResize.bind(this);
|
||||||
|
|
||||||
|
this.resizeObserver = new ResizeObserver(this._onContainerResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,7 +88,13 @@ export default class PersistedElement extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
collectChildContainer(ref) {
|
collectChildContainer(ref) {
|
||||||
|
if (this.childContainer) {
|
||||||
|
this.resizeObserver.unobserve(this.childContainer);
|
||||||
|
}
|
||||||
this.childContainer = ref;
|
this.childContainer = ref;
|
||||||
|
if (ref) {
|
||||||
|
this.resizeObserver.observe(ref);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collectChild(ref) {
|
collectChild(ref) {
|
||||||
@ -101,6 +112,11 @@ export default class PersistedElement extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.updateChildVisibility(this.child, false);
|
this.updateChildVisibility(this.child, false);
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onContainerResize() {
|
||||||
|
this.updateChildPosition(this.child, this.childContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChild() {
|
updateChild() {
|
||||||
|
87
src/components/views/elements/PersistentApp.js
Normal file
87
src/components/views/elements/PersistentApp.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
|
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
||||||
|
import WidgetUtils from '../../../utils/WidgetUtils';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'PersistentApp',
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
roomId: RoomViewStore.getRoomId(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
if (this._roomStoreToken) {
|
||||||
|
this._roomStoreToken.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRoomViewStoreUpdate: function(payload) {
|
||||||
|
if (RoomViewStore.getRoomId() === this.state.roomId) return;
|
||||||
|
this.setState({
|
||||||
|
roomId: RoomViewStore.getRoomId(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
if (ActiveWidgetStore.getPersistentWidgetId()) {
|
||||||
|
const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(ActiveWidgetStore.getPersistentWidgetId());
|
||||||
|
if (this.state.roomId !== persistentWidgetInRoomId) {
|
||||||
|
const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId);
|
||||||
|
// get the widget data
|
||||||
|
const appEvent = WidgetUtils.getRoomWidgets(persistentWidgetInRoom).find((ev) => {
|
||||||
|
return ev.getStateKey() === ActiveWidgetStore.getPersistentWidgetId();
|
||||||
|
});
|
||||||
|
const app = WidgetUtils.makeAppConfig(
|
||||||
|
appEvent.getStateKey(), appEvent.getContent(), appEvent.sender, persistentWidgetInRoomId,
|
||||||
|
);
|
||||||
|
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, persistentWidgetInRoomId);
|
||||||
|
const AppTile = sdk.getComponent('elements.AppTile');
|
||||||
|
return <AppTile
|
||||||
|
key={app.id}
|
||||||
|
id={app.id}
|
||||||
|
url={app.url}
|
||||||
|
name={app.name}
|
||||||
|
type={app.type}
|
||||||
|
fullWidth={true}
|
||||||
|
room={persistentWidgetInRoom}
|
||||||
|
userId={MatrixClientPeg.get().credentials.userId}
|
||||||
|
show={true}
|
||||||
|
creatorUserId={app.creatorUserId}
|
||||||
|
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
|
||||||
|
waitForIframeLoad={app.waitForIframeLoad}
|
||||||
|
whitelistCapabilities={capWhitelist}
|
||||||
|
showDelete={false}
|
||||||
|
showMinimise={false}
|
||||||
|
miniMode={true}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -29,7 +29,6 @@ import ScalarAuthClient from '../../../ScalarAuthClient';
|
|||||||
import ScalarMessaging from '../../../ScalarMessaging';
|
import ScalarMessaging from '../../../ScalarMessaging';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import WidgetUtils from '../../../utils/WidgetUtils';
|
import WidgetUtils from '../../../utils/WidgetUtils';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
|
||||||
|
|
||||||
// The maximum number of widgets that can be added in a room
|
// The maximum number of widgets that can be added in a room
|
||||||
const MAX_WIDGETS = 2;
|
const MAX_WIDGETS = 2;
|
||||||
@ -107,55 +106,6 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a URI according to a set of template variables. Variables will be
|
|
||||||
* passed through encodeURIComponent.
|
|
||||||
* @param {string} pathTemplate The path with template variables e.g. '/foo/$bar'.
|
|
||||||
* @param {Object} variables The key/value pairs to replace the template
|
|
||||||
* variables with. E.g. { '$bar': 'baz' }.
|
|
||||||
* @return {string} The result of replacing all template variables e.g. '/foo/baz'.
|
|
||||||
*/
|
|
||||||
encodeUri: function(pathTemplate, variables) {
|
|
||||||
for (const key in variables) {
|
|
||||||
if (!variables.hasOwnProperty(key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pathTemplate = pathTemplate.replace(
|
|
||||||
key, encodeURIComponent(variables[key]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return pathTemplate;
|
|
||||||
},
|
|
||||||
|
|
||||||
_initAppConfig: function(appId, app, sender) {
|
|
||||||
const user = MatrixClientPeg.get().getUser(this.props.userId);
|
|
||||||
const params = {
|
|
||||||
'$matrix_user_id': this.props.userId,
|
|
||||||
'$matrix_room_id': this.props.room.roomId,
|
|
||||||
'$matrix_display_name': user ? user.displayName : this.props.userId,
|
|
||||||
'$matrix_avatar_url': user ? MatrixClientPeg.get().mxcUrlToHttp(user.avatarUrl) : '',
|
|
||||||
|
|
||||||
// TODO: Namespace themes through some standard
|
|
||||||
'$theme': SettingsStore.getValue("theme"),
|
|
||||||
};
|
|
||||||
|
|
||||||
app.id = appId;
|
|
||||||
app.name = app.name || app.type;
|
|
||||||
|
|
||||||
if (app.data) {
|
|
||||||
Object.keys(app.data).forEach((key) => {
|
|
||||||
params['$' + key] = app.data[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
app.waitForIframeLoad = (app.data.waitForIframeLoad === 'false' ? false : true);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.url = this.encodeUri(app.url, params);
|
|
||||||
app.creatorUserId = (sender && sender.userId) ? sender.userId : null;
|
|
||||||
|
|
||||||
return app;
|
|
||||||
},
|
|
||||||
|
|
||||||
onRoomStateEvents: function(ev, state) {
|
onRoomStateEvents: function(ev, state) {
|
||||||
if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') {
|
if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') {
|
||||||
return;
|
return;
|
||||||
@ -165,7 +115,7 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
_getApps: function() {
|
_getApps: function() {
|
||||||
return WidgetUtils.getRoomWidgets(this.props.room).map((ev) => {
|
return WidgetUtils.getRoomWidgets(this.props.room).map((ev) => {
|
||||||
return this._initAppConfig(ev.getStateKey(), ev.getContent(), ev.sender);
|
return WidgetUtils.makeAppConfig(ev.getStateKey(), ev.getContent(), ev.sender, this.props.room.roomId);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -213,15 +163,8 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", this.props.room.room_id);
|
|
||||||
|
|
||||||
const apps = this.state.apps.map((app, index, arr) => {
|
const apps = this.state.apps.map((app, index, arr) => {
|
||||||
const capWhitelist = enableScreenshots ? ["m.capability.screenshot"] : [];
|
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId);
|
||||||
|
|
||||||
// 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}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 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.
|
||||||
@ -92,7 +92,8 @@ module.exports = React.createClass({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
const PersistentApp = sdk.getComponent('elements.PersistentApp');
|
||||||
|
return <PersistentApp />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,6 +32,9 @@ class ActiveWidgetStore {
|
|||||||
|
|
||||||
// A WidgetMessaging instance for each widget ID
|
// A WidgetMessaging instance for each widget ID
|
||||||
this._widgetMessagingByWidgetId = {};
|
this._widgetMessagingByWidgetId = {};
|
||||||
|
|
||||||
|
// What room ID each widget is associated with (if it's a room widget)
|
||||||
|
this._roomIdByWidgetId = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
setWidgetPersistence(widgetId, val) {
|
setWidgetPersistence(widgetId, val) {
|
||||||
@ -46,6 +49,10 @@ class ActiveWidgetStore {
|
|||||||
return this._persistentWidgetId === widgetId;
|
return this._persistentWidgetId === widgetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPersistentWidgetId() {
|
||||||
|
return this._persistentWidgetId;
|
||||||
|
}
|
||||||
|
|
||||||
setWidgetCapabilities(widgetId, caps) {
|
setWidgetCapabilities(widgetId, caps) {
|
||||||
this._capsByWidgetId[widgetId] = caps;
|
this._capsByWidgetId[widgetId] = caps;
|
||||||
}
|
}
|
||||||
@ -76,6 +83,18 @@ class ActiveWidgetStore {
|
|||||||
delete this._widgetMessagingByWidgetId[widgetId];
|
delete this._widgetMessagingByWidgetId[widgetId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRoomId(widgetId) {
|
||||||
|
return this._roomIdByWidgetId[widgetId];
|
||||||
|
}
|
||||||
|
|
||||||
|
setRoomId(widgetId, roomId) {
|
||||||
|
this._roomIdByWidgetId[widgetId] = roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
delRoomId(widgetId) {
|
||||||
|
delete this._roomIdByWidgetId[widgetId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.singletonActiveWidgetStore === undefined) {
|
if (global.singletonActiveWidgetStore === undefined) {
|
||||||
|
@ -19,6 +19,27 @@ import MatrixClientPeg from '../MatrixClientPeg';
|
|||||||
import SdkConfig from "../SdkConfig";
|
import SdkConfig from "../SdkConfig";
|
||||||
import dis from '../dispatcher';
|
import dis from '../dispatcher';
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a URI according to a set of template variables. Variables will be
|
||||||
|
* passed through encodeURIComponent.
|
||||||
|
* @param {string} pathTemplate The path with template variables e.g. '/foo/$bar'.
|
||||||
|
* @param {Object} variables The key/value pairs to replace the template
|
||||||
|
* variables with. E.g. { '$bar': 'baz' }.
|
||||||
|
* @return {string} The result of replacing all template variables e.g. '/foo/baz'.
|
||||||
|
*/
|
||||||
|
function encodeUri(pathTemplate, variables) {
|
||||||
|
for (const key in variables) {
|
||||||
|
if (!variables.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pathTemplate = pathTemplate.replace(
|
||||||
|
key, encodeURIComponent(variables[key]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return pathTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
export default class WidgetUtils {
|
export default class WidgetUtils {
|
||||||
/* Returns true if user is able to send state events to modify widgets in this room
|
/* Returns true if user is able to send state events to modify widgets in this room
|
||||||
@ -324,4 +345,47 @@ export default class WidgetUtils {
|
|||||||
});
|
});
|
||||||
return client.setAccountData('m.widgets', userWidgets);
|
return client.setAccountData('m.widgets', userWidgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static makeAppConfig(appId, app, sender, roomId) {
|
||||||
|
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
|
const user = MatrixClientPeg.get().getUser(myUserId);
|
||||||
|
const params = {
|
||||||
|
'$matrix_user_id': myUserId,
|
||||||
|
'$matrix_room_id': roomId,
|
||||||
|
'$matrix_display_name': user ? user.displayName : myUserId,
|
||||||
|
'$matrix_avatar_url': user ? MatrixClientPeg.get().mxcUrlToHttp(user.avatarUrl) : '',
|
||||||
|
|
||||||
|
// TODO: Namespace themes through some standard
|
||||||
|
'$theme': SettingsStore.getValue("theme"),
|
||||||
|
};
|
||||||
|
|
||||||
|
app.id = appId;
|
||||||
|
app.name = app.name || app.type;
|
||||||
|
|
||||||
|
if (app.data) {
|
||||||
|
Object.keys(app.data).forEach((key) => {
|
||||||
|
params['$' + key] = app.data[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
app.waitForIframeLoad = (app.data.waitForIframeLoad === 'false' ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.url = encodeUri(app.url, params);
|
||||||
|
app.creatorUserId = (sender && sender.userId) ? sender.userId : null;
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCapWhitelistForAppTypeInRoomId(appType, roomId) {
|
||||||
|
const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", roomId);
|
||||||
|
|
||||||
|
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 (appType == 'jitsi') capWhitelist.push("m.always_on_screen");
|
||||||
|
|
||||||
|
return capWhitelist;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user