diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss
index 1c809f0743..4c763c5991 100644
--- a/res/css/views/messages/_MImageBody.scss
+++ b/res/css/views/messages/_MImageBody.scss
@@ -20,5 +20,29 @@ limitations under the License.
}
.mx_MImageBody_thumbnail {
- max-width: 100%;
-}
\ No newline at end of file
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+}
+
+.mx_MImageBody_thumbnail_container {
+ // Prevent the padding-bottom (added inline in MImageBody.js) from
+ // affecting elements below the container.
+ overflow: hidden;
+
+ // Make sure the _thumbnail is positioned relative to the _container
+ position: relative;
+}
+
+.mx_MImageBody_thumbnail_spinner {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+}
+
+// Inner img and TintableSvg should be centered around 0, 0
+.mx_MImageBody_thumbnail_spinner > * {
+ transform: translate(-50%, -50%);
+}
diff --git a/res/css/views/messages/_MStickerBody.scss b/res/css/views/messages/_MStickerBody.scss
index 3e6bbe5aa4..e4977bcc34 100644
--- a/res/css/views/messages/_MStickerBody.scss
+++ b/res/css/views/messages/_MStickerBody.scss
@@ -14,33 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_MStickerBody {
- display: block;
- margin-right: 34px;
- min-height: 110px;
- padding: 20px 0;
+.mx_MStickerBody_wrapper {
+ padding: 20px 0px;
}
-.mx_MStickerBody_image_container {
- display: inline-block;
- position: relative;
-}
-
-.mx_MStickerBody_image {
- max-width: 100%;
- opacity: 0;
-}
-
-.mx_MStickerBody_image_visible {
- opacity: 1;
-}
-
-.mx_MStickerBody_placeholder {
- position: absolute;
- opacity: 1;
-}
-
-.mx_MStickerBody_placeholder_invisible {
- transition: 500ms;
- opacity: 0;
+.mx_MStickerBody_tooltip {
+ position: absolute;
+ top: 50%;
}
diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js
index 4dfd89ec0a..c42840b03a 100644
--- a/src/components/views/messages/MImageBody.js
+++ b/src/components/views/messages/MImageBody.js
@@ -22,10 +22,8 @@ import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import MFileBody from './MFileBody';
-import ImageUtils from '../../../ImageUtils';
import Modal from '../../../Modal';
import sdk from '../../../index';
-import dis from '../../../dispatcher';
import { decryptFile } from '../../../utils/DecryptFile';
import Promise from 'bluebird';
import { _t } from '../../../languageHandler';
@@ -52,14 +50,12 @@ export default class extends React.Component {
constructor(props) {
super(props);
- this.onAction = this.onAction.bind(this);
this.onImageError = this.onImageError.bind(this);
this.onImageLoad = this.onImageLoad.bind(this);
this.onImageEnter = this.onImageEnter.bind(this);
this.onImageLeave = this.onImageLeave.bind(this);
this.onClientSync = this.onClientSync.bind(this);
this.onClick = this.onClick.bind(this);
- this.fixupHeight = this.fixupHeight.bind(this);
this._isGif = this._isGif.bind(this);
this.state = {
@@ -68,6 +64,8 @@ export default class extends React.Component {
decryptedBlob: null,
error: null,
imgError: false,
+ imgLoaded: false,
+ hover: false,
};
}
@@ -122,6 +120,8 @@ export default class extends React.Component {
}
onImageEnter(e) {
+ this.setState({ hover: true });
+
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
return;
}
@@ -130,6 +130,8 @@ export default class extends React.Component {
}
onImageLeave(e) {
+ this.setState({ hover: false });
+
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
return;
}
@@ -145,6 +147,7 @@ export default class extends React.Component {
onImageLoad() {
this.props.onWidgetLoad();
+ this.setState({ imgLoaded: true });
}
_getContentUrl() {
@@ -179,7 +182,6 @@ export default class extends React.Component {
}
componentDidMount() {
- this.dispatcherRef = dis.register(this.onAction);
const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null);
@@ -210,7 +212,6 @@ export default class extends React.Component {
});
}).done();
}
- this.fixupHeight();
this._afterComponentDidMount();
}
@@ -221,7 +222,6 @@ export default class extends React.Component {
componentWillUnmount() {
this.unmounted = true;
- dis.unregister(this.dispatcherRef);
this.context.matrixClient.removeListener('sync', this.onClientSync);
this._afterComponentWillUnmount();
@@ -238,60 +238,87 @@ export default class extends React.Component {
_afterComponentWillUnmount() {
}
- onAction(payload) {
- if (payload.action === "timeline_resize") {
- this.fixupHeight();
- }
- }
-
- fixupHeight() {
- if (!this.refs.image) {
- console.warn(`Refusing to fix up height on ${this.displayName} with no image element`);
- return;
- }
-
- const content = this.props.mxEvent.getContent();
- const timelineWidth = this.refs.body.offsetWidth;
- const maxHeight = this.props.maxImageHeight || 600; // let images take up as much width as they can so long
- // as the height doesn't exceed 600px. The alternative here would be 600*timelineWidth/800; to scale them down
- // to fit inside a 4:3 bounding box
-
- // FIXME: this will break on clientside generated thumbnails (as per e2e rooms)
- // which may well be much smaller than the 800x600 bounding box.
-
- // FIXME: It will also break really badly for images with broken or missing thumbnails
-
- // FIXME: Because we don't know what size of thumbnail the server's actually going to send
- // us, we can't even really layout the page nicely for it. Instead we have to assume
- // it'll target 800x600 and we'll downsize if needed to make things fit.
-
- // console.log("trying to fit image into timelineWidth of " + this.refs.body.offsetWidth + " or " + this.refs.body.clientWidth);
- let thumbHeight = null;
- if (content.info) {
- thumbHeight = ImageUtils.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight);
- }
- this.refs.image.style.height = thumbHeight + "px";
- // console.log("Image height now", thumbHeight);
- }
-
_messageContent(contentUrl, thumbUrl, content) {
+ // The maximum height of the thumbnail as it is rendered as an
+ const maxHeight = Math.min(this.props.maxImageHeight || 600, content.info.h);
+ // The maximum width of the thumbnail, as dictated by its natural
+ // maximum height.
+ const maxWidth = content.info.w * maxHeight / content.info.h;
+
+ let img = null;
+ let placeholder = null;
+
+ // e2e image hasn't been decrypted yet
+ if (content.file !== undefined && this.state.decryptedUrl === null) {
+ placeholder =
;
+ } else if (!this.state.imgLoaded) {
+ // Deliberately, getSpinner is left unimplemented here, MStickerBody overides
+ placeholder = this.getPlaceholder();
+ }
+
+ const showPlaceholder = Boolean(placeholder);
+
+ if (thumbUrl && !this.state.imgError) {
+ // Restrict the width of the thumbnail here, otherwise it will fill the container
+ // which has the same width as the timeline
+ // mx_MImageBody_thumbnail resizes img to exactly container size
+ img =
;
+ }
+
const thumbnail = (
-
-
-
+