Merge pull request #2565 from matrix-org/bwindels/widgetmakeover

Redesign: widget makeover
pull/21833/head
Bruno Windels 2019-02-02 07:30:45 +00:00 committed by GitHub
commit a6914274b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 380 additions and 125 deletions

View File

@ -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;
@ -265,14 +317,11 @@ form.mx_Custom_Widget_Form div {
}
.mx_AppPermissionButton {
padding: 5px;
border: none;
padding: 5px 20px;
border-radius: 5px;
color: $warning-color;
background-color: $primary-bg-color;
}
.mx_AppPermissionButton:hover {
background-color: $primary-fg-color;
background-color: $button-bg-color;
color: $button-fg-color;
cursor: pointer;
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="11.014242"
height="12"
viewBox="0 0 11.014242 12"
version="1.1"
id="svg6"
sodipodi:docname="bin.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="750"
inkscape:window-height="480"
id="namedview8"
showgrid="false"
fit-margin-top="0.5"
fit-margin-left="0.5"
fit-margin-bottom="0.5"
fit-margin-right="0.5"
inkscape:zoom="19.666667"
inkscape:cx="5.5071212"
inkscape:cy="6"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:current-layer="svg6" />
<g
id="g4"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
transform="translate(1.0071212)">
<path
d="M 0,3 H 9 M 8,3 v 7 A 1,1 0 0 1 7,11 H 2 A 1,1 0 0 1 1,10 V 3 M 2.5,3 V 2 a 1,1 0 0 1 1,-1 h 2 a 1,1 0 0 1 1,1 v 1 m -3,2.5 v 3 m 2,-3 v 3"
id="path2"
style="stroke:#000000;stroke-opacity:1"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11">
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
<path d="M11 8a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V2.5a1 1 0 0 1 1-1h2L4 0h3l1 1.5h2a1 1 0 0 1 1 1V8z"/>
<circle cx="5.5" cy="5" r="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" viewBox="0 0 12 11">
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 6.33V9a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h2.67"/>
<path d="M9 0l2 2-5 5H4V5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 11 11">
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round">
<path d="M8.5 6v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h3M7 1h3v3M4.5 6.5L10 1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="11"
height="11"
viewBox="0 0 11 11"
version="1.1"
id="svg6"
sodipodi:docname="maximise.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="750"
inkscape:window-height="480"
id="namedview8"
showgrid="false"
inkscape:zoom="21.454545"
inkscape:cx="5.5"
inkscape:cy="5.5"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:current-layer="svg6" />
<g
fill="none"
fill-rule="evenodd"
stroke="#7AC9A1"
stroke-linecap="round"
stroke-linejoin="round"
id="g4"
style="stroke:#000000;stroke-opacity:1">
<path
d="M7 1h3v3M4 10H1V7M10 1L6.5 4.5M1 10l3.5-3.5"
id="path2"
style="stroke:#000000;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="10.2101"
height="10.2101"
viewBox="0 0 10.2101 10.210099"
version="1.1"
id="svg6"
sodipodi:docname="minimise.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview8"
showgrid="false"
fit-margin-top="0.1"
fit-margin-left="0.1"
fit-margin-right="0.1"
fit-margin-bottom="0.1"
inkscape:zoom="26.222222"
inkscape:cx="-1.5686788"
inkscape:cy="5.0287789"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<g
id="g4"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
transform="translate(-0.39494988,0.60505012)">
<path
d="m 1.5,5.5 h 3 v 3 m 5,-5 h -3 v -3 m 0,3 L 10,0 M 1,9 4.5,5.5"
id="path2"
inkscape:connector-curvature="0"
style="stroke:#000000;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11">
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 1.5v3H9M1 9.5v-3h3"/>
<path d="M2.255 4A4.5 4.5 0 0 1 9.68 2.32L12 4.5m-11 2l2.32 2.18A4.5 4.5 0 0 0 10.745 7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 346 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
<circle cx="5" cy="5" r="5"/>
<path d="M6.5 3.5l-3 3M3.5 3.5l3 3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@ -172,7 +172,7 @@ $panel-divider-color: #dee1f3;
// ********************
$widget-menu-bar-bg-color: $tertiary-accent-color;
$widget-menu-bar-bg-color: $secondary-accent-color;
// ********************

View File

@ -47,7 +47,7 @@ export default class AppPermission extends React.Component {
return (
<div className='mx_AppPermissionWarning'>
<div className='mx_AppPermissionWarningImage'>
<img src={require("../../../../res/img/warning.svg")} alt={_t('Warning!')} />
<img src={require("../../../../res/img/feather-icons/warning-triangle.svg")} alt={_t('Warning!')} />
</div>
<div className='mx_AppPermissionWarningText'>
<span className='mx_AppPermissionWarningTextLabel'>{ _t('Do you want to load widget from URL:') }</span> <span className='mx_AppPermissionWarningTextURL'>{ this.state.curlBase }</span>

View File

@ -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 (
<div className={appTileClass} id={this.props.id}>
{ this.props.showMenubar &&
<div ref="menu_bar" className="mx_AppTileMenuBar" onClick={this.onClickMenuBar}>
<div ref="menu_bar" className={menuBarClasses} onClick={this.onClickMenuBar}>
<span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}>
{ this.props.showMinimise && <TintableSvgButton
src={windowStateIcon}
className="mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
{ /* Minimise widget */ }
{ showMinimiseButton && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_minimise"
title={_t('Minimize apps')}
width="10"
height="10"
onClick={this._onMinimiseClick}
/> }
{ /* Maximise widget */ }
{ showMaximiseButton && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_maximise"
title={_t('Minimize apps')}
onClick={this._onMinimiseClick}
/> }
{ /* Title */ }
{ this.props.showTitle && this._getTileTitle() }
</span>
<span className="mx_AppTileMenuBarWidgets">
{ /* Reload widget */ }
{ this.props.showReload && <TintableSvgButton
src={reloadWidgetIcon}
className="mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
{ this.props.showReload && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_reload"
title={_t('Reload widget')}
onClick={this._onReloadWidgetClick}
width="10"
height="10"
/> }
{ /* Popout widget */ }
{ this.props.showPopout && <TintableSvgButton
src={popoutWidgetIcon}
className="mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
{ this.props.showPopout && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
title={_t('Popout widget')}
onClick={this._onPopoutWidgetClick}
width="10"
height="10"
/> }
{ /* Snapshot widget */ }
{ showPictureSnapshotButton && <TintableSvgButton
src={showPictureSnapshotIcon}
className="mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
{ showPictureSnapshotButton && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_snapshot"
title={_t('Picture')}
onClick={this._onSnapshotClick}
width="10"
height="10"
/> }
{ /* Edit widget */ }
{ showEditButton && <TintableSvgButton
src={require("../../../../res/img/edit_green.svg")}
className={"mx_AppTileMenuBarWidget " +
(this.props.showDelete ? "mx_AppTileMenuBarWidgetPadding" : "")}
{ showEditButton && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_edit"
title={_t('Edit')}
onClick={this._onEditClick}
width="10"
height="10"
/> }
{ /* Delete widget */ }
{ this.props.showDelete && <TintableSvgButton
src={deleteIcon}
className={deleteClasses}
title={_t(deleteWidgetLabel)}
{ showDeleteButton && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_delete"
title={_t('Delete widget')}
onClick={this._onDeleteClick}
width="10"
height="10"
/> }
{ /* Cancel widget */ }
{ showCancelButton && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_cancel"
title={_t('Revoke widget access')}
onClick={this._onCancelClick}
/> }
</span>
</div> }