mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 23:25:43 -03:00
feat(settings): add search functionality to settings modal (P2)
Implement Phase 2 search bar feature for settings modal: - Add search input to settings modal header with icon and clear button - Implement real-time filtering with 150ms debounce for performance - Add visual highlighting for matched search terms using accent color - Implement empty search results state with user-friendly message - Add keyboard shortcuts (Escape to clear search) - Auto-expand sections containing matching content during search - Fix header layout to prevent overlap with close button - Update progress tracker documenting P2 completion - Add translation keys for search feature (placeholder, clear, no results) - Sync translations across all language files Files changed: - templates/components/modals/settings_modal.html - static/css/components/modal/settings-modal.css - static/js/managers/SettingsManager.js - locales/*.json (10 language files) - docs/ui-ux-optimization/progress-tracker.md
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
## Project Overview
|
## Project Overview
|
||||||
**Goal**: Optimize Settings Modal UI/UX with left navigation sidebar
|
**Goal**: Optimize Settings Modal UI/UX with left navigation sidebar
|
||||||
**Started**: 2026-02-23
|
**Started**: 2026-02-23
|
||||||
**Current Phase**: P0 - Left Navigation Sidebar
|
**Current Phase**: P2 - Search Bar (Completed)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -74,25 +74,42 @@ None currently
|
|||||||
|
|
||||||
## Phase 1: Section Collapse/Expand (P1)
|
## Phase 1: Section Collapse/Expand (P1)
|
||||||
|
|
||||||
### Status: Planned
|
### Status: Completed ✓
|
||||||
|
|
||||||
|
### Completion Notes
|
||||||
|
- All sections now have collapse/expand functionality
|
||||||
|
- Chevron icon rotates smoothly on toggle
|
||||||
|
- State persistence via localStorage working correctly
|
||||||
|
- CSS animations for smooth height transitions
|
||||||
|
- Settings order reorganized to match sidebar navigation
|
||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
- [ ] Add collapse/expand toggle to section headers
|
- [x] Add collapse/expand toggle to section headers
|
||||||
- [ ] Add chevron icon with rotation animation
|
- [x] Add chevron icon with rotation animation
|
||||||
- [ ] Implement localStorage for state persistence
|
- [x] Implement localStorage for state persistence
|
||||||
- [ ] Add CSS animations for smooth transitions
|
- [x] Add CSS animations for smooth transitions
|
||||||
|
- [x] Reorder settings sections to match sidebar navigation
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 2: Search Bar (P1)
|
## Phase 2: Search Bar (P1)
|
||||||
|
|
||||||
### Status: Planned
|
### Status: Completed ✓
|
||||||
|
|
||||||
|
### Completion Notes
|
||||||
|
- Search input added to settings modal header with icon and clear button
|
||||||
|
- Real-time filtering with debounced input (150ms delay)
|
||||||
|
- Highlight matching terms with accent color background
|
||||||
|
- Handle empty search results with user-friendly message
|
||||||
|
- Keyboard shortcuts: Escape to clear search
|
||||||
|
- Sections with matches are automatically expanded
|
||||||
|
- All translation keys added and synchronized across languages
|
||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
- [ ] Add search input to header area
|
- [x] Add search input to header area
|
||||||
- [ ] Implement real-time filtering
|
- [x] Implement real-time filtering
|
||||||
- [ ] Add highlight for matched terms
|
- [x] Add highlight for matched terms
|
||||||
- [ ] Handle empty search results
|
- [x] Handle empty search results
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -121,7 +138,29 @@ None currently
|
|||||||
|
|
||||||
## Change Log
|
## Change Log
|
||||||
|
|
||||||
### 2026-02-23
|
### 2026-02-23 (P2)
|
||||||
|
- Completed Phase 2: Search Bar
|
||||||
|
- Added search input to settings modal header with search icon and clear button
|
||||||
|
- Implemented real-time filtering with 150ms debounce for performance
|
||||||
|
- Added visual highlighting for matched search terms using accent color
|
||||||
|
- Implemented empty search results state with user-friendly message
|
||||||
|
- Added keyboard shortcuts (Escape to clear search)
|
||||||
|
- Sections with matching content are automatically expanded during search
|
||||||
|
- Updated SettingsManager.js with search initialization and filtering logic
|
||||||
|
- Added comprehensive CSS styles for search input, highlights, and responsive design
|
||||||
|
- Added translation keys for search feature (placeholder, clear, no results)
|
||||||
|
- Synchronized translations across all language files
|
||||||
|
|
||||||
|
### 2026-02-23 (P1)
|
||||||
|
- Completed Phase 1: Section Collapse/Expand
|
||||||
|
- Added collapse/expand functionality to all settings sections
|
||||||
|
- Implemented chevron icon with smooth rotation animation
|
||||||
|
- Added localStorage persistence for collapse state
|
||||||
|
- Reorganized settings sections to match sidebar navigation order
|
||||||
|
- Updated SettingsManager.js with section collapse initialization
|
||||||
|
- Added CSS styles for smooth transitions and animations
|
||||||
|
|
||||||
|
### 2026-02-23 (P0)
|
||||||
- Created project documentation
|
- Created project documentation
|
||||||
- Started Phase 0 implementation
|
- Started Phase 0 implementation
|
||||||
- Analyzed existing code structure
|
- Analyzed existing code structure
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "Portabler Modus",
|
"locationLabel": "Portabler Modus",
|
||||||
"locationHelp": "Aktiviere, um settings.json im Repository zu belassen; deaktiviere, um es im Benutzerkonfigurationsordner zu speichern."
|
"locationHelp": "Aktiviere, um settings.json im Repository zu belassen; deaktiviere, um es im Benutzerkonfigurationsordner zu speichern."
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "Download",
|
"download": "Download",
|
||||||
"advanced": "Advanced"
|
"advanced": "Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Search settings...",
|
||||||
|
"clear": "Clear search",
|
||||||
|
"noResults": "No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "Portable mode",
|
"locationLabel": "Portable mode",
|
||||||
"locationHelp": "Enable to keep settings.json inside the repository; disable to store it in your user config directory."
|
"locationHelp": "Enable to keep settings.json inside the repository; disable to store it in your user config directory."
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "Modo portátil",
|
"locationLabel": "Modo portátil",
|
||||||
"locationHelp": "Activa para mantener settings.json dentro del repositorio; desactívalo para guardarlo en tu directorio de configuración de usuario."
|
"locationHelp": "Activa para mantener settings.json dentro del repositorio; desactívalo para guardarlo en tu directorio de configuración de usuario."
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "Mode portable",
|
"locationLabel": "Mode portable",
|
||||||
"locationHelp": "Activez pour garder settings.json dans le dépôt ; désactivez pour le placer dans votre dossier de configuration utilisateur."
|
"locationHelp": "Activez pour garder settings.json dans le dépôt ; désactivez pour le placer dans votre dossier de configuration utilisateur."
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "מצב נייד",
|
"locationLabel": "מצב נייד",
|
||||||
"locationHelp": "הפעל כדי לשמור את settings.json בתוך המאגר; בטל כדי לשמור אותו בתיקיית ההגדרות של המשתמש."
|
"locationHelp": "הפעל כדי לשמור את settings.json בתוך המאגר; בטל כדי לשמור אותו בתיקיית ההגדרות של המשתמש."
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "ポータブルモード",
|
"locationLabel": "ポータブルモード",
|
||||||
"locationHelp": "有効にすると settings.json をリポジトリ内に保持し、無効にするとユーザー設定ディレクトリに格納します。"
|
"locationHelp": "有効にすると settings.json をリポジトリ内に保持し、無効にするとユーザー設定ディレクトリに格納します。"
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "휴대용 모드",
|
"locationLabel": "휴대용 모드",
|
||||||
"locationHelp": "활성화하면 settings.json을 리포지토리에 유지하고, 비활성화하면 사용자 구성 디렉터리에 저장합니다."
|
"locationHelp": "활성화하면 settings.json을 리포지토리에 유지하고, 비활성화하면 사용자 구성 디렉터리에 저장합니다."
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "Портативный режим",
|
"locationLabel": "Портативный режим",
|
||||||
"locationHelp": "Включите, чтобы хранить settings.json в репозитории; выключите, чтобы сохранить его в папке конфигурации пользователя."
|
"locationHelp": "Включите, чтобы хранить settings.json в репозитории; выключите, чтобы сохранить его в папке конфигурации пользователя."
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "便携模式",
|
"locationLabel": "便携模式",
|
||||||
"locationHelp": "开启可将 settings.json 保存在仓库中;关闭则保存在用户配置目录。"
|
"locationHelp": "开启可将 settings.json 保存在仓库中;关闭则保存在用户配置目录。"
|
||||||
|
|||||||
@@ -275,6 +275,11 @@
|
|||||||
"download": "[TODO: Translate] Download",
|
"download": "[TODO: Translate] Download",
|
||||||
"advanced": "[TODO: Translate] Advanced"
|
"advanced": "[TODO: Translate] Advanced"
|
||||||
},
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "[TODO: Translate] Search settings...",
|
||||||
|
"clear": "[TODO: Translate] Clear search",
|
||||||
|
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
|
||||||
|
},
|
||||||
"storage": {
|
"storage": {
|
||||||
"locationLabel": "可攜式模式",
|
"locationLabel": "可攜式模式",
|
||||||
"locationHelp": "啟用可將 settings.json 保存在儲存庫中;停用則保存在使用者設定目錄。"
|
"locationHelp": "啟用可將 settings.json 保存在儲存庫中;停用則保存在使用者設定目錄。"
|
||||||
|
|||||||
@@ -120,9 +120,115 @@
|
|||||||
.settings-header {
|
.settings-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
|
||||||
gap: var(--space-1);
|
gap: var(--space-1);
|
||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
|
padding-right: 40px; /* Space for close button */
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header .settings-search-wrapper {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Input Styles */
|
||||||
|
.settings-search-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-search-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 0.9em;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 28px 6px 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--lora-surface);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-search-input:focus {
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(var(--lora-accent-rgb, 79, 70, 229), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-search-input::placeholder {
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-search-clear {
|
||||||
|
position: absolute;
|
||||||
|
right: 6px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: none;
|
||||||
|
background: rgba(var(--border-color-rgb, 148, 163, 184), 0.3);
|
||||||
|
color: var(--text-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.7em;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-search-clear:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: rgba(var(--border-color-rgb, 148, 163, 184), 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Highlight Styles */
|
||||||
|
.settings-search-highlight {
|
||||||
|
background-color: rgba(var(--lora-accent-rgb, 79, 70, 229), 0.3);
|
||||||
|
color: var(--lora-accent);
|
||||||
|
padding: 0 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section visibility during search */
|
||||||
|
.settings-section.search-match,
|
||||||
|
.setting-item.search-match {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section.search-hidden,
|
||||||
|
.setting-item.search-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty search results state */
|
||||||
|
.settings-search-empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-4);
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-search-empty i {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-search-empty p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header h2 {
|
.settings-header h2 {
|
||||||
@@ -461,13 +567,66 @@
|
|||||||
padding-top: var(--space-2);
|
padding-top: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-section h3 {
|
.settings-section-header {
|
||||||
font-size: 1.1em;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding: var(--space-1) 0;
|
||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-header:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-header h3 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin: 0;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-section-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-toggle:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-toggle .chevron {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section.collapsed .settings-section-toggle .chevron {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-content {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.3s ease, opacity 0.3s ease;
|
||||||
|
max-height: 2000px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section.collapsed .settings-section-content {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.setting-item {
|
.setting-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column; /* Changed to column for help text placement */
|
flex-direction: column; /* Changed to column for help text placement */
|
||||||
@@ -806,6 +965,17 @@ input:checked + .toggle-slider:before {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header .settings-search-wrapper {
|
||||||
|
margin-left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-nav {
|
.settings-nav {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ export class SettingsManager {
|
|||||||
|
|
||||||
this.setupPriorityTagInputs();
|
this.setupPriorityTagInputs();
|
||||||
this.initializeNavigation();
|
this.initializeNavigation();
|
||||||
|
this.initializeSearch();
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
@@ -435,10 +436,266 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize section collapse/expand
|
||||||
|
this.initializeSectionCollapse();
|
||||||
|
|
||||||
// Initial update
|
// Initial update
|
||||||
updateActiveNav();
|
updateActiveNav();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeSectionCollapse() {
|
||||||
|
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
|
||||||
|
const STORAGE_KEY = 'settingsModal_collapsedSections';
|
||||||
|
|
||||||
|
// Load collapsed state from localStorage
|
||||||
|
let collapsedSections = {};
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (stored) {
|
||||||
|
collapsedSections = JSON.parse(stored);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to load collapsed sections state:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
sections.forEach(section => {
|
||||||
|
const sectionId = section.getAttribute('data-section') || section.id;
|
||||||
|
const header = section.querySelector('.settings-section-header');
|
||||||
|
const toggleBtn = section.querySelector('.settings-section-toggle');
|
||||||
|
|
||||||
|
if (!header || !toggleBtn) return;
|
||||||
|
|
||||||
|
// Apply initial collapsed state
|
||||||
|
if (collapsedSections[sectionId]) {
|
||||||
|
section.classList.add('collapsed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle toggle click
|
||||||
|
const toggleSection = () => {
|
||||||
|
const isCollapsed = section.classList.toggle('collapsed');
|
||||||
|
|
||||||
|
// Save state to localStorage
|
||||||
|
collapsedSections[sectionId] = isCollapsed;
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(collapsedSections));
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to save collapsed sections state:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Click on header or toggle button
|
||||||
|
header.addEventListener('click', (e) => {
|
||||||
|
// Don't toggle if clicking on interactive elements within header
|
||||||
|
if (e.target.closest('a, button:not(.settings-section-toggle), input, select')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toggleSection();
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleBtn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleSection();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeSearch() {
|
||||||
|
const searchInput = document.getElementById('settingsSearchInput');
|
||||||
|
const searchClear = document.getElementById('settingsSearchClear');
|
||||||
|
|
||||||
|
if (!searchInput) return;
|
||||||
|
|
||||||
|
// Debounced search handler
|
||||||
|
let searchTimeout;
|
||||||
|
const debouncedSearch = (query) => {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
searchTimeout = setTimeout(() => {
|
||||||
|
this.performSearch(query);
|
||||||
|
}, 150);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle input changes
|
||||||
|
searchInput.addEventListener('input', (e) => {
|
||||||
|
const query = e.target.value.trim();
|
||||||
|
|
||||||
|
// Show/hide clear button
|
||||||
|
if (searchClear) {
|
||||||
|
searchClear.style.display = query ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
debouncedSearch(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle clear button click
|
||||||
|
if (searchClear) {
|
||||||
|
searchClear.addEventListener('click', () => {
|
||||||
|
searchInput.value = '';
|
||||||
|
searchClear.style.display = 'none';
|
||||||
|
searchInput.focus();
|
||||||
|
this.performSearch('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Escape key to clear search
|
||||||
|
searchInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
if (searchInput.value) {
|
||||||
|
searchInput.value = '';
|
||||||
|
if (searchClear) searchClear.style.display = 'none';
|
||||||
|
this.performSearch('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
performSearch(query) {
|
||||||
|
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
|
||||||
|
const settingsForm = document.querySelector('.settings-form');
|
||||||
|
|
||||||
|
// Remove existing empty state
|
||||||
|
const existingEmptyState = settingsForm?.querySelector('.settings-search-empty');
|
||||||
|
if (existingEmptyState) {
|
||||||
|
existingEmptyState.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
// Reset all sections to visible and remove highlights
|
||||||
|
sections.forEach(section => {
|
||||||
|
section.classList.remove('search-hidden', 'search-match');
|
||||||
|
this.removeSearchHighlights(section);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerQuery = query.toLowerCase();
|
||||||
|
let matchCount = 0;
|
||||||
|
|
||||||
|
sections.forEach(section => {
|
||||||
|
const sectionText = this.getSectionSearchableText(section);
|
||||||
|
const hasMatch = sectionText.includes(lowerQuery);
|
||||||
|
|
||||||
|
if (hasMatch) {
|
||||||
|
section.classList.remove('search-hidden');
|
||||||
|
section.classList.add('search-match');
|
||||||
|
this.highlightSearchMatches(section, lowerQuery);
|
||||||
|
matchCount++;
|
||||||
|
|
||||||
|
// Expand section if it has matches
|
||||||
|
section.classList.remove('collapsed');
|
||||||
|
} else {
|
||||||
|
section.classList.add('search-hidden');
|
||||||
|
section.classList.remove('search-match');
|
||||||
|
this.removeSearchHighlights(section);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show empty state if no matches found
|
||||||
|
if (matchCount === 0 && settingsForm) {
|
||||||
|
const emptyState = document.createElement('div');
|
||||||
|
emptyState.className = 'settings-search-empty';
|
||||||
|
emptyState.innerHTML = `
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<p>${translate('settings.search.noResults', { query }, `No settings found matching "${query}"`)}</p>
|
||||||
|
`;
|
||||||
|
settingsForm.appendChild(emptyState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSectionSearchableText(section) {
|
||||||
|
// Get all text content from labels, help text, and headers
|
||||||
|
const labels = section.querySelectorAll('label');
|
||||||
|
const helpTexts = section.querySelectorAll('.input-help');
|
||||||
|
const headers = section.querySelectorAll('h3');
|
||||||
|
|
||||||
|
let text = '';
|
||||||
|
|
||||||
|
labels.forEach(el => text += ' ' + el.textContent);
|
||||||
|
helpTexts.forEach(el => text += ' ' + el.textContent);
|
||||||
|
headers.forEach(el => text += ' ' + el.textContent);
|
||||||
|
|
||||||
|
return text.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightSearchMatches(section, query) {
|
||||||
|
// Remove existing highlights first
|
||||||
|
this.removeSearchHighlights(section);
|
||||||
|
|
||||||
|
if (!query) return;
|
||||||
|
|
||||||
|
// Highlight in labels and help text
|
||||||
|
const textElements = section.querySelectorAll('label, .input-help, h3');
|
||||||
|
|
||||||
|
textElements.forEach(element => {
|
||||||
|
const walker = document.createTreeWalker(
|
||||||
|
element,
|
||||||
|
NodeFilter.SHOW_TEXT,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const textNodes = [];
|
||||||
|
let node;
|
||||||
|
while (node = walker.nextNode()) {
|
||||||
|
if (node.textContent.toLowerCase().includes(query)) {
|
||||||
|
textNodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textNodes.forEach(textNode => {
|
||||||
|
const parent = textNode.parentElement;
|
||||||
|
const text = textNode.textContent;
|
||||||
|
const lowerText = text.toLowerCase();
|
||||||
|
|
||||||
|
// Split text by query and wrap matches in highlight spans
|
||||||
|
const parts = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
let index;
|
||||||
|
|
||||||
|
while ((index = lowerText.indexOf(query, lastIndex)) !== -1) {
|
||||||
|
// Add text before match
|
||||||
|
if (index > lastIndex) {
|
||||||
|
parts.push(document.createTextNode(text.substring(lastIndex, index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add highlighted match
|
||||||
|
const highlight = document.createElement('span');
|
||||||
|
highlight.className = 'settings-search-highlight';
|
||||||
|
highlight.textContent = text.substring(index, index + query.length);
|
||||||
|
parts.push(highlight);
|
||||||
|
|
||||||
|
lastIndex = index + query.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add remaining text
|
||||||
|
if (lastIndex < text.length) {
|
||||||
|
parts.push(document.createTextNode(text.substring(lastIndex)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace original text node with highlighted version
|
||||||
|
if (parts.length > 1) {
|
||||||
|
parts.forEach(part => parent.insertBefore(part, textNode));
|
||||||
|
parent.removeChild(textNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSearchHighlights(section) {
|
||||||
|
const highlights = section.querySelectorAll('.settings-search-highlight');
|
||||||
|
|
||||||
|
highlights.forEach(highlight => {
|
||||||
|
const parent = highlight.parentElement;
|
||||||
|
if (parent) {
|
||||||
|
// Replace highlight with its text content
|
||||||
|
parent.insertBefore(document.createTextNode(highlight.textContent), highlight);
|
||||||
|
parent.removeChild(highlight);
|
||||||
|
|
||||||
|
// Normalize to merge adjacent text nodes
|
||||||
|
parent.normalize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async openSettingsFileLocation() {
|
async openSettingsFileLocation() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/lm/settings/open-location', {
|
const response = await fetch('/api/lm/settings/open-location', {
|
||||||
|
|||||||
@@ -11,6 +11,22 @@
|
|||||||
title="{{ t('settings.openSettingsFileLocation.tooltip') }}">
|
title="{{ t('settings.openSettingsFileLocation.tooltip') }}">
|
||||||
<i class="fas fa-external-link-alt" aria-hidden="true"></i>
|
<i class="fas fa-external-link-alt" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="settings-search-wrapper">
|
||||||
|
<i class="fas fa-search settings-search-icon" aria-hidden="true"></i>
|
||||||
|
<input type="text"
|
||||||
|
id="settingsSearchInput"
|
||||||
|
class="settings-search-input"
|
||||||
|
placeholder="{{ t('settings.search.placeholder') }}"
|
||||||
|
aria-label="{{ t('settings.search.placeholder') }}"
|
||||||
|
autocomplete="off">
|
||||||
|
<button type="button"
|
||||||
|
id="settingsSearchClear"
|
||||||
|
class="settings-search-clear"
|
||||||
|
aria-label="{{ t('settings.search.clear') }}"
|
||||||
|
style="display: none;">
|
||||||
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<!-- Navigation Sidebar -->
|
<!-- Navigation Sidebar -->
|
||||||
@@ -39,6 +55,7 @@
|
|||||||
<div class="settings-nav-group-title">{{ t('settings.nav.advanced') }}</div>
|
<div class="settings-nav-group-title">{{ t('settings.nav.advanced') }}</div>
|
||||||
<button type="button" class="settings-nav-item" data-target="section-priority-tags">{{ t('settings.priorityTags.title') }}</button>
|
<button type="button" class="settings-nav-item" data-target="section-priority-tags">{{ t('settings.priorityTags.title') }}</button>
|
||||||
<button type="button" class="settings-nav-item" data-target="section-exclusions">{{ t('settings.autoOrganizeExclusions.label') }}</button>
|
<button type="button" class="settings-nav-item" data-target="section-exclusions">{{ t('settings.autoOrganizeExclusions.label') }}</button>
|
||||||
|
<button type="button" class="settings-nav-item" data-target="section-metadata-skip-paths">{{ t('settings.metadataRefreshSkipPaths.label') }}</button>
|
||||||
<button type="button" class="settings-nav-item" data-target="section-metadata-archive">{{ t('settings.sections.metadataArchive') }}</button>
|
<button type="button" class="settings-nav-item" data-target="section-metadata-archive">{{ t('settings.sections.metadataArchive') }}</button>
|
||||||
<button type="button" class="settings-nav-item" data-target="section-proxy">{{ t('settings.sections.proxySettings') }}</button>
|
<button type="button" class="settings-nav-item" data-target="section-proxy">{{ t('settings.sections.proxySettings') }}</button>
|
||||||
<button type="button" class="settings-nav-item" data-target="section-misc">{{ t('settings.sections.misc') }}</button>
|
<button type="button" class="settings-nav-item" data-target="section-misc">{{ t('settings.sections.misc') }}</button>
|
||||||
@@ -49,7 +66,15 @@
|
|||||||
<!-- Content Area -->
|
<!-- Content Area -->
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<div class="settings-form">
|
<div class="settings-form">
|
||||||
<div id="section-api-key" class="setting-item api-key-item">
|
<!-- 1. API Key Section (General) -->
|
||||||
|
<div id="section-api-key" class="setting-item api-key-item" data-section="api-key">
|
||||||
|
<div class="settings-section-header">
|
||||||
|
<h3>{{ t('settings.civitaiApiKey') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<label for="civitaiApiKey">{{ t('settings.civitaiApiKey') }}:</label>
|
<label for="civitaiApiKey">{{ t('settings.civitaiApiKey') }}:</label>
|
||||||
@@ -72,9 +97,17 @@
|
|||||||
{{ t('settings.civitaiApiKeyHelp') }}
|
{{ t('settings.civitaiApiKeyHelp') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="section-storage" class="settings-section">
|
<!-- 2. Storage Location Section (General) -->
|
||||||
|
<div id="section-storage" class="settings-section" data-section="storage">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.storageLocation') }}</h3>
|
<h3>{{ t('settings.sections.storageLocation') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -93,10 +126,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="section-content-filtering" class="settings-section">
|
<!-- 3. Content Filtering Section (Interface) -->
|
||||||
|
<div id="section-content-filtering" class="settings-section" data-section="content-filtering">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.contentFiltering') }}</h3>
|
<h3>{{ t('settings.sections.contentFiltering') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -133,11 +173,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Add Video Settings Section -->
|
<!-- 4. Video Settings Section (Interface) -->
|
||||||
<div id="section-video" class="settings-section">
|
<div id="section-video" class="settings-section" data-section="video">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.videoSettings') }}</h3>
|
<h3>{{ t('settings.sections.videoSettings') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -156,11 +202,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Add Layout Settings Section -->
|
<!-- 5. Layout Settings Section (Interface) -->
|
||||||
<div id="section-layout" class="settings-section">
|
<div id="section-layout" class="settings-section" data-section="layout">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.layoutSettings') }}</h3>
|
<h3>{{ t('settings.sections.layoutSettings') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -203,7 +255,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Model Name Display setting -->
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -221,7 +272,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Card Info Display setting -->
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -256,7 +306,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Language Selection -->
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -282,11 +331,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Add Folder Settings Section -->
|
<!-- 6. Folder Settings Section (Download) -->
|
||||||
<div id="section-folder" class="settings-section">
|
<div id="section-folder" class="settings-section" data-section="folder">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.folderSettings') }}</h3>
|
<h3>{{ t('settings.sections.folderSettings') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -311,7 +366,6 @@
|
|||||||
<div class="setting-control select-control">
|
<div class="setting-control select-control">
|
||||||
<select id="defaultLoraRoot" onchange="settingsManager.saveSelectSetting('defaultLoraRoot', 'default_lora_root')">
|
<select id="defaultLoraRoot" onchange="settingsManager.saveSelectSetting('defaultLoraRoot', 'default_lora_root')">
|
||||||
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
||||||
<!-- Options will be loaded dynamically -->
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,7 +382,6 @@
|
|||||||
<div class="setting-control select-control">
|
<div class="setting-control select-control">
|
||||||
<select id="defaultCheckpointRoot" onchange="settingsManager.saveSelectSetting('defaultCheckpointRoot', 'default_checkpoint_root')">
|
<select id="defaultCheckpointRoot" onchange="settingsManager.saveSelectSetting('defaultCheckpointRoot', 'default_checkpoint_root')">
|
||||||
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
||||||
<!-- Options will be loaded dynamically -->
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -345,7 +398,6 @@
|
|||||||
<div class="setting-control select-control">
|
<div class="setting-control select-control">
|
||||||
<select id="defaultUnetRoot" onchange="settingsManager.saveSelectSetting('defaultUnetRoot', 'default_unet_root')">
|
<select id="defaultUnetRoot" onchange="settingsManager.saveSelectSetting('defaultUnetRoot', 'default_unet_root')">
|
||||||
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
||||||
<!-- Options will be loaded dynamically -->
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -362,7 +414,6 @@
|
|||||||
<div class="setting-control select-control">
|
<div class="setting-control select-control">
|
||||||
<select id="defaultEmbeddingRoot" onchange="settingsManager.saveSelectSetting('defaultEmbeddingRoot', 'default_embedding_root')">
|
<select id="defaultEmbeddingRoot" onchange="settingsManager.saveSelectSetting('defaultEmbeddingRoot', 'default_embedding_root')">
|
||||||
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
||||||
<!-- Options will be loaded dynamically -->
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -371,10 +422,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Update Flag Strategy Section -->
|
<!-- 7. Update Flag Strategy Section (Download) -->
|
||||||
<div id="section-update-flags" class="settings-section">
|
<div id="section-update-flags" class="settings-section" data-section="update-flags">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.updateFlags') }}</h3>
|
<h3>{{ t('settings.sections.updateFlags') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -409,11 +467,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Default Path Customization Section -->
|
<!-- 8. Path Templates Section (Download) -->
|
||||||
<div id="section-path-templates" class="settings-section">
|
<div id="section-path-templates" class="settings-section" data-section="path-templates">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.downloadPathTemplates.title') }}</h3>
|
<h3>{{ t('settings.downloadPathTemplates.title') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="input-help">
|
<div class="input-help">
|
||||||
{{ t('settings.downloadPathTemplates.help') }}
|
{{ t('settings.downloadPathTemplates.help') }}
|
||||||
@@ -428,7 +492,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- LoRA Template Configuration -->
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -455,7 +518,6 @@
|
|||||||
<div class="template-preview" id="loraPreview"></div>
|
<div class="template-preview" id="loraPreview"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Checkpoint Template Configuration -->
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -482,7 +544,6 @@
|
|||||||
<div class="template-preview" id="checkpointPreview"></div>
|
<div class="template-preview" id="checkpointPreview"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Embedding Template Configuration -->
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -508,7 +569,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="template-preview" id="embeddingPreview"></div>
|
<div class="template-preview" id="embeddingPreview"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
@@ -527,91 +587,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mappings-container">
|
<div class="mappings-container">
|
||||||
<div id="baseModelMappingsContainer">
|
<div id="baseModelMappingsContainer">
|
||||||
<!-- Mapping rows will be added dynamically -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="section-priority-tags" class="setting-item priority-tags-item">
|
|
||||||
<div class="setting-row priority-tags-header">
|
|
||||||
<div class="setting-info priority-tags-info">
|
|
||||||
<label>{{ t('settings.priorityTags.title') }}</label>
|
|
||||||
<a class="settings-action-link priority-tags-help-link" href="https://github.com/willmiao/ComfyUI-Lora-Manager/wiki/Priority-Tags-Configuration-Guide" target="_blank" rel="noopener" aria-label="{{ t('settings.priorityTags.helpLinkLabel') }}" title="{{ t('settings.priorityTags.helpLinkLabel') }}">
|
|
||||||
<i class="fas fa-question-circle" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-help">{{ t('settings.priorityTags.description') }}</div>
|
|
||||||
<div class="priority-tags-tabs">
|
|
||||||
<input type="radio" id="priority-tags-tab-lora" name="priority-tags-tab" class="priority-tags-tab-input" checked>
|
|
||||||
<input type="radio" id="priority-tags-tab-checkpoint" name="priority-tags-tab" class="priority-tags-tab-input">
|
|
||||||
<input type="radio" id="priority-tags-tab-embedding" name="priority-tags-tab" class="priority-tags-tab-input">
|
|
||||||
|
|
||||||
<div class="priority-tags-tablist">
|
|
||||||
<label class="priority-tags-tab-label" for="priority-tags-tab-lora" id="priority-tags-tab-lora-label">{{ t('settings.priorityTags.modelTypes.lora') }}</label>
|
|
||||||
<label class="priority-tags-tab-label" for="priority-tags-tab-checkpoint" id="priority-tags-tab-checkpoint-label">{{ t('settings.priorityTags.modelTypes.checkpoint') }}</label>
|
|
||||||
<label class="priority-tags-tab-label" for="priority-tags-tab-embedding" id="priority-tags-tab-embedding-label">{{ t('settings.priorityTags.modelTypes.embedding') }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="priority-tags-panels">
|
|
||||||
<div class="priority-tags-panel" id="priority-tags-panel-lora" aria-labelledby="priority-tags-tab-lora-label">
|
|
||||||
<textarea id="loraPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
|
|
||||||
<div class="settings-input-error-message" id="loraPriorityTagsError"></div>
|
|
||||||
</div>
|
|
||||||
<div class="priority-tags-panel" id="priority-tags-panel-checkpoint" aria-labelledby="priority-tags-tab-checkpoint-label">
|
|
||||||
<textarea id="checkpointPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
|
|
||||||
<div class="settings-input-error-message" id="checkpointPriorityTagsError"></div>
|
|
||||||
</div>
|
|
||||||
<div class="priority-tags-panel" id="priority-tags-panel-embedding" aria-labelledby="priority-tags-tab-embedding-label">
|
|
||||||
<textarea id="embeddingPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
|
|
||||||
<div class="settings-input-error-message" id="embeddingPriorityTagsError"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="section-exclusions" class="setting-item priority-tags-item auto-organize-exclusions-item">
|
<!-- 9. Example Images Settings Section (Download) -->
|
||||||
<div class="setting-row priority-tags-header">
|
<div id="section-example-images" class="settings-section" data-section="example-images">
|
||||||
<div class="setting-info priority-tags-info">
|
<div class="settings-section-header">
|
||||||
<label>{{ t('settings.autoOrganizeExclusions.label') }}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-help">
|
|
||||||
{{ t('settings.autoOrganizeExclusions.help') }}
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
id="autoOrganizeExclusions"
|
|
||||||
class="priority-tags-input auto-organize-exclusions-input"
|
|
||||||
rows="3"
|
|
||||||
placeholder="{{ t('settings.autoOrganizeExclusions.placeholder') }}"
|
|
||||||
onblur="settingsManager.saveAutoOrganizeExclusions()"
|
|
||||||
></textarea>
|
|
||||||
<div class="settings-input-error-message" id="autoOrganizeExclusionsError"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="setting-item priority-tags-item auto-organize-exclusions-item">
|
|
||||||
<div class="setting-row priority-tags-header">
|
|
||||||
<div class="setting-info priority-tags-info">
|
|
||||||
<label>{{ t('settings.metadataRefreshSkipPaths.label') }}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-help">
|
|
||||||
{{ t('settings.metadataRefreshSkipPaths.help') }}
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
id="metadataRefreshSkipPaths"
|
|
||||||
class="priority-tags-input auto-organize-exclusions-input"
|
|
||||||
rows="3"
|
|
||||||
placeholder="{{ t('settings.metadataRefreshSkipPaths.placeholder') }}"
|
|
||||||
onblur="settingsManager.saveMetadataRefreshSkipPaths()"
|
|
||||||
></textarea>
|
|
||||||
<div class="settings-input-error-message" id="metadataRefreshSkipPathsError"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Add Example Images Settings Section -->
|
|
||||||
<div id="section-example-images" class="settings-section">
|
|
||||||
<h3>{{ t('settings.sections.exampleImages') }}</h3>
|
<h3>{{ t('settings.sections.exampleImages') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -665,11 +655,107 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Metadata Archive Section -->
|
<!-- 10. Priority Tags Section (Advanced) -->
|
||||||
<div id="section-metadata-archive" class="settings-section">
|
<div id="section-priority-tags" class="setting-item priority-tags-item" data-section="priority-tags">
|
||||||
|
<div class="settings-section-header">
|
||||||
|
<h3>
|
||||||
|
{{ t('settings.priorityTags.title') }}
|
||||||
|
<a class="settings-action-link priority-tags-help-link" href="https://github.com/willmiao/ComfyUI-Lora-Manager/wiki/Priority-Tags-Configuration-Guide" target="_blank" rel="noopener" aria-label="{{ t('settings.priorityTags.helpLinkLabel') }}" title="{{ t('settings.priorityTags.helpLinkLabel') }}">
|
||||||
|
<i class="fas fa-question-circle" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
|
<div class="input-help">{{ t('settings.priorityTags.description') }}</div>
|
||||||
|
<div class="priority-tags-tabs">
|
||||||
|
<input type="radio" id="priority-tags-tab-lora" name="priority-tags-tab" class="priority-tags-tab-input" checked>
|
||||||
|
<input type="radio" id="priority-tags-tab-checkpoint" name="priority-tags-tab" class="priority-tags-tab-input">
|
||||||
|
<input type="radio" id="priority-tags-tab-embedding" name="priority-tags-tab" class="priority-tags-tab-input">
|
||||||
|
|
||||||
|
<div class="priority-tags-tablist">
|
||||||
|
<label class="priority-tags-tab-label" for="priority-tags-tab-lora" id="priority-tags-tab-lora-label">{{ t('settings.priorityTags.modelTypes.lora') }}</label>
|
||||||
|
<label class="priority-tags-tab-label" for="priority-tags-tab-checkpoint" id="priority-tags-tab-checkpoint-label">{{ t('settings.priorityTags.modelTypes.checkpoint') }}</label>
|
||||||
|
<label class="priority-tags-tab-label" for="priority-tags-tab-embedding" id="priority-tags-tab-embedding-label">{{ t('settings.priorityTags.modelTypes.embedding') }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="priority-tags-panels">
|
||||||
|
<div class="priority-tags-panel" id="priority-tags-panel-lora" aria-labelledby="priority-tags-tab-lora-label">
|
||||||
|
<textarea id="loraPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
|
||||||
|
<div class="settings-input-error-message" id="loraPriorityTagsError"></div>
|
||||||
|
</div>
|
||||||
|
<div class="priority-tags-panel" id="priority-tags-panel-checkpoint" aria-labelledby="priority-tags-tab-checkpoint-label">
|
||||||
|
<textarea id="checkpointPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
|
||||||
|
<div class="settings-input-error-message" id="checkpointPriorityTagsError"></div>
|
||||||
|
</div>
|
||||||
|
<div class="priority-tags-panel" id="priority-tags-panel-embedding" aria-labelledby="priority-tags-tab-embedding-label">
|
||||||
|
<textarea id="embeddingPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
|
||||||
|
<div class="settings-input-error-message" id="embeddingPriorityTagsError"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 11. Auto-organize Exclusions Section (Advanced) -->
|
||||||
|
<div id="section-exclusions" class="setting-item priority-tags-item auto-organize-exclusions-item" data-section="exclusions">
|
||||||
|
<div class="settings-section-header">
|
||||||
|
<h3>{{ t('settings.autoOrganizeExclusions.label') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
|
<div class="input-help">
|
||||||
|
{{ t('settings.autoOrganizeExclusions.help') }}
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
id="autoOrganizeExclusions"
|
||||||
|
class="priority-tags-input auto-organize-exclusions-input"
|
||||||
|
rows="3"
|
||||||
|
placeholder="{{ t('settings.autoOrganizeExclusions.placeholder') }}"
|
||||||
|
onblur="settingsManager.saveAutoOrganizeExclusions()"
|
||||||
|
></textarea>
|
||||||
|
<div class="settings-input-error-message" id="autoOrganizeExclusionsError"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 12. Metadata Refresh Skip Paths Section (Advanced) -->
|
||||||
|
<div id="section-metadata-skip-paths" class="setting-item priority-tags-item auto-organize-exclusions-item" data-section="metadata-skip-paths">
|
||||||
|
<div class="settings-section-header">
|
||||||
|
<h3>{{ t('settings.metadataRefreshSkipPaths.label') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
|
<div class="input-help">
|
||||||
|
{{ t('settings.metadataRefreshSkipPaths.help') }}
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
id="metadataRefreshSkipPaths"
|
||||||
|
class="priority-tags-input auto-organize-exclusions-input"
|
||||||
|
rows="3"
|
||||||
|
placeholder="{{ t('settings.metadataRefreshSkipPaths.placeholder') }}"
|
||||||
|
onblur="settingsManager.saveMetadataRefreshSkipPaths()"
|
||||||
|
></textarea>
|
||||||
|
<div class="settings-input-error-message" id="metadataRefreshSkipPathsError"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 13. Metadata Archive Section (Advanced) -->
|
||||||
|
<div id="section-metadata-archive" class="settings-section" data-section="metadata-archive">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.metadataArchive') }}</h3>
|
<h3>{{ t('settings.sections.metadataArchive') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -689,7 +775,6 @@
|
|||||||
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="metadata-archive-status" id="metadataArchiveStatus">
|
<div class="metadata-archive-status" id="metadataArchiveStatus">
|
||||||
<!-- Status will be populated by JavaScript -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -712,11 +797,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Proxy Settings Section -->
|
<!-- 14. Proxy Settings Section (Advanced) -->
|
||||||
<div id="section-proxy" class="settings-section">
|
<div id="section-proxy" class="settings-section" data-section="proxy">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.proxySettings') }}</h3>
|
<h3>{{ t('settings.sections.proxySettings') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -835,10 +926,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Misc. Section -->
|
<!-- 15. Misc Section (Advanced) -->
|
||||||
<div id="section-misc" class="settings-section">
|
<div id="section-misc" class="settings-section" data-section="misc">
|
||||||
|
<div class="settings-section-header">
|
||||||
<h3>{{ t('settings.sections.misc') }}</h3>
|
<h3>{{ t('settings.sections.misc') }}</h3>
|
||||||
|
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
|
||||||
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
@@ -857,6 +955,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user