diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 4a46063376..e60ceda822 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -56,9 +56,8 @@ limitations under the License. max-width: 960px; width: 50%; margin-right: 5px; - border: 1px solid $primary-hairline-color; - border-radius: 2px; - background-color: $dialog-background-bg-color; + border: 5px solid $widget-menu-bar-bg-color; + border-radius: 4px; } .mx_AppTile:last-child { @@ -71,8 +70,8 @@ limitations under the License. height: 100%; margin: 0; padding: 0; - border: 1px solid $primary-hairline-color; - border-radius: 2px; + border: 5px solid $widget-menu-bar-bg-color; + border-radius: 4px; } .mx_AppTile_mini { @@ -93,9 +92,7 @@ limitations under the License. .mx_AppTileMenuBar { margin: 0; - padding: 2px 10px; - border-bottom: 1px solid $primary-hairline-color; - font-size: 10px; + font-size: 12px; background-color: $widget-menu-bar-bg-color; display: flex; flex-direction: row; @@ -104,6 +101,10 @@ limitations under the License. cursor: pointer; } +.mx_AppTileMenuBar_expanded { + padding-bottom: 5px; +} + .mx_AppTileMenuBarTitle { display: flex; flex-direction: row; @@ -111,6 +112,10 @@ limitations under the License. pointer-events: none; } +.mx_AppTileMenuBarTitle > :last-child { + margin-left: 9px; +} + .mx_AppTileMenuBarWidgets { float: right; display: flex; @@ -118,6 +123,53 @@ limitations under the License. align-items: center; } +.mx_AppTileMenuBar_iconButton { + width: 12px; + height: 12px; + mask-repeat: no-repeat; + mask-position: 0 center; + mask-size: auto 12px; + background-color: $topleftmenu-color; + margin: 0 3px; +} + +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_minimise { + mask-image: url('$(res)/img/feather-icons/widget/minimise.svg'); + background-color: $accent-color; +} + +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_maximise { + mask-image: url('$(res)/img/feather-icons/widget/maximise.svg'); + background-color: $accent-color; +} + +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_reload { + mask-image: url('$(res)/img/feather-icons/widget/refresh.svg'); + mask-size: 100%; +} + +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_popout { + mask-image: url('$(res)/img/feather-icons/widget/external-link.svg'); +} + +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_snapshot { + mask-image: url('$(res)/img/feather-icons/widget/camera.svg'); +} + +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_edit { + mask-image: url('$(res)/img/feather-icons/widget/edit.svg'); +} + +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_delete { + mask-image: url('$(res)/img/feather-icons/widget/bin.svg'); + background-color: $warning-color; +} + +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_cancel { + mask-image: url('$(res)/img/feather-icons/widget/x-circle.svg'); +} + +/* delete ? */ .mx_AppTileMenuBarWidget { cursor: pointer; width: 10px; diff --git a/res/img/feather-icons/widget/bin.svg b/res/img/feather-icons/widget/bin.svg new file mode 100644 index 0000000000..7616d8931b --- /dev/null +++ b/res/img/feather-icons/widget/bin.svg @@ -0,0 +1,65 @@ + + diff --git a/res/img/feather-icons/widget/camera.svg b/res/img/feather-icons/widget/camera.svg new file mode 100644 index 0000000000..5502493068 --- /dev/null +++ b/res/img/feather-icons/widget/camera.svg @@ -0,0 +1,6 @@ + diff --git a/res/img/feather-icons/widget/edit.svg b/res/img/feather-icons/widget/edit.svg new file mode 100644 index 0000000000..749e83f982 --- /dev/null +++ b/res/img/feather-icons/widget/edit.svg @@ -0,0 +1,6 @@ + diff --git a/res/img/feather-icons/widget/external-link.svg b/res/img/feather-icons/widget/external-link.svg new file mode 100644 index 0000000000..6732f298f4 --- /dev/null +++ b/res/img/feather-icons/widget/external-link.svg @@ -0,0 +1,5 @@ + diff --git a/res/img/feather-icons/widget/maximise.svg b/res/img/feather-icons/widget/maximise.svg new file mode 100644 index 0000000000..96185da135 --- /dev/null +++ b/res/img/feather-icons/widget/maximise.svg @@ -0,0 +1,63 @@ + + diff --git a/res/img/feather-icons/widget/minimise.svg b/res/img/feather-icons/widget/minimise.svg new file mode 100644 index 0000000000..f05e939960 --- /dev/null +++ b/res/img/feather-icons/widget/minimise.svg @@ -0,0 +1,65 @@ + + diff --git a/res/img/feather-icons/widget/refresh.svg b/res/img/feather-icons/widget/refresh.svg new file mode 100644 index 0000000000..0994bbdd52 --- /dev/null +++ b/res/img/feather-icons/widget/refresh.svg @@ -0,0 +1,6 @@ + diff --git a/res/img/feather-icons/widget/x-circle.svg b/res/img/feather-icons/widget/x-circle.svg new file mode 100644 index 0000000000..951407b39c --- /dev/null +++ b/res/img/feather-icons/widget/x-circle.svg @@ -0,0 +1,6 @@ + diff --git a/res/themes/dharma/css/_dharma.scss b/res/themes/dharma/css/_dharma.scss index 482b3c51cb..e389cc944c 100644 --- a/res/themes/dharma/css/_dharma.scss +++ b/res/themes/dharma/css/_dharma.scss @@ -172,7 +172,7 @@ $panel-divider-color: #dee1f3; // ******************** -$widget-menu-bar-bg-color: $tertiary-accent-color; +$widget-menu-bar-bg-color: $secondary-accent-color; // ******************** diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 85d28baee7..1c8edc1b6d 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -24,9 +24,9 @@ import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; import ScalarAuthClient from '../../../ScalarAuthClient'; import WidgetMessaging from '../../../WidgetMessaging'; -import TintableSvgButton from './TintableSvgButton'; +import AccessibleButton from './AccessibleButton'; import Modal from '../../../Modal'; -import { _t, _td } from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; import sdk from '../../../index'; import AppPermission from './AppPermission'; import AppWarning from './AppWarning'; @@ -34,6 +34,7 @@ import MessageSpinner from './MessageSpinner'; import WidgetUtils from '../../../utils/WidgetUtils'; import dis from '../../../dispatcher'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; +import classNames from 'classnames'; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ENABLE_REACT_PERF = false; @@ -51,6 +52,7 @@ export default class AppTile extends React.Component { this._onLoaded = this._onLoaded.bind(this); this._onEditClick = this._onEditClick.bind(this); this._onDeleteClick = this._onDeleteClick.bind(this); + this._onCancelClick = this._onCancelClick.bind(this); this._onSnapshotClick = this._onSnapshotClick.bind(this); this.onClickMenuBar = this.onClickMenuBar.bind(this); this._onMinimiseClick = this._onMinimiseClick.bind(this); @@ -267,55 +269,59 @@ export default class AppTile extends React.Component { _onDeleteClick() { if (this.props.onDeleteClick) { this.props.onDeleteClick(); - } else { - if (this._canUserModify()) { - // Show delete confirmation dialog - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, { - title: _t("Delete Widget"), - description: _t( - "Deleting a widget removes it for all users in this room." + - " Are you sure you want to delete this widget?"), - button: _t("Delete widget"), - onFinished: (confirmed) => { - if (!confirmed) { - return; - } - this.setState({deleting: true}); + } else if (this._canUserModify()) { + // Show delete confirmation dialog + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, { + title: _t("Delete Widget"), + description: _t( + "Deleting a widget removes it for all users in this room." + + " Are you sure you want to delete this widget?"), + button: _t("Delete widget"), + onFinished: (confirmed) => { + if (!confirmed) { + return; + } + this.setState({deleting: true}); - // 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/riot-web/issues/7351 - if (this.refs.appFrame) { - // 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 Riot instance is located. - this.refs.appFrame.src = 'about:blank'; - } + // 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/riot-web/issues/7351 + if (this.refs.appFrame) { + // 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 Riot instance is located. + this.refs.appFrame.src = 'about:blank'; + } - WidgetUtils.setRoomWidget( - this.props.room.roomId, - this.props.id, - ).catch((e) => { - console.error('Failed to delete widget', e); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + WidgetUtils.setRoomWidget( + this.props.room.roomId, + this.props.id, + ).catch((e) => { + console.error('Failed to delete widget', e); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to remove widget', '', ErrorDialog, { - title: _t('Failed to remove widget'), - description: _t('An error ocurred whilst trying to remove the widget from the room'), - }); - }).finally(() => { - this.setState({deleting: false}); + Modal.createTrackedDialog('Failed to remove widget', '', ErrorDialog, { + title: _t('Failed to remove widget'), + description: _t('An error ocurred whilst trying to remove the widget from the room'), }); - }, - }); - } else { - console.log("Revoke widget permissions - %s", this.props.id); - this._revokeWidgetPermission(); - } + }).finally(() => { + this.setState({deleting: false}); + }); + }, + }); + } + } + + _onCancelClick() { + if (this.props.onDeleteClick) { + this.props.onDeleteClick(); + } else { + console.log("Revoke widget permissions - %s", this.props.id); + this._revokeWidgetPermission(); } } @@ -394,15 +400,6 @@ export default class AppTile extends React.Component { }); } - // Widget labels to render, depending upon user permissions - // These strings are translated at the point that they are inserted in to the DOM, in the render method - _deleteWidgetLabel() { - if (this._canUserModify()) { - return _td('Delete widget'); - } - return _td('Revoke widget access'); - } - /* TODO -- Store permission in account data so that it is persisted across multiple devices */ _grantWidgetPermission() { console.warn('Granting permission to load widget - ', this.state.widgetUrl); @@ -580,23 +577,14 @@ export default class AppTile extends React.Component { } // editing is done in scalar - const showEditButton = Boolean(this._scalarClient && this._canUserModify()); - const deleteWidgetLabel = this._deleteWidgetLabel(); - let deleteIcon = require("../../../../res/img/cancel_green.svg"); - let deleteClasses = 'mx_AppTileMenuBarWidget'; - if (this._canUserModify()) { - deleteIcon = require("../../../../res/img/icon-delete-pink.svg"); - deleteClasses += ' mx_AppTileMenuBarWidgetDelete'; - } - + const canUserModify = this._canUserModify(); + const showEditButton = Boolean(this._scalarClient && canUserModify); + const showDeleteButton = canUserModify; + const showCancelButton = !showDeleteButton; // Picture snapshot - only show button when apps are maximised. const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show; - const showPictureSnapshotIcon = require("../../../../res/img/camera_green.svg"); - const popoutWidgetIcon = require("../../../../res/img/button-new-window.svg"); - const reloadWidgetIcon = require("../../../../res/img/button-refresh.svg"); - const minimizeIcon = require("../../../../res/img/minimize.svg"); - const maximizeIcon = require("../../../../res/img/maximize.svg"); - const windowStateIcon = (this.props.show ? minimizeIcon : maximizeIcon); + const showMinimiseButton = this.props.showMinimise && this.props.show; + const showMaximiseButton = this.props.showMinimise && !this.props.show; let appTileClass; if (this.props.miniMode) { @@ -607,71 +595,67 @@ export default class AppTile extends React.Component { appTileClass = 'mx_AppTile'; } + const menuBarClasses = classNames({ + mx_AppTileMenuBar: true, + mx_AppTileMenuBar_expanded: this.props.show, + }) + return (