diff --git a/locales/de.json b/locales/de.json index 1d2d9f64..cd1776ca 100644 --- a/locales/de.json +++ b/locales/de.json @@ -248,6 +248,8 @@ "compact": "7 (1080p), 8 (2K), 10 (4K)" }, "displayDensityWarning": "Warnung: Höhere Dichten können bei Systemen mit begrenzten Ressourcen zu Performance-Problemen führen.", + "showFolderSidebar": "Ordner-Seitenleiste anzeigen", + "showFolderSidebarHelp": "Blenden Sie die Ordner-Navigationsleiste auf den Modellseiten ein oder aus. Wenn deaktiviert, bleiben Seitenleiste und Hoverbereich verborgen.", "cardInfoDisplay": "Karten-Info-Anzeige", "cardInfoDisplayOptions": { "always": "Immer sichtbar", diff --git a/locales/en.json b/locales/en.json index 826e5055..6a935263 100644 --- a/locales/en.json +++ b/locales/en.json @@ -238,7 +238,7 @@ "displayDensity": "Display Density", "displayDensityOptions": { "default": "Default", - "medium": "Medium", + "medium": "Medium", "compact": "Compact" }, "displayDensityHelp": "Choose how many cards to display per row:", @@ -248,6 +248,8 @@ "compact": "7 (1080p), 8 (2K), 10 (4K)" }, "displayDensityWarning": "Warning: Higher densities may cause performance issues on systems with limited resources.", + "showFolderSidebar": "Show Folder Sidebar", + "showFolderSidebarHelp": "Toggle the folder navigation sidebar on model pages. When disabled, the sidebar and hover area stay hidden.", "cardInfoDisplay": "Card Info Display", "cardInfoDisplayOptions": { "always": "Always Visible", diff --git a/locales/es.json b/locales/es.json index 82b5d629..5033beaa 100644 --- a/locales/es.json +++ b/locales/es.json @@ -248,6 +248,8 @@ "compact": "7 (1080p), 8 (2K), 10 (4K)" }, "displayDensityWarning": "Advertencia: Densidades más altas pueden causar problemas de rendimiento en sistemas con recursos limitados.", + "showFolderSidebar": "Mostrar barra lateral de carpetas", + "showFolderSidebarHelp": "Activa o desactiva la barra lateral de navegación de carpetas en las páginas de modelos. Cuando está desactivada, la barra lateral y el área de desplazamiento permanecen ocultas.", "cardInfoDisplay": "Visualización de información de tarjeta", "cardInfoDisplayOptions": { "always": "Siempre visible", diff --git a/locales/fr.json b/locales/fr.json index 11d2d649..0ccecae7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -248,6 +248,8 @@ "compact": "7 (1080p), 8 (2K), 10 (4K)" }, "displayDensityWarning": "Attention : Des densités plus élevées peuvent causer des problèmes de performance sur les systèmes avec des ressources limitées.", + "showFolderSidebar": "Afficher la barre latérale des dossiers", + "showFolderSidebarHelp": "Activez ou désactivez la barre latérale de navigation des dossiers sur les pages de modèles. Lorsqu'elle est désactivée, la barre latérale et la zone de survol restent masquées.", "cardInfoDisplay": "Affichage des informations de carte", "cardInfoDisplayOptions": { "always": "Toujours visible", diff --git a/locales/he.json b/locales/he.json index be80306b..7303bf54 100644 --- a/locales/he.json +++ b/locales/he.json @@ -248,6 +248,8 @@ "compact": "7 (1080p), 8 (2K), 10 (4K)" }, "displayDensityWarning": "אזהרה: צפיפויות גבוהות יותר עלולות לגרום לבעיות ביצועים במערכות עם משאבים מוגבלים.", + "showFolderSidebar": "הצג סרגל צד תיקיות", + "showFolderSidebarHelp": "הפעל או כבה את סרגל הצד לניווט תיקיות בדפי המודל. כאשר הוא כבוי, סרגל הצד ואזור הריחוף נשארים מוסתרים.", "cardInfoDisplay": "תצוגת מידע בכרטיס", "cardInfoDisplayOptions": { "always": "תמיד גלוי", diff --git a/locales/ja.json b/locales/ja.json index 08388178..351b2995 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -248,6 +248,8 @@ "compact": "7(1080p)、8(2K)、10(4K)" }, "displayDensityWarning": "警告:高密度設定は、リソースが限られたシステムでパフォーマンスの問題を引き起こす可能性があります。", + "showFolderSidebar": "フォルダサイドバーを表示", + "showFolderSidebarHelp": "モデルページのフォルダナビゲーションサイドバーを表示/非表示にします。無効にするとサイドバーとホバーエリアは表示されません。", "cardInfoDisplay": "カード情報表示", "cardInfoDisplayOptions": { "always": "常に表示", diff --git a/locales/ko.json b/locales/ko.json index ab170ce4..2b200a49 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -248,6 +248,8 @@ "compact": "7개 (1080p), 8개 (2K), 10개 (4K)" }, "displayDensityWarning": "경고: 높은 밀도는 리소스가 제한된 시스템에서 성능 문제를 일으킬 수 있습니다.", + "showFolderSidebar": "폴더 사이드바 표시", + "showFolderSidebarHelp": "모델 페이지에서 폴더 탐색 사이드바를 켜거나 끕니다. 비활성화하면 사이드바와 호버 영역이 표시되지 않습니다.", "cardInfoDisplay": "카드 정보 표시", "cardInfoDisplayOptions": { "always": "항상 표시", diff --git a/locales/ru.json b/locales/ru.json index 19ff8ead..973b9c9c 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -248,6 +248,8 @@ "compact": "7 (1080p), 8 (2K), 10 (4K)" }, "displayDensityWarning": "Предупреждение: Высокая плотность может вызвать проблемы с производительностью на системах с ограниченными ресурсами.", + "showFolderSidebar": "Показывать боковую панель папок", + "showFolderSidebarHelp": "Включает или выключает боковую панель навигации по папкам на страницах моделей. При отключении панель и область наведения скрыты.", "cardInfoDisplay": "Отображение информации карточки", "cardInfoDisplayOptions": { "always": "Всегда видимо", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index eb51f513..ba27192c 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -248,6 +248,8 @@ "compact": "7(1080p),8(2K),10(4K)" }, "displayDensityWarning": "警告:高密度可能导致资源有限的系统性能下降。", + "showFolderSidebar": "显示文件夹侧边栏", + "showFolderSidebarHelp": "在模型页面启用或禁用文件夹导航侧边栏。关闭后,侧边栏和悬停区域将保持隐藏。", "cardInfoDisplay": "卡片信息显示", "cardInfoDisplayOptions": { "always": "始终可见", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 9919efff..b4c4526f 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -248,6 +248,8 @@ "compact": "7(1080p)、8(2K)、10(4K)" }, "displayDensityWarning": "警告:較高密度可能導致資源有限的系統效能下降。", + "showFolderSidebar": "顯示資料夾側邊欄", + "showFolderSidebarHelp": "在模型頁面啟用或停用資料夾導覽側邊欄。停用後,側邊欄與滑鼠懸停區域將保持隱藏。", "cardInfoDisplay": "卡片資訊顯示", "cardInfoDisplayOptions": { "always": "永遠顯示", diff --git a/py/routes/handlers/misc_handlers.py b/py/routes/handlers/misc_handlers.py index 57598fc8..cb170ece 100644 --- a/py/routes/handlers/misc_handlers.py +++ b/py/routes/handlers/misc_handlers.py @@ -193,6 +193,7 @@ class SettingsHandler: "autoplay_on_hover", "display_density", "card_info_display", + "show_folder_sidebar", "include_trigger_words", "show_only_sfw", "compact_mode", diff --git a/py/services/settings_manager.py b/py/services/settings_manager.py index de9f3183..17f6d18c 100644 --- a/py/services/settings_manager.py +++ b/py/services/settings_manager.py @@ -51,6 +51,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = { "autoplay_on_hover": False, "display_density": "default", "card_info_display": "always", + "show_folder_sidebar": True, "include_trigger_words": False, "compact_mode": False, "priority_tags": DEFAULT_PRIORITY_TAG_CONFIG.copy(), diff --git a/static/css/components/sidebar.css b/static/css/components/sidebar.css index 1696620d..340950e9 100644 --- a/static/css/components/sidebar.css +++ b/static/css/components/sidebar.css @@ -21,6 +21,10 @@ pointer-events: none; } +.folder-sidebar.hidden-by-setting { + display: none !important; +} + /* Visible state */ .folder-sidebar.visible { transform: translateX(0); @@ -59,6 +63,10 @@ pointer-events: all; } +.sidebar-hover-area.hidden-by-setting { + display: none !important; +} + .sidebar-hover-area.disabled { pointer-events: none; } diff --git a/static/js/components/SidebarManager.js b/static/js/components/SidebarManager.js index 463eceb9..5140c142 100644 --- a/static/js/components/SidebarManager.js +++ b/static/js/components/SidebarManager.js @@ -31,6 +31,9 @@ export class SidebarManager { this.dragHandlersInitialized = false; this.folderTreeElement = null; this.currentDropTarget = null; + this.lastPageControls = null; + this.isDisabledBySetting = false; + this.initializationPromise = null; // Bind methods this.handleTreeClick = this.handleTreeClick.bind(this); @@ -55,7 +58,15 @@ export class SidebarManager { this.handleFolderDrop = this.handleFolderDrop.bind(this); } + setHostPageControls(pageControls) { + this.lastPageControls = pageControls; + } + async initialize(pageControls) { + if (this.isDisabledBySetting) { + return; + } + // Clean up previous initialization if exists if (this.isInitialized) { this.cleanup(); @@ -63,6 +74,7 @@ export class SidebarManager { this.pageControls = pageControls; this.pageType = pageControls.pageType; + this.lastPageControls = pageControls; this.apiClient = getModelApiClient(); // Set initial sidebar state immediately (hidden by default) @@ -73,6 +85,10 @@ export class SidebarManager { this.updateSidebarTitle(); this.restoreSidebarState(); await this.loadFolderTree(); + if (this.isDisabledBySetting) { + this.cleanup(); + return; + } this.restoreSelectedFolder(); // Apply final state with animation after everything is loaded @@ -132,8 +148,9 @@ export class SidebarManager { // Remove resize event listener window.removeEventListener('resize', this.updateContainerMargin); - + console.log('SidebarManager cleaned up'); + this.initializationPromise = null; } removeEventHandlers() { @@ -471,9 +488,11 @@ export class SidebarManager { } setInitialSidebarState() { + if (this.isDisabledBySetting) return; + const sidebar = document.getElementById('folderSidebar'); const hoverArea = document.getElementById('sidebarHoverArea'); - + if (!sidebar || !hoverArea) return; // Get stored pin state @@ -492,6 +511,8 @@ export class SidebarManager { } applyFinalSidebarState() { + if (this.isDisabledBySetting) return; + // Use requestAnimationFrame to ensure DOM is ready requestAnimationFrame(() => { this.updateAutoHideState(); @@ -668,6 +689,8 @@ export class SidebarManager { } updateAutoHideState() { + if (this.isDisabledBySetting) return; + const sidebar = document.getElementById('folderSidebar'); const hoverArea = document.getElementById('sidebarHoverArea'); @@ -708,8 +731,8 @@ export class SidebarManager { updateContainerMargin() { const container = document.querySelector('.container'); const sidebar = document.getElementById('folderSidebar'); - - if (!container || !sidebar) return; + + if (!container || !sidebar || this.isDisabledBySetting) return; // Reset margin to default container.style.marginLeft = ''; @@ -729,6 +752,65 @@ export class SidebarManager { } } + updateDomVisibility(enabled) { + const sidebar = document.getElementById('folderSidebar'); + const hoverArea = document.getElementById('sidebarHoverArea'); + + if (sidebar) { + sidebar.classList.toggle('hidden-by-setting', !enabled); + sidebar.setAttribute('aria-hidden', (!enabled).toString()); + } + + if (hoverArea) { + hoverArea.classList.toggle('hidden-by-setting', !enabled); + if (!enabled) { + hoverArea.classList.add('disabled'); + } + } + } + + async setSidebarEnabled(enabled) { + this.isDisabledBySetting = !enabled; + this.updateDomVisibility(enabled); + + if (!enabled) { + this.isHovering = false; + this.isVisible = false; + + if (this.isInitialized) { + this.cleanup(); + } else { + const container = document.querySelector('.container'); + if (container) { + container.style.marginLeft = ''; + } + } + + return; + } + + if (this.isInitialized) { + this.updateAutoHideState(); + return; + } + + if (!this.lastPageControls) { + return; + } + + if (!this.initializationPromise) { + this.initializationPromise = this.initialize(this.lastPageControls) + .catch((error) => { + console.error('Sidebar initialization failed:', error); + }) + .finally(() => { + this.initializationPromise = null; + }); + } + + await this.initializationPromise; + } + updatePinButton() { const pinBtn = document.getElementById('sidebarPinToggle'); if (pinBtn) { @@ -1327,6 +1409,10 @@ export class SidebarManager { } async refresh() { + if (this.isDisabledBySetting || !this.isInitialized) { + return; + } + await this.loadFolderTree(); this.restoreSelectedFolder(); } diff --git a/static/js/components/controls/PageControls.js b/static/js/components/controls/PageControls.js index e83112bd..3ab587ae 100644 --- a/static/js/components/controls/PageControls.js +++ b/static/js/components/controls/PageControls.js @@ -1,5 +1,5 @@ // PageControls.js - Manages controls for both LoRAs and Checkpoints pages -import { getCurrentPageState, setCurrentPageType } from '../../state/index.js'; +import { state, getCurrentPageState, setCurrentPageType } from '../../state/index.js'; import { getStorageItem, setStorageItem, getSessionItem, setSessionItem } from '../../utils/storageHelpers.js'; import { showToast } from '../../utils/uiHelpers.js'; import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js'; @@ -75,7 +75,9 @@ export class PageControls { */ async initSidebarManager() { try { - await this.sidebarManager.initialize(this); + this.sidebarManager.setHostPageControls(this); + const shouldShowSidebar = state?.global?.settings?.show_folder_sidebar !== false; + await this.sidebarManager.setSidebarEnabled(shouldShowSidebar); } catch (error) { console.error('Failed to initialize SidebarManager:', error); } diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js index 0137dd65..6a925970 100644 --- a/static/js/managers/SettingsManager.js +++ b/static/js/managers/SettingsManager.js @@ -8,6 +8,7 @@ import { i18n } from '../i18n/index.js'; import { configureModelCardVideo } from '../components/shared/ModelCard.js'; import { validatePriorityTagString, getPriorityTagSuggestionsMap, invalidatePriorityTagSuggestionsCache } from '../utils/priorityTagHelpers.js'; import { bannerService } from './BannerService.js'; +import { sidebarManager } from '../components/SidebarManager.js'; export class SettingsManager { constructor() { @@ -388,6 +389,12 @@ export class SettingsManager { cardInfoDisplaySelect.value = state.global.settings.card_info_display || 'always'; } + const showFolderSidebarCheckbox = document.getElementById('showFolderSidebar'); + if (showFolderSidebarCheckbox) { + const showSidebarSetting = state.global.settings.show_folder_sidebar; + showFolderSidebarCheckbox.checked = showSidebarSetting !== false; + } + // Set model card footer action const modelCardFooterActionSelect = document.getElementById('modelCardFooterAction'); if (modelCardFooterActionSelect) { @@ -1699,6 +1706,13 @@ export class SettingsManager { // Apply card info display setting const cardInfoDisplay = state.global.settings.card_info_display || 'always'; document.body.classList.toggle('hover-reveal', cardInfoDisplay === 'hover'); + + const shouldShowSidebar = state.global.settings.show_folder_sidebar !== false; + if (sidebarManager && typeof sidebarManager.setSidebarEnabled === 'function') { + sidebarManager.setSidebarEnabled(shouldShowSidebar).catch((error) => { + console.error('Failed to apply sidebar visibility setting:', error); + }); + } } } diff --git a/static/js/state/index.js b/static/js/state/index.js index 9214b9d8..2d6b183c 100644 --- a/static/js/state/index.js +++ b/static/js/state/index.js @@ -26,6 +26,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({ autoplay_on_hover: false, display_density: 'default', card_info_display: 'always', + show_folder_sidebar: true, model_name_display: 'model_name', model_card_footer_action: 'example_images', include_trigger_words: false, diff --git a/templates/components/modals/settings_modal.html b/templates/components/modals/settings_modal.html index eb39bb85..4cbeab99 100644 --- a/templates/components/modals/settings_modal.html +++ b/templates/components/modals/settings_modal.html @@ -104,7 +104,25 @@

{{ t('settings.sections.layoutSettings') }}

- + +
+
+
+ +
+
+ +
+
+
+ {{ t('settings.layoutSettings.showFolderSidebarHelp') }} +
+
+
diff --git a/tests/frontend/components/pageControls.filtering.test.js b/tests/frontend/components/pageControls.filtering.test.js index 67fbdd07..3aa12a51 100644 --- a/tests/frontend/components/pageControls.filtering.test.js +++ b/tests/frontend/components/pageControls.filtering.test.js @@ -18,7 +18,8 @@ const downloadManagerMock = { }; const sidebarManagerMock = { - initialize: vi.fn(async () => { + setHostPageControls: vi.fn(), + setSidebarEnabled: vi.fn(async () => { sidebarManagerMock.isInitialized = true; }), refresh: vi.fn(async () => {}), @@ -70,8 +71,8 @@ beforeEach(() => { performModelUpdateCheckMock.mockResolvedValue({ status: 'success', displayName: 'LoRA', records: [] }); sidebarManagerMock.isInitialized = false; - sidebarManagerMock.initialize.mockImplementation(async () => { - sidebarManagerMock.isInitialized = true; + sidebarManagerMock.setSidebarEnabled.mockImplementation(async (enabled) => { + sidebarManagerMock.isInitialized = enabled; }); global.fetch = vi.fn().mockResolvedValue({