From e776a45dfab7ac770f2d2b2343098298fd4d365a Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 6 Dec 2017 02:59:12 +0300 Subject: [PATCH] allow continuous tweaking in usercss config dialog * don't close on clicking "save" to allow continuous tweaking * dirty item's label is marked with * and switches to italic * "save" button is enabled when some value differs from saved * "use default" button is enabled when some value differs from its default * "close" becomes "cancel" when there are unsaved changed values --- manage/config-dialog.css | 10 +++ manage/config-dialog.js | 137 +++++++++++++++++++++++++++++---------- msgbox/msgbox.css | 7 -- msgbox/msgbox.js | 31 ++------- 4 files changed, 116 insertions(+), 69 deletions(-) diff --git a/manage/config-dialog.css b/manage/config-dialog.css index 73ceee9f..9edc3e1a 100644 --- a/manage/config-dialog.css +++ b/manage/config-dialog.css @@ -41,6 +41,16 @@ cursor: pointer; } +.config-dialog .dirty:after { + content: "*"; + position: absolute; + left: 6px; +} + +.config-dialog .dirty { + font-style: italic; +} + .config-dialog input, .config-dialog select, .config-dialog .onoffswitch { diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 972cb92d..e8929ab1 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -6,35 +6,77 @@ function configDialog(style) { 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', + 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: [ - t('confirmSave'), - { - textContent: t('confirmDefault'), - onclick: useDefault - }, - t('confirmCancel') - ] - }).then(({button, esc}) => { - if (button !== 1) { - colorpicker.hide(); + {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; } - if (button > 0 || esc || !vars.length || !vars.some(va => va.dirty)) { + return data; + } + + function onshow(box) { + if (isPopup) { + adjustSizeForPopup(box); + } + 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); @@ -52,7 +94,7 @@ function configDialog(style) { error = ['type ', '*' + va.type, ' != ', '*' + bgva.type]; } else if ((va.type === 'select' || va.type === 'dropdown') && - va.value !== null && va.value !== undefined && + !isDefault(va) && bgva.options.every(o => o.name !== va.value)) { error = `'${va.value}' not in the updated '${va.type}' list`; } else if (!va.dirty) { @@ -76,8 +118,24 @@ function configDialog(style) { $create({tag: 'li', appendChild: msg}))), ]); } - return numValid && BG.usercssHelper.save(style); - }); + 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) { @@ -86,7 +144,7 @@ function configDialog(style) { case 'color': children = [ $create('.cm-colorview', [ - va.inputColor = $create('.color-swatch', { + va.input = $create('.color-swatch', { va, onclick: showColorpicker }), @@ -98,10 +156,10 @@ function configDialog(style) { children = [ $create('span.onoffswitch', [ va.input = $create('input.slider', { + va, type: 'checkbox', onchange() { - va.dirty = true; - va.value = String(Number(va.input.checked)); + va.value = va.input.checked ? '1' : '0'; }, }), $create('span'), @@ -116,8 +174,8 @@ function configDialog(style) { children = [ $create('.select-resizer', [ va.input = $create('select', { + va, onchange() { - va.dirty = true; va.value = this.value; } }, @@ -132,10 +190,11 @@ function configDialog(style) { default: children = [ va.input = $create('input', { + va, type: 'text', oninput() { - va.dirty = true; - va.value = va.input.value; + va.value = this.value; + this.dispatchEvent(new Event('change', {bubbles: true})); }, }), ]; @@ -151,10 +210,9 @@ function configDialog(style) { function renderValues() { for (const va of vars) { - const useDefault = va.value === null || va.value === undefined; - const value = useDefault ? va.default : va.value; + const value = isDefault(va) ? va.default : va.value; if (va.type === 'color') { - va.inputColor.style.backgroundColor = value; + va.input.style.backgroundColor = value; if (colorpicker.options.va === va) { colorpicker.setColor(value); } @@ -166,15 +224,6 @@ function configDialog(style) { } } - 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 showColorpicker() { window.removeEventListener('keydown', messageBox.listeners.key, true); const box = $('#message-box-contents'); @@ -191,9 +240,9 @@ function configDialog(style) { function onColorChanged(newColor) { if (newColor) { - this.va.dirty = true; this.va.value = newColor; - this.va.inputColor.style.backgroundColor = newColor; + this.va.input.style.backgroundColor = newColor; + this.va.input.dispatchEvent(new Event('change', {bubbles: true})); } debounce(restoreEscInDialog); } @@ -203,4 +252,22 @@ function configDialog(style) { 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'); + } } diff --git a/msgbox/msgbox.css b/msgbox/msgbox.css index 1ababb94..cd6cf48d 100644 --- a/msgbox/msgbox.css +++ b/msgbox/msgbox.css @@ -143,13 +143,6 @@ top: auto; right: auto; } -#message-box.calculate-size { - white-space: nowrap; -} -#message-box.calculate-size > div { - max-width: none; - max-height: none; -} @keyframes fadein { from { diff --git a/msgbox/msgbox.js b/msgbox/msgbox.js index 839da3d1..c02b1eef 100644 --- a/msgbox/msgbox.js +++ b/msgbox/msgbox.js @@ -12,25 +12,6 @@ function messageBox({ bindGlobalListeners(); createElement(); document.body.appendChild(messageBox.element); - if (location.href.includes('popup.html')) { - messageBox.isPopup = true; - messageBox.element.classList.add('stylus-popup'); - - // calculate size - messageBox.element.classList.add('calculate-size'); - const {offsetWidth, offsetHeight} = messageBox.element.children[0]; - messageBox.element.classList.remove('calculate-size'); - - // for colorpicker - const MIN_WIDTH = 350; - const MIN_HEIGHT = 250; - - 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.minWidth = `${width}px`; - document.body.style.minHeight = `${height}px`; - } if (onshow) { onshow(messageBox.element); } @@ -89,11 +70,11 @@ function messageBox({ $create(`#${id}-contents`, tHTML(contents)), $create(`#${id}-buttons`, buttons.map((content, buttonIndex) => content && - $create('button', { + $create('button', Object.assign({ buttonIndex, - textContent: content.textContent || content, - onclick: content.onclick || messageBox.listeners.button, - }))), + textContent: typeof content === 'object' ? '' : content, + onclick: messageBox.listeners.button, + }, typeof content === 'object' && content)))), ]), ]); } @@ -115,10 +96,6 @@ function messageBox({ messageBox.element.remove(); messageBox.element = null; messageBox.resolve = null; - if (messageBox.isPopup) { - document.body.style.minWidth = ''; - document.body.style.minHeight = ''; - } } }