Make MStickerBody extend MImageBody.

This commit is contained in:
Richard Lewis 2018-02-26 14:01:33 +00:00
parent b64736aa44
commit 5ca0fc3ab5
2 changed files with 82 additions and 251 deletions

View file

@ -30,35 +30,45 @@ import Promise from 'bluebird';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
module.exports = React.createClass({ export default class extends React.Component {
displayName: 'MImageBody', displayName: 'MImageBody'
propTypes: { static propTypes = {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
/* called when the image has loaded */ /* called when the image has loaded */
onWidgetLoad: PropTypes.func.isRequired, onWidgetLoad: PropTypes.func.isRequired,
}, }
contextTypes: { static contextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient), matrixClient: PropTypes.instanceOf(MatrixClient),
}, }
getInitialState: function() { constructor(props) {
return { super(props);
this.onAction = this.onAction.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 = {
decryptedUrl: null, decryptedUrl: null,
decryptedThumbnailUrl: null, decryptedThumbnailUrl: null,
decryptedBlob: null, decryptedBlob: null,
error: null, error: null,
imgError: false, imgError: false,
}; };
}, }
componentWillMount() { componentWillMount() {
this.unmounted = false; this.unmounted = false;
this.context.matrixClient.on('sync', this.onClientSync); this.context.matrixClient.on('sync', this.onClientSync);
}, }
onClientSync(syncState, prevState) { onClientSync(syncState, prevState) {
if (this.unmounted) return; if (this.unmounted) return;
@ -71,9 +81,9 @@ module.exports = React.createClass({
imgError: false, imgError: false,
}); });
} }
}, }
onClick: function onClick(ev) { onClick(ev) {
if (ev.button == 0 && !ev.metaKey) { if (ev.button == 0 && !ev.metaKey) {
ev.preventDefault(); ev.preventDefault();
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
@ -93,49 +103,49 @@ module.exports = React.createClass({
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
} }
}, }
_isGif: function() { _isGif() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
return ( return (
content && content &&
content.info && content.info &&
content.info.mimetype === "image/gif" content.info.mimetype === "image/gif"
); );
}, }
onImageEnter: function(e) { onImageEnter(e) {
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) { if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
return; return;
} }
const imgElement = e.target; const imgElement = e.target;
imgElement.src = this._getContentUrl(); imgElement.src = this._getContentUrl();
}, }
onImageLeave: function(e) { onImageLeave(e) {
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) { if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
return; return;
} }
const imgElement = e.target; const imgElement = e.target;
imgElement.src = this._getThumbUrl(); imgElement.src = this._getThumbUrl();
}, }
onImageError: function() { onImageError() {
this.setState({ this.setState({
imgError: true, imgError: true,
}); });
}, }
_getContentUrl: function() { _getContentUrl() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined) { if (content.file !== undefined) {
return this.state.decryptedUrl; return this.state.decryptedUrl;
} else { } else {
return this.context.matrixClient.mxcUrlToHttp(content.url); return this.context.matrixClient.mxcUrlToHttp(content.url);
} }
}, }
_getThumbUrl: function() { _getThumbUrl() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined) { if (content.file !== undefined) {
// Don't use the thumbnail for clients wishing to autoplay gifs. // Don't use the thumbnail for clients wishing to autoplay gifs.
@ -146,9 +156,9 @@ module.exports = React.createClass({
} else { } else {
return this.context.matrixClient.mxcUrlToHttp(content.url, 800, 600); return this.context.matrixClient.mxcUrlToHttp(content.url, 800, 600);
} }
}, }
componentDidMount: function() { componentDidMount() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this.fixupHeight(); this.fixupHeight();
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
@ -182,23 +192,23 @@ module.exports = React.createClass({
}); });
}).done(); }).done();
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
this.unmounted = true; this.unmounted = true;
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
this.context.matrixClient.removeListener('sync', this.onClientSync); this.context.matrixClient.removeListener('sync', this.onClientSync);
}, }
onAction: function(payload) { onAction(payload) {
if (payload.action === "timeline_resize") { if (payload.action === "timeline_resize") {
this.fixupHeight(); this.fixupHeight();
} }
}, }
fixupHeight: function() { fixupHeight() {
if (!this.refs.image) { if (!this.refs.image) {
console.warn("Refusing to fix up height on MImageBody with no image element"); console.warn(`Refusing to fix up height on ${this.displayName} with no image element`);
return; return;
} }
@ -214,10 +224,25 @@ module.exports = React.createClass({
} }
this.refs.image.style.height = thumbHeight + "px"; this.refs.image.style.height = thumbHeight + "px";
// console.log("Image height now", thumbHeight); // console.log("Image height now", thumbHeight);
}, }
render: function() { _messageContent(contentUrl, thumbUrl, content) {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); return (
<span className="mx_MImageBody" ref="body">
<a href={contentUrl} onClick={this.onClick}>
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
alt={content.body}
onError={this.onImageError}
onLoad={this.props.onWidgetLoad}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} />
</a>
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
</span>
);
}
render() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (this.state.error !== null) { if (this.state.error !== null) {
@ -265,19 +290,7 @@ module.exports = React.createClass({
} }
if (thumbUrl) { if (thumbUrl) {
return ( return this._messageContent(contentUrl, thumbUrl, content);
<span className="mx_MImageBody" ref="body">
<a href={contentUrl} onClick={this.onClick}>
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
alt={content.body}
onError={this.onImageError}
onLoad={this.props.onWidgetLoad}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} />
</a>
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
</span>
);
} else if (content.body) { } else if (content.body) {
return ( return (
<span className="mx_MImageBody"> <span className="mx_MImageBody">
@ -291,5 +304,5 @@ module.exports = React.createClass({
</span> </span>
); );
} }
}, }
}); }

