From b1a653f18f0cf47b7201c229a5ccc99dfe6ef51f Mon Sep 17 00:00:00 2001 From: Will Miao Date: Sun, 13 Sep 2026 11:35:48 +0800 Subject: [PATCH] fix(other-models): hide the folder sidebar by default on the Other page Other-model downloads now default to a flat layout, so a fresh library shows an empty folder tree there while the sidebar still consumes 230px. Default the per-page visibility to hidden for "other" through a small per-page default set. The preference stays persisted per page, so an explicit show/hide toggle wins afterwards, and the existing edge indicator keeps the hidden sidebar discoverable and recoverable. Primary pages keep their visible default. --- static/js/components/SidebarManager.js | 12 +++- .../sidebarManager.visibility.test.js | 71 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/frontend/components/sidebarManager.visibility.test.js diff --git a/static/js/components/SidebarManager.js b/static/js/components/SidebarManager.js index 8c127be6..19cda3f1 100644 --- a/static/js/components/SidebarManager.js +++ b/static/js/components/SidebarManager.js @@ -11,6 +11,13 @@ import { performFolderUpdateCheck } from '../utils/updateCheckHelpers.js'; import { escapeHtml, escapeAttribute } from './shared/utils.js'; import { MODEL_CARD_DRAG_MIME_TYPE } from '../utils/constants.js'; +// Pages whose folder sidebar starts hidden. "other" downloads default to a flat +// layout (no subfolders are created), so on a fresh library the tree is empty +// there and the sidebar would only consume horizontal space. The preference is +// still persisted per page once the user toggles it, and the edge indicator +// makes the hidden sidebar discoverable/recoverable. +const SIDEBAR_DEFAULT_HIDDEN_PAGES = new Set(['other']); + export class SidebarManager { constructor() { this.pageControls = null; @@ -1785,7 +1792,10 @@ export class SidebarManager { const expandedPaths = getStorageItem(`${this.pageType}_expandedNodes`, []); const displayMode = getStorageItem(`${this.pageType}_displayMode`, 'tree'); // 'tree' or 'list', default to 'tree' const recursiveSearchEnabled = getStorageItem(`${this.pageType}_recursiveSearch`, true); - this.isDisabledByPage = getStorageItem(`${this.pageType}_sidebarDisabled`, false); + this.isDisabledByPage = getStorageItem( + `${this.pageType}_sidebarDisabled`, + SIDEBAR_DEFAULT_HIDDEN_PAGES.has(this.pageType) + ); this.expandedNodes = new Set(expandedPaths); this.displayMode = displayMode; diff --git a/tests/frontend/components/sidebarManager.visibility.test.js b/tests/frontend/components/sidebarManager.visibility.test.js new file mode 100644 index 00000000..0721de3a --- /dev/null +++ b/tests/frontend/components/sidebarManager.visibility.test.js @@ -0,0 +1,71 @@ +import { describe, it, beforeEach, expect, vi } from 'vitest'; + +const { + SIDEBAR_MANAGER_MODULE, + STORAGE_HELPERS_MODULE, + MODEL_API_FACTORY_MODULE, + I18N_MODULE, + BULK_MANAGER_MODULE, + UI_HELPERS_MODULE, + UPDATE_CHECK_MODULE, +} = vi.hoisted(() => ({ + SIDEBAR_MANAGER_MODULE: new URL('../../../static/js/components/SidebarManager.js', import.meta.url).pathname, + STORAGE_HELPERS_MODULE: new URL('../../../static/js/utils/storageHelpers.js', import.meta.url).pathname, + MODEL_API_FACTORY_MODULE: new URL('../../../static/js/api/modelApiFactory.js', import.meta.url).pathname, + I18N_MODULE: new URL('../../../static/js/utils/i18nHelpers.js', import.meta.url).pathname, + BULK_MANAGER_MODULE: new URL('../../../static/js/managers/BulkManager.js', import.meta.url).pathname, + UI_HELPERS_MODULE: new URL('../../../static/js/utils/uiHelpers.js', import.meta.url).pathname, + UPDATE_CHECK_MODULE: new URL('../../../static/js/utils/updateCheckHelpers.js', import.meta.url).pathname, +})); + +vi.mock(MODEL_API_FACTORY_MODULE, () => ({ getModelApiClient: vi.fn() })); +vi.mock(I18N_MODULE, () => ({ translate: (key, _args, fallback) => fallback || key })); +vi.mock(BULK_MANAGER_MODULE, () => ({ bulkManager: {} })); +vi.mock(UI_HELPERS_MODULE, () => ({ showToast: vi.fn() })); +vi.mock(UPDATE_CHECK_MODULE, () => ({ performFolderUpdateCheck: vi.fn() })); + +const { SidebarManager } = await import(SIDEBAR_MANAGER_MODULE); +const { setStorageItem } = await import(STORAGE_HELPERS_MODULE); + +function createManager(pageType) { + const manager = new SidebarManager(); + manager.pageType = pageType; + manager.pageControls = { pageState: { searchOptions: {} } }; + return manager; +} + +describe('SidebarManager default visibility', () => { + beforeEach(() => { + localStorage.clear(); + }); + + it('hides the folder sidebar by default on the other page', () => { + const manager = createManager('other'); + + manager.restoreSidebarState(); + + expect(manager.isDisabledByPage).toBe(true); + }); + + it('keeps the folder sidebar visible by default on the primary pages', () => { + for (const pageType of ['loras', 'checkpoints', 'embeddings', 'recipes']) { + const manager = createManager(pageType); + + manager.restoreSidebarState(); + + expect(manager.isDisabledByPage, pageType).toBe(false); + } + }); + + it('lets an explicit stored preference override the other-page default', () => { + setStorageItem('other_sidebarDisabled', false); + const shown = createManager('other'); + shown.restoreSidebarState(); + expect(shown.isDisabledByPage).toBe(false); + + setStorageItem('other_sidebarDisabled', true); + const hidden = createManager('other'); + hidden.restoreSidebarState(); + expect(hidden.isDisabledByPage).toBe(true); + }); +});