diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 107bf243d3..5576bcc9a2 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -1616,10 +1616,6 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', .module-quote { @include button-reset; - - display: block; - // To leave room for image thumbnail - min-height: 54px; width: 100%; position: relative; @@ -1760,7 +1756,8 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', padding-top: 7px; padding-bottom: 7px; - max-width: 100%; + // To leave room for image thumbnail + min-height: 54px; } .module-quote__primary__author { @@ -1899,16 +1896,11 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05', } .module-quote__icon-container { - flex: initial; - min-width: 54px; - width: 54px; + background-size: cover; + background-position: center center; + background-repeat: no-repeat; + flex: auto auto 54px; position: relative; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } } .module-quote__icon-container__inner { diff --git a/ts/components/conversation/Quote.tsx b/ts/components/conversation/Quote.tsx index 254da42b77..184278ea00 100644 --- a/ts/components/conversation/Quote.tsx +++ b/ts/components/conversation/Quote.tsx @@ -1,7 +1,8 @@ -// Copyright 2018-2020 Signal Messenger, LLC +// Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React from 'react'; +import React, { useRef, useState, useEffect, ReactNode } from 'react'; +import { noop } from 'lodash'; import classNames from 'classnames'; import * as MIME from '../../types/MIME'; @@ -132,11 +133,7 @@ export class Quote extends React.Component { }); }; - public renderImage( - url: string, - i18n: LocalizerType, - icon?: string - ): JSX.Element { + public renderImage(url: string, icon?: string): JSX.Element { const iconElement = icon ? (
@@ -151,14 +148,9 @@ export class Quote extends React.Component { ) : null; return ( -
- {i18n('quoteThumbnailAlt')} + {iconElement} -
+ ); } @@ -213,7 +205,7 @@ export class Quote extends React.Component { } public renderIconContainer(): JSX.Element | null { - const { attachment, i18n } = this.props; + const { attachment } = this.props; const { imageBroken } = this.state; if (!attachment) { @@ -225,12 +217,12 @@ export class Quote extends React.Component { if (GoogleChrome.isVideoTypeSupported(contentType)) { return objectUrl && !imageBroken - ? this.renderImage(objectUrl, i18n, 'play') + ? this.renderImage(objectUrl, 'play') : this.renderIcon('movie'); } if (GoogleChrome.isImageTypeSupported(contentType)) { return objectUrl && !imageBroken - ? this.renderImage(objectUrl, i18n) + ? this.renderImage(objectUrl) : this.renderIcon('image'); } if (MIME.isAudio(contentType)) { @@ -441,3 +433,51 @@ export class Quote extends React.Component { ); } } + +function ThumbnailImage({ + src, + onError, + children, +}: Readonly<{ + src: string; + onError: () => void; + children: ReactNode; +}>): JSX.Element { + const imageRef = useRef(new Image()); + const [loadedSrc, setLoadedSrc] = useState(null); + + useEffect(() => { + const image = new Image(); + image.onload = () => { + setLoadedSrc(src); + }; + image.src = src; + imageRef.current = image; + return () => { + image.onload = noop; + }; + }, [src]); + + useEffect(() => { + setLoadedSrc(null); + }, [src]); + + useEffect(() => { + const image = imageRef.current; + image.onerror = onError; + return () => { + image.onerror = noop; + }; + }, [onError]); + + return ( +
+ {children} +
+ ); +} diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 0eeff76972..da1d5a3001 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -14797,6 +14797,24 @@ "updated": "2019-11-01T22:46:33.013Z", "reasonDetail": "Used for setting focus only" }, + { + "rule": "React-useRef", + "path": "ts/components/conversation/Quote.js", + "line": " const imageRef = react_1.useRef(new Image());", + "lineNumber": 227, + "reasonCategory": "usageTrusted", + "updated": "2021-01-20T21:30:08.430Z", + "reasonDetail": "Doesn't touch the DOM." + }, + { + "rule": "React-useRef", + "path": "ts/components/conversation/Quote.tsx", + "line": " const imageRef = useRef(new Image());", + "lineNumber": 446, + "reasonCategory": "usageTrusted", + "updated": "2021-01-20T21:30:08.430Z", + "reasonDetail": "Doesn't touch the DOM." + }, { "rule": "React-useRef", "path": "ts/components/conversation/ReactionPicker.js",