From a0b9891e04377f64f18a51c2c6274fc8f515bb56 Mon Sep 17 00:00:00 2001 From: Quorafind Date: Thu, 16 Oct 2025 23:48:22 +0800 Subject: [PATCH] fix(workspace): improve async handling and error recovery in module visibility - Fix async handling in hidden modules toggle with proper error recovery - Add type imports for SidebarComponentType and FeatureComponentType - Refactor module visibility toggle to use workspace manager API methods - Add try-catch-finally structure to FluentTaskView initialization - Enhance error handling and logging in view initialization - Add DEBUG_MODE configuration for conditional logging - Improve state refresh after module visibility changes --- .../settings/tabs/WorkspaceSettingTab.ts | 128 +++++++--- src/pages/FluentTaskView.ts | 225 ++++++++++++------ 2 files changed, 239 insertions(+), 114 deletions(-) diff --git a/src/components/features/settings/tabs/WorkspaceSettingTab.ts b/src/components/features/settings/tabs/WorkspaceSettingTab.ts index 0891bf22..ffc3a137 100644 --- a/src/components/features/settings/tabs/WorkspaceSettingTab.ts +++ b/src/components/features/settings/tabs/WorkspaceSettingTab.ts @@ -1,7 +1,12 @@ import { Menu, Setting, setIcon } from "obsidian"; import { TaskProgressBarSettingTab } from "@/setting"; import TaskProgressBarPlugin from "@/index"; -import { WorkspaceData, ModuleDefinition } from "@/types/workspace"; +import { + WorkspaceData, + ModuleDefinition, + SidebarComponentType, + FeatureComponentType, +} from "@/types/workspace"; import { t } from "@/translations/helper"; import { CreateWorkspaceModal, @@ -351,7 +356,7 @@ function renderHiddenModulesConfig( }); const modules = getAvailableModules(plugin); - const hiddenModules = workspace.settings.hiddenModules || { + let hiddenModules = workspace.settings.hiddenModules || { views: [], sidebarComponents: [], features: [], @@ -366,9 +371,11 @@ function renderHiddenModulesConfig( groupTitle: string, groupIcon: string, moduleList: ModuleDefinition[], - hiddenList: string[], + initialHiddenList: string[], moduleType: "views" | "sidebarComponents" | "features", ) => { + let hiddenList = [...initialHiddenList]; + const groupEl = groupsContainer.createDiv({ cls: "workspace-module-group", }); @@ -429,57 +436,100 @@ function renderHiddenModulesConfig( text: module.name, }); - // Toggle handler - const toggleVisibility = () => { + const toggleVisibility = async () => { const newHiddenList = [...hiddenList]; const index = newHiddenList.indexOf(module.id); + const shouldBeVisible = checkbox.checked; - if (checkbox.checked && index !== -1) { - // Remove from hidden list (make visible) + if (shouldBeVisible && index !== -1) { newHiddenList.splice(index, 1); - itemEl.removeClass("is-hidden"); - } else if (!checkbox.checked && index === -1) { - // Add to hidden list (make hidden) + } else if (!shouldBeVisible && index === -1) { newHiddenList.push(module.id); - itemEl.addClass("is-hidden"); } - // Update workspace settings - if (!workspace.settings.hiddenModules) { - workspace.settings.hiddenModules = { - views: [], - sidebarComponents: [], - features: [], - }; - } - workspace.settings.hiddenModules[moduleType] = newHiddenList; + try { + switch (moduleType) { + case "views": + await plugin.workspaceManager?.setHiddenViews( + [...newHiddenList], + workspace.id, + ); + break; + case "sidebarComponents": + await plugin.workspaceManager?.setHiddenSidebarComponents( + newHiddenList as SidebarComponentType[], + workspace.id, + ); + break; + case "features": + await plugin.workspaceManager?.setHiddenFeatures( + newHiddenList as FeatureComponentType[], + workspace.id, + ); + break; + } - // Save and trigger update - plugin.workspaceManager?.updateWorkspace( - workspace.id, - workspace, - ); - onUpdate(); + const refreshed = + plugin.workspaceManager?.getWorkspace(workspace.id); + if (refreshed?.settings?.hiddenModules) { + hiddenModules = refreshed.settings.hiddenModules; + } else { + hiddenModules = { + views: [], + sidebarComponents: [], + features: [], + }; + } - // Update count badge - const countBadge = headerEl.querySelector( - ".workspace-module-group-count", - ); - if (countBadge) { - countBadge.textContent = t("{{hidden}}/{{total}} hidden", { - interpolation: { - hidden: newHiddenList.length.toString(), - total: totalCount.toString(), - }, - }); + hiddenList = newHiddenList; + switch (moduleType) { + case "views": + hiddenModules.views = newHiddenList; + break; + case "sidebarComponents": + hiddenModules.sidebarComponents = + newHiddenList as SidebarComponentType[]; + break; + case "features": + hiddenModules.features = + newHiddenList as FeatureComponentType[]; + break; + } + + itemEl.toggleClass("is-hidden", !shouldBeVisible); + + const countBadge = headerEl.querySelector( + ".workspace-module-group-count", + ); + if (countBadge) { + countBadge.textContent = t( + "{{hidden}}/{{total}} hidden", + { + interpolation: { + hidden: newHiddenList.length.toString(), + total: totalCount.toString(), + }, + }, + ); + } + + onUpdate(); + } catch (error) { + console.error( + "[WorkspaceSettings] Failed to update hidden modules", + error, + ); + checkbox.checked = !checkbox.checked; } }; - checkbox.addEventListener("change", toggleVisibility); + checkbox.addEventListener("change", () => { + void toggleVisibility(); + }); itemEl.addEventListener("click", (e) => { if (e.target !== checkbox) { checkbox.checked = !checkbox.checked; - toggleVisibility(); + void toggleVisibility(); } }); }); diff --git a/src/pages/FluentTaskView.ts b/src/pages/FluentTaskView.ts index 81b71e71..9e94d7c4 100644 --- a/src/pages/FluentTaskView.ts +++ b/src/pages/FluentTaskView.ts @@ -43,6 +43,12 @@ export const FLUENT_TASK_VIEW = "fluent-task-genius-view"; export class FluentTaskView extends ItemView { private plugin: TaskProgressBarPlugin; + // ==================== + // DEBUG CONFIGURATION + // ==================== + // Set to false in production to reduce console output + private readonly DEBUG_MODE = false; + // ==================== // MANAGERS (added via addChild for lifecycle management) // ==================== @@ -131,86 +137,144 @@ export class FluentTaskView extends ItemView { console.log("[TG-V2] onOpen started"); this.isInitializing = true; - this.contentEl.empty(); - this.contentEl.toggleClass( - ["task-genius-fluent-view", "task-genius-view"], - true - ); - - // Create root container (use exact same class as original) - this.rootContainerEl = this.contentEl.createDiv({ - cls: "tg-fluent-container", - }); - - // Add mobile class for proper styling - if (Platform.isPhone) { - this.rootContainerEl.addClass("is-mobile"); - } - - // Initialize managers first (before UI) - this.initializeManagers(); - - // Build UI structure - await this.buildUIStructure(); - - // Subscribe to workspace and global events - this.registerEvents(); - - // Load workspace state - const savedWorkspaceId = - this.workspaceStateManager.getSavedWorkspaceId(); - if (savedWorkspaceId && savedWorkspaceId !== this.workspaceId) { - this.workspaceId = savedWorkspaceId; - this.viewState.currentWorkspace = savedWorkspaceId; - } - - // Apply workspace settings and restore filter state - await this.workspaceStateManager.applyWorkspaceSettings(); - const restored = - this.workspaceStateManager.restoreFilterStateFromWorkspace(); - this.workspaceStateManager.syncFilterState(restored, { - setLiveFilterState: (state) => { - this.liveFilterState = state; - this.app.saveLocalStorage( - "task-genius-view-filter", - state || null, + try { + // ==================== + // PHASE 1-4: UI Setup, Managers, Structure, Events + // ==================== + if (this.DEBUG_MODE) { + console.log( + "[TG-V2] Initializing UI, managers, structure, and events..." ); - }, - setCurrentFilterState: (state) => { - this.currentFilterState = state; - }, - setViewPreferences: ({ - filters, - selectedProject, - viewMode, - clearSearch, - }) => { - this.viewState.filters = filters; - this.viewState.selectedProject = selectedProject; - this.viewState.viewMode = viewMode; - if (clearSearch) { - this.viewState.searchQuery = ""; - this.viewState.filterInputValue = ""; + } + + this.contentEl.empty(); + this.contentEl.toggleClass( + ["task-genius-fluent-view", "task-genius-view"], + true + ); + + // Create root container (use exact same class as original) + this.rootContainerEl = this.contentEl.createDiv({ + cls: "tg-fluent-container", + }); + + // Add mobile class for proper styling + if (Platform.isPhone) { + this.rootContainerEl.addClass("is-mobile"); + } + + // Initialize managers first (before UI) + this.initializeManagers(); + + // Build UI structure + await this.buildUIStructure(); + + // Subscribe to workspace and global events + this.registerEvents(); + + if (this.DEBUG_MODE) { + console.log( + "[TG-V2] ✅ UI, managers, structure, and events initialized" + ); + } + + // ==================== + // PHASE 5: Restore Workspace State + // ==================== + if (this.DEBUG_MODE) { + console.log("[TG-V2] Restoring workspace state..."); + } + + const savedWorkspaceId = + this.workspaceStateManager.getSavedWorkspaceId(); + if (savedWorkspaceId && savedWorkspaceId !== this.workspaceId) { + this.workspaceId = savedWorkspaceId; + this.viewState.currentWorkspace = savedWorkspaceId; + if (this.DEBUG_MODE) { + console.log( + `[TG-V2] Restored workspace ID: ${savedWorkspaceId}` + ); } - }, - onAfterSync: () => { - this.layoutManager?.updateActionButtons(); - }, - }); + } - // Initial data load - await this.dataManager.loadTasks(false); // Will trigger onTasksLoaded callback + // Apply workspace settings and restore filter state + await this.workspaceStateManager.applyWorkspaceSettings(); + const restored = + this.workspaceStateManager.restoreFilterStateFromWorkspace(); + this.workspaceStateManager.syncFilterState(restored, { + setLiveFilterState: (state) => { + this.liveFilterState = state; + this.app.saveLocalStorage( + "task-genius-view-filter", + state || null, + ); + }, + setCurrentFilterState: (state) => { + this.currentFilterState = state; + }, + setViewPreferences: ({ + filters, + selectedProject, + viewMode, + clearSearch, + }) => { + this.viewState.filters = filters; + this.viewState.selectedProject = selectedProject; + this.viewState.viewMode = viewMode; + if (clearSearch) { + this.viewState.searchQuery = ""; + this.viewState.filterInputValue = ""; + } + }, + onAfterSync: () => { + this.layoutManager?.updateActionButtons(); + }, + }); - // Register dataflow listeners for real-time updates - await this.dataManager.registerDataflowListeners(); + // ==================== + // PHASE 6: Load Data (KEY PHASE) + // ==================== + console.log("[TG-V2] Loading tasks..."); + await this.dataManager.loadTasks(false); // Will trigger onTasksLoaded callback + await this.dataManager.registerDataflowListeners(); + console.log( + `[TG-V2] ✅ Loaded ${this.tasks.length} tasks, ${this.filteredTasks.length} after filters` + ); - // Initial render - this.updateView(); + // ==================== + // PHASE 7-8: Initial Render & Responsive Adjustments + // ==================== + // Initial render (will be skipped due to isInitializing) + this.updateView(); - // Check window size and auto-collapse sidebar if needed - this.layoutManager.checkAndCollapseSidebar(); + // Check window size and auto-collapse sidebar if needed + if (this.DEBUG_MODE) { + console.log("[TG-V2] Checking sidebar collapse..."); + } + this.layoutManager.checkAndCollapseSidebar(); + } catch (error) { + console.error("[TG-V2] ❌ Initialization error:", error); + console.error("[TG-V2] Error stack:", (error as Error).stack); + this.loadError = + (error as Error).message || "Failed to initialize view"; + } finally { + // ==================== + // PHASE 9: Finalization (CRITICAL - Always Executes) + // ==================== + if (this.DEBUG_MODE) { + console.log( + `[TG-V2] Finalizing (isInitializing was ${this.isInitializing})` + ); + } - this.isInitializing = false; + this.isInitializing = false; + + if (this.DEBUG_MODE) { + console.log("[TG-V2] Calling final updateView()..."); + } + this.updateView(); + console.log("[TG-V2] ✅ Initialization complete"); + } } /** @@ -788,13 +852,24 @@ export class FluentTaskView extends ItemView { * Update view with current state */ private updateView() { + // Enhanced logging with all critical state + console.log( + `[TG-V2] updateView called: ` + + `isInitializing=${this.isInitializing}, ` + + `viewId=${this.currentViewId}, ` + + `tasks=${this.tasks.length}, ` + + `filtered=${this.filteredTasks.length}, ` + + `isLoading=${this.isLoading}, ` + + `hasError=${!!this.loadError}` + ); + if (this.isInitializing) { - console.log("[TG-V2] Skip update during initialization"); + console.log("[TG-V2] ⏭️ Skip update during initialization"); return; } console.log( - `[TG-V2] updateView: viewId=${this.currentViewId}, tasks=${this.tasks.length}, filtered=${this.filteredTasks.length}` + `[TG-V2] ▶️ Proceeding with view update for ${this.currentViewId}` ); // Update task count