Merge pull request #3492 from matrix-org/travis/hide-images

Add an option to hide image previews
This commit is contained in:
Travis Ralston 2019-10-02 12:46:45 -06:00 committed by GitHub
commit b94c94db04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 139 additions and 22 deletions

View file

@ -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;
}

View file

@ -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;
}

View 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

View file

@ -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>
);
}
}

View file

@ -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

View file

@ -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);

View file

@ -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 = [

View file

@ -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",

View file

@ -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,
},
}; };