View file

@ -16,209 +16,27 @@ limitations under the License.
'use strict'; 'use strict';
import React from 'react'; import MImageBody from "./MImageBody";
import MatrixClientPeg from '../../../MatrixClientPeg';
import ImageUtils from '../../../ImageUtils';
import dis from '../../../dispatcher';
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
import Promise from 'bluebird';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
module.exports = React.createClass({ export default class MStickerBody extends MImageBody {
displayName: 'MStickerBody', displayName: 'MStickerBody'
propTypes: { constructor(props) {
/* the MatrixEvent to show */ super(props);
mxEvent: React.PropTypes.object.isRequired, }
/* called when the image has loaded */ _messageContent(contentUrl, thumbUrl, content) {
onWidgetLoad: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
decryptedUrl: null,
decryptedThumbnailUrl: null,
decryptedBlob: null,
error: null,
};
},
_isGif: function() {
const content = this.props.mxEvent.getContent();
return ( return (
content && <span className="mx_MImageBody" ref="body">
content.info && <img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
content.info.mimetype === "image/gif" alt={content.body}
onLoad={this.props.onWidgetLoad}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} />
</span>
); );
}, }
onImageEnter: function(e) { onClick() {
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) { }
return; }
}
const imgElement = e.target;
imgElement.src = this._getContentUrl();
},
onImageLeave: function(e) {
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
return;
}
const imgElement = e.target;
imgElement.src = this._getThumbUrl();
},
_getContentUrl: function() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined) {
return this.state.decryptedUrl;
} else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
}
},
_getThumbUrl: function() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined) {
// Don't use the thumbnail for clients wishing to autoplay gifs.
if (this.state.decryptedThumbnailUrl) {
return this.state.decryptedThumbnailUrl;
}
return this.state.decryptedUrl;
} else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url, 800, 600);
}
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this.fixupHeight();
const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null);
if (content.info.thumbnail_file) {
thumbnailPromise = decryptFile(
content.info.thumbnail_file,
).then(function(blob) {
return readBlobAsDataUri(blob);
});
}
let decryptedBlob;
thumbnailPromise.then((thumbnailUrl) => {
return decryptFile(content.file).then(function(blob) {
decryptedBlob = blob;
return readBlobAsDataUri(blob);
}).then((contentUrl) => {
this.setState({
decryptedUrl: contentUrl,
decryptedThumbnailUrl: thumbnailUrl,
decryptedBlob: decryptedBlob,
});
this.props.onWidgetLoad();
});
}).catch((err) => {
console.warn("Unable to decrypt attachment: ", err);
// Set a placeholder image when we can't decrypt the image.
this.setState({
error: err,
});
}).done();
}
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onAction: function(payload) {
if (payload.action === "timeline_resize") {
this.fixupHeight();
}
},
fixupHeight: function() {
if (!this.refs.image) {
console.warn("Refusing to fix up height on MStickerBody with no image element");
return;
}
const content = this.props.mxEvent.getContent();
const timelineWidth = this.refs.body.offsetWidth;
const maxHeight = 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
//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);
},
render: function() {
const content = this.props.mxEvent.getContent();
if (this.state.error !== null) {
return (
<span className="mx_MImageBody" ref="body">
<img src="img/warning.svg" width="16" height="16" />
{ _t("Error decrypting image") }
</span>
);
}
if (content.file !== undefined && this.state.decryptedUrl === null) {
// Need to decrypt the attachment
// The attachment is decrypted in componentDidMount.
// For now add an img tag with a spinner.
return (
<span className="mx_MImageBody" ref="body">
<div className="mx_MImageBody_thumbnail" ref="image" style={{
"display": "flex",
"alignItems": "center",
"width": "100%",
}}>
<img src="img/spinner.gif" alt={content.body} width="32" height="32" style={{
"margin": "auto",
}} />
</div>
</span>
);
}
const contentUrl = this._getContentUrl();
let thumbUrl;
if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
thumbUrl = contentUrl;
} else {
thumbUrl = this._getThumbUrl();
}
if (thumbUrl) {
return (
<span className="mx_MImageBody" ref="body">
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
alt={content.body}
onLoad={this.props.onWidgetLoad}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} />
</span>
);
} else if (content.body) {
return (
<span className="mx_MImageBody">
{ _t("Image '%(Body)s' cannot be displayed.", {Body: content.body}) }
</span>
);
} else {
return (
<span className="mx_MImageBody">
{ _t("This image cannot be displayed.") }
</span>
);
}
},
});