set css plugin theme properties

This commit is contained in:
antelle 2017-04-27 12:02:41 +02:00
parent a9e51b75ee
commit c4adde0560
6 changed files with 196 additions and 40 deletions

View File

@ -98,7 +98,8 @@ module.exports = function(grunt) {
{ test: /\.json$/, loader: 'json-loader' },
{ test: /argon2-asm\.min\.js$/, loader: 'raw-loader' },
{ test: /argon2\.wasm$/, loader: 'base64-loader' },
{ test: /argon2\.min\.js/, loader: 'raw-loader' }
{ test: /argon2\.min\.js/, loader: 'raw-loader' },
{ test: /\.scss$/, loader: 'raw-loader' }
]
},
plugins: [

View File

@ -1,6 +1,7 @@
const kdbxweb = require('kdbxweb');
const Backbone = require('backbone');
const PluginApi = require('./plugin-api');
const ThemeVars = require('./theme-vars');
const Logger = require('../util/logger');
const SettingsManager = require('../comp/settings-manager');
const IoCache = require('../storage/io-cache');
@ -205,7 +206,7 @@ const Plugin = Backbone.Model.extend(_.extend({}, PluginStatus, {
},
installWithResources() {
this.logger.info('Installing plugin code');
this.logger.info('Installing plugin resources');
const manifest = this.get('manifest');
const promises = [];
if (this.resources.css) {
@ -271,16 +272,37 @@ const Plugin = Backbone.Model.extend(_.extend({}, PluginStatus, {
applyCss(name, data, theme) {
return Promise.resolve().then(() => {
const text = kdbxweb.ByteUtils.bytesToString(data);
this.createElementInHead('style', 'plugin-css-' + name, 'text/css', text);
const id = 'plugin-css-' + name;
this.createElementInHead('style', id, 'text/css', text);
if (theme) {
const locKey = this.getThemeLocaleKey(theme.name);
SettingsManager.allThemes[theme.name] = locKey;
BaseLocale[locKey] = theme.title;
for (const styleSheet of document.styleSheets) {
if (styleSheet.ownerNode.id === id) {
this.processThemeStyleSheet(styleSheet, theme);
break;
}
}
}
this.logger.debug('Plugin style installed');
});
},
processThemeStyleSheet(styleSheet, theme) {
const themeSelector = '.th-' + theme.name;
for (const rule of styleSheet.cssRules) {
if (rule.selectorText === themeSelector) {
this.addThemeVariables(rule);
break;
}
}
},
addThemeVariables(rule) {
ThemeVars.apply(rule.style);
},
applyJs(name, data) {
return Promise.resolve().then(() => {
let text = kdbxweb.ByteUtils.bytesToString(data);

View File

@ -0,0 +1,117 @@
const ThemeVarsScss = require('../../styles/base/_theme-vars.scss');
const ThemeDefaults = require('../../styles/themes/_theme-defaults.scss');
const Color = require('../util/color');
const ThemeVars = {
themeDefaults: null,
init() {
if (this.themeDefaults) {
return;
}
this.themeDefaults = {};
const propRegex = /\s([\w\-]+):\s*([^,\s]+)/g;
let match;
do {
match = propRegex.exec(ThemeDefaults);
if (match) {
const [, name, value] = match;
this.themeDefaults['--' + name] = value;
}
} while (match);
},
apply(cssStyle) {
this.init();
const lines = ThemeVarsScss.split('\n');
for (const line of lines) {
const match = line.match(/\s*([^:]+):\s*(.*?),?\s*$/);
if (!match) {
continue;
}
const [, name, def] = match;
const propName = '--' + name;
const currentValue = cssStyle.getPropertyValue(propName);
if (currentValue) {
continue;
}
let result = def.replace(/map-get\(\$t,\s*([\w\-]+)\)/g, '--$1');
let replaced = true;
const locals = [];
while (replaced) {
replaced = false;
result = result.replace(/([\w\-]+)\([^()]+\)/, fnText => {
replaced = true;
const [, name, argsStr] = fnText.match(/([\w\-]+)\((.*)\)/);
const args = argsStr.trim().split(/\s*,\s*/).filter(arg => arg).map(arg => this.resolveArg(arg, cssStyle, locals));
locals.push(this.fn[name](...args));
return 'L' + (locals.length - 1);
});
}
result = locals[locals.length - 1];
cssStyle.setProperty(propName, result);
}
},
resolveArg(arg, cssStyle, locals) {
if (/^--/.test(arg)) {
let cssProp = cssStyle.getPropertyValue(arg);
if (cssProp) {
cssProp = cssProp.trim();
}
if (cssProp) {
arg = cssProp;
} else {
if (this.themeDefaults[arg]) {
arg = this.themeDefaults[arg];
} else {
throw new Error('Css property missing: ' + arg);
}
}
}
if (/^L/.test(arg)) {
return locals[arg.substr(1)];
}
if (/%$/.test(arg)) {
return arg.replace(/%$/, '') / 100;
}
if (/^-?[\d.]+?$/.test(arg)) {
return +arg;
}
if (/^(#|rgb)/.test(arg)) {
return new Color(arg);
}
throw new Error('Bad css arg: ' + arg);
},
fn: {
'mix'(color1, color2, percent) {
return color1.mix(color2, percent).toRgba();
},
'semi-mute-percent'(mutePercent) {
return mutePercent / 2;
},
'rgba'(color, alpha) {
const res = new Color(color);
res.a = alpha;
return res.toRgba();
},
'text-contrast-color'(color, lshift, thBg, thText) {
if (color.l - lshift >= thBg.l) {
return thText.toRgba();
}
return thBg.toRgba();
},
'lightness-alpha'(color, lightness, alpha) {
const res = new Color(color);
res.l += Math.min(0, Math.max(1, lightness));
res.a += Math.min(0, Math.max(1, alpha));
return res.toHsla();
},
'shade'(color, percent) {
return Color.black.mix(color, percent).toRgba();
}
}
};
module.exports = ThemeVars;

View File

@ -2,13 +2,37 @@ const Colors = require('../const/colors');
const KnownColors = {};
const Color = function(str) {
const start = str[0] === '#' ? 1 : 0;
const len = str.length === 3 ? 1 : 2;
this.r = parseInt(str.substr(start, len), 16);
this.g = parseInt(str.substr(start + len, len), 16);
this.b = parseInt(str.substr(start + len * 2, len), 16);
this.setHsl();
const Color = function(arg) {
const rgbaMatch = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(,\s*([\d.]+))?\)$/.exec(arg);
if (rgbaMatch) {
this.r = +rgbaMatch[1];
this.g = +rgbaMatch[2];
this.b = +rgbaMatch[2];
this.a = rgbaMatch[4] ? rgbaMatch[4] : 1;
this.setHsl();
} else {
const hexMatch = /^#?([0-9a-f]{3,6})$/i.exec(arg);
if (hexMatch) {
const digits = hexMatch[1];
const len = digits.length === 3 ? 1 : 2;
this.r = parseInt(digits.substr(0, len), 16);
this.g = parseInt(digits.substr(len, len), 16);
this.b = parseInt(digits.substr(len * 2, len), 16);
this.a = 1;
this.setHsl();
} else if (arg instanceof Color) {
this.r = arg.r;
this.g = arg.g;
this.b = arg.b;
this.h = arg.h;
this.s = arg.s;
this.l = arg.l;
this.a = arg.a;
} else {
this.r = this.g = this.b = this.h = this.s = this.l = 0;
this.a = 1;
}
}
};
Color.prototype.setHsl = function() {
@ -44,10 +68,28 @@ Color.prototype.toHex = function() {
return '#' + hex(this.r) + hex(this.g) + hex(this.b);
};
Color.prototype.toRgba = function() {
return `rgba(${Math.round(this.r)},${Math.round(this.g)},${Math.round(this.b)},${this.a})`;
};
Color.prototype.toHsla = function() {
return `hsla(${Math.round(this.h * 100)},${Math.round(this.s * 100)}%,${Math.round(this.l * 100)}%,${this.a})`;
};
Color.prototype.distanceTo = function(color) {
return Math.abs(this.h - color.h);
};
Color.prototype.mix = function(another, weight) {
const res = new Color(this);
const anotherWeight = 1 - weight;
res.r = this.r * weight + another.r * anotherWeight;
res.g = this.g * weight + another.g * anotherWeight;
res.b = this.b * weight + another.b * anotherWeight;
res.a = this.a * weight + another.a * anotherWeight;
return res;
};
Color.getNearest = function(colorStr) {
const color = new Color(colorStr);
if (!color.s) {
@ -73,6 +115,8 @@ _.forEach(Colors.ColorsValues, (val, name) => {
KnownColors[name] = new Color(val);
});
Color.black = new Color('#000');
function hex(num) {
const str = (num || 0).toString(16);
return str.length < 2 ? '0' + str : str;

View File

@ -11,7 +11,7 @@
"licence": "MIT",
"url": "https://keeweb.info",
"resources": {
"css": "wBU9HL+b7PPRUxzjRdSJnPCP+zHpmY7Uv/mSgLHDDKMwUilWws+Gh6d8ldtfRDPx3DLIQ4dU3QZ0UrJz7f9rnC0n7RXEzOY7TPgU9lsK5f/kDPBEIIAB/VaVy40Ii2G72sZVyS6oTB67Rw1MQO0OHEgreYhnD/IyWi7r2iPi8jEDmmyLIDx/oS6HNxU/30KqcFQYkqysfxpqqo6kQ5WJ4O7a9rrRnJZezU9IwnCKxmPgQSA3FtuUh0qTrK7aiFndWn/COxklKhMiaocvB5oZcoHYvlZYPFKYVAaDWVfSUW4+OsAaTbutjNtCuLSFLm0MCa6keVpFYgdRplDTYWlLyA=="
"css": "bW0s1nSdHaQvJS/EzLLrkIS14un5uXQ3MVWqQ+RuOP5/ZvtlOERvI8jPLtpyUuHfMOQItUVmult/jTI5/1ya0P1VHPYewo89Wwgy9s9umxnkQ3RfKVUZi0Kq/qhamV8dH37rTW7NUrrCayUBSu+LT36sl03+cixNRoRKTkaLayqFP/YZxyQFzbswiGMWkFE9PrP8vHJZMFa/4Bim1w85jZksheMRoODRLSs9TExTHe+Tteh9LxlswUAdL0CVfzI4NlnBbX++ua1p76MBs0DQOb1mVhkCfNRI0xE4vD7djcCqNZrMPgFPj/4d2SBY3NC4WG4Kg6Up286mS1LfdONyLg=="
},
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1QB+yQofkqIHbHFVAtWhjEFjaxvNekyQx/aK7nEpZzqM8ReoVWbJGVA+7z7MhymwZanbL8uAUrSpNTp5eFWltDksxqHqmXT4UcFU+4reLjYfgwjIaA3c9Q+2JA1Iowqbv3NDcDKm6Ug+dROr04VDCfYE4WRYgGdTYHDbJs5svxUgQ25uc0KKUWAvhYbSKsw43AwmbPbKkHdfZHiS5ZST99HVEJWxn3Kd2zLY9Kk70nu9MzypMLDqxUqjKgdeRCIyZeAYzB75miH3B5vMKhFcdmA8+D6WU2N+6gzKsY5BfqF729uFKSTo4JUKQ5fMU0lKSDHG4qGrkgnURfAUuj9YMQIDAQAB",
"theme": {

View File

@ -1,39 +1,11 @@
.th-rainbow {
/* basic theme style variables */
/* theme style variables */
--background-color: #FAFAFA;
--medium-color: #050505;
--text-color: #424243;
--action-color: #475FD7;
--error-color: #E75675;
--muted-color: #b1b1b1;
/* dependent theme style variables */
--muted-color-border: #d5d5d5;
--text-selection-bg-color: rgba(71, 95, 215, 0.3);
--text-selection-bg-color-error: rgba(231, 86, 117, 0.8);
--text-semi-muted-color: #424244;
--text-contrast-action-color: #FAFAFA;
--text-contrast-error-color: #FAFAFA;
--base-border-color: gray;
--accent-border-color: #5b5b5b;
--light-border-color: #e2e2e2;
--form-box-shadow-color-focus: rgba(50, 77, 210, 0.7);
--form-box-shadow-color-focus-error: rgba(228, 64, 99, 0.7);
--dropdown-box-shadow-color: rgba(5, 5, 5, 0.05);
--secondary-background-color: #e2e2e2;
--intermediate-background-color: #f3f3f3;
--intermediate-pressed-background-color: #f4f4f4;
--disabled-background-color: #eeeeee;
--action-background-color-focus: #394cac;
--action-background-color-focus-tr: rgba(57, 76, 172, 0.1);
--error-background-color-focus: #b9455e;
--error-background-color-focus-tr: rgba(185, 69, 94, 0.1);
--action-background-color-active: #3547a1;
--action-background-color-active-tr: rgba(53, 71, 161, 0.15);
--error-background-color-active: #ad4158;
--error-background-color-active-tr: rgba(173, 65, 88, 0.15);
--modal-background-color: rgba(250, 250, 250, 0.9);
--modal-background-color-tr: rgba(250, 250, 250, 0);
}
/* any other custom css goes here */