From 8299881024f5749d57ab6627b9df0f32a0b4c5a5 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Wed, 17 Jun 2026 09:49:24 +0800 Subject: [PATCH] refactor(sidebar): remove pin/unpin and global hide, use per-page hide only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove pin/unpin and auto-hide hover mechanism (isPinned, isHovering, hoverTimeout, showSidebar/hideSidebar, updateAutoHideState, etc.) - Remove global show_folder_sidebar setting (SettingsManager, PageControls, recipes, backend default) - Simplify sidebar visibility to a single per-page toggle: · Dedicated chevron-left button in header to hide sidebar · Edge indicator (chevron-right) to restore when hidden · No dropdown, no hover area, no pin button - Add _migrateOldSettings() to convert old sidebarPinned and show_folder_sidebar states to per-page sidebarDisabled - Fix sidebar flicker on page load: CSS defaults to off-screen, JS explicitly sets .visible or .hidden-by-setting - Remove obsolete CSS classes: auto-hide, hover-active, collapsed - Remove i18n keys: pinSidebar, unpinSidebar, moreOptions - Update test mocks for the new initialize() interface --- locales/de.json | 3 - locales/en.json | 3 - locales/es.json | 3 - locales/fr.json | 3 - locales/he.json | 3 - locales/ja.json | 3 - locales/ko.json | 3 - locales/ru.json | 3 - locales/zh-CN.json | 3 - locales/zh-TW.json | 3 - py/services/settings_manager.py | 1 - static/css/components/sidebar.css | 120 +--- static/js/components/SidebarManager.js | 543 +++--------------- static/js/components/controls/PageControls.js | 10 +- static/js/managers/SettingsManager.js | 13 - static/js/recipes.js | 3 +- static/js/state/index.js | 1 - templates/components/folder_sidebar.html | 21 +- .../components/modals/settings_modal.html | 18 - .../components/pageControls.filtering.test.js | 5 +- ...ingsManager.downloadSkipBaseModels.test.js | 6 - tests/frontend/pages/recipesPage.test.js | 9 + 22 files changed, 87 insertions(+), 693 deletions(-) diff --git a/locales/de.json b/locales/de.json index 832cb98f..18f95d7d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "Stammverzeichnis", - "moreOptions": "Weitere Optionen", "collapseAll": "Alle Ordner einklappen", - "pinSidebar": "Sidebar anheften", - "unpinSidebar": "Sidebar lösen", "hideOnThisPage": "Seitenleiste auf dieser Seite ausblenden", "showSidebar": "Seitenleiste anzeigen", "sidebarHiddenNotification": "Seitenleiste auf der Seite {page} ausgeblendet", diff --git a/locales/en.json b/locales/en.json index 35148560..78ddc154 100644 --- a/locales/en.json +++ b/locales/en.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "Root", - "moreOptions": "More options", "collapseAll": "Collapse All Folders", - "pinSidebar": "Pin Sidebar", - "unpinSidebar": "Unpin Sidebar", "hideOnThisPage": "Hide sidebar on this page", "showSidebar": "Show sidebar", "sidebarHiddenNotification": "Folder sidebar hidden on {page} page", diff --git a/locales/es.json b/locales/es.json index 5b74d23e..37a43de0 100644 --- a/locales/es.json +++ b/locales/es.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "Raíz", - "moreOptions": "Más opciones", "collapseAll": "Colapsar todas las carpetas", - "pinSidebar": "Fijar barra lateral", - "unpinSidebar": "Desfijar barra lateral", "hideOnThisPage": "Ocultar barra lateral en esta página", "showSidebar": "Mostrar barra lateral", "sidebarHiddenNotification": "Barra lateral oculta en la página {page}", diff --git a/locales/fr.json b/locales/fr.json index d17a5ab5..6f16b50a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "Racine", - "moreOptions": "Plus d'options", "collapseAll": "Réduire tous les dossiers", - "pinSidebar": "Épingler la barre latérale", - "unpinSidebar": "Désépingler la barre latérale", "hideOnThisPage": "Masquer la barre latérale sur cette page", "showSidebar": "Afficher la barre latérale", "sidebarHiddenNotification": "Barre latérale masquée sur la page {page}", diff --git a/locales/he.json b/locales/he.json index 40c4dd30..01400f76 100644 --- a/locales/he.json +++ b/locales/he.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "שורש", - "moreOptions": "אפשרויות נוספות", "collapseAll": "כווץ את כל התיקיות", - "pinSidebar": "נעל סרגל צד", - "unpinSidebar": "שחרר סרגל צד", "hideOnThisPage": "הסתר סרגל צד בדף זה", "showSidebar": "הצג סרגל צד", "sidebarHiddenNotification": "סרגל הצד מוסתר בדף {page}", diff --git a/locales/ja.json b/locales/ja.json index 69be6591..cdab2fe5 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "ルート", - "moreOptions": "その他のオプション", "collapseAll": "すべてのフォルダを折りたたむ", - "pinSidebar": "サイドバーを固定", - "unpinSidebar": "サイドバーの固定を解除", "hideOnThisPage": "このページでサイドバーを非表示", "showSidebar": "サイドバーを表示", "sidebarHiddenNotification": "{page}ページでサイドバーが非表示になっています", diff --git a/locales/ko.json b/locales/ko.json index 156e9a82..43d9fa55 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "루트", - "moreOptions": "더 많은 옵션", "collapseAll": "모든 폴더 접기", - "pinSidebar": "사이드바 고정", - "unpinSidebar": "사이드바 고정 해제", "hideOnThisPage": "이 페이지에서 사이드바 숨기기", "showSidebar": "사이드바 표시", "sidebarHiddenNotification": "{page} 페이지에서 사이드바가 숨겨져 있습니다", diff --git a/locales/ru.json b/locales/ru.json index 4d5c388b..508056dc 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "Корень", - "moreOptions": "Дополнительные параметры", "collapseAll": "Свернуть все папки", - "pinSidebar": "Закрепить боковую панель", - "unpinSidebar": "Открепить боковую панель", "hideOnThisPage": "Скрыть боковую панель на этой странице", "showSidebar": "Показать боковую панель", "sidebarHiddenNotification": "Боковая панель скрыта на странице {page}", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index a6047357..65e7a4bb 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "根目录", - "moreOptions": "更多选项", "collapseAll": "折叠所有文件夹", - "pinSidebar": "固定侧边栏", - "unpinSidebar": "取消固定侧边栏", "hideOnThisPage": "隐藏此页面侧边栏", "showSidebar": "显示侧边栏", "sidebarHiddenNotification": "{page}页面的文件夹侧边栏已隐藏", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index a29a6ac7..742c8070 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -956,10 +956,7 @@ }, "sidebar": { "modelRoot": "根目錄", - "moreOptions": "更多選項", "collapseAll": "全部摺疊資料夾", - "pinSidebar": "固定側邊欄", - "unpinSidebar": "取消固定側邊欄", "hideOnThisPage": "隱藏此頁面側邊欄", "showSidebar": "顯示側邊欄", "sidebarHiddenNotification": "{page}頁面的資料夾側邊欄已隱藏", diff --git a/py/services/settings_manager.py b/py/services/settings_manager.py index af7bc764..4a697f14 100644 --- a/py/services/settings_manager.py +++ b/py/services/settings_manager.py @@ -91,7 +91,6 @@ 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 65e4a87f..a52e878e 100644 --- a/static/css/components/sidebar.css +++ b/static/css/components/sidebar.css @@ -8,69 +8,28 @@ border: 1px solid var(--border-color); border-radius: var(--border-radius-xs); overflow: hidden; - transition: var(--transition-slow); flex-shrink: 0; z-index: var(--z-overlay); box-shadow: var(--shadow-header); display: flex; flex-direction: column; backdrop-filter: blur(8px); - /* Default state: hidden off-screen */ + /* Default: hidden off-screen — prevents flash before JS runs */ transform: translateX(-100%); opacity: 0; pointer-events: none; } -.folder-sidebar.hidden-by-setting { - display: none !important; -} - -/* Visible state */ .folder-sidebar.visible { transform: translateX(0); opacity: 1; pointer-events: all; } -/* Auto-hide states */ -.folder-sidebar.auto-hide { - transform: translateX(-100%); - opacity: 0; - pointer-events: none; -} - -.folder-sidebar.auto-hide.hover-active { - transform: translateX(0); - opacity: 1; - pointer-events: all; -} - -.folder-sidebar.collapsed { - transform: translateX(-100%); - opacity: 0; - pointer-events: none; -} - -/* Hover detection area for auto-hide */ -.sidebar-hover-area { - position: fixed; - top: 68px; - left: 0; - width: 20px; - height: calc(100vh - 88px); - z-index: calc(var(--z-overlay) - 1); - background: transparent; - pointer-events: all; -} - -.sidebar-hover-area.hidden-by-setting { +.folder-sidebar.hidden-by-setting { display: none !important; } -.sidebar-hover-area.disabled { - pointer-events: none; -} - .sidebar-header { display: flex; align-items: center; @@ -151,65 +110,6 @@ display: none; } -/* ===== Sidebar More Options Dropdown ===== */ -.sidebar-more-dropdown { - position: absolute; - top: 100%; - right: 8px; - min-width: 190px; - background: var(--bg-color); - border: 1px solid var(--border-color); - border-radius: var(--border-radius-xs); - box-shadow: var(--shadow-lg); - z-index: calc(var(--z-overlay) + 20); - display: none; - overflow: hidden; - margin-top: 2px; -} - -.sidebar-more-dropdown.open { - display: block; - animation: dropdownFadeIn 0.15s ease; -} - -@keyframes dropdownFadeIn { - from { opacity: 0; transform: translateY(-4px); } - to { opacity: 1; transform: translateY(0); } -} - -.sidebar-dropdown-item { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 12px; - cursor: pointer; - font-size: 0.85em; - color: var(--text-color); - transition: var(--transition-base); - white-space: nowrap; -} - -.sidebar-dropdown-item:hover { - background: var(--lora-surface); -} - -.sidebar-dropdown-item i { - width: 16px; - text-align: center; - color: var(--text-muted); - font-size: 0.9em; - flex-shrink: 0; -} - -.sidebar-dropdown-item:hover i { - color: var(--text-color); -} - -.sidebar-dropdown-item.disabled { - opacity: 0.4; - pointer-events: none; -} - /* ===== Sidebar Hidden Indicator (left edge) ===== */ .sidebar-hidden-indicator { position: fixed; @@ -630,7 +530,7 @@ opacity: 0.3; } -/* Responsive Design */ +/* Responsive Design — Mobile: overlay when shown */ @media (max-width: 1024px) { .folder-sidebar { top: 68px; @@ -640,13 +540,9 @@ height: calc(100vh - 88px); z-index: calc(var(--z-overlay) + 10); } - - .folder-sidebar.collapsed { - transform: translateX(-100%); - } - - /* Mobile overlay */ - .folder-sidebar:not(.collapsed)::before { + + /* Mobile overlay when sidebar is shown */ + .folder-sidebar.visible::before { content: ''; position: fixed; top: 0; @@ -665,11 +561,11 @@ max-width: 280px; left: 0px; } - + .sidebar-breadcrumb-nav { font-size: 0.8em; } - + .sidebar-breadcrumb-item { padding: 3px 6px; } diff --git a/static/js/components/SidebarManager.js b/static/js/components/SidebarManager.js index 8d4e27bf..3f83004b 100644 --- a/static/js/components/SidebarManager.js +++ b/static/js/components/SidebarManager.js @@ -17,12 +17,8 @@ export class SidebarManager { this.treeData = {}; this.selectedPath = ''; this.expandedNodes = new Set(); - this.isVisible = true; - this.isPinned = false; this.apiClient = null; this.openDropdown = null; - this.hoverTimeout = null; - this.isHovering = false; this.isInitialized = false; this.displayMode = 'tree'; // 'tree' or 'list' this.foldersList = []; @@ -35,9 +31,7 @@ export class SidebarManager { this.folderTreeElement = null; this.currentDropTarget = null; this.lastPageControls = null; - this.isDisabledBySetting = false; this.isDisabledByPage = false; - this.isMoreDropdownOpen = false; this.initializationPromise = null; this.isCreatingFolder = false; this._pendingDragState = null; // 用于保存拖拽创建文件夹时的状态 @@ -48,12 +42,7 @@ export class SidebarManager { this.handleBreadcrumbClick = this.handleBreadcrumbClick.bind(this); this.handleDocumentClick = this.handleDocumentClick.bind(this); this.handleSidebarHeaderClick = this.handleSidebarHeaderClick.bind(this); - this.handlePinToggle = this.handlePinToggle.bind(this); this.handleCollapseAll = this.handleCollapseAll.bind(this); - this.handleMouseEnter = this.handleMouseEnter.bind(this); - this.handleMouseLeave = this.handleMouseLeave.bind(this); - this.handleHoverAreaEnter = this.handleHoverAreaEnter.bind(this); - this.handleHoverAreaLeave = this.handleHoverAreaLeave.bind(this); this.updateContainerMargin = this.updateContainerMargin.bind(this); this.handleDisplayModeToggle = this.handleDisplayModeToggle.bind(this); this.handleFolderListClick = this.handleFolderListClick.bind(this); @@ -70,9 +59,7 @@ export class SidebarManager { this.handleSidebarDrop = this.handleSidebarDrop.bind(this); this.handleCreateFolderSubmit = this.handleCreateFolderSubmit.bind(this); this.handleCreateFolderCancel = this.handleCreateFolderCancel.bind(this); - this.handleMoreToggle = this.handleMoreToggle.bind(this); - this.handleMoreDropdownItemClick = this.handleMoreDropdownItemClick.bind(this); - this.handleDocumentClickForMore = this.handleDocumentClickForMore.bind(this); + this.handleHideToggle = this.handleHideToggle.bind(this); this.getPageDisplayName = this.getPageDisplayName.bind(this); } @@ -81,12 +68,6 @@ export class SidebarManager { } async initialize(pageControls, options = {}) { - const { forceInitialize = false } = options; - - if (this.isDisabledBySetting && !forceInitialize) { - return; - } - // Clean up previous initialization if exists if (this.isInitialized) { this.cleanup(); @@ -99,25 +80,15 @@ export class SidebarManager { || pageControls?.sidebarApiClient || getModelApiClient(); - // Set initial sidebar state immediately (hidden by default) - this.setInitialSidebarState(); - this.setupEventHandlers(); this.initializeDragAndDrop(); this.updateSidebarTitle(); this.restoreSidebarState(); - // Re-apply DOM visibility now that per-page state is known - this.updateDomVisibility(!this.isDisabledBySetting); + // Apply DOM visibility based on per-page state + this.updateDomVisibility(); await this.loadFolderTree(); - if (this.isDisabledBySetting && !forceInitialize) { - this.cleanup(); - return; - } this.restoreSelectedFolder(); - // Apply final state with animation after everything is loaded - this.applyFinalSidebarState(); - // Update container margin based on initial sidebar state this.updateContainerMargin(); @@ -128,12 +99,6 @@ export class SidebarManager { cleanup() { if (!this.isInitialized) return; - // Clear any pending timeouts - if (this.hoverTimeout) { - clearTimeout(this.hoverTimeout); - this.hoverTimeout = null; - } - // Clean up event handlers this.removeEventHandlers(); @@ -151,11 +116,6 @@ export class SidebarManager { this.sidebarDragHandlersInitialized = false; } - const moreDropdown = document.getElementById('sidebarMoreDropdown'); - if (moreDropdown) { - moreDropdown.classList.remove('open'); - } - this.isMoreDropdownOpen = false; this.hideSidebarHiddenIndicator(); // Reset state @@ -165,7 +125,6 @@ export class SidebarManager { this.selectedPath = ''; this.expandedNodes = new Set(); this.openDropdown = null; - this.isHovering = false; this.isDisabledByPage = false; this.apiClient = null; this.isInitialized = false; @@ -185,19 +144,13 @@ export class SidebarManager { } removeEventHandlers() { - const pinToggleBtn = document.getElementById('sidebarPinToggle'); const collapseAllBtn = document.getElementById('sidebarCollapseAll'); const folderTree = document.getElementById('sidebarFolderTree'); const sidebarBreadcrumbNav = document.getElementById('sidebarBreadcrumbNav'); const sidebarHeader = document.getElementById('sidebarHeader'); - const sidebar = document.getElementById('folderSidebar'); - const hoverArea = document.getElementById('sidebarHoverArea'); const displayModeToggleBtn = document.getElementById('sidebarDisplayModeToggle'); const recursiveToggleBtn = document.getElementById('sidebarRecursiveToggle'); - if (pinToggleBtn) { - pinToggleBtn.removeEventListener('click', this.handlePinToggle); - } if (collapseAllBtn) { collapseAllBtn.removeEventListener('click', this.handleCollapseAll); } @@ -212,14 +165,6 @@ export class SidebarManager { if (sidebarHeader) { sidebarHeader.removeEventListener('click', this.handleSidebarHeaderClick); } - if (sidebar) { - sidebar.removeEventListener('mouseenter', this.handleMouseEnter); - sidebar.removeEventListener('mouseleave', this.handleMouseLeave); - } - if (hoverArea) { - hoverArea.removeEventListener('mouseenter', this.handleHoverAreaEnter); - hoverArea.removeEventListener('mouseleave', this.handleHoverAreaLeave); - } // Remove document click handler document.removeEventListener('click', this.handleDocumentClick); @@ -234,17 +179,10 @@ export class SidebarManager { recursiveToggleBtn.removeEventListener('click', this.handleRecursiveToggle); } - const moreToggle = document.getElementById('sidebarMoreToggle'); - if (moreToggle) { - moreToggle.removeEventListener('click', this.handleMoreToggle); + const hideToggle = document.getElementById('sidebarHideToggle'); + if (hideToggle) { + hideToggle.removeEventListener('click', this.handleHideToggle); } - - const moreDropdown = document.getElementById('sidebarMoreDropdown'); - if (moreDropdown) { - moreDropdown.removeEventListener('click', this.handleMoreDropdownItemClick); - } - - document.removeEventListener('click', this.handleDocumentClickForMore); } initializeDragAndDrop() { @@ -919,60 +857,6 @@ export class SidebarManager { this.currentDropTarget = null; } - async init() { - this.apiClient = this.pageControls?.getSidebarApiClient?.() - || this.pageControls?.sidebarApiClient - || getModelApiClient(); - - // Set initial sidebar state immediately (hidden by default) - this.setInitialSidebarState(); - - this.setupEventHandlers(); - this.initializeDragAndDrop(); - this.updateSidebarTitle(); - this.restoreSidebarState(); - await this.loadFolderTree(); - this.restoreSelectedFolder(); - - // Apply final state with animation after everything is loaded - this.applyFinalSidebarState(); - - // Update container margin based on initial sidebar state - this.updateContainerMargin(); - } - - setInitialSidebarState() { - if (this.isDisabledBySetting) return; - - const sidebar = document.getElementById('folderSidebar'); - const hoverArea = document.getElementById('sidebarHoverArea'); - - if (!sidebar || !hoverArea) return; - - // Get stored pin state - const isPinned = getStorageItem(`${this.pageType}_sidebarPinned`, true); - this.isPinned = isPinned; - - // Sidebar starts hidden by default (CSS handles this) - // Just set up the hover area state - if (window.innerWidth <= 1024) { - hoverArea.classList.add('disabled'); - } else if (this.isPinned) { - hoverArea.classList.add('disabled'); - } else { - hoverArea.classList.remove('disabled'); - } - } - - applyFinalSidebarState() { - if (this.isDisabledBySetting) return; - - // Use requestAnimationFrame to ensure DOM is ready - requestAnimationFrame(() => { - this.updateAutoHideState(); - }); - } - updateSidebarTitle() { const sidebarTitle = document.getElementById('sidebarTitle'); if (sidebarTitle) { @@ -987,12 +871,6 @@ export class SidebarManager { sidebarHeader.addEventListener('click', this.handleSidebarHeaderClick); } - // Pin toggle button - const pinToggleBtn = document.getElementById('sidebarPinToggle'); - if (pinToggleBtn) { - pinToggleBtn.addEventListener('click', this.handlePinToggle); - } - // Collapse all button const collapseAllBtn = document.getElementById('sidebarCollapseAll'); if (collapseAllBtn) { @@ -1018,34 +896,18 @@ export class SidebarManager { sidebarBreadcrumbNav.addEventListener('click', this.handleBreadcrumbClick); } - // Hover detection for auto-hide - const sidebar = document.getElementById('folderSidebar'); - const hoverArea = document.getElementById('sidebarHoverArea'); - - if (sidebar) { - sidebar.addEventListener('mouseenter', this.handleMouseEnter); - sidebar.addEventListener('mouseleave', this.handleMouseLeave); - } - - if (hoverArea) { - hoverArea.addEventListener('mouseenter', this.handleHoverAreaEnter); - hoverArea.addEventListener('mouseleave', this.handleHoverAreaLeave); - } - // Close sidebar when clicking outside on mobile document.addEventListener('click', (e) => { - if (window.innerWidth <= 1024 && this.isVisible) { + if (window.innerWidth <= 1024) { const sidebar = document.getElementById('folderSidebar'); - - if (sidebar && !sidebar.contains(e.target)) { - this.hideSidebar(); + if (sidebar && !sidebar.contains(e.target) && !this.isDisabledByPage) { + sidebar.classList.remove('visible'); } } }); // Handle window resize window.addEventListener('resize', () => { - this.updateAutoHideState(); this.updateContainerMargin(); }); @@ -1074,18 +936,11 @@ export class SidebarManager { }); } - // More options dropdown - const moreToggle = document.getElementById('sidebarMoreToggle'); - if (moreToggle) { - moreToggle.addEventListener('click', this.handleMoreToggle); + // Dedicated hide sidebar button + const hideToggle = document.getElementById('sidebarHideToggle'); + if (hideToggle) { + hideToggle.addEventListener('click', this.handleHideToggle); } - - const moreDropdown = document.getElementById('sidebarMoreDropdown'); - if (moreDropdown) { - moreDropdown.addEventListener('click', this.handleMoreDropdownItemClick); - } - - document.addEventListener('click', this.handleDocumentClickForMore); } handleDocumentClick(event) { @@ -1102,14 +957,9 @@ export class SidebarManager { } } - handlePinToggle(event) { + handleHideToggle(event) { event.stopPropagation(); - this.isPinned = !this.isPinned; - this.updateAutoHideState(); - this.updatePinButton(); - this.updateMoreDropdownLabels(); - this.saveSidebarState(); - this.updateContainerMargin(); + this.toggleHideOnThisPage(); } handleCollapseAll(event) { @@ -1119,102 +969,13 @@ export class SidebarManager { this.saveExpandedState(); } - handleMouseEnter() { - this.isHovering = true; - if (this.hoverTimeout) { - clearTimeout(this.hoverTimeout); - this.hoverTimeout = null; - } + // ===== Sidebar visibility (per-page) and container margin ===== - if (!this.isPinned) { - this.showSidebar(); - } - } - - handleMouseLeave() { - this.isHovering = false; - if (!this.isPinned) { - this.hoverTimeout = setTimeout(() => { - if (!this.isHovering) { - this.hideSidebar(); - } - }, 300); - } - } - - handleHoverAreaEnter() { - if (!this.isPinned) { - this.showSidebar(); - } - } - - handleHoverAreaLeave() { - // Let the sidebar's mouse leave handler deal with hiding - } - - showSidebar() { - const sidebar = document.getElementById('folderSidebar'); - if (sidebar && !this.isPinned) { - sidebar.classList.add('hover-active'); - this.isVisible = true; - this.updateContainerMargin(); - } - } - - hideSidebar() { - const sidebar = document.getElementById('folderSidebar'); - if (sidebar && !this.isPinned) { - sidebar.classList.remove('hover-active'); - this.isVisible = false; - this.updateContainerMargin(); - } - } - - updateAutoHideState() { - if (this.isDisabledBySetting || this.isDisabledByPage) return; - - const sidebar = document.getElementById('folderSidebar'); - const hoverArea = document.getElementById('sidebarHoverArea'); - - if (!sidebar || !hoverArea) return; - - if (window.innerWidth <= 1024) { - // Mobile: always use collapsed state - sidebar.classList.remove('auto-hide', 'hover-active', 'visible'); - sidebar.classList.add('collapsed'); - hoverArea.classList.add('disabled'); - this.isVisible = false; - } else if (this.isPinned) { - // Desktop pinned: always visible - sidebar.classList.remove('auto-hide', 'collapsed', 'hover-active'); - sidebar.classList.add('visible'); - hoverArea.classList.add('disabled'); - this.isVisible = true; - } else { - // Desktop auto-hide: use hover detection - sidebar.classList.remove('collapsed', 'visible'); - sidebar.classList.add('auto-hide'); - hoverArea.classList.remove('disabled'); - - if (this.isHovering) { - sidebar.classList.add('hover-active'); - this.isVisible = true; - } else { - sidebar.classList.remove('hover-active'); - this.isVisible = false; - } - } - - // Update container margin when sidebar state changes - this.updateContainerMargin(); - } - - // New method to update container margin based on sidebar state updateContainerMargin() { const container = document.querySelector('.container'); const sidebar = document.getElementById('folderSidebar'); - if (!container || !sidebar || this.isDisabledBySetting) return; + if (!container || !sidebar) return; // Always reset margin first — needed when transitioning from visible to hidden container.style.marginLeft = ''; @@ -1222,194 +983,40 @@ export class SidebarManager { // When per-page disabled, skip adjustment but margin is already reset if (this.isDisabledByPage) return; - // Only adjust margin if sidebar is visible and pinned - if ((this.isPinned || this.isHovering) && this.isVisible) { - const sidebarWidth = sidebar.offsetWidth; - const viewportWidth = window.innerWidth; - const containerWidth = container.offsetWidth; + // Sidebar is visible — adjust margin if we need room + const sidebarWidth = sidebar.offsetWidth; + const viewportWidth = window.innerWidth; + const containerWidth = container.offsetWidth; - // Check if there's enough space for both sidebar and container - // We need: sidebar width + container width + some padding < viewport width - if (sidebarWidth + containerWidth + sidebarWidth > viewportWidth) { - // Not enough space, push container to the right - container.style.marginLeft = `${sidebarWidth + 10}px`; - } + if (sidebarWidth + containerWidth + sidebarWidth > viewportWidth) { + container.style.marginLeft = `${sidebarWidth + 10}px`; } } - updateDomVisibility(enabled) { - // Per-page disable adds on top of global setting - const isVisible = enabled && !this.isDisabledByPage; + updateDomVisibility() { + const isHidden = this.isDisabledByPage; const sidebar = document.getElementById('folderSidebar'); - const hoverArea = document.getElementById('sidebarHoverArea'); if (sidebar) { - sidebar.classList.toggle('hidden-by-setting', !isVisible); - sidebar.setAttribute('aria-hidden', (!isVisible).toString()); + sidebar.classList.toggle('visible', !isHidden); + sidebar.classList.toggle('hidden-by-setting', isHidden); + sidebar.setAttribute('aria-hidden', isHidden.toString()); } - if (hoverArea) { - hoverArea.classList.toggle('hidden-by-setting', !isVisible); - if (!isVisible) { - hoverArea.classList.add('disabled'); - } - } - - // Show or hide the "sidebar hidden" notification - if (enabled && this.isDisabledByPage) { + // Show or hide the "sidebar hidden" edge indicator + if (isHidden) { this.showSidebarHiddenIndicator(); } else { this.hideSidebarHiddenIndicator(); } } - async setSidebarEnabled(enabled) { - this.isDisabledBySetting = !enabled; - this.updateDomVisibility(enabled); - - const shouldForceInitialization = !enabled && !this.isInitialized; - const needsInitialization = !this.isInitialized || shouldForceInitialization; - - if (this.lastPageControls && needsInitialization) { - if (!this.initializationPromise) { - this.initializationPromise = this.initialize(this.lastPageControls, { - forceInitialize: shouldForceInitialization, - }) - .catch((error) => { - console.error('Sidebar initialization failed:', error); - }) - .finally(() => { - this.initializationPromise = null; - }); - } - - await this.initializationPromise; - } else if (this.initializationPromise) { - await this.initializationPromise; - } - - if (!enabled) { - this.isHovering = false; - this.isVisible = false; - - const container = document.querySelector('.container'); - if (container) { - container.style.marginLeft = ''; - } - - if (this.isInitialized) { - this.updateBreadcrumbs(); - this.updateSidebarHeader(); - } - - return; - } - - if (this.isInitialized) { - this.updateAutoHideState(); - } - } - - updatePinButton() { - const pinBtn = document.getElementById('sidebarPinToggle'); - if (pinBtn) { - pinBtn.classList.toggle('active', this.isPinned); - pinBtn.title = this.isPinned - ? translate('sidebar.unpinSidebar') - : translate('sidebar.pinSidebar'); - } - } - - // ===== More Options Dropdown ===== - - handleMoreToggle(event) { - event.stopPropagation(); - const dropdown = document.getElementById('sidebarMoreDropdown'); - if (!dropdown) return; - - this.isMoreDropdownOpen = !dropdown.classList.contains('open'); - dropdown.classList.toggle('open', this.isMoreDropdownOpen); - this.updateMoreDropdownLabels(); - } - - handleMoreDropdownItemClick(event) { - const item = event.target.closest('.sidebar-dropdown-item'); - if (!item) return; - - const action = item.dataset.action; - if (!action) return; - - const dropdown = document.getElementById('sidebarMoreDropdown'); - if (dropdown) { - dropdown.classList.remove('open'); - this.isMoreDropdownOpen = false; - } - - switch (action) { - case 'toggle-pin': - this.handlePinToggle(event); - break; - case 'toggle-hide': - this.toggleHideOnThisPage(); - break; - } - } - - handleDocumentClickForMore(event) { - const dropdown = document.getElementById('sidebarMoreDropdown'); - const toggle = document.getElementById('sidebarMoreToggle'); - if (!dropdown || !toggle) return; - - if (!dropdown.contains(event.target) && !toggle.contains(event.target)) { - dropdown.classList.remove('open'); - this.isMoreDropdownOpen = false; - } - } - - updateMoreDropdownLabels() { - const pinLabel = document.getElementById('sidebarMorePinLabel'); - if (pinLabel) { - pinLabel.textContent = this.isPinned - ? translate('sidebar.unpinSidebar') - : translate('sidebar.pinSidebar'); - } - - const hideItem = document.querySelector('.sidebar-dropdown-item[data-action="toggle-hide"]'); - if (hideItem) { - const hideIcon = hideItem.querySelector('i'); - const hideLabel = hideItem.querySelector('span'); - if (this.isDisabledByPage) { - hideLabel.textContent = translate('sidebar.showSidebar'); - if (hideIcon) { - hideIcon.className = 'fas fa-eye'; - } - } else { - hideLabel.textContent = translate('sidebar.hideOnThisPage'); - if (hideIcon) { - hideIcon.className = 'fas fa-eye-slash'; - } - } - } - } toggleHideOnThisPage() { this.isDisabledByPage = !this.isDisabledByPage; setStorageItem(`${this.pageType}_sidebarDisabled`, this.isDisabledByPage); - this.updateDomVisibility(!this.isDisabledBySetting); - this.updateAutoHideState(); + this.updateDomVisibility(); this.updateContainerMargin(); - this.updateMoreDropdownLabels(); - - if (!this.isDisabledByPage) { - this.hideSidebarHiddenIndicator(); - } else { - showToast( - 'sidebar.sidebarHiddenNotification', - { page: this.getPageDisplayName() }, - 'info', - `Sidebar hidden on ${this.getPageDisplayName()} page` - ); - } } getPageDisplayName() { @@ -1733,11 +1340,6 @@ export class SidebarManager { // Reload models with new filter await this.pageControls.resetAndReload(); - - // Auto-hide sidebar on mobile after selection - if (window.innerWidth <= 1024) { - this.hideSidebar(); - } } handleFolderListClick(event) { @@ -2047,65 +1649,55 @@ export class SidebarManager { } } - toggleSidebar() { - const sidebar = document.getElementById('folderSidebar'); - const toggleBtn = document.querySelector('.sidebar-toggle-btn'); - - if (!sidebar) return; - - this.isVisible = !this.isVisible; - - if (this.isVisible) { - sidebar.classList.remove('collapsed'); - sidebar.classList.add('visible'); - } else { - sidebar.classList.remove('visible'); - sidebar.classList.add('collapsed'); - } - - if (toggleBtn) { - toggleBtn.classList.toggle('active', this.isVisible); - } - - this.saveSidebarState(); - } - - closeSidebar() { - const sidebar = document.getElementById('folderSidebar'); - const toggleBtn = document.querySelector('.sidebar-toggle-btn'); - - if (!sidebar) return; - - this.isVisible = false; - sidebar.classList.remove('visible'); - sidebar.classList.add('collapsed'); - - if (toggleBtn) { - toggleBtn.classList.remove('active'); - } - - this.saveSidebarState(); - } - restoreSidebarState() { - const isPinned = getStorageItem(`${this.pageType}_sidebarPinned`, true); + // Migration: old pin/unpin and global hide → per-page hide + this._migrateOldSettings(); + 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.isPinned = isPinned; this.expandedNodes = new Set(expandedPaths); this.displayMode = displayMode; this.recursiveSearchEnabled = recursiveSearchEnabled; - this.updatePinButton(); this.updateDisplayModeButton(); this.updateCollapseAllButton(); this.updateSearchRecursiveOption(); this.updateRecursiveToggleButton(); } + /** + * One-time migration: old pin/unpin and global show_folder_sidebar → per-page hide + * - sidebarPinned=false (was auto-hide) → sidebarDisabled=true for that page + * - show_folder_sidebar=false (global) → sidebarDisabled=true for ALL pages + */ + _migrateOldSettings() { + if (getStorageItem('_sidebar_migration_done')) return; + + const PAGES = ['loras', 'recipes', 'checkpoints', 'embeddings']; + + // 1. Migrate global hide setting to per-page + if (state?.global?.settings?.show_folder_sidebar === false) { + PAGES.forEach(p => setStorageItem(`${p}_sidebarDisabled`, true)); + } + + // 2. Migrate unpinned (auto-hide) to per-page hide + PAGES.forEach(p => { + const wasPinned = getStorageItem(`${p}_sidebarPinned`, true); + const alreadyDisabled = getStorageItem(`${p}_sidebarDisabled`, false); + if (wasPinned === false && !alreadyDisabled) { + // Was auto-hide → user didn't want sidebar taking space + setStorageItem(`${p}_sidebarDisabled`, true); + } + // Clean up old keys + localStorage.removeItem(`${p}_sidebarPinned`); + }); + + setStorageItem('_sidebar_migration_done', true); + } + restoreSelectedFolder() { const activeFolder = getStorageItem(`${this.pageType}_activeFolder`); if (activeFolder && typeof activeFolder === 'string') { @@ -2118,11 +1710,6 @@ export class SidebarManager { this.updateSidebarHeader(); this.updateBreadcrumbs(); // Always update breadcrumbs } - // Removed hidden class toggle since breadcrumbs are always visible now - } - - saveSidebarState() { - setStorageItem(`${this.pageType}_sidebarPinned`, this.isPinned); } saveExpandedState() { @@ -2134,7 +1721,7 @@ export class SidebarManager { } async refresh() { - if (this.isDisabledBySetting || !this.isInitialized) { + if (!this.isInitialized) { return; } diff --git a/static/js/components/controls/PageControls.js b/static/js/components/controls/PageControls.js index 9424e8dd..801a2bd0 100644 --- a/static/js/components/controls/PageControls.js +++ b/static/js/components/controls/PageControls.js @@ -93,8 +93,7 @@ export class PageControls { async initSidebarManager() { try { this.sidebarManager.setHostPageControls(this); - const shouldShowSidebar = state?.global?.settings?.show_folder_sidebar !== false; - await this.sidebarManager.setSidebarEnabled(shouldShowSidebar); + await this.sidebarManager.initialize(this); } catch (error) { console.error('Failed to initialize SidebarManager:', error); } @@ -664,13 +663,6 @@ export class PageControls { } this.updateActionButtonStates(); - - if (this.sidebarManager) { - const shouldShowSidebar = !isExcludedView && state?.global?.settings?.show_folder_sidebar !== false; - this.sidebarManager.setSidebarEnabled(shouldShowSidebar).catch((error) => { - console.error('Failed to update sidebar visibility:', error); - }); - } } suspendInteractiveModes() { diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js index adcaf7f2..d65d7baf 100644 --- a/static/js/managers/SettingsManager.js +++ b/static/js/managers/SettingsManager.js @@ -15,7 +15,6 @@ 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'; const VALID_MATURE_BLUR_LEVELS = new Set(['PG13', 'R', 'X', 'XXX']); @@ -884,12 +883,6 @@ 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) { @@ -2949,12 +2942,6 @@ export class SettingsManager { const showVersionOnCard = state.global.settings.show_version_on_card !== false; document.body.classList.toggle('hide-card-version', !showVersionOnCard); - 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/recipes.js b/static/js/recipes.js index f3d03728..67a5a43c 100644 --- a/static/js/recipes.js +++ b/static/js/recipes.js @@ -95,8 +95,7 @@ class RecipeManager { async _initSidebar() { try { sidebarManager.setHostPageControls(this.pageControls); - const shouldShowSidebar = state?.global?.settings?.show_folder_sidebar !== false; - await sidebarManager.setSidebarEnabled(shouldShowSidebar); + await sidebarManager.initialize(this.pageControls); } catch (error) { console.error('Failed to initialize recipe sidebar:', error); } diff --git a/static/js/state/index.js b/static/js/state/index.js index 26bd0bc9..30a786c7 100644 --- a/static/js/state/index.js +++ b/static/js/state/index.js @@ -36,7 +36,6 @@ 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', lora_syntax_format: 'legacy', model_card_footer_action: 'example_images', diff --git a/templates/components/folder_sidebar.html b/templates/components/folder_sidebar.html index fb7a88a1..1dc80788 100644 --- a/templates/components/folder_sidebar.html +++ b/templates/components/folder_sidebar.html @@ -1,6 +1,3 @@ - - -
- -