feat: Refactor SidebarManager integration and cleanup methods for improved state management

This commit is contained in:
Will Miao
2025-08-26 21:38:33 +08:00
parent 083f4805b2
commit d62cff9841
5 changed files with 110 additions and 94 deletions

View File

@@ -1,5 +1,5 @@
import { state, getCurrentPageState } from '../state/index.js';
import { showToast, updateFolderTags } from '../utils/uiHelpers.js';
import { showToast } from '../utils/uiHelpers.js';
import { getStorageItem, getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js';
import {
getCompleteApiConfig,
@@ -9,6 +9,7 @@ import {
WS_ENDPOINTS
} from './apiConfig.js';
import { resetAndReload } from './modelApiFactory.js';
import { sidebarManager } from '../components/SidebarManager.js';
/**
* Abstract base class for all model API clients
@@ -103,15 +104,7 @@ export class BaseModelApiClient {
pageState.currentPage = pageState.currentPage + 1;
if (updateFolders) {
const response = await fetch(this.apiConfig.endpoints.folders);
if (response.ok) {
const data = await response.json();
updateFolderTags(data.folders);
} else {
const errorData = await response.json().catch(() => ({}));
const errorMsg = errorData && errorData.error ? errorData.error : response.statusText;
console.error(`Error getting folders: ${errorMsg}`);
}
sidebarManager.refresh();
}
return result;

View File

@@ -5,9 +5,9 @@ import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
import { getModelApiClient } from '../api/modelApiFactory.js';
export class SidebarManager {
constructor(pageControls) {
this.pageControls = pageControls;
this.pageType = pageControls.pageType;
constructor() {
this.pageControls = null;
this.pageType = null;
this.treeData = {};
this.selectedPath = '';
this.expandedNodes = new Set();
@@ -17,6 +17,7 @@ export class SidebarManager {
this.openDropdown = null;
this.hoverTimeout = null;
this.isHovering = false;
this.isInitialized = false;
// Bind methods
this.handleTreeClick = this.handleTreeClick.bind(this);
@@ -29,8 +30,95 @@ export class SidebarManager {
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.handleHoverAreaEnter = this.handleHoverAreaEnter.bind(this);
this.handleHoverAreaLeave = this.handleHoverAreaLeave.bind(this);
}
async initialize(pageControls) {
// Clean up previous initialization if exists
if (this.isInitialized) {
this.cleanup();
}
this.pageControls = pageControls;
this.pageType = pageControls.pageType;
this.apiClient = getModelApiClient();
this.init();
// Set initial sidebar state immediately (hidden by default)
this.setInitialSidebarState();
this.setupEventHandlers();
this.updateSidebarTitle();
this.restoreSidebarState();
await this.loadFolderTree();
this.restoreSelectedFolder();
// Apply final state with animation after everything is loaded
this.applyFinalSidebarState();
this.isInitialized = true;
console.log(`SidebarManager initialized for ${this.pageType} page`);
}
cleanup() {
if (!this.isInitialized) return;
// Clear any pending timeouts
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
this.hoverTimeout = null;
}
// Clean up event handlers
this.removeEventHandlers();
// Reset state
this.pageControls = null;
this.pageType = null;
this.treeData = {};
this.selectedPath = '';
this.expandedNodes = new Set();
this.openDropdown = null;
this.isHovering = false;
this.apiClient = null;
this.isInitialized = false;
console.log('SidebarManager cleaned up');
}
removeEventHandlers() {
const pinToggleBtn = document.getElementById('sidebarPinToggle');
const collapseAllBtn = document.getElementById('sidebarCollapseAll');
const folderTree = document.getElementById('sidebarFolderTree');
const sidebarBreadcrumbNav = document.getElementById('sidebarBreadcrumbNav');
const sidebarHeader = document.getElementById('sidebarHeader');
const sidebar = document.getElementById('folderSidebar');
const hoverArea = document.getElementById('sidebarHoverArea');
if (pinToggleBtn) {
pinToggleBtn.removeEventListener('click', this.handlePinToggle);
}
if (collapseAllBtn) {
collapseAllBtn.removeEventListener('click', this.handleCollapseAll);
}
if (folderTree) {
folderTree.removeEventListener('click', this.handleTreeClick);
}
if (sidebarBreadcrumbNav) {
sidebarBreadcrumbNav.removeEventListener('click', this.handleBreadcrumbClick);
}
if (sidebarHeader) {
sidebarHeader.removeEventListener('click', this.handleSidebarHeaderClick);
}
if (sidebar) {
sidebar.removeEventListener('mouseenter', this.handleMouseEnter);
sidebar.removeEventListener('mouseleave', this.handleMouseLeave);
}
if (hoverArea) {
hoverArea.removeEventListener('mouseenter', this.handleHoverAreaEnter);
hoverArea.removeEventListener('mouseleave', this.handleHoverAreaLeave);
}
// Remove document click handler
document.removeEventListener('click', this.handleDocumentClick);
}
async init() {
@@ -692,45 +780,9 @@ export class SidebarManager {
}
destroy() {
// Clear any pending timeouts
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
}
// Clean up event handlers
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 (pinToggleBtn) {
pinToggleBtn.removeEventListener('click', this.handlePinToggle);
}
if (collapseAllBtn) {
collapseAllBtn.removeEventListener('click', this.handleCollapseAll);
}
if (folderTree) {
folderTree.removeEventListener('click', this.handleTreeClick);
}
if (sidebarBreadcrumbNav) {
sidebarBreadcrumbNav.removeEventListener('click', this.handleBreadcrumbClick);
}
if (sidebarHeader) {
sidebarHeader.removeEventListener('click', this.handleSidebarHeaderClick);
}
if (sidebar) {
sidebar.removeEventListener('mouseenter', this.handleMouseEnter);
sidebar.removeEventListener('mouseleave', this.handleMouseLeave);
}
if (hoverArea) {
hoverArea.removeEventListener('mouseenter', this.handleHoverAreaEnter);
hoverArea.removeEventListener('mouseleave', this.handleHoverAreaLeave);
}
// Remove document click handler
document.removeEventListener('click', this.handleDocumentClick);
this.cleanup();
}
}
// Create and export global instance
export const sidebarManager = new SidebarManager();

View File

@@ -3,6 +3,7 @@ import { getCurrentPageState, setCurrentPageType } from '../../state/index.js';
import { getStorageItem, setStorageItem, getSessionItem, setSessionItem } from '../../utils/storageHelpers.js';
import { showToast } from '../../utils/uiHelpers.js';
import { SidebarManager } from '../SidebarManager.js';
import { sidebarManager } from '../SidebarManager.js';
/**
* PageControls class - Unified control management for model pages
@@ -24,8 +25,8 @@ export class PageControls {
// Store API methods
this.api = null;
// Initialize sidebar manager
this.sidebarManager = null;
// Use global sidebar manager
this.sidebarManager = sidebarManager;
// Initialize event listeners
this.initEventListeners();
@@ -69,7 +70,7 @@ export class PageControls {
*/
async initSidebarManager() {
try {
this.sidebarManager = new SidebarManager(this);
await this.sidebarManager.initialize(this);
console.log('SidebarManager initialized');
} catch (error) {
console.error('Failed to initialize SidebarManager:', error);
@@ -440,8 +441,10 @@ export class PageControls {
* Clean up resources
*/
destroy() {
if (this.sidebarManager) {
this.sidebarManager.destroy();
// Note: We don't destroy the global sidebar manager, just clean it up
// The global instance will be reused for other page controls
if (this.sidebarManager && this.sidebarManager.isInitialized) {
this.sidebarManager.cleanup();
}
}
}

View File

@@ -1,10 +1,11 @@
import { showToast, updateFolderTags } from '../utils/uiHelpers.js';
import { showToast } from '../utils/uiHelpers.js';
import { state, getCurrentPageState } from '../state/index.js';
import { modalManager } from './ModalManager.js';
import { bulkManager } from './BulkManager.js';
import { getStorageItem } from '../utils/storageHelpers.js';
import { getModelApiClient } from '../api/modelApiFactory.js';
import { FolderTreeManager } from '../components/FolderTreeManager.js';
import { sidebarManager } from '../components/SidebarManager.js';
class MoveManager {
constructor() {
@@ -224,12 +225,7 @@ class MoveManager {
}
// Refresh folder tags after successful move
try {
const foldersData = await apiClient.fetchModelFolders();
updateFolderTags(foldersData.folders);
} catch (error) {
console.error('Error refreshing folder tags:', error);
}
sidebarManager.refresh();
modalManager.closeModal('moveModal');

View File

@@ -665,32 +665,4 @@ export async function openExampleImagesFolder(modelHash) {
showToast('Failed to open example images folder', 'error');
return false;
}
}
/**
* Update the folder tags display with new folder list
* @param {Array} folders - List of folder names
*/
export function updateFolderTags(folders) {
const folderTagsContainer = document.querySelector('.folder-tags');
if (!folderTagsContainer) return;
// Keep track of currently selected folder
const pageState = getCurrentPageState();
const currentFolder = pageState.activeFolder;
// Create HTML for folder tags
const tagsHTML = folders.map(folder => {
const isActive = folder === currentFolder;
return `<div class="tag ${isActive ? 'active' : ''}" data-folder="${folder}">${folder}</div>`;
}).join('');
// Update the container
folderTagsContainer.innerHTML = tagsHTML;
// Scroll active folder into view (no need to reattach click handlers)
const activeTag = folderTagsContainer.querySelector(`.tag[data-folder="${currentFolder}"]`);
if (activeTag) {
activeTag.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}