Merge pull request #639 from willmiao/codex/add-setting-to-toggle-folder-sidebar, fixes #630

feat: add setting to toggle folder sidebar visibility
This commit is contained in:
pixelpaws
2025-11-03 07:04:08 +08:00
committed by GitHub
19 changed files with 163 additions and 11 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -248,6 +248,8 @@
"compact": "7 (1080p), 8 (2K), 10 (4K)"
},
"displayDensityWarning": "אזהרה: צפיפויות גבוהות יותר עלולות לגרום לבעיות ביצועים במערכות עם משאבים מוגבלים.",
"showFolderSidebar": "הצג סרגל צד תיקיות",
"showFolderSidebarHelp": "הפעל או כבה את סרגל הצד לניווט תיקיות בדפי המודל. כאשר הוא כבוי, סרגל הצד ואזור הריחוף נשארים מוסתרים.",
"cardInfoDisplay": "תצוגת מידע בכרטיס",
"cardInfoDisplayOptions": {
"always": "תמיד גלוי",

View File

@@ -248,6 +248,8 @@
"compact": "71080p、82K、104K"
},
"displayDensityWarning": "警告:高密度設定は、リソースが限られたシステムでパフォーマンスの問題を引き起こす可能性があります。",
"showFolderSidebar": "フォルダサイドバーを表示",
"showFolderSidebarHelp": "モデルページのフォルダナビゲーションサイドバーを表示/非表示にします。無効にするとサイドバーとホバーエリアは表示されません。",
"cardInfoDisplay": "カード情報表示",
"cardInfoDisplayOptions": {
"always": "常に表示",

View File

@@ -248,6 +248,8 @@
"compact": "7개 (1080p), 8개 (2K), 10개 (4K)"
},
"displayDensityWarning": "경고: 높은 밀도는 리소스가 제한된 시스템에서 성능 문제를 일으킬 수 있습니다.",
"showFolderSidebar": "폴더 사이드바 표시",
"showFolderSidebarHelp": "모델 페이지에서 폴더 탐색 사이드바를 켜거나 끕니다. 비활성화하면 사이드바와 호버 영역이 표시되지 않습니다.",
"cardInfoDisplay": "카드 정보 표시",
"cardInfoDisplayOptions": {
"always": "항상 표시",

View File

@@ -248,6 +248,8 @@
"compact": "7 (1080p), 8 (2K), 10 (4K)"
},
"displayDensityWarning": "Предупреждение: Высокая плотность может вызвать проблемы с производительностью на системах с ограниченными ресурсами.",
"showFolderSidebar": "Показывать боковую панель папок",
"showFolderSidebarHelp": "Включает или выключает боковую панель навигации по папкам на страницах моделей. При отключении панель и область наведения скрыты.",
"cardInfoDisplay": "Отображение информации карточки",
"cardInfoDisplayOptions": {
"always": "Всегда видимо",

View File

@@ -248,6 +248,8 @@
"compact": "71080p82K104K"
},
"displayDensityWarning": "警告:高密度可能导致资源有限的系统性能下降。",
"showFolderSidebar": "显示文件夹侧边栏",
"showFolderSidebarHelp": "在模型页面启用或禁用文件夹导航侧边栏。关闭后,侧边栏和悬停区域将保持隐藏。",
"cardInfoDisplay": "卡片信息显示",
"cardInfoDisplayOptions": {
"always": "始终可见",

View File

@@ -248,6 +248,8 @@
"compact": "71080p、82K、104K"
},
"displayDensityWarning": "警告:較高密度可能導致資源有限的系統效能下降。",
"showFolderSidebar": "顯示資料夾側邊欄",
"showFolderSidebarHelp": "在模型頁面啟用或停用資料夾導覽側邊欄。停用後,側邊欄與滑鼠懸停區域將保持隱藏。",
"cardInfoDisplay": "卡片資訊顯示",
"cardInfoDisplayOptions": {
"always": "永遠顯示",

View File

@@ -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",

View File

@@ -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(),

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
});
}
}
}

View File

@@ -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,

View File

@@ -104,7 +104,25 @@
<!-- Add Layout Settings Section -->
<div class="settings-section">
<h3>{{ t('settings.sections.layoutSettings') }}</h3>
<div class="setting-item">
<div class="setting-row">
<div class="setting-info">
<label for="showFolderSidebar">{{ t('settings.layoutSettings.showFolderSidebar') }}</label>
</div>
<div class="setting-control">
<label class="toggle-switch">
<input type="checkbox" id="showFolderSidebar"
onchange="settingsManager.saveToggleSetting('showFolderSidebar', 'show_folder_sidebar')">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="input-help">
{{ t('settings.layoutSettings.showFolderSidebarHelp') }}
</div>
</div>
<div class="setting-item">
<div class="setting-row">
<div class="setting-info">

View File

@@ -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({