Add: colorParser

This commit is contained in:
eight 2017-09-06 04:26:01 +08:00
parent 1f44898475
commit 4e0f4b34bb
4 changed files with 176 additions and 117 deletions

View File

@ -46,6 +46,49 @@ var usercss = (function () {
}
};
const colorParser = (function () {
const el = document.createElement('div');
// https://bugs.webkit.org/show_bug.cgi?id=14563
document.head.appendChild(el);
function _parse(color) {
const [r, g, b, a = 1] = color.match(/[.\d]+/g).map(Number);
return {r, g, b, a};
}
function parse(color) {
el.style.color = color;
if (el.style.color === '') {
throw new Error(`"${color}" is not a valid color`);
}
color = getComputedStyle(el).color;
el.style.color = '';
return _parse(color);
}
function format({r, g, b, a = 1}) {
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
function pad(s) {
if (s.padStart) {
// chrome 57+
return s.padStart(2, '0');
}
return `00${s}`.slice(-2);
}
function formatHex({r, g, b, a = null}) {
const values = [r, g, b];
if (a !== null) {
values.push(Math.floor(a * 255));
}
return '#' + values.map(n => pad(n.toString(16))).join('');
}
return {parse, format, formatHex};
})();
function getMetaSource(source) {
const commentRe = /\/\*[\s\S]*?\*\//g;
const metaRe = /==userstyle==[\s\S]*?==\/userstyle==/i;
@ -219,5 +262,5 @@ var usercss = (function () {
// FIXME: validate variable formats
}
return {buildMeta, buildCode};
return {buildMeta, buildCode, colorParser};
})();

View File

@ -139,6 +139,8 @@
<script src="manage/filters.js"></script>
<script src="manage/updater-ui.js"></script>
<script src="manage/object-diff.js"></script>
<script src="js/usercss.js"></script>
<script src="manage/config-dialog.js"></script>
<script src="manage/manage.js"></script>
</head>

121
manage/config-dialog.js Normal file
View File

@ -0,0 +1,121 @@
/* global usercss messageBox */
'use strict';
function configDialog(style) {
const {colorParser} = usercss;
const form = buildConfigForm();
return messageBox({
title: `Configure ${style.name}`,
className: 'config-dialog',
contents: form.el,
buttons: [
t('confirmSave'),
{
textContent: t('confirmDefault'),
onclick: form.useDefault
},
t('confirmCancel')
]
}).then(result => {
if (result.button !== 0 && !result.enter) {
return;
}
return form.getVars();
});
function buildConfigForm() {
const labels = [];
const vars = deepCopy(style.vars);
for (const key of Object.keys(vars)) {
const va = vars[key];
let appendChild;
if (va.type === 'color') {
va.inputColor = $element({tag: 'input', type: 'color'});
// FIXME: i18n
va.inputAlpha = $element({tag: 'input', type: 'range', min: 0, max: 1, title: 'Opacity', step: 'any'});
va.inputColor.onchange = va.inputAlpha.oninput = () => {
va.dirty = true;
const color = colorParser.parse(va.inputColor.value);
color.a = Number(va.inputAlpha.value);
va.value = colorParser.format(color);
va.inputColor.style.opacity = color.a;
};
appendChild = [va.label, va.inputColor, va.inputAlpha];
} else if (va.type === 'checkbox') {
va.input = $element({tag: 'input', type: 'checkbox'});
va.input.onchange = () => {
va.dirty = true;
va.value = String(Number(va.input.checked));
};
appendChild = [va.input, $element({tag: 'span', appendChild: va.label})];
} else if (va.type === 'select') {
va.input = $element({
tag: 'select',
appendChild: Object.keys(va.select).map(key => $element({
tag: 'option', value: key, appendChild: va.select[key]
}))
});
va.input.onchange = () => {
va.dirty = true;
va.value = va.input.value;
};
appendChild = [va.label, va.input];
} else {
va.input = $element({tag: 'input', type: 'text'});
va.input.oninput = () => {
va.dirty = true;
va.value = va.input.value;
};
appendChild = [va.label, va.input];
}
labels.push($element({
tag: 'label',
className: `config-${va.type}`,
appendChild
}));
}
drawValues();
function drawValues() {
for (const key of Object.keys(vars)) {
const va = vars[key];
const value = va.value === null || va.value === undefined ?
va.default : va.value;
if (va.type === 'color') {
const color = colorParser.parse(value);
va.inputAlpha.value = color.a;
va.inputColor.style.opacity = color.a;
delete color.a;
va.inputColor.value = colorParser.formatHex(color);
} else if (va.type === 'checkbox') {
va.input.checked = Number(value);
} else {
va.input.value = value;
}
}
}
function useDefault() {
for (const key of Object.keys(vars)) {
const va = vars[key];
va.dirty = va.value !== null && va.value !== undefined &&
va.value !== va.default;
va.value = null;
}
drawValues();
}
function getVars() {
return vars;
}
return {
el: labels,
useDefault,
getVars
};
}
}

View File

@ -2,6 +2,7 @@
/* global filtersSelector, filterAndAppend */
/* global checkUpdate, handleUpdateInstalled */
/* global objectDiff */
/* global configDialog */
'use strict';
let installed;
@ -282,128 +283,20 @@ Object.assign(handleEvent, {
},
config(event, {styleMeta: style}) {
const form = buildConfigForm();
messageBox({
title: `Configure ${style.name}`,
className: 'config-dialog',
contents: form.el,
buttons: [
t('confirmSave'),
{
textContent: t('confirmDefault'),
onclick: form.useDefault
},
t('confirmCancel')
]
}).then(result => {
if (result.button !== 0 && !result.enter) {
configDialog(style).then(vars => {
if (!vars) {
return;
}
const keys = Object.keys(vars).filter(k => vars[k].dirty);
if (!keys.length) {
return;
}
style.reason = 'config';
const vars = form.getVars();
let dirty = false;
for (const key of Object.keys(vars)) {
if (vars[key].dirty) {
dirty = true;
for (const key of keys) {
style.vars[key].value = vars[key].value;
}
}
if (!dirty) {
return;
}
saveStyleSafe(style);
});
function buildConfigForm() {
const labels = [];
const vars = deepCopy(style.vars);
for (const key of Object.keys(vars)) {
const va = vars[key];
let appendChild;
if (va.type === 'color') {
va.inputColor = $element({tag: 'input', type: 'color'});
// FIXME: i18n
va.inputAlpha = $element({tag: 'input', type: 'range', min: 0, max: 255, title: 'Opacity'});
va.inputColor.onchange = va.inputAlpha.oninput = () => {
va.dirty = true;
va.value = va.inputColor.value + Number(va.inputAlpha.value).toString(16);
va.inputColor.style.opacity = va.inputAlpha.value / 255;
};
appendChild = [va.label, va.inputColor, va.inputAlpha];
} else if (va.type === 'checkbox') {
va.input = $element({tag: 'input', type: 'checkbox'});
va.input.onchange = () => {
va.dirty = true;
va.value = String(Number(va.input.checked));
};
appendChild = [va.input, $element({tag: 'span', appendChild: va.label})];
} else if (va.type === 'select') {
va.input = $element({
tag: 'select',
appendChild: Object.keys(va.select).map(key => $element({
tag: 'option', value: key, appendChild: va.select[key]
}))
});
va.input.onchange = () => {
va.dirty = true;
va.value = va.input.value;
};
appendChild = [va.label, va.input];
} else {
va.input = $element({tag: 'input', type: 'text'});
va.input.oninput = () => {
va.dirty = true;
va.value = va.input.value;
};
appendChild = [va.label, va.input];
}
labels.push($element({
tag: 'label',
className: `config-${va.type}`,
appendChild
}));
}
drawValues();
function drawValues() {
for (const key of Object.keys(vars)) {
const va = vars[key];
const value = va.value === null || va.value === undefined ?
va.default : va.value;
if (va.type === 'color') {
va.inputColor.value = value.slice(0, -2);
va.inputAlpha.value = parseInt(value.slice(-2), 16);
va.inputColor.style.opacity = va.inputAlpha.value / 255;
} else if (va.type === 'checkbox') {
va.input.checked = Number(value);
} else {
va.input.value = value;
}
}
}
function useDefault() {
for (const key of Object.keys(vars)) {
const va = vars[key];
va.dirty = va.value !== null && va.value !== undefined &&
va.value !== va.default;
va.value = null;
}
drawValues();
}
function getVars() {
return vars;
}
return {
el: labels,
useDefault,
getVars
};
}
},
entryClicked(event) {