From 727a8dfd3a2448c00b91cca82b88281aacf413c0 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Tue, 27 Oct 2026 19:51:29 -0700 Subject: [PATCH] fix(gallery): keep the default view in Page options and save viewer choices alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System › Appearance's Default layout is the workspace's own key, so the legacy gallery's Default View Mode stays a page option instead of losing its only control. The viewer's mute and loop buttons remember just that choice instead of saving the Page options dialog's unfilled inputs over the stored page options. The pending-thumbnails toast names System › Sources & scans. --- README.md | 8 +++--- TESTING.md | 6 ++-- tests/browser/legacy-system-links.spec.mjs | 11 ++++---- tests/js/gallery-settings.test.js | 32 ++++++++++++++------- web/gallery.html | 7 +++++ web/js/gallery.js | 33 ++++++++++++++-------- 6 files changed, 64 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 88fe63ca..3d1ad807 100644 --- a/README.md +++ b/README.md @@ -330,9 +330,9 @@ The comprehensive gallery system provides complete access to all your ComfyUI ou #### **⚙️ Gallery Settings** -The gallery's **Settings** opens System › Appearance in a new tab: default layout, image quality, media info on cards, sidebar, video autoplay/mute/loop and metadata loading are set there and apply to this page too. +The gallery's **Settings** opens System › Appearance in a new tab: image quality, details on cards, collapsed viewer details, video autoplay/mute/loop, metadata loading and file paths are set there and apply to this page too. -The gallery's **Page options** keep the page's own settings: the thumbnail check at startup, lazy loading, items per page, grid columns, infinite scrolling, debug mode and API timeout. Reset puts back only these. +The gallery's **Page options** keep the page's own settings: the thumbnail check at startup, lazy loading, default view mode, items per page, grid columns, infinite scrolling, debug mode and API timeout. Reset puts back only these. Thumbnail generation, the server thumbnail and browser cache clears, refreshing the file listing and finding duplicate images are in System › Sources & scans and System › Maintenance (Page options links both); each runs as an Activity job you can follow after closing the page. @@ -496,7 +496,7 @@ A CivitAI API key is **required if your library contains NSFW LoRAs** — CivitA To add your key: 1. Get your API key from [civitai.com/user/account](https://civitai.com/user/account) -2. Paste it into the **CivitAI API Key** field in System › Integrations +2. Paste it into the **CivitAI API key** field in System › Integrations 3. Save and re-import to download any previously skipped images The key is sent only over HTTPS to CivitAI's own hosts. CivitAI serves images through a redirect to its storage host; PromptManager follows such a redirect only over HTTPS to a host on CivitAI's own `civitai.com`, `civitai.red` or `civitai.green` domains, at most three hops, and never sends the key with it. When downloads fail, the import's status line says how many; when none could be downloaded, it starts with a warning. The server log has one warning per failed image with the reason (HTTP status, refused redirect, size cap, unreadable image, timeout or network error). @@ -522,7 +522,7 @@ Keep your database optimized in System › Maintenance (the admin dashboard's ** - **Missing-file links**: remove image records for missing files - **Unused prompt components**: remove prompts nothing uses - **Run health checks**: verify database integrity and relationships (the Health report) -- **Database statistics**: System › Diagnostics & logs and the Overview's Statistics +- **Database size and prompt counts**: System › Diagnostics & logs (Database) and the Overview's Statistics Each operation opens a review that shows what it will change and the safety backup it takes, then runs as an Activity job. diff --git a/TESTING.md b/TESTING.md index 26222af8..a1817942 100644 --- a/TESTING.md +++ b/TESTING.md @@ -4117,7 +4117,9 @@ opens `workspace#/system/` in a new tab) and removes the legacy copy of a ca only when its row above is `covered`. One row per removed capability: Capability is the exact Capability cell of its coverage row, Page is `admin` or `gallery`, Removed ids and Removed methods list what went (`—` for none), and Native test copies that row's Test -cell. An id that stays on a System link is not listed, only its handler. +cell. An id that stays on a System link is not listed, only its handler. The gallery keeps +its Default View Mode in Page options: System › Appearance's Default layout writes the +workspace's own `workspaceLayout`, and nothing native writes the legacy `defaultViewMode`. `tests/vendor/legacy-coverage.test.mjs` fails when a row cites a capability that is not `covered` or copies another test, when an id or method is listed twice for one page, or @@ -4150,7 +4152,7 @@ legacy page workflows), not by this check. | Database backup | admin | — | `backupDatabase()` | Back up downloads through the api client and revokes the object URL (D103) (workspace-system-backups.spec.mjs) | | Restore from file | admin | `#restoreModal`, `#restoreFileInput`, `#confirmRestore`, `#cancelRestore` | `showRestoreModal()`, `confirmRestore()` | Review restore stages the chosen file and shows the real summary with the backup box (D105) (workspace-system-backups.spec.mjs) | | Gallery thumbnail generation | gallery | `#generateThumbnailsBtn`, `#thumbnailStatus`, `#thumbnailProgress`, `#progressText`, `#progressPercent`, `#cancelThumbnailsBtn`, `#progressBar`, `#progressDetails`, `#progressETA` | `generateThumbnails()`, `generateThumbnailsWithProgress()` | Start closes the dialog, toasts, and opens the job in Activity (workspace-system-sources.spec.mjs) | -| Default view mode, image quality | gallery | `#imageQualitySelect`, `#defaultViewModeSelect` | — | defaults apply to a new visit but not to the current view or a saved view (workspace-system.spec.mjs) | +| Default view mode, image quality | gallery | `#imageQualitySelect` | — | defaults apply to a new visit but not to the current view or a saved view (workspace-system.spec.mjs) | | Image info on cards; sidebar collapsed | gallery | `#showImageInfoToggle`, `#sidebarCollapsedByDefaultToggle` | — | added rows reach their consumers (workspace-system.spec.mjs) | | Video autoplay, mute, loop | gallery | `#videoAutoplayToggle`, `#videoMuteToggle`, `#videoLoopToggle` | — | video defaults reach the viewer (workspace-system.spec.mjs) | | Auto-load metadata, cache metadata, show file paths | gallery | `#autoLoadMetadataToggle`, `#cacheMetadataToggle`, `#showFilePathsToggle` | — | metadata off shows an explicit load and loads once on request (workspace-system.spec.mjs) | diff --git a/tests/browser/legacy-system-links.spec.mjs b/tests/browser/legacy-system-links.spec.mjs index 17d03509..899e02c7 100644 --- a/tests/browser/legacy-system-links.spec.mjs +++ b/tests/browser/legacy-system-links.spec.mjs @@ -227,7 +227,7 @@ test('Page options keeps the page-only settings admin', async ({ page, request } test('Page options keeps the page-only settings gallery', async ({ page }) => { // Native Appearance keys and the startup prompt's own keys are in the shared object before the page opens. - const stored = { defaultViewMode: 'list', imageQuality: 'high', showImageInfo: false, videoMute: false, + const stored = { imageQuality: 'high', showImageInfo: false, videoMute: false, autoLoadMetadata: false, workspaceLayout: 'masonry', thumbnailPromptDismissed: 'sig', thumbnailsGenerated: true, checkThumbnailsAtStartup: false }; await page.addInitScript(value => { @@ -244,15 +244,16 @@ test('Page options keeps the page-only settings gallery', async ({ page }) => { await expect(dialog.locator('h3')).toHaveText('Page options'); // Exactly the page-only settings (G5). expect(await dialog.locator('input, select, textarea').evaluateAll(fields => fields.map(field => field.id))).toEqual([ - 'checkThumbnailsAtStartup', 'lazyLoadingToggle', 'defaultLimitSelect', 'gridColumnsSelect', 'infiniteScrollToggle', - 'debugModeToggle', 'apiTimeoutInput']); + 'checkThumbnailsAtStartup', 'lazyLoadingToggle', 'defaultViewModeSelect', 'defaultLimitSelect', 'gridColumnsSelect', + 'infiniteScrollToggle', 'debugModeToggle', 'apiTimeoutInput']); await page.locator('#defaultLimitSelect').selectOption('50'); + await page.locator('#defaultViewModeSelect').selectOption('list'); await page.locator('#debugModeToggle').check(); await page.locator('#saveSettingsBtn').click(); await expect(dialog).toBeHidden(); const read = () => page.evaluate(() => JSON.parse(localStorage.getItem('gallerySettings'))); let saved = await read(); - expect(saved).toMatchObject({ ...stored, defaultLimit: 50, debugMode: true }); + expect(saved).toMatchObject({ ...stored, defaultLimit: 50, debugMode: true, defaultViewMode: 'list' }); // Reset puts back only the page options' defaults; the Appearance and startup-prompt keys stay. await openPageOptions(page); @@ -261,7 +262,7 @@ test('Page options keeps the page-only settings gallery', async ({ page }) => { await expect(page.getByText('Page options reset to defaults')).toBeVisible(); await expect(page.locator('#defaultLimitSelect')).toHaveValue('100'); saved = await read(); - expect(saved).toMatchObject({ defaultViewMode: 'list', imageQuality: 'high', showImageInfo: false, videoMute: false, + expect(saved).toMatchObject({ defaultViewMode: 'grid', imageQuality: 'high', showImageInfo: false, videoMute: false, autoLoadMetadata: false, workspaceLayout: 'masonry', thumbnailPromptDismissed: 'sig', thumbnailsGenerated: true, defaultLimit: 100, debugMode: false, checkThumbnailsAtStartup: true, lazyLoading: true, gridColumns: 8, infiniteScroll: false, apiTimeout: 30 }); diff --git a/tests/js/gallery-settings.test.js b/tests/js/gallery-settings.test.js index 172deccd..0de5fada 100644 --- a/tests/js/gallery-settings.test.js +++ b/tests/js/gallery-settings.test.js @@ -76,7 +76,7 @@ test("the workspace keys have their own defaults and validation", () => { assert.equal(normalizeGallerySettings({ defaultViewMode: "masonry" }).defaultViewMode, "grid"); }); -// #330 S13: the legacy gallery's Page options edit only the page-only keys; System › Appearance owns the rest, so a Save +// #330 S13: the legacy gallery's Page options edit only the page-only keys (and its default view, see PAGE_OPTION_KEYS); System › Appearance owns the rest, so a Save // or a Reset from the legacy page keeps every other stored key. function legacyGallery(stored, controls, extra = {}) { const fs = require("node:fs"); @@ -101,24 +101,26 @@ function legacyGallery(stored, controls, extra = {}) { } const PAGE_OPTION_CONTROLS = () => ({ - checkThumbnailsAtStartup: { checked: false }, lazyLoadingToggle: { checked: false }, defaultLimitSelect: { value: "200" }, + checkThumbnailsAtStartup: { checked: false }, lazyLoadingToggle: { checked: false }, defaultViewModeSelect: { value: "list" }, + defaultLimitSelect: { value: "200" }, gridColumnsSelect: { value: "6" }, infiniteScrollToggle: { checked: true }, debugModeToggle: { checked: true }, apiTimeoutInput: { value: "45" }, }); test("a legacy gallery Save keeps the workspace keys and every other stored key", () => { const { manager, saved } = legacyGallery({ workspaceLayout: "masonry", workspaceDensity: "compact", someOtherTool: 1, - defaultViewMode: "list", showImageInfo: false, videoMute: false, thumbnailPromptDismissed: "sig" }, PAGE_OPTION_CONTROLS()); + imageQuality: "high", showImageInfo: false, videoMute: false, thumbnailPromptDismissed: "sig" }, PAGE_OPTION_CONTROLS()); manager.saveSettings(); const result = saved(); assert.equal(result.workspaceLayout, "masonry"); assert.equal(result.workspaceDensity, "compact"); assert.equal(result.someOtherTool, 1); - assert.equal(result.defaultViewMode, "list"); + assert.equal(result.imageQuality, "high"); assert.equal(result.showImageInfo, false); assert.equal(result.videoMute, false); assert.equal(result.thumbnailPromptDismissed, "sig"); - // The page-only keys come from the dialog. + // The page-only keys come from the dialog (the default view stays a page option: System's layout is the workspace's). + assert.equal(result.defaultViewMode, "list"); assert.equal(result.lazyLoading, false); assert.equal(result.debugMode, true); assert.equal(result.checkThumbnailsAtStartup, false); @@ -130,19 +132,19 @@ test("a legacy gallery Save keeps the workspace keys and every other stored key" test("a legacy gallery Reset restores only the page options and keeps every other stored key", () => { const controls = PAGE_OPTION_CONTROLS(); - const { manager, saved, calls } = legacyGallery({ workspaceLayout: "masonry", defaultViewMode: "list", videoMute: false, + const { manager, saved, calls } = legacyGallery({ workspaceLayout: "masonry", imageQuality: "high", videoMute: false, thumbnailPromptDismissed: "sig", thumbnailsGenerated: true, lazyLoading: false, debugMode: true, defaultLimit: 200, - gridColumns: 6, infiniteScroll: true, apiTimeout: 45, checkThumbnailsAtStartup: false }, controls, + gridColumns: 6, infiniteScroll: true, apiTimeout: 45, checkThumbnailsAtStartup: false, defaultViewMode: "list" }, controls, { confirm: message => (calls.push(["confirm", message]), true) }); manager.resetSettings(); const result = saved(); const { DEFAULT_SETTINGS } = require("../../web/js/gallery-settings.js"); - for (const key of ["checkThumbnailsAtStartup", "lazyLoading", "defaultLimit", "gridColumns", "infiniteScroll", "debugMode", - "apiTimeout"]) { + for (const key of ["checkThumbnailsAtStartup", "lazyLoading", "defaultViewMode", "defaultLimit", "gridColumns", + "infiniteScroll", "debugMode", "apiTimeout"]) { assert.equal(result[key], DEFAULT_SETTINGS[key], key); } assert.equal(result.workspaceLayout, "masonry"); - assert.equal(result.defaultViewMode, "list"); + assert.equal(result.imageQuality, "high"); assert.equal(result.videoMute, false); assert.equal(result.thumbnailPromptDismissed, "sig"); assert.equal(result.thumbnailsGenerated, true); @@ -152,3 +154,13 @@ test("a legacy gallery Reset restores only the page options and keeps every othe assert.deepEqual(calls, [["confirm", "Reset these page options to their defaults?"], ["apply", DEFAULT_SETTINGS.lazyLoading], ["success", "Page options reset to defaults"]]); }); + +test("a viewer mute or loop click remembers only that key and never reads the Page options dialog", () => { + const { manager, saved, calls } = legacyGallery({ defaultLimit: 200, lazyLoading: false, videoMute: true }, {}); + manager.storeSetting("videoMute", false); + const result = saved(); + assert.equal(result.videoMute, false); + assert.equal(result.defaultLimit, 200); + assert.equal(result.lazyLoading, false); + assert.deepEqual(calls, []); +}); diff --git a/web/gallery.html b/web/gallery.html index 74a9bef7..6db204a5 100644 --- a/web/gallery.html +++ b/web/gallery.html @@ -217,6 +217,13 @@

Display Settings

+
+ + +