Fix tinting for download icon
As shown in https://github.com/vector-im/element-web/issues/16546
This commit is contained in:
parent
af9f17219b
commit
a7debdd946
3 changed files with 42 additions and 87 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,19 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_MFileBody_download {
|
.mx_MFileBody_download {
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
|
|
||||||
|
.mx_MFileBody_download_icon {
|
||||||
|
// 12px instead of 14px to better match surrounding font size
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
mask-size: 12px;
|
||||||
|
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url("$(res)/img/download.svg");
|
||||||
|
background-color: $accent-color;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MFileBody_download a {
|
.mx_MFileBody_download a {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016, 2018, 2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2018 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,52 +17,24 @@ limitations under the License.
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import filesize from 'filesize';
|
import filesize from 'filesize';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {decryptFile} from '../../../utils/DecryptFile';
|
import {decryptFile} from '../../../utils/DecryptFile';
|
||||||
import Tinter from '../../../Tinter';
|
|
||||||
import request from 'browser-request';
|
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
import {mediaFromContent} from "../../../customisations/Media";
|
import {mediaFromContent} from "../../../customisations/Media";
|
||||||
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
|
|
||||||
|
let downloadIconUrl; // cached copy of the download.svg asset for the sandboxed iframe later on
|
||||||
|
|
||||||
// A cached tinted copy of require("../../../../res/img/download.svg")
|
async function cacheDownloadIcon() {
|
||||||
let tintedDownloadImageURL;
|
if (downloadIconUrl) return; // cached already
|
||||||
// Track a list of mounted MFileBody instances so that we can update
|
const svg = await fetch(require("../../../../res/img/download.svg")).then(r => r.text());
|
||||||
// the require("../../../../res/img/download.svg") when the tint changes.
|
downloadIconUrl = "data:image/svg+xml;base64," + window.btoa(svg);
|
||||||
let nextMountId = 0;
|
|
||||||
const mounts = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the tinted copy of require("../../../../res/img/download.svg") when the tint changes.
|
|
||||||
*/
|
|
||||||
function updateTintedDownloadImage() {
|
|
||||||
// Download the svg as an XML document.
|
|
||||||
// We could cache the XML response here, but since the tint rarely changes
|
|
||||||
// it's probably not worth it.
|
|
||||||
// Also note that we can't use fetch here because fetch doesn't support
|
|
||||||
// file URLs, which the download image will be if we're running from
|
|
||||||
// the filesystem (like in an Electron wrapper).
|
|
||||||
request({uri: require("../../../../res/img/download.svg")}, (err, response, body) => {
|
|
||||||
if (err) return;
|
|
||||||
|
|
||||||
const svg = new DOMParser().parseFromString(body, "image/svg+xml");
|
|
||||||
// Apply the fixups to the XML.
|
|
||||||
const fixups = Tinter.calcSvgFixups([{contentDocument: svg}]);
|
|
||||||
Tinter.applySvgFixups(fixups);
|
|
||||||
// Encoded the fixed up SVG as a data URL.
|
|
||||||
const svgString = new XMLSerializer().serializeToString(svg);
|
|
||||||
tintedDownloadImageURL = "data:image/svg+xml;base64," + window.btoa(svgString);
|
|
||||||
// Notify each mounted MFileBody that the URL has changed.
|
|
||||||
Object.keys(mounts).forEach(function(id) {
|
|
||||||
mounts[id].tint();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Tinter.registerTintable(updateTintedDownloadImage);
|
// Cache the asset immediately
|
||||||
|
cacheDownloadIcon();
|
||||||
|
|
||||||
// User supplied content can contain scripts, we have to be careful that
|
// User supplied content can contain scripts, we have to be careful that
|
||||||
// we don't accidentally run those script within the same origin as the
|
// we don't accidentally run those script within the same origin as the
|
||||||
|
@ -106,6 +77,7 @@ function computedStyle(element) {
|
||||||
}
|
}
|
||||||
const style = window.getComputedStyle(element, null);
|
const style = window.getComputedStyle(element, null);
|
||||||
let cssText = style.cssText;
|
let cssText = style.cssText;
|
||||||
|
// noinspection EqualityComparisonWithCoercionJS
|
||||||
if (cssText == "") {
|
if (cssText == "") {
|
||||||
// Firefox doesn't implement ".cssText" for computed styles.
|
// Firefox doesn't implement ".cssText" for computed styles.
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=137687
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=137687
|
||||||
|
@ -145,7 +117,6 @@ export default class MFileBody extends React.Component {
|
||||||
|
|
||||||
this._iframe = createRef();
|
this._iframe = createRef();
|
||||||
this._dummyLink = createRef();
|
this._dummyLink = createRef();
|
||||||
this._downloadImage = createRef();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,48 +153,18 @@ export default class MFileBody extends React.Component {
|
||||||
return media.srcHttp;
|
return media.srcHttp;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
// Add this to the list of mounted components to receive notifications
|
|
||||||
// when the tint changes.
|
|
||||||
this.id = nextMountId++;
|
|
||||||
mounts[this.id] = this;
|
|
||||||
this.tint();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
|
if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
|
||||||
this.props.onHeightChanged();
|
this.props.onHeightChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
// Remove this from the list of mounted components
|
|
||||||
delete mounts[this.id];
|
|
||||||
}
|
|
||||||
|
|
||||||
tint = () => {
|
|
||||||
// Update our tinted copy of require("../../../../res/img/download.svg")
|
|
||||||
if (this._downloadImage.current) {
|
|
||||||
this._downloadImage.current.src = tintedDownloadImageURL;
|
|
||||||
}
|
|
||||||
if (this._iframe.current) {
|
|
||||||
// If the attachment is encrypted then the download image
|
|
||||||
// will be inside the iframe so we wont be able to update
|
|
||||||
// it directly.
|
|
||||||
this._iframe.current.contentWindow.postMessage({
|
|
||||||
imgSrc: tintedDownloadImageURL,
|
|
||||||
style: computedStyle(this._dummyLink.current),
|
|
||||||
}, "*");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const content = this.props.mxEvent.getContent();
|
const content = this.props.mxEvent.getContent();
|
||||||
const text = this.presentableTextForFile(content);
|
const text = this.presentableTextForFile(content);
|
||||||
const isEncrypted = content.file !== undefined;
|
const isEncrypted = content.file !== undefined;
|
||||||
const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment");
|
const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment");
|
||||||
const contentUrl = this._getContentUrl();
|
const contentUrl = this._getContentUrl();
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
const fileSize = content.info ? content.info.size : null;
|
const fileSize = content.info ? content.info.size : null;
|
||||||
const fileType = content.info ? content.info.mimetype : "application/octet-stream";
|
const fileType = content.info ? content.info.mimetype : "application/octet-stream";
|
||||||
|
|
||||||
|
@ -280,7 +221,7 @@ export default class MFileBody extends React.Component {
|
||||||
// When the iframe loads we tell it to render a download link
|
// When the iframe loads we tell it to render a download link
|
||||||
const onIframeLoad = (ev) => {
|
const onIframeLoad = (ev) => {
|
||||||
ev.target.contentWindow.postMessage({
|
ev.target.contentWindow.postMessage({
|
||||||
imgSrc: tintedDownloadImageURL,
|
imgSrc: downloadIconUrl,
|
||||||
style: computedStyle(this._dummyLink.current),
|
style: computedStyle(this._dummyLink.current),
|
||||||
blob: this.state.decryptedBlob,
|
blob: this.state.decryptedBlob,
|
||||||
// Set a download attribute for encrypted files so that the file
|
// Set a download attribute for encrypted files so that the file
|
||||||
|
@ -384,7 +325,7 @@ export default class MFileBody extends React.Component {
|
||||||
{placeholder}
|
{placeholder}
|
||||||
<div className="mx_MFileBody_download">
|
<div className="mx_MFileBody_download">
|
||||||
<a {...downloadProps}>
|
<a {...downloadProps}>
|
||||||
<img src={tintedDownloadImageURL} width="12" height="14" ref={this._downloadImage} />
|
<span className="mx_MFileBody_download_icon" />
|
||||||
{ _t("Download %(text)s", { text: text }) }
|
{ _t("Download %(text)s", { text: text }) }
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
function remoteRender(event) {
|
function remoteRender(event) {
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
|
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("span"); // we'll mask it as an image
|
||||||
img.id = "img";
|
img.id = "img";
|
||||||
img.src = data.imgSrc;
|
|
||||||
img.style = data.imgStyle;
|
|
||||||
|
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.id = "a";
|
a.id = "a";
|
||||||
|
@ -16,6 +14,21 @@ function remoteRender(event) {
|
||||||
a.appendChild(img);
|
a.appendChild(img);
|
||||||
a.appendChild(document.createTextNode(data.textContent));
|
a.appendChild(document.createTextNode(data.textContent));
|
||||||
|
|
||||||
|
// Apply image style after so we can steal the anchor's colour.
|
||||||
|
// Style copied from a rendered version of mx_MFileBody_download_icon
|
||||||
|
img.style = "" +
|
||||||
|
"width: 12px; height: 12px;" +
|
||||||
|
"-webkit-mask-size: 12px;" +
|
||||||
|
"mask-size: 12px;" +
|
||||||
|
"-webkit-mask-position: center;" +
|
||||||
|
"mask-position: center;" +
|
||||||
|
"-webkit-mask-repeat: no-repeat;" +
|
||||||
|
"mask-repeat: no-repeat;" +
|
||||||
|
`-webkit-mask-image: url('${data.imgSrc}');` +
|
||||||
|
`mask-image: url('${data.imgSrc}');` +
|
||||||
|
`background-color: ${a.style.color};` +
|
||||||
|
"display: inline-block;";
|
||||||
|
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
// Don't display scrollbars if the link takes more than one line to display.
|
// Don't display scrollbars if the link takes more than one line to display.
|
||||||
body.style = "margin: 0px; overflow: hidden";
|
body.style = "margin: 0px; overflow: hidden";
|
||||||
|
@ -26,20 +39,8 @@ function remoteRender(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteSetTint(event) {
|
|
||||||
const data = event.data;
|
|
||||||
|
|
||||||
const img = document.getElementById("img");
|
|
||||||
img.src = data.imgSrc;
|
|
||||||
img.style = data.imgStyle;
|
|
||||||
|
|
||||||
const a = document.getElementById("a");
|
|
||||||
a.style = data.style;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onmessage = function(e) {
|
window.onmessage = function(e) {
|
||||||
if (e.origin === window.location.origin) {
|
if (e.origin === window.location.origin) {
|
||||||
if (e.data.blob) remoteRender(e);
|
if (e.data.blob) remoteRender(e);
|
||||||
else remoteSetTint(e);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue