Cache the tinted SVGs for MFileBody as data URLs (#559)
* Use a list of callbacks for things that need tinting. Rather than gutwrenching the internals of TintableSVG inside the Tinter. * Share a data: url for the tinted download svg in MFileBody * Check image exists before tinting * Add comments * Use fetch+DomParser rather than XMLHttpRequest * Remove comment about XMLHttpRequestpull/21833/head
							parent
							
								
									218ced0276
								
							
						
					
					
						commit
						6ccc825f0d
					
				| 
						 | 
				
			
			@ -153,7 +153,25 @@ function rgbToHex(rgb) {
 | 
			
		|||
    return '#' + (0x1000000 + val).toString(16).slice(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List of functions to call when the tint changes.
 | 
			
		||||
const tintables = [];
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a callback to fire when the tint changes.
 | 
			
		||||
     * This is used to rewrite the tintable SVGs with the new tint.
 | 
			
		||||
     *
 | 
			
		||||
     * It's not possible to unregister a tintable callback. So this can only be
 | 
			
		||||
     * used to register a static callback. If a set of tintables will change
 | 
			
		||||
     * over time then the best bet is to register a single callback for the
 | 
			
		||||
     * entire set.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Function} tintable Function to call when the tint changes.
 | 
			
		||||
     */
 | 
			
		||||
    registerTintable : function(tintable) {
 | 
			
		||||
        tintables.push(tintable);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    tint: function(primaryColor, secondaryColor, tertiaryColor) {
 | 
			
		||||
 | 
			
		||||
        if (!cached) {
 | 
			
		||||
| 
						 | 
				
			
			@ -201,12 +219,9 @@ module.exports = {
 | 
			
		|||
 | 
			
		||||
        // tell all the SVGs to go fix themselves up
 | 
			
		||||
        // we don't do this as a dispatch otherwise it will visually lag
 | 
			
		||||
        var TintableSvg = sdk.getComponent("elements.TintableSvg");
 | 
			
		||||
        if (TintableSvg.mounts) {
 | 
			
		||||
            Object.keys(TintableSvg.mounts).forEach((id) => {
 | 
			
		||||
                TintableSvg.mounts[id].tint();
 | 
			
		||||
        tintables.forEach(function(tintable) {
 | 
			
		||||
            tintable();
 | 
			
		||||
        });
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // XXX: we could just move this all into TintableSvg, but as it's so similar
 | 
			
		||||
| 
						 | 
				
			
			@ -265,5 +280,5 @@ module.exports = {
 | 
			
		|||
            svgFixup.node.setAttribute(svgFixup.attr, colors[svgFixup.index]);
 | 
			
		||||
        }
 | 
			
		||||
        if (DEBUG) console.log("applySvgFixups end");
 | 
			
		||||
    },
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,4 +74,13 @@ var TintableSvg = React.createClass({
 | 
			
		|||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Register with the Tinter so that we will be told if the tint changes
 | 
			
		||||
Tinter.registerTintable(function() {
 | 
			
		||||
    if (TintableSvg.mounts) {
 | 
			
		||||
        Object.keys(TintableSvg.mounts).forEach((id) => {
 | 
			
		||||
            TintableSvg.mounts[id].tint();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = TintableSvg;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,42 @@ import filesize from 'filesize';
 | 
			
		|||
import MatrixClientPeg from '../../../MatrixClientPeg';
 | 
			
		||||
import sdk from '../../../index';
 | 
			
		||||
import {decryptFile} from '../../../utils/DecryptFile';
 | 
			
		||||
import Tinter from '../../../Tinter';
 | 
			
		||||
import 'isomorphic-fetch';
 | 
			
		||||
import q from 'q';
 | 
			
		||||
 | 
			
		||||
// A cached tinted copy of "img/download.svg"
 | 
			
		||||
var tintedDownloadImageURL;
 | 
			
		||||
// Track a list of mounted MFileBody instances so that we can update
 | 
			
		||||
// the "img/download.svg" when the tint changes.
 | 
			
		||||
var nextMountId = 0;
 | 
			
		||||
const mounts = {};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Updates the tinted copy of "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.
 | 
			
		||||
    q(fetch("img/download.svg")).then(function(response) {
 | 
			
		||||
        return response.text();
 | 
			
		||||
    }).then(function(svgText) {
 | 
			
		||||
        const svg = new DOMParser().parseFromString(svgText, "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();
 | 
			
		||||
        });
 | 
			
		||||
    }).done();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Tinter.registerTintable(updateTintedDownloadImage);
 | 
			
		||||
 | 
			
		||||
module.exports = React.createClass({
 | 
			
		||||
    displayName: 'MFileBody',
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +105,12 @@ module.exports = React.createClass({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    componentDidMount: function() {
 | 
			
		||||
        // Add this to the list of mounted components to receive notifications
 | 
			
		||||
        // when the tint changes.
 | 
			
		||||
        this.id = nextMountId++;
 | 
			
		||||
        mounts[this.id] = this;
 | 
			
		||||
        this.tint();
 | 
			
		||||
        // Check whether we need to decrypt the file content.
 | 
			
		||||
        const content = this.props.mxEvent.getContent();
 | 
			
		||||
        if (content.file !== undefined && this.state.decryptedUrl === null) {
 | 
			
		||||
            decryptFile(content.file).done((url) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -84,12 +125,23 @@ module.exports = React.createClass({
 | 
			
		|||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    componentWillUnmount: function() {
 | 
			
		||||
        // Remove this from the list of mounted components
 | 
			
		||||
        delete mounts[this.id];
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    tint: function() {
 | 
			
		||||
        // Update our tinted copy of "img/download.svg"
 | 
			
		||||
        if (this.refs.downloadImage) {
 | 
			
		||||
            this.refs.downloadImage.src = tintedDownloadImageURL;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    render: function() {
 | 
			
		||||
        const content = this.props.mxEvent.getContent();
 | 
			
		||||
 | 
			
		||||
        const text = this.presentableTextForFile(content);
 | 
			
		||||
 | 
			
		||||
        var TintableSvg = sdk.getComponent("elements.TintableSvg");
 | 
			
		||||
        if (content.file !== undefined && this.state.decryptedUrl === null) {
 | 
			
		||||
 | 
			
		||||
            // Need to decrypt the attachment
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +207,7 @@ module.exports = React.createClass({
 | 
			
		|||
                    <span className="mx_MFileBody">
 | 
			
		||||
                        <div className="mx_MImageBody_download">
 | 
			
		||||
                            <a href={contentUrl} target="_blank" rel="noopener" download={downloadAttr}>
 | 
			
		||||
                                <TintableSvg src="img/download.svg" width="12" height="14"/>
 | 
			
		||||
                                <img src={tintedDownloadImageURL} width="12" height="14" ref="downloadImage"/>
 | 
			
		||||
                                Download {text}
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue