Merge branches 'develop' and 't3chguy/m.relates_to' of github.com:matrix-org/matrix-react-sdk into t3chguy/m.relates_to

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

# Conflicts:
#	src/components/structures/RoomView.js
This commit is contained in:
Michael Telatynski 2018-04-04 11:08:34 +01:00
commit 1d90835de0
No known key found for this signature in database
GPG key ID: 3F879DA5AD802A5E
28 changed files with 1651 additions and 556 deletions

View file

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

View file

@ -0,0 +1,149 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import MImageBody from './MImageBody';
import sdk from '../../../index';
import TintableSVG from '../elements/TintableSvg';
export default class MStickerBody extends MImageBody {
displayName: 'MStickerBody'
constructor(props) {
super(props);
this._onMouseEnter = this._onMouseEnter.bind(this);
this._onMouseLeave = this._onMouseLeave.bind(this);
this._onImageLoad = this._onImageLoad.bind(this);
}
_onMouseEnter() {
this.setState({showTooltip: true});
}
_onMouseLeave() {
this.setState({showTooltip: false});
}
_onImageLoad() {
this.setState({
placeholderClasses: 'mx_MStickerBody_placeholder_invisible',
});
const hidePlaceholderTimer = setTimeout(() => {
this.setState({
placeholderVisible: false,
imageClasses: 'mx_MStickerBody_image_visible',
});
}, 500);
this.setState({hidePlaceholderTimer});
if (this.props.onWidgetLoad) {
this.props.onWidgetLoad();
}
}
_afterComponentDidMount() {
if (this.refs.image.complete) {
// Image already loaded
this.setState({
placeholderVisible: false,
placeholderClasses: '.mx_MStickerBody_placeholder_invisible',
imageClasses: 'mx_MStickerBody_image_visible',
});
} else {
// Image not already loaded
this.setState({
placeholderVisible: true,
placeholderClasses: '',
imageClasses: '',
});
}
}
_afterComponentWillUnmount() {
if (this.state.hidePlaceholderTimer) {
clearTimeout(this.state.hidePlaceholderTimer);
this.setState({hidePlaceholderTimer: null});
}
}
_messageContent(contentUrl, thumbUrl, content) {
let tooltip;
const tooltipBody = (
this.props.mxEvent &&
this.props.mxEvent.getContent() &&
this.props.mxEvent.getContent().body) ?
this.props.mxEvent.getContent().body : null;
if (this.state.showTooltip && tooltipBody) {
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
tooltip = <RoomTooltip
className='mx_RoleButton_tooltip'
label={tooltipBody} />;
}
const gutterSize = 0;
let placeholderSize = 75;
let placeholderFixupHeight = '100px';
let placeholderTop = 0;
let placeholderLeft = 0;
if (content.info) {
placeholderTop = Math.floor((content.info.h/2) - (placeholderSize/2)) + 'px';
placeholderLeft = Math.floor((content.info.w/2) - (placeholderSize/2) + gutterSize) + 'px';
placeholderFixupHeight = content.info.h + 'px';
}
placeholderSize = placeholderSize + 'px';
// Body 'ref' required by MImageBody
return (
<span className='mx_MStickerBody' ref='body'
style={{
height: placeholderFixupHeight,
}}>
<div className={'mx_MStickerBody_image_container'}>
{ this.state.placeholderVisible &&
<div
className={'mx_MStickerBody_placeholder ' + this.state.placeholderClasses}
style={{
top: placeholderTop,
left: placeholderLeft,
}}
>
<TintableSVG
src={'img/icons-show-stickers.svg'}
width={placeholderSize}
height={placeholderSize} />
</div> }
<img
className={'mx_MStickerBody_image ' + this.state.imageClasses}
src={contentUrl}
ref='image'
alt={content.body}
onLoad={this._onImageLoad}
onMouseEnter={this._onMouseEnter}
onMouseLeave={this._onMouseLeave}
/>
{ tooltip }
</div>
</span>
);
}
// Empty to prevent default behaviour of MImageBody
onClick() {
}
}

View file

@ -65,15 +65,19 @@ module.exports = React.createClass({
let BodyType = UnknownBody;
if (msgtype && bodyTypes[msgtype]) {
BodyType = bodyTypes[msgtype];
} else if (this.props.mxEvent.getType() === 'm.sticker') {
BodyType = sdk.getComponent('messages.MStickerBody');
} else if (content.url) {
// Fallback to MFileBody if there's a content URL
BodyType = bodyTypes['m.file'];
}
return <BodyType ref="body" mxEvent={this.props.mxEvent} highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview}
tileShape={this.props.tileShape}
onWidgetLoad={this.props.onWidgetLoad} />;
return <BodyType
ref="body" mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview}
tileShape={this.props.tileShape}
onWidgetLoad={this.props.onWidgetLoad} />;
},
});