mirror of
https://github.com/element-hq/element-web.git
synced 2024-11-30 23:31:28 +03:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
5c80c4fd2f
5 changed files with 153 additions and 246 deletions
|
@ -20,5 +20,29 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MImageBody_thumbnail {
|
.mx_MImageBody_thumbnail {
|
||||||
max-width: 100%;
|
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%);
|
||||||
|
}
|
||||||
|
|
|
@ -14,33 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_MStickerBody {
|
.mx_MStickerBody_wrapper {
|
||||||
display: block;
|
padding: 20px 0px;
|
||||||
margin-right: 34px;
|
|
||||||
min-height: 110px;
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MStickerBody_image_container {
|
.mx_MStickerBody_tooltip {
|
||||||
display: inline-block;
|
position: absolute;
|
||||||
position: relative;
|
top: 50%;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,8 @@ import PropTypes from 'prop-types';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
|
||||||
import MFileBody from './MFileBody';
|
import MFileBody from './MFileBody';
|
||||||
import ImageUtils from '../../../ImageUtils';
|
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
|
||||||
import { decryptFile } from '../../../utils/DecryptFile';
|
import { decryptFile } from '../../../utils/DecryptFile';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -52,14 +50,12 @@ export default class extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onAction = this.onAction.bind(this);
|
|
||||||
this.onImageError = this.onImageError.bind(this);
|
this.onImageError = this.onImageError.bind(this);
|
||||||
this.onImageLoad = this.onImageLoad.bind(this);
|
this.onImageLoad = this.onImageLoad.bind(this);
|
||||||
this.onImageEnter = this.onImageEnter.bind(this);
|
this.onImageEnter = this.onImageEnter.bind(this);
|
||||||
this.onImageLeave = this.onImageLeave.bind(this);
|
this.onImageLeave = this.onImageLeave.bind(this);
|
||||||
this.onClientSync = this.onClientSync.bind(this);
|
this.onClientSync = this.onClientSync.bind(this);
|
||||||
this.onClick = this.onClick.bind(this);
|
this.onClick = this.onClick.bind(this);
|
||||||
this.fixupHeight = this.fixupHeight.bind(this);
|
|
||||||
this._isGif = this._isGif.bind(this);
|
this._isGif = this._isGif.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -68,6 +64,8 @@ export default class extends React.Component {
|
||||||
decryptedBlob: null,
|
decryptedBlob: null,
|
||||||
error: null,
|
error: null,
|
||||||
imgError: false,
|
imgError: false,
|
||||||
|
imgLoaded: false,
|
||||||
|
hover: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +120,8 @@ export default class extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onImageEnter(e) {
|
onImageEnter(e) {
|
||||||
|
this.setState({ hover: true });
|
||||||
|
|
||||||
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,8 @@ export default class extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onImageLeave(e) {
|
onImageLeave(e) {
|
||||||
|
this.setState({ hover: false });
|
||||||
|
|
||||||
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -145,6 +147,7 @@ export default class extends React.Component {
|
||||||
|
|
||||||
onImageLoad() {
|
onImageLoad() {
|
||||||
this.props.onWidgetLoad();
|
this.props.onWidgetLoad();
|
||||||
|
this.setState({ imgLoaded: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
_getContentUrl() {
|
_getContentUrl() {
|
||||||
|
@ -179,7 +182,6 @@ export default class extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
const content = this.props.mxEvent.getContent();
|
const content = this.props.mxEvent.getContent();
|
||||||
if (content.file !== undefined && this.state.decryptedUrl === null) {
|
if (content.file !== undefined && this.state.decryptedUrl === null) {
|
||||||
let thumbnailPromise = Promise.resolve(null);
|
let thumbnailPromise = Promise.resolve(null);
|
||||||
|
@ -210,7 +212,6 @@ export default class extends React.Component {
|
||||||
});
|
});
|
||||||
}).done();
|
}).done();
|
||||||
}
|
}
|
||||||
this.fixupHeight();
|
|
||||||
this._afterComponentDidMount();
|
this._afterComponentDidMount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +222,6 @@ export default class extends React.Component {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
dis.unregister(this.dispatcherRef);
|
|
||||||
this.context.matrixClient.removeListener('sync', this.onClientSync);
|
this.context.matrixClient.removeListener('sync', this.onClientSync);
|
||||||
this._afterComponentWillUnmount();
|
this._afterComponentWillUnmount();
|
||||||
|
|
||||||
|
@ -238,60 +238,87 @@ export default class extends React.Component {
|
||||||
_afterComponentWillUnmount() {
|
_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) {
|
_messageContent(contentUrl, thumbUrl, content) {
|
||||||
|
// The maximum height of the thumbnail as it is rendered as an <img>
|
||||||
|
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 = <img src="img/spinner.gif" alt={content.body} width="32" height="32" />;
|
||||||
|
} 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 = <img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
|
||||||
|
style={{ "max-width": maxWidth + "px" }}
|
||||||
|
alt={content.body}
|
||||||
|
onError={this.onImageError}
|
||||||
|
onLoad={this.onImageLoad}
|
||||||
|
onMouseEnter={this.onImageEnter}
|
||||||
|
onMouseLeave={this.onImageLeave} />;
|
||||||
|
}
|
||||||
|
|
||||||
const thumbnail = (
|
const thumbnail = (
|
||||||
<a href={contentUrl} onClick={this.onClick}>
|
<div className="mx_MImageBody_thumbnail_container" style={{ "max-height": maxHeight + "px" }} >
|
||||||
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
|
{ /* Calculate aspect ratio, using %padding will size _container correctly */ }
|
||||||
alt={content.body}
|
<div style={{ paddingBottom: (100 * content.info.h / content.info.w) + '%' }}></div>
|
||||||
onError={this.onImageError}
|
|
||||||
onLoad={this.onImageLoad}
|
{ showPlaceholder &&
|
||||||
onMouseEnter={this.onImageEnter}
|
<div className="mx_MImageBody_thumbnail" style={{
|
||||||
onMouseLeave={this.onImageLeave} />
|
// Constrain width here so that spinner appears central to the loaded thumbnail
|
||||||
</a>
|
"max-width": content.info.w + "px",
|
||||||
|
}}>
|
||||||
|
<div className="mx_MImageBody_thumbnail_spinner">
|
||||||
|
{ placeholder }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div style={{display: !showPlaceholder ? undefined : 'none'}}>
|
||||||
|
{ img }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ this.state.hover && this.getTooltip() }
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return this.wrapImage(contentUrl, thumbnail);
|
||||||
<span className="mx_MImageBody" ref="body">
|
}
|
||||||
{ thumbUrl && !this.state.imgError ? thumbnail : '' }
|
|
||||||
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
|
// Overidden by MStickerBody
|
||||||
</span>
|
wrapImage(contentUrl, children) {
|
||||||
);
|
return <a href={contentUrl} onClick={this.onClick}>
|
||||||
|
{children}
|
||||||
|
</a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overidden by MStickerBody
|
||||||
|
getPlaceholder() {
|
||||||
|
// MImageBody doesn't show a placeholder whilst the image loads, (but it could do)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overidden by MStickerBody
|
||||||
|
getTooltip() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overidden by MStickerBody
|
||||||
|
getFileBody() {
|
||||||
|
return <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -306,25 +333,6 @@ export default class extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
const contentUrl = this._getContentUrl();
|
||||||
let thumbUrl;
|
let thumbUrl;
|
||||||
if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
|
if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
|
||||||
|
@ -333,6 +341,12 @@ export default class extends React.Component {
|
||||||
thumbUrl = this._getThumbUrl();
|
thumbUrl = this._getThumbUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._messageContent(contentUrl, thumbUrl, content);
|
const thumbnail = this._messageContent(contentUrl, thumbUrl, content);
|
||||||
|
const fileBody = this.getFileBody();
|
||||||
|
|
||||||
|
return <span className="mx_MImageBody" ref="body">
|
||||||
|
{ thumbnail }
|
||||||
|
{ fileBody }
|
||||||
|
</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,143 +18,39 @@ limitations under the License.
|
||||||
|
|
||||||
import MImageBody from './MImageBody';
|
import MImageBody from './MImageBody';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import TintableSVG from '../elements/TintableSvg';
|
|
||||||
|
|
||||||
export default class MStickerBody extends MImageBody {
|
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
// The pixel size of sticker images is generally larger than their intended display
|
|
||||||
// size so they render at native reolution on HiDPI displays. We therefore need to
|
|
||||||
// explicity set the size so they render at the intended size.
|
|
||||||
// XXX: This will be clobberred when we run fixupHeight(), but we need to do it
|
|
||||||
// here otherwise the stickers are momentarily displayed at the pixel size.
|
|
||||||
const imageStyle = {
|
|
||||||
height: content.info.h,
|
|
||||||
// leave the browser the calculate the width automatically
|
|
||||||
};
|
|
||||||
|
|
||||||
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}
|
|
||||||
style={imageStyle}
|
|
||||||
ref='image'
|
|
||||||
alt={content.body}
|
|
||||||
onLoad={this._onImageLoad}
|
|
||||||
onMouseEnter={this._onMouseEnter}
|
|
||||||
onMouseLeave={this._onMouseLeave}
|
|
||||||
/>
|
|
||||||
{ tooltip }
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty to prevent default behaviour of MImageBody
|
// Empty to prevent default behaviour of MImageBody
|
||||||
onClick() {
|
onClick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MStickerBody doesn't need a wrapping `<a href=...>`, but it does need extra padding
|
||||||
|
// which is added by mx_MStickerBody_wrapper
|
||||||
|
wrapImage(contentUrl, children) {
|
||||||
|
return <div className="mx_MStickerBody_wrapper"> { children } </div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder to show in place of the sticker image if
|
||||||
|
// img onLoad hasn't fired yet.
|
||||||
|
getPlaceholder() {
|
||||||
|
const TintableSVG = sdk.getComponent('elements.TintableSvg');
|
||||||
|
return <TintableSVG src="img/icons-show-stickers.svg" width="75" height="75" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tooltip to show on mouse over
|
||||||
|
getTooltip() {
|
||||||
|
const content = this.props.mxEvent && this.props.mxEvent.getContent();
|
||||||
|
|
||||||
|
if (!content || !content.body || !content.info || !content.info.w) return null;
|
||||||
|
|
||||||
|
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
|
||||||
|
return <div style={{left: content.info.w + 'px'}} className="mx_MStickerBody_tooltip">
|
||||||
|
<RoomTooltip label={content.body} />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show "Download this_file.png ..."
|
||||||
|
getFileBody() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,12 +147,7 @@ module.exports = React.createClass({
|
||||||
// For now add an img tag with a spinner.
|
// For now add an img tag with a spinner.
|
||||||
return (
|
return (
|
||||||
<span className="mx_MVideoBody" ref="body">
|
<span className="mx_MVideoBody" ref="body">
|
||||||
<div className="mx_MImageBody_thumbnail" ref="image" style={{
|
<div className="mx_MImageBody_thumbnail mx_MImageBody_thumbnail_spinner" ref="image">
|
||||||
"display": "flex",
|
|
||||||
"align-items": "center",
|
|
||||||
"justify-items": "center",
|
|
||||||
"width": "100%",
|
|
||||||
}}>
|
|
||||||
<img src="img/spinner.gif" alt={content.body} width="16" height="16" />
|
<img src="img/spinner.gif" alt={content.body} width="16" height="16" />
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
Loading…
Reference in a new issue