diff --git a/src/Tinter.js b/src/Tinter.js index 336fb90fa2..534a1d810b 100644 --- a/src/Tinter.js +++ b/src/Tinter.js @@ -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"); - }, + } }; diff --git a/src/components/views/elements/TintableSvg.js b/src/components/views/elements/TintableSvg.js index e8be5f3415..0157131506 100644 --- a/src/components/views/elements/TintableSvg.js +++ b/src/components/views/elements/TintableSvg.js @@ -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; diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index 3f29915561..60b0653f1f 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -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({
- + Download {text}