mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: Implement auto-hide functionality for sidebar and update controls layout
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
.folder-sidebar {
|
||||
position: fixed;
|
||||
top: 68px; /* Below header */
|
||||
left: 0px;
|
||||
left: 10px;
|
||||
width: 230px;
|
||||
height: calc(100vh - 88px);
|
||||
background: var(--bg-color);
|
||||
@@ -17,12 +17,41 @@
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* 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.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -54,9 +83,17 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sidebar-toggle-close {
|
||||
.sidebar-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.sidebar-action-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
@@ -72,12 +109,23 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sidebar-toggle-close:hover {
|
||||
.sidebar-action-btn:hover {
|
||||
opacity: 1;
|
||||
background: var(--lora-surface);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.sidebar-action-btn.active {
|
||||
opacity: 1;
|
||||
background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.15);
|
||||
color: var(--lora-accent);
|
||||
}
|
||||
|
||||
/* Remove old close button styles */
|
||||
.sidebar-toggle-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
@@ -328,38 +376,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar Toggle Button */
|
||||
.sidebar-toggle-container {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn:hover {
|
||||
background: var(--lora-accent);
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn.active {
|
||||
background: var(--lora-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.sidebar-tree-placeholder {
|
||||
padding: 24px 16px;
|
||||
|
||||
@@ -224,33 +224,6 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Sidebar Toggle Button */
|
||||
.sidebar-toggle-container {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn:hover {
|
||||
background: var(--lora-accent);
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Icon-only button style */
|
||||
.icon-only {
|
||||
min-width: unset !important;
|
||||
@@ -298,7 +271,6 @@
|
||||
/* Prevent text selection in control and header areas */
|
||||
.control-group button,
|
||||
.control-group select,
|
||||
.sidebar-toggle-btn,
|
||||
.bulk-operations-panel,
|
||||
.app-header,
|
||||
.header-branding,
|
||||
@@ -306,8 +278,7 @@
|
||||
.main-nav,
|
||||
.nav-item,
|
||||
.header-actions button,
|
||||
.header-controls,
|
||||
.sidebar-toggle-container button {
|
||||
.header-controls {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
@@ -391,14 +362,6 @@
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.sidebar-toggle-container {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn:hover {
|
||||
transform: none; /* Disable hover effects on mobile */
|
||||
}
|
||||
|
||||
.control-group button:hover {
|
||||
transform: none; /* Disable hover effects on mobile */
|
||||
|
||||
@@ -12,15 +12,23 @@ export class SidebarManager {
|
||||
this.selectedPath = '';
|
||||
this.expandedNodes = new Set();
|
||||
this.isVisible = true;
|
||||
this.isPinned = false;
|
||||
this.apiClient = null;
|
||||
this.openDropdown = null;
|
||||
this.hoverTimeout = null;
|
||||
this.isHovering = false;
|
||||
|
||||
// Bind methods
|
||||
this.handleTreeClick = this.handleTreeClick.bind(this);
|
||||
this.handleBreadcrumbClick = this.handleBreadcrumbClick.bind(this);
|
||||
this.toggleSidebar = this.toggleSidebar.bind(this);
|
||||
this.closeSidebar = this.closeSidebar.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.init();
|
||||
}
|
||||
@@ -32,6 +40,7 @@ export class SidebarManager {
|
||||
this.restoreSidebarState();
|
||||
await this.loadFolderTree();
|
||||
this.restoreSelectedFolder();
|
||||
this.updateAutoHideState();
|
||||
}
|
||||
|
||||
updateSidebarTitle() {
|
||||
@@ -42,22 +51,22 @@ export class SidebarManager {
|
||||
}
|
||||
|
||||
setupEventHandlers() {
|
||||
// Sidebar toggle button
|
||||
const toggleBtn = document.querySelector('.sidebar-toggle-btn');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', this.toggleSidebar);
|
||||
}
|
||||
|
||||
// Sidebar header (root selection)
|
||||
// Sidebar header (root selection) - only trigger on title area
|
||||
const sidebarHeader = document.getElementById('sidebarHeader');
|
||||
if (sidebarHeader) {
|
||||
sidebarHeader.addEventListener('click', () => this.selectFolder(''));
|
||||
sidebarHeader.addEventListener('click', this.handleSidebarHeaderClick);
|
||||
}
|
||||
|
||||
// Sidebar close button
|
||||
const closeBtn = document.getElementById('sidebarToggleClose');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', this.closeSidebar);
|
||||
// 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) {
|
||||
collapseAllBtn.addEventListener('click', this.handleCollapseAll);
|
||||
}
|
||||
|
||||
// Tree click handler
|
||||
@@ -72,27 +81,34 @@ 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) {
|
||||
const sidebar = document.getElementById('folderSidebar');
|
||||
const toggleBtn = document.querySelector('.sidebar-toggle-btn');
|
||||
|
||||
if (sidebar && !sidebar.contains(e.target) &&
|
||||
toggleBtn && !toggleBtn.contains(e.target)) {
|
||||
this.closeSidebar();
|
||||
if (sidebar && !sidebar.contains(e.target)) {
|
||||
this.hideSidebar();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth > 1024 && this.isVisible) {
|
||||
const sidebar = document.getElementById('folderSidebar');
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('collapsed');
|
||||
}
|
||||
}
|
||||
this.updateAutoHideState();
|
||||
});
|
||||
|
||||
// Add document click handler for closing dropdowns
|
||||
@@ -106,10 +122,115 @@ export class SidebarManager {
|
||||
}
|
||||
}
|
||||
|
||||
closeDropdown() {
|
||||
if (this.openDropdown) {
|
||||
this.openDropdown.classList.remove('open');
|
||||
this.openDropdown = null;
|
||||
handleSidebarHeaderClick(event) {
|
||||
// Only trigger root selection if clicking on the title area, not the buttons
|
||||
if (!event.target.closest('.sidebar-header-actions')) {
|
||||
this.selectFolder('');
|
||||
}
|
||||
}
|
||||
|
||||
handlePinToggle(event) {
|
||||
event.stopPropagation();
|
||||
this.isPinned = !this.isPinned;
|
||||
this.updateAutoHideState();
|
||||
this.updatePinButton();
|
||||
this.saveSidebarState();
|
||||
}
|
||||
|
||||
handleCollapseAll(event) {
|
||||
event.stopPropagation();
|
||||
this.expandedNodes.clear();
|
||||
this.renderTree();
|
||||
this.saveExpandedState();
|
||||
}
|
||||
|
||||
handleMouseEnter() {
|
||||
this.isHovering = true;
|
||||
if (this.hoverTimeout) {
|
||||
clearTimeout(this.hoverTimeout);
|
||||
this.hoverTimeout = null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
hideSidebar() {
|
||||
const sidebar = document.getElementById('folderSidebar');
|
||||
if (sidebar && !this.isPinned) {
|
||||
sidebar.classList.remove('hover-active');
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateAutoHideState() {
|
||||
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');
|
||||
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');
|
||||
hoverArea.classList.add('disabled');
|
||||
this.isVisible = true;
|
||||
} else {
|
||||
// Desktop auto-hide: use hover detection
|
||||
sidebar.classList.remove('collapsed');
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePinButton() {
|
||||
const pinBtn = document.getElementById('sidebarPinToggle');
|
||||
if (pinBtn) {
|
||||
pinBtn.classList.toggle('active', this.isPinned);
|
||||
pinBtn.title = this.isPinned ? 'Unpin Sidebar' : 'Pin Sidebar';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,15 +376,12 @@ export class SidebarManager {
|
||||
this.pageControls.pageState.activeFolder = path || null;
|
||||
setStorageItem(`${this.pageType}_activeFolder`, path || null);
|
||||
|
||||
// Always show breadcrumb container
|
||||
// Removed hiding breadcrumb container code
|
||||
|
||||
// Reload models with new filter
|
||||
await this.pageControls.resetAndReload();
|
||||
|
||||
// Auto-close sidebar on mobile after selection
|
||||
// Auto-hide sidebar on mobile after selection
|
||||
if (window.innerWidth <= 1024) {
|
||||
this.closeSidebar();
|
||||
this.hideSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,22 +605,13 @@ export class SidebarManager {
|
||||
}
|
||||
|
||||
restoreSidebarState() {
|
||||
const isVisible = getStorageItem(`${this.pageType}_sidebarVisible`, true);
|
||||
const isPinned = getStorageItem(`${this.pageType}_sidebarPinned`, false);
|
||||
const expandedPaths = getStorageItem(`${this.pageType}_expandedNodes`, []);
|
||||
|
||||
this.isVisible = isVisible;
|
||||
this.isPinned = isPinned;
|
||||
this.expandedNodes = new Set(expandedPaths);
|
||||
|
||||
const sidebar = document.getElementById('folderSidebar');
|
||||
const toggleBtn = document.querySelector('.sidebar-toggle-btn');
|
||||
|
||||
if (sidebar) {
|
||||
sidebar.classList.toggle('collapsed', !this.isVisible);
|
||||
}
|
||||
|
||||
if (toggleBtn) {
|
||||
toggleBtn.classList.toggle('active', this.isVisible);
|
||||
}
|
||||
this.updatePinButton();
|
||||
}
|
||||
|
||||
restoreSelectedFolder() {
|
||||
@@ -520,7 +629,7 @@ export class SidebarManager {
|
||||
}
|
||||
|
||||
saveSidebarState() {
|
||||
setStorageItem(`${this.pageType}_sidebarVisible`, this.isVisible);
|
||||
setStorageItem(`${this.pageType}_sidebarPinned`, this.isPinned);
|
||||
}
|
||||
|
||||
saveExpandedState() {
|
||||
@@ -533,18 +642,25 @@ export class SidebarManager {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Clear any pending timeouts
|
||||
if (this.hoverTimeout) {
|
||||
clearTimeout(this.hoverTimeout);
|
||||
}
|
||||
|
||||
// Clean up event handlers
|
||||
const toggleBtn = document.querySelector('.sidebar-toggle-btn');
|
||||
const closeBtn = document.getElementById('sidebarToggleClose');
|
||||
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');
|
||||
|
||||
if (toggleBtn) {
|
||||
toggleBtn.removeEventListener('click', this.toggleSidebar);
|
||||
if (pinToggleBtn) {
|
||||
pinToggleBtn.removeEventListener('click', this.handlePinToggle);
|
||||
}
|
||||
if (closeBtn) {
|
||||
closeBtn.removeEventListener('click', this.closeSidebar);
|
||||
if (collapseAllBtn) {
|
||||
collapseAllBtn.removeEventListener('click', this.handleCollapseAll);
|
||||
}
|
||||
if (folderTree) {
|
||||
folderTree.removeEventListener('click', this.handleTreeClick);
|
||||
@@ -553,7 +669,15 @@ export class SidebarManager {
|
||||
sidebarBreadcrumbNav.removeEventListener('click', this.handleBreadcrumbClick);
|
||||
}
|
||||
if (sidebarHeader) {
|
||||
sidebarHeader.removeEventListener('click', () => this.selectFolder(''));
|
||||
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
|
||||
|
||||
@@ -65,12 +65,6 @@
|
||||
</div>
|
||||
|
||||
<div class="controls-right">
|
||||
<div class="sidebar-toggle-container">
|
||||
<button class="sidebar-toggle-btn icon-only" title="Toggle folder sidebar">
|
||||
<i class="fas fa-folder-tree"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="keyboard-nav-hint tooltip">
|
||||
<i class="fas fa-keyboard"></i>
|
||||
<span class="tooltiptext">
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
<!-- Hover detection area -->
|
||||
<div class="sidebar-hover-area" id="sidebarHoverArea"></div>
|
||||
|
||||
<!-- Folder Navigation Sidebar -->
|
||||
<div class="folder-sidebar" id="folderSidebar">
|
||||
<div class="sidebar-header" id="sidebarHeader">
|
||||
<h3><i class="fas fa-home"></i> <span id="sidebarTitle">Model Root</span></h3>
|
||||
<button class="sidebar-toggle-close" id="sidebarToggleClose">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
<div class="sidebar-header-actions">
|
||||
<button class="sidebar-action-btn" id="sidebarCollapseAll" title="Collapse All Folders">
|
||||
<i class="fas fa-compress-alt"></i>
|
||||
</button>
|
||||
<button class="sidebar-action-btn" id="sidebarPinToggle" title="Pin/Unpin Sidebar">
|
||||
<i class="fas fa-thumbtack"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-tree-container">
|
||||
|
||||
Reference in New Issue
Block a user