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.
This commit is contained in:
Will Miao
2026-09-13 11:35:48 +08:00
parent 6fe0543d2e
commit b1a653f18f
2 changed files with 82 additions and 1 deletions
+11 -1
View File
@@ -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;
@@ -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);
});
});