2019-08-15 01:15:49 +03:00
/ *
2017-06-28 14:23:33 +03:00
Copyright 2017 Vector Creations Ltd
2018-07-11 20:07:32 +03:00
Copyright 2018 New Vector Ltd
2019-08-15 01:15:49 +03:00
Copyright 2019 Michael Telatynski < 7 t3chguy @ gmail . com >
2020-04-01 12:00:33 +03:00
Copyright 2020 The Matrix . org Foundation C . I . C .
2017-05-22 14:34:27 +03:00
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 .
* /
2017-07-12 16:16:47 +03:00
import url from 'url' ;
2019-11-28 21:16:59 +03:00
import React , { createRef } from 'react' ;
2017-12-26 04:03:18 +03:00
import PropTypes from 'prop-types' ;
2019-12-21 00:13:46 +03:00
import { MatrixClientPeg } from '../../../MatrixClientPeg' ;
2019-02-01 13:02:02 +03:00
import AccessibleButton from './AccessibleButton' ;
import { _t } from '../../../languageHandler' ;
2017-07-26 13:28:43 +03:00
import AppPermission from './AppPermission' ;
2017-08-01 19:29:29 +03:00
import AppWarning from './AppWarning' ;
2020-04-25 00:14:41 +03:00
import Spinner from './Spinner' ;
2020-05-14 05:41:41 +03:00
import dis from '../../../dispatcher/dispatcher' ;
2018-07-11 20:07:32 +03:00
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore' ;
2019-02-01 13:02:02 +03:00
import classNames from 'classnames' ;
2020-07-28 20:53:43 +03:00
import SettingsStore from "../../../settings/SettingsStore" ;
2020-10-09 10:42:21 +03:00
import { aboveLeftOf , ContextMenuButton } from "../../structures/ContextMenu" ;
import PersistedElement , { getPersistKey } from "./PersistedElement" ;
2020-04-10 00:11:57 +03:00
import { WidgetType } from "../../../widgets/WidgetType" ;
2020-09-29 23:14:51 +03:00
import { StopGapWidget } from "../../../stores/widgets/StopGapWidget" ;
2020-10-01 05:09:23 +03:00
import { ElementWidgetActions } from "../../../stores/widgets/ElementWidgetActions" ;
2020-10-01 05:20:31 +03:00
import { MatrixCapabilities } from "matrix-widget-api" ;
2020-10-12 11:51:49 +03:00
import RoomWidgetContextMenu from "../context_menus/WidgetContextMenu" ;
2020-10-12 15:16:55 +03:00
import WidgetAvatar from "../avatars/WidgetAvatar" ;
2021-03-09 05:59:41 +03:00
import { replaceableComponent } from "../../../utils/replaceableComponent" ;
2020-04-01 12:00:33 +03:00
2021-03-09 05:59:41 +03:00
@ replaceableComponent ( "views.elements.AppTile" )
2018-01-11 14:49:46 +03:00
export default class AppTile extends React . Component {
2018-01-11 15:33:02 +03:00
constructor ( props ) {
super ( props ) ;
2018-07-11 20:07:32 +03:00
// The key used for PersistedElement
2020-10-09 10:42:21 +03:00
this . _persistKey = getPersistKey ( this . props . app . id ) ;
2021-06-08 14:40:38 +03:00
try {
this . _sgWidget = new StopGapWidget ( this . props ) ;
this . _sgWidget . on ( "preparing" , this . _onWidgetPrepared ) ;
this . _sgWidget . on ( "ready" , this . _onWidgetReady ) ;
} catch ( e ) {
console . log ( "Failed to construct widget" , e ) ;
this . _sgWidget = null ;
}
2020-10-01 19:03:52 +03:00
this . iframe = null ; // ref to the iframe (callback style)
2018-07-11 20:07:32 +03:00
2018-01-11 16:20:58 +03:00
this . state = this . _getNewState ( props ) ;
2019-11-28 21:16:59 +03:00
this . _contextMenuButton = createRef ( ) ;
2020-10-09 10:42:21 +03:00
this . _allowedWidgetsWatchRef = SettingsStore . watchSetting ( "allowedWidgets" , null , this . onAllowedWidgetsChange ) ;
2018-01-11 15:33:02 +03:00
}
2020-10-09 10:42:21 +03:00
// This is a function to make the impact of calling SettingsStore slightly less
hasPermissionToLoad = ( props ) => {
if ( this . _usingLocalWidget ( ) ) return true ;
2020-10-21 14:48:51 +03:00
if ( ! props . room ) return true ; // user widgets always have permissions
2020-10-09 10:42:21 +03:00
const currentlyAllowedWidgets = SettingsStore . getValue ( "allowedWidgets" , props . room . roomId ) ;
2020-10-12 11:51:49 +03:00
if ( currentlyAllowedWidgets [ props . app . eventId ] === undefined ) {
return props . userId === props . creatorUserId ;
}
2020-10-09 10:42:21 +03:00
return ! ! currentlyAllowedWidgets [ props . app . eventId ] ;
} ;
2017-10-31 19:31:46 +03:00
/ * *
2017-11-10 14:50:14 +03:00
* Set initial component state when the App wUrl ( widget URL ) is being updated .
* Component props * must * be passed ( rather than relying on this . props ) .
2017-11-08 20:44:54 +03:00
* @ param { Object } newProps The new properties of the component
2017-10-31 19:31:46 +03:00
* @ return { Object } Updated component state to be set with setState
* /
2017-11-10 13:17:55 +03:00
_getNewState ( newProps ) {
2017-11-02 21:33:11 +03:00
return {
2017-12-29 01:27:12 +03:00
initialising : true , // True while we are mangling the widget URL
2018-07-11 20:07:32 +03:00
// True while the iframe content is loading
loading : this . props . waitForIframeLoad && ! PersistedElement . isMounted ( this . _persistKey ) ,
2017-11-10 14:50:14 +03:00
// 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
2020-10-12 11:51:49 +03:00
hasPermissionToLoad : this . hasPermissionToLoad ( newProps ) ,
2017-11-02 21:33:11 +03:00
error : null ,
2017-12-08 18:12:48 +03:00
widgetPageTitle : newProps . widgetPageTitle ,
2019-11-28 21:16:59 +03:00
menuDisplayed : false ,
2017-11-02 21:33:11 +03:00
} ;
2018-01-11 14:49:46 +03:00
}
2017-10-27 19:49:14 +03:00
2020-10-09 10:42:21 +03:00
onAllowedWidgetsChange = ( ) => {
2020-10-12 11:51:49 +03:00
const hasPermissionToLoad = this . hasPermissionToLoad ( this . props ) ;
2020-10-09 10:42:21 +03:00
if ( this . state . hasPermissionToLoad && ! hasPermissionToLoad ) {
// Force the widget to be non-persistent (able to be deleted/forgotten)
ActiveWidgetStore . destroyPersistentWidget ( this . props . app . id ) ;
PersistedElement . destroyElement ( this . _persistKey ) ;
2021-06-08 14:40:38 +03:00
if ( this . _sgWidget ) this . _sgWidget . stop ( ) ;
2020-10-09 10:42:21 +03:00
}
2020-10-09 10:45:11 +03:00
this . setState ( { hasPermissionToLoad } ) ;
2020-10-09 10:42:21 +03:00
} ;
2017-10-27 19:49:14 +03:00
isMixedContent ( ) {
2017-08-01 19:29:29 +03:00
const parentContentProtocol = window . location . protocol ;
2020-04-01 12:00:33 +03:00
const u = url . parse ( this . props . app . url ) ;
2017-08-01 19:29:29 +03:00
const childContentProtocol = u . protocol ;
2017-08-01 19:49:41 +03:00
if ( parentContentProtocol === 'https:' && childContentProtocol !== 'https:' ) {
2017-08-02 19:05:46 +03:00
console . warn ( "Refusing to load mixed-content app:" ,
2021-04-27 18:23:27 +03:00
parentContentProtocol , childContentProtocol , window . location , this . props . app . url ) ;
2017-08-01 19:29:29 +03:00
return true ;
}
return false ;
2018-01-11 14:49:46 +03:00
}
2017-08-01 19:29:29 +03:00
2020-03-31 23:14:17 +03:00
componentDidMount ( ) {
2019-07-19 21:04:48 +03:00
// Only fetch IM token on mount if we're showing and have permission to load
2021-06-08 14:40:38 +03:00
if ( this . _sgWidget && this . state . hasPermissionToLoad ) {
2020-09-29 23:14:51 +03:00
this . _startWidget ( ) ;
2019-07-19 21:04:48 +03:00
}
2017-10-27 15:47:51 +03:00
2018-01-18 15:02:45 +03:00
// Widget action listeners
2018-07-11 20:07:32 +03:00
this . dispatcherRef = dis . register ( this . _onAction ) ;
2018-01-11 14:49:46 +03:00
}
2017-12-15 19:56:02 +03:00
2018-01-18 15:02:45 +03:00
componentWillUnmount ( ) {
// Widget action listeners
2020-03-31 23:05:56 +03:00
if ( this . dispatcherRef ) dis . unregister ( this . dispatcherRef ) ;
2018-01-18 15:02:45 +03:00
2018-07-11 20:07:32 +03:00
// if it's not remaining on screen, get rid of the PersistedElement container
2020-04-01 12:00:33 +03:00
if ( ! ActiveWidgetStore . getWidgetPersistence ( this . props . app . id ) ) {
ActiveWidgetStore . destroyPersistentWidget ( this . props . app . id ) ;
2018-08-01 17:01:11 +03:00
PersistedElement . destroyElement ( this . _persistKey ) ;
2018-07-11 20:07:32 +03:00
}
2019-08-10 01:05:05 +03:00
2020-09-29 23:14:51 +03:00
if ( this . _sgWidget ) {
this . _sgWidget . stop ( ) ;
2019-10-29 20:49:15 +03:00
}
2020-10-09 10:42:21 +03:00
SettingsStore . unwatchSetting ( this . _allowedWidgetsWatchRef ) ;
2020-09-29 23:14:51 +03:00
}
2019-10-29 20:49:15 +03:00
2020-09-29 23:14:51 +03:00
_resetWidget ( newProps ) {
if ( this . _sgWidget ) {
this . _sgWidget . stop ( ) ;
2017-10-27 15:47:51 +03:00
}
2021-06-08 14:40:38 +03:00
try {
this . _sgWidget = new StopGapWidget ( newProps ) ;
this . _sgWidget . on ( "preparing" , this . _onWidgetPrepared ) ;
this . _sgWidget . on ( "ready" , this . _onWidgetReady ) ;
this . _startWidget ( ) ;
} catch ( e ) {
console . log ( "Failed to construct widget" , e ) ;
this . _sgWidget = null ;
}
2020-09-29 23:14:51 +03:00
}
2017-12-06 00:41:44 +03:00
2020-09-29 23:14:51 +03:00
_startWidget ( ) {
this . _sgWidget . prepare ( ) . then ( ( ) => {
2020-09-29 23:33:46 +03:00
this . setState ( { initialising : false } ) ;
2017-07-06 11:28:48 +03:00
} ) ;
2018-01-11 14:49:46 +03:00
}
2017-09-04 10:31:25 +03:00
2020-09-29 23:33:46 +03:00
_iframeRefChange = ( ref ) => {
2020-10-01 19:03:52 +03:00
this . iframe = ref ;
2020-09-29 23:33:46 +03:00
if ( ref ) {
2021-06-08 14:40:38 +03:00
if ( this . _sgWidget ) this . _sgWidget . start ( ref ) ;
2020-09-29 23:33:46 +03:00
} else {
this . _resetWidget ( this . props ) ;
}
} ;
2017-09-04 10:31:25 +03:00
2020-04-01 23:35:39 +03:00
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
2020-04-01 23:45:54 +03:00
UNSAFE _componentWillReceiveProps ( nextProps ) { // eslint-disable-line camelcase
2020-04-01 12:00:33 +03:00
if ( nextProps . app . url !== this . props . app . url ) {
2017-11-10 13:17:55 +03:00
this . _getNewState ( nextProps ) ;
2019-07-19 21:04:48 +03:00
if ( this . state . hasPermissionToLoad ) {
2020-10-09 10:42:21 +03:00
this . _resetWidget ( nextProps ) ;
2019-07-19 21:04:48 +03:00
}
2020-04-09 23:31:46 +03:00
}
if ( nextProps . widgetPageTitle !== this . props . widgetPageTitle ) {
2017-12-13 13:14:26 +03:00
this . setState ( {
widgetPageTitle : nextProps . widgetPageTitle ,
} ) ;
2017-10-27 19:49:14 +03:00
}
2018-01-11 14:49:46 +03:00
}
2017-10-27 15:47:51 +03:00
2020-04-09 23:31:46 +03:00
/ * *
* Ends all widget interaction , such as cancelling calls and disabling webcams .
* @ private
2020-04-18 14:53:48 +03:00
* @ returns { Promise < * > } Resolves when the widget is terminated , or timeout passed .
2020-04-09 23:31:46 +03:00
* /
2020-10-01 05:49:31 +03:00
async _endWidgetActions ( ) { // widget migration dev note: async to maintain signature
2020-09-29 23:14:51 +03:00
// 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
// stream open, even after death. See https://github.com/vector-im/element-web/issues/7351
2020-10-01 19:03:52 +03:00
if ( this . iframe ) {
2020-09-29 23:14:51 +03:00
// In practice we could just do `+= ''` to trick the browser
// into thinking the URL changed, however I can foresee this
// being optimized out by a browser. Instead, we'll just point
// the iframe at a page that is reasonably safe to use in the
// event the iframe doesn't wink away.
// This is relative to where the Element instance is located.
2020-10-01 19:03:52 +03:00
this . iframe . src = 'about:blank' ;
2020-04-09 23:31:46 +03:00
}
2020-10-13 23:55:44 +03:00
if ( WidgetType . JITSI . matches ( this . props . app . type ) ) {
dis . dispatch ( { action : 'hangup_conference' } ) ;
}
2020-04-09 23:31:46 +03:00
2020-09-29 23:14:51 +03:00
// Delete the widget from the persisted store for good measure.
PersistedElement . destroyElement ( this . _persistKey ) ;
2020-04-09 23:31:46 +03:00
2021-06-08 14:40:38 +03:00
if ( this . _sgWidget ) this . _sgWidget . stop ( { forceDestroy : true } ) ;
2020-04-09 23:31:46 +03:00
}
2020-10-09 00:35:22 +03:00
_onWidgetPrepared = ( ) => {
2018-05-14 13:42:38 +03:00
this . setState ( { loading : false } ) ;
2020-10-09 00:35:22 +03:00
} ;
2020-03-24 18:55:54 +03:00
2020-10-09 00:35:22 +03:00
_onWidgetReady = ( ) => {
2020-09-29 23:14:51 +03:00
if ( WidgetType . JITSI . matches ( this . props . app . type ) ) {
2020-10-01 05:09:23 +03:00
this . _sgWidget . widgetApi . transport . send ( ElementWidgetActions . ClientReady , { } ) ;
2020-09-29 23:14:51 +03:00
}
} ;
2017-10-27 19:49:14 +03:00
2020-10-09 10:42:21 +03:00
_onAction = payload => {
2020-04-01 12:00:33 +03:00
if ( payload . widgetId === this . props . app . id ) {
2018-01-04 21:58:55 +03:00
switch ( payload . action ) {
2018-03-12 16:56:02 +03:00
case 'm.sticker' :
2020-10-01 05:20:31 +03:00
if ( this . _sgWidget . widgetApi . hasCapability ( MatrixCapabilities . StickerSending ) ) {
2020-09-08 12:19:51 +03:00
dis . dispatch ( { action : 'post_sticker_message' , data : payload . data } ) ;
} else {
console . warn ( 'Ignoring sticker message. Invalid capability' ) ;
}
break ;
2018-01-04 21:58:55 +03:00
}
}
2020-10-09 10:42:21 +03:00
} ;
2018-01-04 21:41:47 +03:00
2020-10-09 10:42:21 +03:00
_grantWidgetPermission = ( ) => {
2019-11-19 03:56:33 +03:00
const roomId = this . props . room . roomId ;
2020-04-01 12:00:33 +03:00
console . info ( "Granting permission for widget to load: " + this . props . app . eventId ) ;
2019-11-19 03:56:33 +03:00
const current = SettingsStore . getValue ( "allowedWidgets" , roomId ) ;
2020-04-01 12:00:33 +03:00
current [ this . props . app . eventId ] = true ;
2021-01-14 20:30:25 +03:00
const level = SettingsStore . firstSupportedLevel ( "allowedWidgets" ) ;
SettingsStore . setValue ( "allowedWidgets" , roomId , level , current ) . then ( ( ) => {
2019-11-19 03:56:33 +03:00
this . setState ( { hasPermissionToLoad : true } ) ;
// Fetch a token for the integration manager, now that we're allowed to
2020-09-29 23:14:51 +03:00
this . _startWidget ( ) ;
2019-11-19 03:56:33 +03:00
} ) . catch ( err => {
console . error ( err ) ;
// We don't really need to do anything about this - the user will just hit the button again.
} ) ;
2020-10-09 10:42:21 +03:00
} ;
2017-05-22 20:00:17 +03:00
2017-10-27 19:49:14 +03:00
formatAppTileName ( ) {
2017-06-28 12:27:06 +03:00
let appTileName = "No name" ;
2020-04-01 12:00:33 +03:00
if ( this . props . app . name && this . props . app . name . trim ( ) ) {
appTileName = this . props . app . name . trim ( ) ;
2017-06-28 12:27:06 +03:00
}
return appTileName ;
2018-01-11 14:49:46 +03:00
}
2017-06-28 12:27:06 +03:00
2020-09-09 17:35:05 +03:00
/ * *
* Whether we ' re using a local version of the widget rather than loading the
* actual widget URL
2020-09-09 17:42:25 +03:00
* @ returns { bool } true If using a local version of the widget
2020-09-09 17:35:05 +03:00
* /
_usingLocalWidget ( ) {
return WidgetType . JITSI . matches ( this . props . app . type ) ;
}
2018-02-07 17:44:01 +03:00
_getTileTitle ( ) {
const name = this . formatAppTileName ( ) ;
const titleSpacer = < span > & nbsp ; - & nbsp ; < / s p a n > ;
let title = '' ;
2020-09-29 23:14:51 +03:00
if ( this . state . widgetPageTitle && this . state . widgetPageTitle !== this . formatAppTileName ( ) ) {
2018-02-07 17:44:01 +03:00
title = this . state . widgetPageTitle ;
}
return (
< span >
2020-10-12 15:16:55 +03:00
< WidgetAvatar app = { this . props . app } / >
2018-02-07 17:44:01 +03:00
< b > { name } < / b >
< span > { title ? titleSpacer : '' } { title } < / s p a n >
< / s p a n >
) ;
}
2020-10-09 10:42:21 +03:00
// TODO replace with full screen interactions
_onPopoutWidgetClick = ( ) => {
2020-04-11 15:23:32 +03:00
// 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).
2020-10-09 10:42:21 +03:00
if ( WidgetType . JITSI . matches ( this . props . app . type ) ) {
2020-04-11 15:23:32 +03:00
this . _endWidgetActions ( ) . then ( ( ) => {
2020-10-01 19:03:52 +03:00
if ( this . iframe ) {
2020-04-11 15:23:32 +03:00
// Reload iframe
2020-10-01 19:03:52 +03:00
this . iframe . src = this . _sgWidget . embedUrl ;
2020-04-11 15:23:32 +03:00
this . setState ( { } ) ;
}
} ) ;
}
2018-04-25 14:49:30 +03:00
// Using Object.assign workaround as the following opens in a new window instead of a new tab.
2020-04-01 12:00:33 +03:00
// window.open(this._getPopoutUrl(), '_blank', 'noopener=yes');
2018-04-25 14:49:30 +03:00
Object . assign ( document . createElement ( 'a' ) ,
2020-09-29 23:14:51 +03:00
{ target : '_blank' , href : this . _sgWidget . popoutUrl , rel : 'noreferrer noopener' } ) . click ( ) ;
2020-10-09 10:42:21 +03:00
} ;
2018-05-22 21:14:54 +03:00
2019-11-28 21:16:59 +03:00
_onContextMenuClick = ( ) => {
this . setState ( { menuDisplayed : true } ) ;
} ;
2019-11-21 05:17:42 +03:00
2019-11-28 21:16:59 +03:00
_closeContextMenu = ( ) => {
this . setState ( { menuDisplayed : false } ) ;
2019-11-21 05:17:42 +03:00
} ;
2017-10-27 19:49:14 +03:00
render ( ) {
2017-07-06 11:28:48 +03:00
let appTileBody ;
2017-07-13 02:27:03 +03:00
2017-07-26 13:28:43 +03:00
// Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
2019-11-21 05:17:42 +03:00
// because that would allow the iframe to programmatically remove the sandbox attribute, but
2020-08-03 18:02:26 +03:00
// this would only be for content hosted on the same origin as the element client: anything
2017-07-26 13:28:43 +03:00
// hosted on the same origin as the client will get the same access as if you clicked
// a link to it.
const sandboxFlags = "allow-forms allow-popups allow-popups-to-escape-sandbox " +
2017-07-28 13:14:04 +03:00
"allow-same-origin allow-scripts allow-presentation" ;
2017-07-26 13:28:43 +03:00
2018-02-22 02:10:08 +03:00
// Additional iframe feature pemissions
// (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/)
2021-03-08 11:08:02 +03:00
const iframeFeatures = "microphone; camera; encrypted-media; autoplay; display-capture; clipboard-write;" ;
2018-02-22 02:10:08 +03:00
2018-07-12 20:43:49 +03:00
const appTileBodyClass = 'mx_AppTileBody' + ( this . props . miniMode ? '_mini ' : ' ' ) ;
2021-03-05 20:52:51 +03:00
const appTileBodyStyles = { } ;
if ( this . props . pointerEvents ) {
appTileBodyStyles [ 'pointer-events' ] = this . props . pointerEvents ;
}
2018-07-12 20:43:49 +03:00
2020-10-09 10:42:21 +03:00
const loadingElement = (
< div className = "mx_AppLoading_spinner_fadeIn" >
< Spinner message = { _t ( "Loading..." ) } / >
< / d i v >
) ;
2021-06-08 14:40:38 +03:00
if ( this . _sgWidget === null ) {
appTileBody = (
< div className = { appTileBodyClass } style = { appTileBodyStyles } >
< AppWarning errorMsg = "Error loading Widget" / >
< / d i v >
) ;
} else if ( ! this . state . hasPermissionToLoad ) {
2020-10-21 14:48:51 +03:00
// only possible for room widgets, can assert this.props.room here
2020-10-09 10:42:21 +03:00
const isEncrypted = MatrixClientPeg . get ( ) . isRoomEncrypted ( this . props . room . roomId ) ;
appTileBody = (
2021-03-05 20:52:51 +03:00
< div className = { appTileBodyClass } style = { appTileBodyStyles } >
2020-10-09 10:42:21 +03:00
< AppPermission
roomId = { this . props . room . roomId }
creatorUserId = { this . props . creatorUserId }
url = { this . _sgWidget . embedUrl }
isRoomEncrypted = { isEncrypted }
onPermissionGranted = { this . _grantWidgetPermission }
/ >
2017-11-08 23:38:31 +03:00
< / d i v >
) ;
2020-10-09 10:42:21 +03:00
} else if ( this . state . initialising ) {
appTileBody = (
2021-03-05 20:52:51 +03:00
< div className = { appTileBodyClass + ( this . state . loading ? 'mx_AppLoading' : '' ) } style = { appTileBodyStyles } >
2020-10-09 10:42:21 +03:00
{ loadingElement }
< / d i v >
) ;
} else {
if ( this . isMixedContent ( ) ) {
2019-07-19 21:04:48 +03:00
appTileBody = (
2021-03-05 20:52:51 +03:00
< div className = { appTileBodyClass } style = { appTileBodyStyles } >
2020-10-09 10:42:21 +03:00
< AppWarning errorMsg = "Error - Mixed content" / >
2019-07-19 21:04:48 +03:00
< / d i v >
) ;
2020-10-09 10:42:21 +03:00
} else {
2018-05-09 17:48:53 +03:00
appTileBody = (
2021-03-05 20:52:51 +03:00
< div className = { appTileBodyClass + ( this . state . loading ? 'mx_AppLoading' : '' ) } style = { appTileBodyStyles } >
2020-10-09 10:42:21 +03:00
{ this . state . loading && loadingElement }
< iframe
allow = { iframeFeatures }
ref = { this . _iframeRefChange }
src = { this . _sgWidget . embedUrl }
allowFullScreen = { true }
sandbox = { sandboxFlags }
/ >
2018-05-09 17:48:53 +03:00
< / d i v >
) ;
2020-11-21 02:53:15 +03:00
2020-11-27 01:09:08 +03:00
if ( ! this . props . userWidget ) {
// All room widgets can theoretically be allowed to remain on screen, so we
// wrap them all in a PersistedElement from the get-go. If we wait, the iframe
// will be re-mounted later, which means the widget has to start over, which is
// bad.
// Also wrap the PersistedElement in a div to fix the height, otherwise
// AppTile's border is in the wrong place
appTileBody = < div className = "mx_AppTile_persistedWrapper" >
< PersistedElement persistKey = { this . _persistKey } >
{ appTileBody }
< / P e r s i s t e d E l e m e n t >
< / d i v > ;
}
2017-07-12 16:16:47 +03:00
}
2017-07-06 11:28:48 +03:00
}
2017-07-14 13:17:59 +03:00
2020-04-20 23:05:50 +03:00
let appTileClasses ;
2018-07-23 17:08:17 +03:00
if ( this . props . miniMode ) {
2020-04-20 23:05:50 +03:00
appTileClasses = { mx _AppTile _mini : true } ;
2018-07-23 17:08:17 +03:00
} else if ( this . props . fullWidth ) {
2020-04-20 23:05:50 +03:00
appTileClasses = { mx _AppTileFullWidth : true } ;
2018-07-23 17:08:17 +03:00
} else {
2020-04-20 23:05:50 +03:00
appTileClasses = { mx _AppTile : true } ;
2018-07-23 17:08:17 +03:00
}
2020-04-20 23:05:50 +03:00
appTileClasses = classNames ( appTileClasses ) ;
2018-07-23 17:08:17 +03:00
2019-11-28 21:16:59 +03:00
let contextMenu ;
if ( this . state . menuDisplayed ) {
contextMenu = (
2020-10-09 10:42:21 +03:00
< RoomWidgetContextMenu
{ ... aboveLeftOf ( this . _contextMenuButton . current . getBoundingClientRect ( ) , null ) }
app = { this . props . app }
onFinished = { this . _closeContextMenu }
showUnpin = { ! this . props . userWidget }
2020-10-12 11:51:49 +03:00
userWidget = { this . props . userWidget }
2020-10-09 10:42:21 +03:00
/ >
2019-11-28 21:16:59 +03:00
) ;
}
return < React . Fragment >
2020-04-20 23:05:50 +03:00
< div className = { appTileClasses } id = { this . props . app . id } >
2018-01-11 16:20:58 +03:00
{ this . props . showMenubar &&
2020-10-09 10:42:21 +03:00
< div className = "mx_AppTileMenuBar" >
2018-03-08 20:20:42 +03:00
< span className = "mx_AppTileMenuBarTitle" style = { { pointerEvents : ( this . props . handleMinimisePointerEvents ? 'all' : false ) } } >
2018-02-07 17:44:01 +03:00
{ this . props . showTitle && this . _getTileTitle ( ) }
2017-12-05 21:18:51 +03:00
< / s p a n >
2017-05-22 14:34:27 +03:00
< span className = "mx_AppTileMenuBarWidgets" >
2019-02-01 13:02:02 +03:00
{ this . props . showPopout && < AccessibleButton
className = "mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
2018-04-25 14:49:30 +03:00
title = { _t ( 'Popout widget' ) }
onClick = { this . _onPopoutWidgetClick }
2018-04-25 18:28:27 +03:00
/ > }
2019-11-28 21:16:59 +03:00
{ < ContextMenuButton
2019-11-21 05:17:42 +03:00
className = "mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
2020-10-09 10:42:21 +03:00
label = { _t ( "Options" ) }
2019-11-28 21:16:59 +03:00
isExpanded = { this . state . menuDisplayed }
inputRef = { this . _contextMenuButton }
2019-11-21 05:17:42 +03:00
onClick = { this . _onContextMenuClick }
2018-03-08 20:20:42 +03:00
/ > }
2017-05-22 14:34:27 +03:00
< / s p a n >
2018-01-11 16:20:58 +03:00
< / d i v > }
2017-09-28 13:21:06 +03:00
{ appTileBody }
2017-05-22 14:34:27 +03:00
< / d i v >
2019-11-28 21:16:59 +03:00
{ contextMenu }
< / R e a c t . F r a g m e n t > ;
2018-01-11 14:49:46 +03:00
}
}
2019-11-28 21:16:59 +03:00
AppTile . displayName = 'AppTile' ;
2018-02-07 17:48:43 +03:00
2018-01-11 14:49:46 +03:00
AppTile . propTypes = {
2020-04-01 12:00:33 +03:00
app : PropTypes . object . isRequired ,
2020-09-23 13:02:41 +03:00
// 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 ,
2018-01-11 14:49:46 +03:00
// 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.
2018-02-26 01:36:59 +03:00
fullWidth : PropTypes . bool ,
2018-07-12 20:43:49 +03:00
// Optional. If set, renders a smaller view of the widget
miniMode : PropTypes . bool ,
2018-01-11 14:49:46 +03:00
// UserId of the current user
2018-02-26 01:36:59 +03:00
userId : PropTypes . string . isRequired ,
2018-01-11 14:49:46 +03:00
// UserId of the entity that added / modified the widget
2018-02-26 01:36:59 +03:00
creatorUserId : PropTypes . string ,
waitForIframeLoad : PropTypes . bool ,
showMenubar : PropTypes . bool ,
2018-02-07 17:44:01 +03:00
// Optional onEditClickHandler (overrides default behaviour)
2018-02-26 01:36:59 +03:00
onEditClick : PropTypes . func ,
2018-02-07 17:44:01 +03:00
// Optional onDeleteClickHandler (overrides default behaviour)
2018-02-26 01:36:59 +03:00
onDeleteClick : PropTypes . func ,
2018-03-08 20:20:42 +03:00
// Optional onMinimiseClickHandler
onMinimiseClick : PropTypes . func ,
2018-02-07 17:44:01 +03:00
// Optionally hide the tile title
2018-02-26 01:36:59 +03:00
showTitle : PropTypes . bool ,
2018-03-08 20:20:42 +03:00
// Optionally handle minimise button pointer events (default false)
handleMinimisePointerEvents : PropTypes . bool ,
2018-04-25 18:28:27 +03:00
// Optionally hide the popout widget icon
showPopout : PropTypes . bool ,
2018-05-09 00:44:49 +03:00
// Is this an instance of a user widget
userWidget : PropTypes . bool ,
2021-03-05 20:52:51 +03:00
// sets the pointer-events property on the iframe
pointerEvents : PropTypes . string ,
2018-01-11 14:49:46 +03:00
} ;
AppTile . defaultProps = {
waitForIframeLoad : true ,
2018-01-11 16:20:58 +03:00
showMenubar : true ,
2018-02-07 17:44:01 +03:00
showTitle : true ,
2018-04-25 18:28:27 +03:00
showPopout : true ,
2018-03-08 20:20:42 +03:00
handleMinimisePointerEvents : false ,
2018-05-09 00:44:49 +03:00
userWidget : false ,
2018-07-12 20:43:49 +03:00
miniMode : false ,
2018-01-11 14:49:46 +03:00
} ;