diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 2a43da21..8a3043b1 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -973,6 +973,9 @@ "usercssReplaceTemplateConfirmation": { "message": "Replace the default template for new Usercss styles with the current code?" }, + "usercssConfigIncomplete": { + "message": "The style was updated or deleted after the configuration dialog was shown. These variables were not saved to avoid corrupting the style's metadata:" + }, "versionInvalidOlder": { "message": "The version is older than the installed style.", "description": "Displayed when the version of style is older than the installed one" diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 02c5e540..09e1f22f 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -2,9 +2,15 @@ 'use strict'; function configDialog(style) { - const form = buildConfigForm(); + const varsHash = deepCopy(style.usercssData.vars) || {}; + const varNames = Object.keys(varsHash); + const vars = varNames.map(name => varsHash[name]); + const elements = []; const colorpicker = window.colorpicker(); + buildConfigForm(); + renderValues(); + return messageBox({ title: `${style.name} v${style.usercssData.version}`, className: 'config-dialog', @@ -19,31 +25,67 @@ function configDialog(style) { }), $element({ className: 'config-body', - appendChild: form.elements + appendChild: elements }) ], buttons: [ t('confirmSave'), { textContent: t('confirmDefault'), - onclick: form.useDefault + onclick: useDefault }, t('confirmCancel') ] - }).then(({button, enter}) => { + }).then(({button, esc}) => { if (button !== 1) { colorpicker.hide(); } - if (button === 0 || enter) { - return form.getVars(); + if (button > 0 || esc || !vars.length || !vars.some(va => va.dirty)) { + return; } + 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 = `${va.name}: deleted`; + } else + if (bgva.type !== va.type) { + error = `${va.name}: type '${va.type}' != '${bgva.type}'`; + } else + if ((va.type === 'select' || va.type === 'dropdown') && + bgva.options.every(o => o.name !== va.value)) { + error = `${va.name}: '${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(error); + delete styleVars[va.name]; + } + if (invalid.length) { + messageBox.alert([ + $element({textContent: t('usercssConfigIncomplete'), style: 'max-width: 34em'}), + $element({ + tag: 'ol', + style: 'text-align: left; font-weight: bold;', + appendChild: invalid.map(s => $element({tag: 'li', textContent: s})), + }), + ]); + } + return numValid && BG.usercssHelper.save(style); }); function buildConfigForm() { - const labels = []; - const vars = deepCopy(style.usercssData.vars); - for (const key of Object.keys(vars)) { - const va = vars[key]; + for (const va of vars) { let appendChild; switch (va.type) { case 'color': @@ -52,7 +94,7 @@ function configDialog(style) { appendChild: va.inputColor = $element({ va, className: 'color-swatch', - onclick: onColorClicked, + onclick: showColorpicker, }) })]; break; @@ -97,71 +139,69 @@ function configDialog(style) { appendChild = [va.input]; break; } - appendChild.unshift($element({tag: 'span', appendChild: va.label})); - labels.push($element({ + elements.push($element({ tag: 'label', className: `config-${va.type}`, - appendChild + appendChild: [ + $element({tag: 'span', appendChild: va.label}), + ...appendChild, + ], })); } - drawValues(); + } - function drawValues() { - for (const key of Object.keys(vars)) { - const va = vars[key]; - const useDefault = va.value === null || va.value === undefined; - const value = useDefault ? va.default : va.value; - if (va.type === 'color') { - va.inputColor.style.backgroundColor = value; - } else if (va.type === 'checkbox') { - va.input.checked = Number(value); - } else { - va.input.value = value; + function renderValues() { + for (const va of vars) { + const useDefault = va.value === null || va.value === undefined; + const value = useDefault ? va.default : va.value; + if (va.type === 'color') { + va.inputColor.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 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 useDefault() { + for (const va of vars) { + const hasValue = va.value !== null && va.value !== undefined; + va.dirty = hasValue && va.value !== va.default; + va.value = null; } + renderValues(); + } - function getVars() { - return vars; + 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.dirty = true; + this.va.value = newColor; + this.va.inputColor.style.backgroundColor = newColor; } + debounce(restoreEscInDialog); + } - function onColorClicked() { - window.removeEventListener('keydown', messageBox.listeners.key, true); - const box = $('#message-box-contents'); - colorpicker.show({ - color: this.va.value || this.va.default, - top: this.getBoundingClientRect().bottom - 5, - left: box.getBoundingClientRect().left - 360, - hideDelay: 1e6, - guessBrightness: box, - callback: newColor => { - if (newColor) { - this.va.dirty = true; - this.va.value = newColor; - this.style.backgroundColor = newColor; - } - setTimeout(() => { - if (!$('.colorpicker-popup')) { - window.addEventListener('keydown', messageBox.listeners.key, true); - } - }); - }, - }); + function restoreEscInDialog() { + if (!$('.colorpicker-popup') && messageBox.element) { + window.addEventListener('keydown', messageBox.listeners.key, true); } - - return { - elements: labels, - useDefault, - getVars - }; } } diff --git a/manage/manage.js b/manage/manage.js index 4165aa11..bb308f45 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -194,7 +194,7 @@ function createStyleElement({style, name}) { if (style.updateUrl && newUI.enabled) { $('.actions', entry).appendChild(template.updaterIcons.cloneNode(true)); } - if (shouldShowConfig() && newUI.enabled) { + if (style.usercssData && Object.keys(style.usercssData.vars).length > 0 && newUI.enabled) { $('.actions', entry).appendChild(template.configureIcon.cloneNode(true)); } @@ -203,10 +203,6 @@ function createStyleElement({style, name}) { createStyleTargetsElement({entry, style, postponeFavicons: name}); return entry; - - function shouldShowConfig() { - return style.usercssData && Object.keys(style.usercssData.vars).length > 0; - } } @@ -287,24 +283,6 @@ Object.assign(handleEvent, { '.configure-usercss': 'config' }, - config(event, {styleMeta: style}) { - configDialog(style).then(vars => { - if (!vars) { - return; - } - const keys = Object.keys(vars).filter(k => vars[k].dirty); - if (!keys.length) { - return; - } - style.reason = 'config'; - for (const key of keys) { - style.usercssData.vars[key].value = vars[key].value; - } - onBackgroundReady() - .then(() => BG.usercssHelper.save(style)); - }); - }, - entryClicked(event) { const target = event.target; const entry = target.closest('.entry'); @@ -407,6 +385,10 @@ Object.assign(handleEvent, { } } }, + + config(event, {styleMeta}) { + configDialog(styleMeta); + }, });