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 prefs */
/* global tabMan */
/* global usercssMan */
'use strict';
/*
@ -202,7 +203,12 @@ const styleMan = (() => {
/** @returns {Promise<StyleObj[]>} */
async importMany(items) {
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);
return Promise.all(items.map((item, 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>} */
async install(style, reason = null) {
if (ready.then) await ready;

View File

@ -16,29 +16,27 @@ const colorConverter = (() => {
// 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 : '';
const a = formatAlpha(color.a);
const hasA = Boolean(a);
if (type === 'rgb' && color.type === 'hsl') {
const {a} = color;
let aStr = formatAlpha(a);
if (aStr) aStr = ', ' + aStr;
if (type !== 'hsl' && color.type === 'hsl') {
color = HSVtoRGB(HSLtoHSV(color));
}
const {r, g, b, h, s, l} = color;
switch (type) {
case 'hex': {
const rgbStr = (0x1000000 + (r << 16) + (g << 8) + (b | 0)).toString(16).slice(1);
const aStr = hasA ? (0x100 + Math.round(a * 255)).toString(16).slice(1) : '';
const hexStr = `#${rgbStr + aStr}`.replace(/^#(.)\1(.)\2(.)\3(?:(.)\4)?$/, '#$1$2$3$4');
return hexUppercase ? hexStr.toUpperCase() : hexStr.toLowerCase();
let res = '#' + hex2(r) + hex2(g) + hex2(b) + (aStr ? hex2(Math.round(a * 255)) : '');
if (!usoMode) res = res.replace(/^#(.)\1(.)\2(.)\3(?:(.)\4)?$/, '#$1$2$3$4');
return hexUppercase ? res.toUpperCase() : res;
}
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':
return hasA ?
`hsla(${h}, ${s}%, ${l}%, ${a})` :
`hsl(${h}, ${s}%, ${l}%)`;
return `hsl${aStr ? 'a' : ''}(${h}, ${s}%, ${l}%${aStr})`;
}
}
@ -215,6 +213,10 @@ const colorConverter = (() => {
const int = Math.round(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([

View File

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