preserve opacity in colorpicker for preprocessor uso config (#1200)

USO has always produced 6-digit #rrggbb so some styles rely on it
and use /*[[color]]*/11 notation to specify the transparency.
Now we will try to preserve the opacity customized by the user
via colorpicker unless the style specifies it inline.
This commit is contained in:
tophf 2021-03-05 17:25:05 +03:00 committed by GitHub
parent 7d08fea5e1
commit 65ac351699
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 60 deletions

View File

@ -5,6 +5,7 @@
/* global db */ /* global db */
/* global prefs */ /* global prefs */
/* global tabMan */ /* global tabMan */
/* global usercssMan */
'use strict'; 'use strict';
/* /*
@ -202,7 +203,12 @@ const styleMan = (() => {
/** @returns {Promise<StyleObj[]>} */ /** @returns {Promise<StyleObj[]>} */
async importMany(items) { async importMany(items) {
if (ready.then) await ready; if (ready.then) await ready;
items.forEach(beforeSave); for (const style of items) {
beforeSave(style);
if (style.sourceCode && style.usercssData) {
await usercssMan.buildCode(style);
}
}
const events = await db.exec('putMany', items); const events = await db.exec('putMany', items);
return Promise.all(items.map((item, i) => { return Promise.all(items.map((item, i) => {
afterSave(item, events[i]); afterSave(item, events[i]);
@ -210,12 +216,6 @@ const styleMan = (() => {
})); }));
}, },
/** @returns {Promise<StyleObj>} */
async import(data) {
if (ready.then) await ready;
return handleSave(await saveStyle(data), 'import');
},
/** @returns {Promise<StyleObj>} */ /** @returns {Promise<StyleObj>} */
async install(style, reason = null) { async install(style, reason = null) {
if (ready.then) await ready; if (ready.then) await ready;

View File

@ -16,29 +16,27 @@ const colorConverter = (() => {
// NAMED_COLORS is added below // NAMED_COLORS is added below
}; };
function format(color = '', type = color.type, hexUppercase) { function format(color = '', type = color.type, hexUppercase, usoMode) {
if (!color || !type) return typeof color === 'string' ? color : ''; if (!color || !type) return typeof color === 'string' ? color : '';
const a = formatAlpha(color.a); const {a} = color;
const hasA = Boolean(a); let aStr = formatAlpha(a);
if (type === 'rgb' && color.type === 'hsl') { if (aStr) aStr = ', ' + aStr;
if (type !== 'hsl' && color.type === 'hsl') {
color = HSVtoRGB(HSLtoHSV(color)); color = HSVtoRGB(HSLtoHSV(color));
} }
const {r, g, b, h, s, l} = color; const {r, g, b, h, s, l} = color;
switch (type) { switch (type) {
case 'hex': { case 'hex': {
const rgbStr = (0x1000000 + (r << 16) + (g << 8) + (b | 0)).toString(16).slice(1); let res = '#' + hex2(r) + hex2(g) + hex2(b) + (aStr ? hex2(Math.round(a * 255)) : '');
const aStr = hasA ? (0x100 + Math.round(a * 255)).toString(16).slice(1) : ''; if (!usoMode) res = res.replace(/^#(.)\1(.)\2(.)\3(?:(.)\4)?$/, '#$1$2$3$4');
const hexStr = `#${rgbStr + aStr}`.replace(/^#(.)\1(.)\2(.)\3(?:(.)\4)?$/, '#$1$2$3$4'); return hexUppercase ? res.toUpperCase() : res;
return hexUppercase ? hexStr.toUpperCase() : hexStr.toLowerCase(); }
case 'rgb': {
const rgb = [r, g, b].map(Math.round).join(', ');
return usoMode ? rgb : `rgb${aStr ? 'a' : ''}(${rgb}${aStr})`;
} }
case 'rgb':
return hasA ?
`rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${a})` :
`rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
case 'hsl': case 'hsl':
return hasA ? return `hsl${aStr ? 'a' : ''}(${h}, ${s}%, ${l}%${aStr})`;
`hsla(${h}, ${s}%, ${l}%, ${a})` :
`hsl(${h}, ${s}%, ${l}%)`;
} }
} }
@ -215,6 +213,10 @@ const colorConverter = (() => {
const int = Math.round(num); const int = Math.round(num);
return Math.abs(int - num) < 1e-3 ? int : num; return Math.abs(int - num) < 1e-3 ? int : num;
} }
function hex2(val) {
return (val < 16 ? '0' : '') + (val >> 0).toString(16);
}
})(); })();
colorConverter.NAMED_COLORS = new Map([ colorConverter.NAMED_COLORS = new Map([

View File

@ -47,53 +47,50 @@ const BUILDERS = Object.assign(Object.create(null), {
uso: { uso: {
pre(source, vars) { pre(source, vars) {
require(['/js/color/color-converter']); /* global colorConverter */ require(['/js/color/color-converter']); /* global colorConverter */
const pool = new Map(); const pool = Object.create(null);
return doReplace(source); return doReplace(source);
function getValue(name, rgbName) { function doReplace(text) {
if (!vars.hasOwnProperty(name)) { return text.replace(/(\/\*\[\[([\w-]+)]]\*\/)([0-9a-f]{2}(?=\W))?/gi, (_, cmt, name, alpha) => {
if (name.endsWith('-rgb')) { const key = alpha ? name + '[A]' : name;
return getValue(name.slice(0, -4), name); let val = pool[key];
if (val === undefined) {
val = pool[key] = getValue(name, null, alpha);
} }
return null; return (val != null ? val : cmt) + (alpha || '');
});
}
function getValue(name, isUsoRgb, alpha) {
const v = vars[name];
if (!v) {
return name.endsWith('-rgb')
? getValue(name.slice(0, -4), true)
: null;
} }
const {type, value} = vars[name]; let {value} = v;
switch (type) { switch (v.type) {
case 'color': { case 'color':
let color = pool.get(rgbName || name); value = colorConverter.parse(value) || null;
if (color == null) { if (value) {
color = colorConverter.parse(value); /* #rrggbb - inline alpha is present; an opaque hsl/a; #rrggbb originally
if (color) { * rgba(r, g, b, a) - transparency <1 is present (Chrome pre-66 compatibility)
if (color.type === 'hsl') { * rgb(r, g, b) - if color is rgb/a with a=1, note: r/g/b will be rounded
color = colorConverter.HSVtoRGB(colorConverter.HSLtoHSV(color)); * r, g, b - if the var has `-rgb` suffix per USO specification
} * TODO: when minimum_chrome_version >= 66 try to keep `value` intact */
const {r, g, b} = color; if (alpha) delete value.a;
color = rgbName const isRgb = isUsoRgb || value.type === 'rgb' || value.a != null && value.a !== 1;
? `${r}, ${g}, ${b}` const usoMode = isUsoRgb || !isRgb;
: `#${(0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; value = colorConverter.format(value, isRgb ? 'rgb' : 'hex', undefined, usoMode);
}
// the pool stores `false` for bad colors to differentiate from a yet unknown color
pool.set(rgbName || name, color || false);
} }
return color || null; return value;
}
case 'dropdown': case 'dropdown':
case 'select': // prevent infinite recursion case 'select':
pool.set(name, ''); pool[name] = ''; // prevent infinite recursion
return doReplace(value); return doReplace(value);
} }
return value; return value;
} }
function doReplace(text) {
return text.replace(/\/\*\[\[([\w-]+)\]\]\*\//g, (match, name) => {
if (!pool.has(name)) {
const value = getValue(name);
pool.set(name, value === null ? match : value);
}
return pool.get(name);
});
}
}, },
}, },
}); });

View File

@ -278,7 +278,7 @@ async function importFromString(jsonString) {
tasks = tasks.then(() => API.styles.delete(id)); tasks = tasks.then(() => API.styles.delete(id));
const oldStyle = oldStylesById.get(id); const oldStyle = oldStylesById.get(id);
if (oldStyle) { if (oldStyle) {
tasks = tasks.then(() => API.styles.import(oldStyle)); tasks = tasks.then(() => API.styles.importMany([oldStyle]));
} }
} }
// taskUI is superfast and updates style list only in this page, // taskUI is superfast and updates style list only in this page,