diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b670c1..2cb610af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- A widget's settings no longer open filled in from the last widget of the same + type you edited. + - A tile with only a fixed label set now shows that badge. - Saving settings no longer warns about deleting the stored password when diff --git a/ui/admin/index.html b/ui/admin/index.html index 4eec2f65..a5b14afe 100644 --- a/ui/admin/index.html +++ b/ui/admin/index.html @@ -303,6 +303,6 @@

General

- + diff --git a/ui/js/admin-app-form.js b/ui/js/admin-app-form.js index da34cb40..090de2bb 100644 --- a/ui/js/admin-app-form.js +++ b/ui/js/admin-app-form.js @@ -1,8 +1,8 @@ import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=b18c93ed'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { loadLocalIcons, resolveIcon, iconChain, cdnIconName } from '/js/icons.js?v=69c2b9bd'; -import { state } from '/js/admin-state.js?v=42393ee1'; -import { isDockBlocked, DOCK_MAX, clearsStoredSecret } from '/js/admin-logic.js?v=80a4637d'; +import { state } from '/js/admin-state.js?v=b7731aa4'; +import { isDockBlocked, DOCK_MAX, clearsStoredSecret } from '/js/admin-logic.js?v=f3f87abf'; import { t } from '/js/i18n.js?v=d056c9c5'; import { toast, @@ -13,8 +13,8 @@ import { initInlineEdit, setTogDisabled, wireChecklist, -} from '/js/admin-shared.js?v=9c6bb360'; -import { renderColorControl, BADGE_SWATCHES } from '/js/admin-color-control.js?v=c853fe21'; +} from '/js/admin-shared.js?v=3d2627a9'; +import { renderColorControl, BADGE_SWATCHES } from '/js/admin-color-control.js?v=3a3a1bad'; import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1'; export function buildFolderForm(body, item) { diff --git a/ui/js/admin-auth.js b/ui/js/admin-auth.js index 0090af30..1597716f 100644 --- a/ui/js/admin-auth.js +++ b/ui/js/admin-auth.js @@ -1,4 +1,4 @@ -import { ag, ap } from '/js/admin-shared.js?v=9c6bb360'; +import { ag, ap } from '/js/admin-shared.js?v=3d2627a9'; import { t } from '/js/i18n.js?v=d056c9c5'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; import { el, inp as inpById, qa } from '/js/utils.js?v=b18c93ed'; diff --git a/ui/js/admin-color-control.js b/ui/js/admin-color-control.js index f78f3fc9..bb7a5880 100644 --- a/ui/js/admin-color-control.js +++ b/ui/js/admin-color-control.js @@ -1,4 +1,4 @@ -import { PE_SVG, initInlineEdit } from '/js/admin-shared.js?v=9c6bb360'; +import { PE_SVG, initInlineEdit } from '/js/admin-shared.js?v=3d2627a9'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { qa, q } from '/js/utils.js?v=b18c93ed'; diff --git a/ui/js/admin-logic.js b/ui/js/admin-logic.js index 9be9a7ee..6f8df4c6 100644 --- a/ui/js/admin-logic.js +++ b/ui/js/admin-logic.js @@ -1,6 +1,15 @@ // @ts-check /* Keep this module free of the DOM and of imports. Tests load it directly. */ +/* Values typed into a widget's config form survive a re-render of that same + form, and nothing else. Opening the editor again is a new session, so the + saved config wins, or one widget's form would fill in from the last widget of + the same type that was open. */ +export function carriesTypedValues(cached, wtype, session) { + if (!cached || !cached.form) return false; + return cached.type === wtype && cached.session === session; +} + export function seedCarried(config, carryKeys) { const out = Object.create(null); for (const k of carryKeys || []) if (config && config[k] !== undefined) out[k] = config[k]; diff --git a/ui/js/admin-settings.js b/ui/js/admin-settings.js index ff71ce51..0546d40d 100644 --- a/ui/js/admin-settings.js +++ b/ui/js/admin-settings.js @@ -1,7 +1,7 @@ -import { toast, ag, ap } from '/js/admin-shared.js?v=9c6bb360'; +import { toast, ag, ap } from '/js/admin-shared.js?v=3d2627a9'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; import { t } from '/js/i18n.js?v=d056c9c5'; -import { shouldWritePassword, settingsSaveBlocker, clearsStoredPassword, BLOCK } from '/js/admin-logic.js?v=80a4637d'; +import { shouldWritePassword, settingsSaveBlocker, clearsStoredPassword, BLOCK } from '/js/admin-logic.js?v=f3f87abf'; import { el, inp, q, qa, setUserText } from '/js/utils.js?v=b18c93ed'; /* Mirrors the server's rule: auth cannot be switched on with no password. */ diff --git a/ui/js/admin-shared.js b/ui/js/admin-shared.js index a58fa6b5..ab025c53 100644 --- a/ui/js/admin-shared.js +++ b/ui/js/admin-shared.js @@ -1,5 +1,5 @@ /* Stateless helpers shared by the admin modules. Mutable state stays out. */ -import { nextActiveIndex, recoversSession, toastMs } from '/js/admin-logic.js?v=80a4637d'; +import { nextActiveIndex, recoversSession, toastMs } from '/js/admin-logic.js?v=f3f87abf'; import { el, qa, q } from '/js/utils.js?v=b18c93ed'; import { t } from '/js/i18n.js?v=d056c9c5'; diff --git a/ui/js/admin-state.js b/ui/js/admin-state.js index 10b8baf4..cc5e884b 100644 --- a/ui/js/admin-state.js +++ b/ui/js/admin-state.js @@ -28,4 +28,6 @@ export const state = { _wAutoCfg: {}, _autoForm: null, _autoFormType: null, + _autoFormSession: null, + _evSession: 0, }; diff --git a/ui/js/admin-widget-form.js b/ui/js/admin-widget-form.js index 186206e2..c26957f7 100644 --- a/ui/js/admin-widget-form.js +++ b/ui/js/admin-widget-form.js @@ -1,8 +1,14 @@ -import { state } from '/js/admin-state.js?v=42393ee1'; -import { PE_SVG, CHEV_SVG, initInlineEdit } from '/js/admin-shared.js?v=9c6bb360'; -import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=e2dfacc3'; +import { state } from '/js/admin-state.js?v=b7731aa4'; +import { PE_SVG, CHEV_SVG, initInlineEdit } from '/js/admin-shared.js?v=3d2627a9'; +import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=bf180b36'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { sizesForView, widgetConfigMode, rejectionLines, refusedNoticeKey } from '/js/admin-logic.js?v=80a4637d'; +import { + sizesForView, + widgetConfigMode, + rejectionLines, + refusedNoticeKey, + carriesTypedValues, +} from '/js/admin-logic.js?v=f3f87abf'; import { t } from '/js/i18n.js?v=d056c9c5'; import { q, qi, qa } from '/js/utils.js?v=b18c93ed'; @@ -35,8 +41,14 @@ export function buildWidgetForm(body, item) { } function _renderWidgetForm(body) { - /* Re-render of the same registry widget: keep typed values. */ - if (state._autoForm && state._autoFormType === state._wtype) { + /* Re-render of the same form in the same editing session: keep typed values. */ + if ( + carriesTypedValues( + { form: state._autoForm, type: state._autoFormType, session: state._autoFormSession }, + state._wtype, + state._evSession, + ) + ) { state._wAutoCfg = Object.assign({}, state._wAutoCfg, state._autoForm.getValues()); } state._autoForm = null; @@ -120,6 +132,7 @@ function _renderWidgetForm(body) { }, }); state._autoFormType = state._wtype; + state._autoFormSession = state._evSession; } else if (_mode === 'unavailable') _renderUnavailableConfig(body); else _renderCustomConfig(body); } diff --git a/ui/js/admin.js b/ui/js/admin.js index be893a92..00ab543a 100644 --- a/ui/js/admin.js +++ b/ui/js/admin.js @@ -1,7 +1,7 @@ -import { buildAppForm, buildFolderForm, serializeKvRows } from '/js/admin-app-form.js?v=70e2d15b'; -import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=cb00465f'; +import { buildAppForm, buildFolderForm, serializeKvRows } from '/js/admin-app-form.js?v=f8fda76d'; +import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=76be763e'; import { applyDrop, canJoinFolder } from '/js/admin-drag-logic.js?v=8fcf583a'; -import { reorderItems, resolveAdminSection } from '/js/admin-logic.js?v=80a4637d'; +import { reorderItems, resolveAdminSection } from '/js/admin-logic.js?v=f3f87abf'; import { buildAppItem, claimFolderChildren, @@ -10,10 +10,10 @@ import { snapshotItems, upsertItem, } from '/js/admin-save-logic.js?v=52a970d3'; -import { loadSettings, showBgFields } from '/js/admin-settings.js?v=263d3422'; -import { ag, ap, initInlineEdit, setReauthHandler, toast } from '/js/admin-shared.js?v=9c6bb360'; -import { state } from '/js/admin-state.js?v=42393ee1'; -import { buildWidgetForm } from '/js/admin-widget-form.js?v=04ee8ab3'; +import { loadSettings, showBgFields } from '/js/admin-settings.js?v=e9bec962'; +import { ag, ap, initInlineEdit, setReauthHandler, toast } from '/js/admin-shared.js?v=3d2627a9'; +import { state } from '/js/admin-state.js?v=b7731aa4'; +import { buildWidgetForm } from '/js/admin-widget-form.js?v=5292c6a0'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { initI18n, LANGUAGES, t } from '/js/i18n.js?v=d056c9c5'; import { iconChain, loadLocalIcons, resolveIcon } from '/js/icons.js?v=69c2b9bd'; @@ -695,6 +695,7 @@ function openModal(idx) { state._evItem = item; state._evIsEdit = isEdit; + state._evSession += 1; _renderEditBody(); showEditView(); diff --git a/ui/js/widget-config-form.js b/ui/js/widget-config-form.js index 55c8dce6..6a3f33ab 100644 --- a/ui/js/widget-config-form.js +++ b/ui/js/widget-config-form.js @@ -4,8 +4,8 @@ import { t } from '/js/i18n.js?v=d056c9c5'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { wireChecklist } from '/js/admin-shared.js?v=9c6bb360'; -import { renderColorControl } from '/js/admin-color-control.js?v=c853fe21'; +import { wireChecklist } from '/js/admin-shared.js?v=3d2627a9'; +import { renderColorControl } from '/js/admin-color-control.js?v=3a3a1bad'; import { seedCarried, applyOptionSet, @@ -13,7 +13,7 @@ import { requiredFieldMissing, groupBounds, visibleFieldFlags, -} from '/js/admin-logic.js?v=80a4637d'; +} from '/js/admin-logic.js?v=f3f87abf'; import { optionsErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1'; import { qi } from '/js/utils.js?v=b18c93ed'; diff --git a/ui/test/admin-logic.test.mjs b/ui/test/admin-logic.test.mjs index eeba15dc..0096ff17 100644 --- a/ui/test/admin-logic.test.mjs +++ b/ui/test/admin-logic.test.mjs @@ -6,6 +6,7 @@ import { nextActiveIndex, groupBounds, visibleFieldKeys, + carriesTypedValues, visibleFieldFlags, collectFieldValues, clearsStoredSecret, @@ -176,6 +177,20 @@ test('visibleFieldKeys does not leak a field across a hidden branch default', () assert.deepEqual([...shown], ['source']); }); +/* Two tiles of one widget type each open the same form. Carrying values by type + alone filled the second tile's form from the first, so a Beszel tile opened + after a Glances one showed Glances as its source. */ +test('typed values are carried within one editing session, never into the next', () => { + const form = { getValues: () => ({}) }; + const opened = { form, type: 'system-summary', session: 4 }; + + assert.equal(carriesTypedValues(opened, 'system-summary', 4), true, 'a re-render of the same open form'); + assert.equal(carriesTypedValues(opened, 'system-summary', 5), false, 'another widget of the same type'); + assert.equal(carriesTypedValues(opened, 'dns', 4), false, 'a different widget type'); + assert.equal(carriesTypedValues({ form: null, type: 'system-summary', session: 4 }, 'system-summary', 4), false); + assert.equal(carriesTypedValues(null, 'system-summary', 4), false); +}); + test('only the visible copy of a repeated key is saved', () => { /* The system summary declares its slot list once per source. Both are built, one is on screen, and the saved config must carry that one alone. */