stylus/manage/config-dialog.js

275 lines
8.3 KiB
JavaScript

/* global messageBox */
'use strict';
function configDialog(style) {
const data = style.usercssData;
const varsHash = deepCopy(data.vars) || {};
const varNames = Object.keys(varsHash);
const vars = varNames.map(name => varsHash[name]);
let varsInitial = getInitialValues(varsHash);
const elements = [];
const colorpicker = window.colorpicker();
const isPopup = location.href.includes('popup.html');
const buttons = {};
buildConfigForm();
renderValues();
return messageBox({
title: `${style.name} v${data.version}`,
className: 'config-dialog' + (isPopup ? ' stylus-popup' : ''),
contents: [
$create('.config-heading', data.supportURL &&
$createLink({className: '.external-support', href: data.supportURL}, t('externalFeedback'))),
$create('.config-body', elements)
],
buttons: [
{textContent: t('confirmSave'), dataset: {cmd: 'save'}, disabled: true, onclick: save},
{textContent: t('confirmDefault'), dataset: {cmd: 'default'}, onclick: useDefault},
{textContent: t('confirmClose'), dataset: {cmd: 'close'}},
],
onshow,
}).then(() => {
document.body.style.minWidth = '';
document.body.style.minHeight = '';
colorpicker.hide();
});
function getInitialValues(source) {
const data = {};
for (const name of varNames) {
const va = source[name];
data[name] = isDefault(va) ? va.default : va.value;
}
return data;
}
function onshow(box) {
if (isPopup) {
adjustSizeForPopup(box);
box.style.animationDuration = '0s';
}
box.addEventListener('change', onchange);
buttons.save = $('[data-cmd="save"]', box);
buttons.default = $('[data-cmd="default"]', box);
buttons.close = $('[data-cmd="close"]', box);
}
function onchange({target}) {
// invoked after element's own onchange so 'va' contains the updated value
const va = target.va;
if (va) {
va.dirty = varsInitial[va.name] !== (isDefault(va) ? va.default : va.value);
target.closest('label').classList.toggle('dirty', va.dirty);
updateButtons();
}
}
function updateButtons() {
const someDirty = vars.some(va => va.dirty);
buttons.save.disabled = !someDirty;
buttons.default.disabled = vars.every(isDefault);
buttons.close.textContent = t(someDirty ? 'confirmCancel' : 'confirmClose');
}
function save() {
if (!vars.length || !vars.some(va => va.dirty)) {
return;
}
style.enabled = true;
style.reason = 'config';
const styleVars = style.usercssData.vars;
const bgStyle = BG.cachedStyles.byId.get(style.id);
const bgVars = bgStyle && (bgStyle.usercssData || {}).vars || {};
const invalid = [];
let numValid = 0;
for (const va of vars) {
const bgva = bgVars[va.name];
let error;
if (!bgva) {
error = 'deleted';
delete styleVars[va.name];
} else
if (bgva.type !== va.type) {
error = ['type ', '*' + va.type, ' != ', '*' + bgva.type];
} else
if ((va.type === 'select' || va.type === 'dropdown') &&
!isDefault(va) &&
bgva.options.every(o => o.name !== va.value)) {
error = `'${va.value}' not in the updated '${va.type}' list`;
} else if (!va.dirty) {
continue;
} else {
styleVars[va.name].value = va.value;
numValid++;
continue;
}
invalid.push(['*' + va.name, ': ', ...error].map(e =>
e[0] === '*' && $create('b', e.slice(1)) || e));
if (bgva) {
styleVars[va.name].value = deepCopy(bgva);
}
}
if (invalid.length) {
messageBox.alert([
$create('div', {style: 'max-width: 34em'}, t('usercssConfigIncomplete')),
$create('ol', {style: 'text-align: left'},
invalid.map(msg =>
$create({tag: 'li', appendChild: msg}))),
]);
}
return numValid && BG.usercssHelper.save(style).then(saved => {
varsInitial = getInitialValues(deepCopy(saved.usercssData.vars));
vars.forEach(va => onchange({target: va.input}));
updateButtons();
});
}
function useDefault() {
for (const va of vars) {
va.value = null;
onchange({target: va.input});
}
renderValues();
}
function isDefault(va) {
return va.value === null || va.value === undefined || va.value === va.default;
}
function buildConfigForm() {
for (const va of vars) {
let children;
switch (va.type) {
case 'color':
children = [
$create('.cm-colorview', [
va.input = $create('.color-swatch', {
va,
onclick: showColorpicker
}),
]),
];
break;
case 'checkbox':
children = [
$create('span.onoffswitch', [
va.input = $create('input.slider', {
va,
type: 'checkbox',
onchange() {
va.value = va.input.checked ? '1' : '0';
},
}),
$create('span'),
]),
];
break;
case 'select':
case 'dropdown':
case 'image':
// TODO: a image picker input?
children = [
$create('.select-resizer', [
va.input = $create('select', {
va,
onchange() {
va.value = this.value;
}
},
va.options.map(o =>
$create('option', {value: o.name}, o.label))),
$create('SVG:svg.svg-icon.select-arrow',
$create('SVG:use', {'xlink:href': '#svg-icon-select-arrow'})),
]),
];
break;
default:
children = [
va.input = $create('input', {
va,
type: 'text',
oninput() {
va.value = this.value;
this.dispatchEvent(new Event('change', {bubbles: true}));
},
}),
];
break;
}
elements.push(
$create(`label.config-${va.type}`, [
$create('span', va.label),
...children,
]));
}
}
function renderValues() {
for (const va of vars) {
const value = isDefault(va) ? va.default : va.value;
if (va.type === 'color') {
va.input.style.backgroundColor = value;
if (colorpicker.options.va === va) {
colorpicker.setColor(value);
}
} else if (va.type === 'checkbox') {
va.input.checked = Number(value);
} else {
va.input.value = value;
}
}
}
function showColorpicker() {
window.removeEventListener('keydown', messageBox.listeners.key, true);
const box = $('#message-box-contents');
colorpicker.show({
va: this.va,
color: this.va.value || this.va.default,
top: this.getBoundingClientRect().bottom - 5,
left: box.getBoundingClientRect().left - 360,
hideDelay: 1e6,
guessBrightness: box,
callback: onColorChanged,
});
}
function onColorChanged(newColor) {
if (newColor) {
this.va.value = newColor;
this.va.input.style.backgroundColor = newColor;
this.va.input.dispatchEvent(new Event('change', {bubbles: true}));
}
debounce(restoreEscInDialog);
}
function restoreEscInDialog() {
if (!$('.colorpicker-popup') && messageBox.element) {
window.addEventListener('keydown', messageBox.listeners.key, true);
}
}
function adjustSizeForPopup(box) {
box.style = 'white-space: nowrap !important';
box.firstElementChild.style = 'max-width: none; max-height: none;'.replace(/;/g, '!important;');
const {offsetWidth, offsetHeight} = box.firstElementChild;
box.style = box.firstElementChild.style = '';
const colorpicker = document.body.appendChild(
$create('.colorpicker-popup', {style: 'display: none!important'}));
const MIN_WIDTH = parseFloat(getComputedStyle(colorpicker).width) || 350;
const MIN_HEIGHT = 250;
colorpicker.remove();
const width = Math.max(Math.min(offsetWidth / 0.9 + 2, 800), MIN_WIDTH);
const height = Math.max(Math.min(offsetHeight / 0.9 + 2, 600), MIN_HEIGHT);
document.body.style.setProperty('min-width', width + 'px', 'important');
document.body.style.setProperty('min-height', height + 'px', 'important');
}
}