mirror of https://github.com/vector-im/riot-web
332 lines
11 KiB
JavaScript
332 lines
11 KiB
JavaScript
/*
|
|
Copyright 2015 OpenMarket Ltd
|
|
Copyright 2017 New Vector Ltd
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// FIXME: these vars should be bundled up and attached to
|
|
// module.exports otherwise this will break when included by both
|
|
// react-sdk and apps layered on top.
|
|
|
|
const DEBUG = 0;
|
|
|
|
// The default colour keys to be replaced as referred to in CSS
|
|
// (should be overridden by .mx_theme_accentColor and .mx_theme_secondaryAccentColor)
|
|
const keyRgb = [
|
|
"rgb(118, 207, 166)", // Vector Green
|
|
"rgb(234, 245, 240)", // Vector Light Green
|
|
"rgb(211, 239, 225)", // Unused: BottomLeftMenu (20% Green overlaid on Light Green)
|
|
];
|
|
|
|
// Some algebra workings for calculating the tint % of Vector Green & Light Green
|
|
// x * 118 + (1 - x) * 255 = 234
|
|
// x * 118 + 255 - 255 * x = 234
|
|
// x * 118 - x * 255 = 234 - 255
|
|
// (255 - 118) x = 255 - 234
|
|
// x = (255 - 234) / (255 - 118) = 0.16
|
|
|
|
// The colour keys to be replaced as referred to in SVGs
|
|
const keyHex = [
|
|
"#76CFA6", // Vector Green
|
|
"#EAF5F0", // Vector Light Green
|
|
"#D3EFE1", // Unused: BottomLeftMenu (20% Green overlaid on Light Green)
|
|
"#FFFFFF", // white highlights of the SVGs (for switching to dark theme)
|
|
];
|
|
|
|
// cache of our replacement colours
|
|
// defaults to our keys.
|
|
const colors = [
|
|
keyHex[0],
|
|
keyHex[1],
|
|
keyHex[2],
|
|
keyHex[3],
|
|
];
|
|
|
|
const cssFixups = [
|
|
// {
|
|
// style: a style object that should be fixed up taken from a stylesheet
|
|
// attr: name of the attribute to be clobbered, e.g. 'color'
|
|
// index: ordinal of primary, secondary or tertiary
|
|
// }
|
|
];
|
|
|
|
// CSS attributes to be fixed up
|
|
const cssAttrs = [
|
|
"color",
|
|
"backgroundColor",
|
|
"borderColor",
|
|
"borderTopColor",
|
|
"borderBottomColor",
|
|
"borderLeftColor",
|
|
];
|
|
|
|
const svgAttrs = [
|
|
"fill",
|
|
"stroke",
|
|
];
|
|
|
|
let cached = false;
|
|
|
|
function hexToRgb(color) {
|
|
if (color[0] === '#') color = color.slice(1);
|
|
if (color.length === 3) {
|
|
color = color[0] + color[0] +
|
|
color[1] + color[1] +
|
|
color[2] + color[2];
|
|
}
|
|
const val = parseInt(color, 16);
|
|
const r = (val >> 16) & 255;
|
|
const g = (val >> 8) & 255;
|
|
const b = val & 255;
|
|
return [r, g, b];
|
|
}
|
|
|
|
function rgbToHex(rgb) {
|
|
const val = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
|
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);
|
|
},
|
|
|
|
getKeyRgb: function() {
|
|
return keyRgb;
|
|
},
|
|
|
|
getCurrentColors: function() {
|
|
return colors;
|
|
},
|
|
|
|
tint: function(primaryColor, secondaryColor, tertiaryColor) {
|
|
if (!cached) {
|
|
this.calcCssFixups();
|
|
cached = true;
|
|
}
|
|
|
|
if (!primaryColor) {
|
|
primaryColor = keyRgb[0];
|
|
secondaryColor = keyRgb[1];
|
|
}
|
|
|
|
if (!secondaryColor) {
|
|
const x = 0.16; // average weighting factor calculated from vector green & light green
|
|
const rgb = hexToRgb(primaryColor);
|
|
rgb[0] = x * rgb[0] + (1 - x) * 255;
|
|
rgb[1] = x * rgb[1] + (1 - x) * 255;
|
|
rgb[2] = x * rgb[2] + (1 - x) * 255;
|
|
secondaryColor = rgbToHex(rgb);
|
|
}
|
|
|
|
if (!tertiaryColor) {
|
|
const x = 0.19;
|
|
const rgb1 = hexToRgb(primaryColor);
|
|
const rgb2 = hexToRgb(secondaryColor);
|
|
rgb1[0] = x * rgb1[0] + (1 - x) * rgb2[0];
|
|
rgb1[1] = x * rgb1[1] + (1 - x) * rgb2[1];
|
|
rgb1[2] = x * rgb1[2] + (1 - x) * rgb2[2];
|
|
tertiaryColor = rgbToHex(rgb1);
|
|
}
|
|
|
|
if (colors[0] === primaryColor &&
|
|
colors[1] === secondaryColor &&
|
|
colors[2] === tertiaryColor) {
|
|
return;
|
|
}
|
|
|
|
colors[0] = primaryColor;
|
|
colors[1] = secondaryColor;
|
|
colors[2] = tertiaryColor;
|
|
|
|
if (DEBUG) console.log("Tinter.tint");
|
|
|
|
// go through manually fixing up the stylesheets.
|
|
this.applyCssFixups();
|
|
|
|
// tell all the SVGs to go fix themselves up
|
|
// we don't do this as a dispatch otherwise it will visually lag
|
|
tintables.forEach(function(tintable) {
|
|
tintable();
|
|
});
|
|
},
|
|
|
|
tintSvgWhite: function(whiteColor) {
|
|
if (!whiteColor) {
|
|
whiteColor = colors[3];
|
|
}
|
|
if (colors[3] === whiteColor) {
|
|
return;
|
|
}
|
|
colors[3] = whiteColor;
|
|
tintables.forEach(function(tintable) {
|
|
tintable();
|
|
});
|
|
},
|
|
|
|
calcCssFixups: function() {
|
|
if (DEBUG) console.log("calcCssFixups start");
|
|
|
|
cssFixups.length = 0;
|
|
|
|
// update keyRgb from the current theme CSS itself, if it defines it
|
|
if (document.getElementById('mx_theme_accentColor')) {
|
|
keyRgb[0] = window.getComputedStyle(
|
|
document.getElementById('mx_theme_accentColor')
|
|
).color;
|
|
}
|
|
if (document.getElementById('mx_theme_secondaryAccentColor')) {
|
|
keyRgb[1] = window.getComputedStyle(
|
|
document.getElementById('mx_theme_secondaryAccentColor')
|
|
).color;
|
|
}
|
|
|
|
for (let i = 0; i < document.styleSheets.length; i++) {
|
|
const ss = document.styleSheets[i];
|
|
if (!ss) continue; // well done safari >:(
|
|
// Chromium apparently sometimes returns null here; unsure why.
|
|
// see $14534907369972FRXBx:matrix.org in HQ
|
|
// ...ah, it's because there's a third party extension like
|
|
// privacybadger inserting its own stylesheet in there with a
|
|
// resource:// URI or something which results in a XSS error.
|
|
// See also #vector:matrix.org/$145357669685386ebCfr:matrix.org
|
|
// ...except some browsers apparently return stylesheets without
|
|
// hrefs, which we have no choice but ignore right now
|
|
|
|
// XXX seriously? we are hardcoding the name of vector's CSS file in
|
|
// here?
|
|
//
|
|
// Why do we need to limit it to vector's CSS file anyway - if there
|
|
// are other CSS files affecting the doc don't we want to apply the
|
|
// same transformations to them?
|
|
//
|
|
// Iterating through the CSS looking for matches to hack on feels
|
|
// pretty horrible anyway. And what if the application skin doesn't use
|
|
// Vector Green as its primary color?
|
|
// --richvdh
|
|
|
|
// Yes, tinting assumes that you are using the Riot skin for now.
|
|
// The right solution will be to move the CSS over to react-sdk.
|
|
// And yes, the default assets for the base skin might as well use
|
|
// Vector Green as any other colour.
|
|
// --matthew
|
|
|
|
if (ss.href && !ss.href.match(/\/bundle.*\.css$/)) continue;
|
|
if (ss.disabled) continue;
|
|
if (!ss.cssRules) continue;
|
|
|
|
for (let j = 0; j < ss.cssRules.length; j++) {
|
|
const rule = ss.cssRules[j];
|
|
if (!rule.style) continue;
|
|
if (rule.selectorText && rule.selectorText.match(/#mx_theme/)) continue;
|
|
for (let k = 0; k < cssAttrs.length; k++) {
|
|
const attr = cssAttrs[k];
|
|
for (let l = 0; l < keyRgb.length; l++) {
|
|
if (rule.style[attr] === keyRgb[l]) {
|
|
cssFixups.push({
|
|
style: rule.style,
|
|
attr: attr,
|
|
index: l,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (DEBUG) console.log("calcCssFixups end");
|
|
},
|
|
|
|
applyCssFixups: function() {
|
|
if (DEBUG) console.log("applyCssFixups start");
|
|
for (let i = 0; i < cssFixups.length; i++) {
|
|
const cssFixup = cssFixups[i];
|
|
cssFixup.style[cssFixup.attr] = colors[cssFixup.index];
|
|
}
|
|
if (DEBUG) console.log("applyCssFixups end");
|
|
},
|
|
|
|
// XXX: we could just move this all into TintableSvg, but as it's so similar
|
|
// to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg)
|
|
// keeping it here for now.
|
|
calcSvgFixups: function(svgs) {
|
|
// go through manually fixing up SVG colours.
|
|
// we could do this by stylesheets, but keeping the stylesheets
|
|
// updated would be a PITA, so just brute-force search for the
|
|
// key colour; cache the element and apply.
|
|
|
|
if (DEBUG) console.log("calcSvgFixups start for " + svgs);
|
|
const fixups = [];
|
|
for (let i = 0; i < svgs.length; i++) {
|
|
var svgDoc;
|
|
try {
|
|
svgDoc = svgs[i].contentDocument;
|
|
} catch(e) {
|
|
let msg = 'Failed to get svg.contentDocument of ' + svgs[i].toString();
|
|
if (e.message) {
|
|
msg += e.message;
|
|
}
|
|
if (e.stack) {
|
|
msg += ' | stack: ' + e.stack;
|
|
}
|
|
console.error(e);
|
|
}
|
|
if (!svgDoc) continue;
|
|
const tags = svgDoc.getElementsByTagName("*");
|
|
for (let j = 0; j < tags.length; j++) {
|
|
const tag = tags[j];
|
|
for (let k = 0; k < svgAttrs.length; k++) {
|
|
const attr = svgAttrs[k];
|
|
for (let l = 0; l < keyHex.length; l++) {
|
|
if (tag.getAttribute(attr) &&
|
|
tag.getAttribute(attr).toUpperCase() === keyHex[l])
|
|
{
|
|
fixups.push({
|
|
node: tag,
|
|
attr: attr,
|
|
index: l,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (DEBUG) console.log("calcSvgFixups end");
|
|
|
|
return fixups;
|
|
},
|
|
|
|
applySvgFixups: function(fixups) {
|
|
if (DEBUG) console.log("applySvgFixups start for " + fixups);
|
|
for (let i = 0; i < fixups.length; i++) {
|
|
const svgFixup = fixups[i];
|
|
svgFixup.node.setAttribute(svgFixup.attr, colors[svgFixup.index]);
|
|
}
|
|
if (DEBUG) console.log("applySvgFixups end");
|
|
},
|
|
};
|