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
This commit is contained in:
Quorafind 2025-10-16 23:48:22 +08:00
parent 09ed881196
commit a0b9891e04
2 changed files with 239 additions and 114 deletions

View file

@ -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();
}
});
});

View file

@ -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