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
	
	 Mark Haines
						Mark Haines