mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
refactor(settings): implement macOS Settings style for settings modal
- Reorganize settings into 4 sections: General, Interface, Download, Advanced - Implement section switching instead of scrolling (macOS Settings style) - Remove collapsible/expandable sections and redundant 'SETTINGS' label - Add accent-colored underline for section headers - Update navigation with larger, more prominent active state - Add fade-in animation for section transitions - Update search to auto-switch to matching section - Refactor CSS: 800x600 fixed modal size, remove collapse styles - Refactor JS: simplify navigation logic, remove scroll spy and collapse code Refs: Phase 0 settings modal optimization
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
/* Settings styles */
|
||||
/* Settings Modal - macOS Settings Style */
|
||||
.settings-toggle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@@ -20,9 +20,10 @@
|
||||
}
|
||||
|
||||
.settings-modal {
|
||||
max-width: 950px;
|
||||
width: 90vw;
|
||||
max-height: 85vh;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
max-width: 95vw;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -49,17 +50,6 @@
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.settings-nav-title {
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-color);
|
||||
opacity: 0.6;
|
||||
margin-bottom: var(--space-2);
|
||||
padding: 0 var(--space-1);
|
||||
}
|
||||
|
||||
.settings-nav-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
@@ -70,28 +60,30 @@
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
/* Hide group titles - we use flat navigation */
|
||||
.settings-nav-group-title {
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
padding: var(--space-1) var(--space-1);
|
||||
margin-bottom: var(--space-1);
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide settings title */
|
||||
.settings-nav-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-nav-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
padding: 10px 14px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-color);
|
||||
text-align: left;
|
||||
font-size: 0.9em;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border-radius: var(--border-radius-xs);
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.settings-nav-item:hover {
|
||||
@@ -102,7 +94,7 @@
|
||||
.settings-nav-item.active {
|
||||
background: var(--lora-accent);
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Content Area */
|
||||
@@ -560,71 +552,46 @@
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
/* Settings Styles */
|
||||
/* Settings Section - macOS Settings Style */
|
||||
.settings-section {
|
||||
margin-top: var(--space-3);
|
||||
border-top: 1px solid var(--lora-border);
|
||||
padding-top: var(--space-2);
|
||||
display: none;
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.settings-section.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.settings-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
padding: var(--space-1) 0;
|
||||
margin-bottom: var(--space-2);
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-section-header:hover {
|
||||
opacity: 0.8;
|
||||
padding: 0 0 var(--space-2) 0;
|
||||
margin-bottom: var(--space-3);
|
||||
border-bottom: 2px solid var(--lora-accent);
|
||||
}
|
||||
|
||||
.settings-section-header h3 {
|
||||
font-size: 1.1em;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Remove toggle button styles */
|
||||
.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;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
|
||||
@@ -371,132 +371,39 @@ export class SettingsManager {
|
||||
}
|
||||
|
||||
initializeNavigation() {
|
||||
const settingsContent = document.querySelector('.settings-content');
|
||||
const navItems = document.querySelectorAll('.settings-nav-item');
|
||||
const sections = document.querySelectorAll('.settings-section');
|
||||
|
||||
if (!settingsContent || navItems.length === 0) return;
|
||||
if (navItems.length === 0 || sections.length === 0) return;
|
||||
|
||||
// Handle navigation item clicks
|
||||
// Handle navigation item clicks - macOS Settings style: show section instead of scroll
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const targetId = item.dataset.target;
|
||||
if (!targetId) return;
|
||||
const sectionId = item.dataset.section;
|
||||
if (!sectionId) return;
|
||||
|
||||
const targetSection = document.getElementById(targetId);
|
||||
// Hide all sections
|
||||
sections.forEach(section => {
|
||||
section.classList.remove('active');
|
||||
});
|
||||
|
||||
// Show target section
|
||||
const targetSection = document.getElementById(`section-${sectionId}`);
|
||||
if (targetSection) {
|
||||
targetSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
targetSection.classList.add('active');
|
||||
}
|
||||
|
||||
// Update active state
|
||||
// Update active nav state
|
||||
navItems.forEach(nav => nav.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Setup scroll spy
|
||||
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
|
||||
|
||||
const updateActiveNav = () => {
|
||||
const scrollTop = settingsContent.scrollTop;
|
||||
const contentHeight = settingsContent.clientHeight;
|
||||
|
||||
let currentSection = null;
|
||||
let minDistance = Infinity;
|
||||
|
||||
sections.forEach(section => {
|
||||
const sectionTop = section.offsetTop - settingsContent.offsetTop;
|
||||
const distance = Math.abs(sectionTop - scrollTop);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
currentSection = section;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentSection) {
|
||||
const sectionId = currentSection.id;
|
||||
navItems.forEach(item => {
|
||||
item.classList.remove('active');
|
||||
if (item.dataset.target === sectionId) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Use requestAnimationFrame for performance
|
||||
let ticking = false;
|
||||
settingsContent.addEventListener('scroll', () => {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
updateActiveNav();
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize section collapse/expand
|
||||
this.initializeSectionCollapse();
|
||||
|
||||
// Initial update
|
||||
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);
|
||||
// Show first section by default
|
||||
const firstSection = sections[0];
|
||||
if (firstSection) {
|
||||
firstSection.classList.add('active');
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -549,7 +456,8 @@ export class SettingsManager {
|
||||
}
|
||||
|
||||
performSearch(query) {
|
||||
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
|
||||
const sections = document.querySelectorAll('.settings-section');
|
||||
const navItems = document.querySelectorAll('.settings-nav-item');
|
||||
const settingsForm = document.querySelector('.settings-form');
|
||||
|
||||
// Remove existing empty state
|
||||
@@ -559,15 +467,15 @@ export class SettingsManager {
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
// Reset all sections to visible and remove highlights
|
||||
// Reset: remove highlights only, keep current section visible
|
||||
sections.forEach(section => {
|
||||
section.classList.remove('search-hidden', 'search-match');
|
||||
this.removeSearchHighlights(section);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const lowerQuery = query.toLowerCase();
|
||||
let firstMatchSection = null;
|
||||
let matchCount = 0;
|
||||
|
||||
sections.forEach(section => {
|
||||
@@ -575,20 +483,37 @@ export class SettingsManager {
|
||||
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');
|
||||
// Track first match to auto-switch
|
||||
if (!firstMatchSection) {
|
||||
firstMatchSection = section;
|
||||
}
|
||||
} else {
|
||||
section.classList.add('search-hidden');
|
||||
section.classList.remove('search-match');
|
||||
this.removeSearchHighlights(section);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-switch to first matching section
|
||||
if (firstMatchSection) {
|
||||
const sectionId = firstMatchSection.id.replace('section-', '');
|
||||
|
||||
// Hide all sections
|
||||
sections.forEach(section => section.classList.remove('active'));
|
||||
|
||||
// Show matching section
|
||||
firstMatchSection.classList.add('active');
|
||||
|
||||
// Update nav active state
|
||||
navItems.forEach(item => {
|
||||
item.classList.remove('active');
|
||||
if (item.dataset.section === sectionId) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show empty state if no matches found
|
||||
if (matchCount === 0 && settingsForm) {
|
||||
const emptyState = document.createElement('div');
|
||||
|
||||
Reference in New Issue
Block a user