mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 11:15:53 +03:00
Merge pull request #3492 from matrix-org/travis/hide-images
Add an option to hide image previews
This commit is contained in:
commit
b94c94db04
9 changed files with 139 additions and 22 deletions
|
@ -59,3 +59,36 @@ limitations under the License.
|
||||||
color: $imagebody-giflabel-color;
|
color: $imagebody-giflabel-color;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_HiddenImagePlaceholder {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
// To center the text in the middle of the frame
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
|
||||||
|
.mx_HiddenImagePlaceholder_button {
|
||||||
|
color: $accent-color;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile:hover .mx_HiddenImagePlaceholder {
|
||||||
|
background-color: $primary-bg-color;
|
||||||
|
}
|
||||||
|
|
|
@ -22,3 +22,14 @@ limitations under the License.
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MStickerBody_hidden {
|
||||||
|
max-width: 220px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
// To center the text in the middle of the frame
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
6
res/img/feather-customised/eye.svg
Normal file
6
res/img/feather-customised/eye.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="14" viewBox="0 0 18 14">
|
||||||
|
<g fill="none" fill-rule="evenodd" stroke="#03B381" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||||
|
<path d="M0 6s3-6 8.25-6 8.25 6 8.25 6-3 6-8.25 6S0 6 0 6z"/>
|
||||||
|
<circle cx="8.25" cy="6" r="2.25"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 345 B |
|
@ -64,6 +64,7 @@ export default class MImageBody extends React.Component {
|
||||||
imgLoaded: false,
|
imgLoaded: false,
|
||||||
loadedImageDimensions: null,
|
loadedImageDimensions: null,
|
||||||
hover: false,
|
hover: false,
|
||||||
|
showImage: SettingsStore.getValue("showImages"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +87,19 @@ export default class MImageBody extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showImage() {
|
||||||
|
localStorage.setItem("mx_ShowImage_" + this.props.mxEvent.getId(), "true");
|
||||||
|
this.setState({showImage: true});
|
||||||
|
}
|
||||||
|
|
||||||
onClick(ev) {
|
onClick(ev) {
|
||||||
if (ev.button === 0 && !ev.metaKey) {
|
if (ev.button === 0 && !ev.metaKey) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
if (!this.state.showImage) {
|
||||||
|
this.showImage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const content = this.props.mxEvent.getContent();
|
const content = this.props.mxEvent.getContent();
|
||||||
const httpUrl = this._getContentUrl();
|
const httpUrl = this._getContentUrl();
|
||||||
const ImageView = sdk.getComponent("elements.ImageView");
|
const ImageView = sdk.getComponent("elements.ImageView");
|
||||||
|
@ -120,7 +131,7 @@ export default class MImageBody extends React.Component {
|
||||||
onImageEnter(e) {
|
onImageEnter(e) {
|
||||||
this.setState({ hover: true });
|
this.setState({ hover: true });
|
||||||
|
|
||||||
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
if (!this.state.showImage || !this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const imgElement = e.target;
|
const imgElement = e.target;
|
||||||
|
@ -130,7 +141,7 @@ export default class MImageBody extends React.Component {
|
||||||
onImageLeave(e) {
|
onImageLeave(e) {
|
||||||
this.setState({ hover: false });
|
this.setState({ hover: false });
|
||||||
|
|
||||||
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
if (!this.state.showImage || !this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const imgElement = e.target;
|
const imgElement = e.target;
|
||||||
|
@ -280,6 +291,12 @@ export default class MImageBody extends React.Component {
|
||||||
});
|
});
|
||||||
}).done();
|
}).done();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remember that the user wanted to show this particular image
|
||||||
|
if (!this.state.showImage && localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true") {
|
||||||
|
this.setState({showImage: true});
|
||||||
|
}
|
||||||
|
|
||||||
this._afterComponentDidMount();
|
this._afterComponentDidMount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,13 +338,19 @@ export default class MImageBody extends React.Component {
|
||||||
// By doing this, the image "pops" into the timeline, but is still restricted
|
// By doing this, the image "pops" into the timeline, but is still restricted
|
||||||
// by the same width and height logic below.
|
// by the same width and height logic below.
|
||||||
if (!this.state.loadedImageDimensions) {
|
if (!this.state.loadedImageDimensions) {
|
||||||
return this.wrapImage(contentUrl,
|
let imageElement;
|
||||||
<img style={{display: 'none'}} src={thumbUrl} ref="image"
|
if (!this.state.showImage) {
|
||||||
alt={content.body}
|
imageElement = <HiddenImagePlaceholder />;
|
||||||
onError={this.onImageError}
|
} else {
|
||||||
onLoad={this.onImageLoad}
|
imageElement = (
|
||||||
/>,
|
<img style={{display: 'none'}} src={thumbUrl} ref="image"
|
||||||
);
|
alt={content.body}
|
||||||
|
onError={this.onImageError}
|
||||||
|
onLoad={this.onImageLoad}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.wrapImage(contentUrl, imageElement);
|
||||||
}
|
}
|
||||||
infoWidth = this.state.loadedImageDimensions.naturalWidth;
|
infoWidth = this.state.loadedImageDimensions.naturalWidth;
|
||||||
infoHeight = this.state.loadedImageDimensions.naturalHeight;
|
infoHeight = this.state.loadedImageDimensions.naturalHeight;
|
||||||
|
@ -356,19 +379,26 @@ export default class MImageBody extends React.Component {
|
||||||
placeholder = this.getPlaceholder();
|
placeholder = this.getPlaceholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
const showPlaceholder = Boolean(placeholder);
|
let showPlaceholder = Boolean(placeholder);
|
||||||
|
|
||||||
if (thumbUrl && !this.state.imgError) {
|
if (thumbUrl && !this.state.imgError) {
|
||||||
// Restrict the width of the thumbnail here, otherwise it will fill the container
|
// Restrict the width of the thumbnail here, otherwise it will fill the container
|
||||||
// which has the same width as the timeline
|
// which has the same width as the timeline
|
||||||
// mx_MImageBody_thumbnail resizes img to exactly container size
|
// mx_MImageBody_thumbnail resizes img to exactly container size
|
||||||
img = <img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
|
img = (
|
||||||
style={{ maxWidth: maxWidth + "px" }}
|
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
|
||||||
alt={content.body}
|
style={{ maxWidth: maxWidth + "px" }}
|
||||||
onError={this.onImageError}
|
alt={content.body}
|
||||||
onLoad={this.onImageLoad}
|
onError={this.onImageError}
|
||||||
onMouseEnter={this.onImageEnter}
|
onLoad={this.onImageLoad}
|
||||||
onMouseLeave={this.onImageLeave} />;
|
onMouseEnter={this.onImageEnter}
|
||||||
|
onMouseLeave={this.onImageLeave} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.showImage) {
|
||||||
|
img = <HiddenImagePlaceholder style={{ maxWidth: maxWidth + "px" }} />;
|
||||||
|
showPlaceholder = false; // because we're hiding the image, so don't show the sticker icon.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._isGif() && !SettingsStore.getValue("autoplayGifsAndVideos") && !this.state.hover) {
|
if (this._isGif() && !SettingsStore.getValue("autoplayGifsAndVideos") && !this.state.hover) {
|
||||||
|
@ -454,3 +484,22 @@ export default class MImageBody extends React.Component {
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class HiddenImagePlaceholder extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
hover: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let className = 'mx_HiddenImagePlaceholder';
|
||||||
|
if (this.props.hover) className += ' mx_HiddenImagePlaceholder_hover';
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<div className='mx_HiddenImagePlaceholder_button'>
|
||||||
|
<img src={require("../../../../res/img/feather-customised/eye.svg")} width={17} height={12} />
|
||||||
|
<span>{_t("Show image")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,21 +14,27 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MImageBody from './MImageBody';
|
import MImageBody from './MImageBody';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
|
||||||
export default class MStickerBody extends MImageBody {
|
export default class MStickerBody extends MImageBody {
|
||||||
// Empty to prevent default behaviour of MImageBody
|
// Mostly empty to prevent default behaviour of MImageBody
|
||||||
onClick() {
|
onClick(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (!this.state.showImage) {
|
||||||
|
this.showImage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MStickerBody doesn't need a wrapping `<a href=...>`, but it does need extra padding
|
// MStickerBody doesn't need a wrapping `<a href=...>`, but it does need extra padding
|
||||||
// which is added by mx_MStickerBody_wrapper
|
// which is added by mx_MStickerBody_wrapper
|
||||||
wrapImage(contentUrl, children) {
|
wrapImage(contentUrl, children) {
|
||||||
return <div className="mx_MStickerBody_wrapper"> { children } </div>;
|
let onClick = null;
|
||||||
|
if (!this.state.showImage) {
|
||||||
|
onClick = this.onClick;
|
||||||
|
}
|
||||||
|
return <div className="mx_MStickerBody_wrapper" onClick={onClick}> { children } </div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placeholder to show in place of the sticker image if
|
// Placeholder to show in place of the sticker image if
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import { linkifyElement } from '../../../HtmlUtils';
|
import { linkifyElement } from '../../../HtmlUtils';
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
@ -102,6 +103,9 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
|
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
|
||||||
let image = p["og:image"];
|
let image = p["og:image"];
|
||||||
|
if (!SettingsStore.getValue("showImages")) {
|
||||||
|
image = null; // Don't render a button to show the image, just hide it outright
|
||||||
|
}
|
||||||
const imageMaxWidth = 100; const imageMaxHeight = 100;
|
const imageMaxWidth = 100; const imageMaxHeight = 100;
|
||||||
if (image && image.startsWith("mxc://")) {
|
if (image && image.startsWith("mxc://")) {
|
||||||
image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight);
|
image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight);
|
||||||
|
|
|
@ -43,6 +43,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
|
||||||
'showJoinLeaves',
|
'showJoinLeaves',
|
||||||
'showAvatarChanges',
|
'showAvatarChanges',
|
||||||
'showDisplaynameChanges',
|
'showDisplaynameChanges',
|
||||||
|
'showImages',
|
||||||
];
|
];
|
||||||
|
|
||||||
static ROOM_LIST_SETTINGS = [
|
static ROOM_LIST_SETTINGS = [
|
||||||
|
|
|
@ -369,6 +369,7 @@
|
||||||
"Low bandwidth mode": "Low bandwidth mode",
|
"Low bandwidth mode": "Low bandwidth mode",
|
||||||
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)",
|
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)",
|
||||||
"Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)",
|
"Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)",
|
||||||
|
"Show previews/thumbnails for images": "Show previews/thumbnails for images",
|
||||||
"Collecting app version information": "Collecting app version information",
|
"Collecting app version information": "Collecting app version information",
|
||||||
"Collecting logs": "Collecting logs",
|
"Collecting logs": "Collecting logs",
|
||||||
"Uploading report": "Uploading report",
|
"Uploading report": "Uploading report",
|
||||||
|
@ -1036,6 +1037,7 @@
|
||||||
"Download %(text)s": "Download %(text)s",
|
"Download %(text)s": "Download %(text)s",
|
||||||
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
||||||
"Error decrypting image": "Error decrypting image",
|
"Error decrypting image": "Error decrypting image",
|
||||||
|
"Show image": "Show image",
|
||||||
"Error decrypting video": "Error decrypting video",
|
"Error decrypting video": "Error decrypting video",
|
||||||
"Agree": "Agree",
|
"Agree": "Agree",
|
||||||
"Disagree": "Disagree",
|
"Disagree": "Disagree",
|
||||||
|
|
|
@ -413,4 +413,9 @@ export const SETTINGS = {
|
||||||
),
|
),
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
"showImages": {
|
||||||
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
|
displayName: _td("Show previews/thumbnails for images"),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue