feat(workspace): add color customization support

Add color picker to workspace creation and rename modals, allowing
users to assign custom colors to workspaces for better visual
distinction. Colors are displayed on workspace icons throughout
the interface.

Additional improvements:
- Improve async/await error handling in Orchestrator, FileSource,
  and IcsSource initialization
- Add ensureHiddenModulesInitialized method for safer module
  visibility configuration
- Optimize view mode changes to prevent unnecessary re-renders
  when clicking the same navigation tab
This commit is contained in:
Quorafind 2025-10-18 13:54:44 +08:00
parent b475615592
commit dee954193f
8 changed files with 254 additions and 148 deletions

View file

@ -156,8 +156,11 @@ export class TopNavigation extends Component {
tab.createSpan({ text: label });
this.registerDomEvent(tab, "click", () => {
this.setViewMode(mode);
this.onViewModeChange(mode);
// Only trigger view change if switching to a different mode
if (this.currentViewMode !== mode) {
this.setViewMode(mode);
this.onViewModeChange(mode);
}
});
}

View file

@ -1,6 +1,7 @@
import { App, Notice } from "obsidian";
import {
EffectiveSettings,
HiddenModulesConfig,
WORKSPACE_SCOPED_KEYS,
WorkspaceData,
WorkspaceOverrides,
@ -278,6 +279,7 @@ export class WorkspaceManager {
name: string,
baseWorkspaceId?: string,
icon?: string,
color?: string,
): Promise<WorkspaceData> {
const config = this.getWorkspacesConfig();
const id = this.generateId();
@ -315,6 +317,16 @@ export class WorkspaceManager {
newWorkspace.icon = baseWorkspace.icon;
}
// Add color if provided, otherwise inherit from base workspace if cloning
if (color) {
newWorkspace.color = color;
} else if (
baseWorkspace?.color &&
baseId !== config.defaultWorkspaceId
) {
newWorkspace.color = baseWorkspace.color;
}
config.byId[id] = newWorkspace;
config.order.push(id);
@ -361,6 +373,7 @@ export class WorkspaceManager {
workspaceId: string,
newName: string,
icon?: string,
color?: string,
): Promise<void> {
const config = this.getWorkspacesConfig();
const workspace = config.byId[workspaceId];
@ -373,6 +386,9 @@ export class WorkspaceManager {
if (icon !== undefined) {
workspace.icon = icon;
}
if (color !== undefined) {
workspace.color = color;
}
workspace.updatedAt = Date.now();
await this.plugin.saveSettings();
@ -673,6 +689,36 @@ export class WorkspaceManager {
// Module visibility methods
/**
* Ensure hiddenModules structure is fully initialized
* @param workspace - The workspace to initialize
* @returns The initialized hiddenModules object
*/
private ensureHiddenModulesInitialized(
workspace: WorkspaceData,
): Required<HiddenModulesConfig> {
if (!workspace.settings) {
workspace.settings = {};
}
if (!workspace.settings.hiddenModules) {
workspace.settings.hiddenModules = {
views: [],
sidebarComponents: [],
features: [],
};
}
if (!workspace.settings.hiddenModules.views) {
workspace.settings.hiddenModules.views = [];
}
if (!workspace.settings.hiddenModules.sidebarComponents) {
workspace.settings.hiddenModules.sidebarComponents = [];
}
if (!workspace.settings.hiddenModules.features) {
workspace.settings.hiddenModules.features = [];
}
return workspace.settings.hiddenModules as Required<HiddenModulesConfig>;
}
/**
* Check if a view is hidden in the specified workspace
* @param viewId - The view ID to check
@ -778,21 +824,16 @@ export class WorkspaceManager {
if (!workspace) return;
// Initialize hiddenModules if needed
if (!workspace.settings.hiddenModules) {
workspace.settings.hiddenModules = {};
}
if (!workspace.settings.hiddenModules.views) {
workspace.settings.hiddenModules.views = [];
}
// Ensure complete initialization and get the initialized object
const hiddenModules = this.ensureHiddenModulesInitialized(workspace);
const index = workspace.settings.hiddenModules.views.indexOf(viewId);
const index = hiddenModules.views.indexOf(viewId);
if (index > -1) {
// Currently hidden, make visible
workspace.settings.hiddenModules.views.splice(index, 1);
hiddenModules.views.splice(index, 1);
} else {
// Currently visible, hide it
workspace.settings.hiddenModules.views.push(viewId);
hiddenModules.views.push(viewId);
}
workspace.updatedAt = Date.now();
@ -818,11 +859,10 @@ export class WorkspaceManager {
if (!workspace) return;
if (!workspace.settings.hiddenModules) {
workspace.settings.hiddenModules = {};
}
// Ensure complete initialization and get the initialized object
const hiddenModules = this.ensureHiddenModulesInitialized(workspace);
hiddenModules.views = [...viewIds];
workspace.settings.hiddenModules.views = [...viewIds];
workspace.updatedAt = Date.now();
this.clearCache();
await this.plugin.saveSettings();
@ -851,11 +891,10 @@ export class WorkspaceManager {
if (!workspace) return;
if (!workspace.settings.hiddenModules) {
workspace.settings.hiddenModules = {};
}
// Ensure complete initialization and get the initialized object
const hiddenModules = this.ensureHiddenModulesInitialized(workspace);
hiddenModules.sidebarComponents = [...componentIds];
workspace.settings.hiddenModules.sidebarComponents = [...componentIds];
workspace.updatedAt = Date.now();
this.clearCache();
await this.plugin.saveSettings();
@ -884,11 +923,10 @@ export class WorkspaceManager {
if (!workspace) return;
if (!workspace.settings.hiddenModules) {
workspace.settings.hiddenModules = {};
}
// Ensure complete initialization and get the initialized object
const hiddenModules = this.ensureHiddenModulesInitialized(workspace);
hiddenModules.features = [...featureIds];
workspace.settings.hiddenModules.features = [...featureIds];
workspace.updatedAt = Date.now();
this.clearCache();
await this.plugin.saveSettings();

View file

@ -93,6 +93,12 @@ export function renderWorkspaceSettingsTab(
});
const iconEl = nameWithIcon.createDiv({ cls: "workspace-list-icon" });
setIcon(iconEl, workspace.icon || "layers");
// Apply workspace color to icon
if (workspace.color) {
iconEl.style.color = workspace.color;
}
nameWithIcon.createSpan({ text: workspace.name });
setting

View file

@ -1,4 +1,11 @@
import { Modal, Notice, Setting, ButtonComponent, setIcon } from "obsidian";
import {
Modal,
Notice,
Setting,
ButtonComponent,
setIcon,
ColorComponent,
} from "obsidian";
import type TaskProgressBarPlugin from "@/index";
import { WorkspaceData } from "@/types/workspace";
import { t } from "@/translations/helper";
@ -11,7 +18,7 @@ export function createWorkspaceIconSelector(
containerEl: HTMLElement,
plugin: TaskProgressBarPlugin,
initialIcon: string,
onIconSelected: (iconId: string) => void
onIconSelected: (iconId: string) => void,
): ButtonComponent {
const iconButton = new ButtonComponent(containerEl);
iconButton.setIcon(initialIcon);
@ -35,27 +42,27 @@ export class CreateWorkspaceModal extends Modal {
private nameInput: HTMLInputElement;
private baseSelect: HTMLSelectElement;
private selectedIcon: string = "layers";
private selectedColor: string = "#888888";
private name: string = "";
private selectWorkspaceId: string = "";
constructor(
private plugin: TaskProgressBarPlugin,
private onCreated: (workspace: WorkspaceData) => void
private onCreated: (workspace: WorkspaceData) => void,
) {
super(plugin.app);
}
onOpen() {
const {contentEl} = this;
contentEl.createEl("h2", {text: t("Create New Workspace")});
const { contentEl } = this;
contentEl.createEl("h2", { text: t("Create New Workspace") });
// Name input
new Setting(contentEl)
.setName(t("Workspace Name"))
.setDesc(t("A descriptive name for the workspace"))
.addText((text) => {
text
.setPlaceholder(t("Enter workspace name"))
text.setPlaceholder(t("Enter workspace name"))
.setValue("")
.onChange((value) => {
this.name = value;
@ -78,9 +85,19 @@ export class CreateWorkspaceModal extends Modal {
this.selectedIcon,
(iconId) => {
this.selectedIcon = iconId;
}
},
);
// Color selection
new Setting(contentEl)
.setName(t("Workspace Color"))
.setDesc(t("Choose a color for this workspace"))
.addColorPicker((color) => {
color.setValue(this.selectedColor).onChange((value) => {
this.selectedColor = value;
});
});
// Base workspace selector
new Setting(contentEl)
.setName(t("Copy Settings From"))
@ -88,11 +105,12 @@ export class CreateWorkspaceModal extends Modal {
.addDropdown((dropdown) => {
dropdown.addOption("", t("Default settings"));
const currentWorkspace = this.plugin.workspaceManager?.getActiveWorkspace();
const currentWorkspace =
this.plugin.workspaceManager?.getActiveWorkspace();
if (currentWorkspace) {
dropdown.addOption(
currentWorkspace.id,
`Current (${currentWorkspace.name})`
`Current (${currentWorkspace.name})`,
);
}
@ -135,22 +153,18 @@ export class CreateWorkspaceModal extends Modal {
}
if (this.plugin.workspaceManager) {
console.log("[TG-WORKSPACE] modal:create", {
name,
baseId,
icon: this.selectedIcon,
});
const workspace = await this.plugin.workspaceManager.createWorkspace(
name,
baseId,
this.selectedIcon
);
const workspace =
await this.plugin.workspaceManager.createWorkspace(
name,
baseId,
this.selectedIcon,
this.selectedColor,
);
new Notice(
t('Workspace "{{name}}" created', {
interpolation: {name: name},
})
interpolation: { name: name },
}),
);
this.onCreated(workspace);
@ -167,7 +181,7 @@ export class CreateWorkspaceModal extends Modal {
}
onClose() {
const {contentEl} = this;
const { contentEl } = this;
contentEl.empty();
}
}
@ -178,29 +192,29 @@ export class CreateWorkspaceModal extends Modal {
export class RenameWorkspaceModal extends Modal {
private nameInput: HTMLInputElement;
private selectedIcon: string;
private selectedColor: string;
constructor(
private plugin: TaskProgressBarPlugin,
private workspace: WorkspaceData,
private onRenamed: () => void
private onRenamed: () => void,
) {
super(plugin.app);
this.selectedIcon = workspace.icon || "layers";
this.selectedColor = workspace.color || "#888888";
}
onOpen() {
const {contentEl} = this;
contentEl.createEl("h2", {text: t("Rename Workspace")});
const { contentEl } = this;
contentEl.createEl("h2", { text: t("Rename Workspace") });
// Name input
new Setting(contentEl)
.setName(t("New Name"))
.addText((text) => {
text
.setValue(this.workspace.name)
.setPlaceholder(t("Enter new name"));
this.nameInput = text.inputEl;
});
new Setting(contentEl).setName(t("New Name")).addText((text) => {
text.setValue(this.workspace.name).setPlaceholder(
t("Enter new name"),
);
this.nameInput = text.inputEl;
});
// Icon selection
const iconSetting = new Setting(contentEl)
@ -217,9 +231,19 @@ export class RenameWorkspaceModal extends Modal {
this.selectedIcon,
(iconId) => {
this.selectedIcon = iconId;
}
},
);
// Color selection
new Setting(contentEl)
.setName(t("Workspace Color"))
.setDesc(t("Choose a color for this workspace"))
.addColorPicker((color) => {
color.setValue(this.selectedColor).onChange((value) => {
this.selectedColor = value;
});
});
// Buttons
const buttonContainer = contentEl.createDiv({
cls: "modal-button-container",
@ -247,12 +271,14 @@ export class RenameWorkspaceModal extends Modal {
id: this.workspace.id,
name: newName,
icon: this.selectedIcon,
color: this.selectedColor,
});
await this.plugin.workspaceManager.renameWorkspace(
this.workspace.id,
newName,
this.selectedIcon
this.selectedIcon,
this.selectedColor,
);
new Notice(t("Workspace updated"));
@ -271,7 +297,7 @@ export class RenameWorkspaceModal extends Modal {
}
onClose() {
const {contentEl} = this;
const { contentEl } = this;
contentEl.empty();
}
}
@ -283,19 +309,19 @@ export class DeleteWorkspaceModal extends Modal {
constructor(
private plugin: TaskProgressBarPlugin,
private workspace: WorkspaceData,
private onDeleted: () => void
private onDeleted: () => void,
) {
super(plugin.app);
}
onOpen() {
const {contentEl} = this;
contentEl.createEl("h2", {text: t("Delete Workspace")});
const { contentEl } = this;
contentEl.createEl("h2", { text: t("Delete Workspace") });
contentEl.createEl("p", {
text: t(
'Are you sure you want to delete "{{name}}"? This action cannot be undone.',
{interpolation: {name: this.workspace.name}}
{ interpolation: { name: this.workspace.name } },
),
});
@ -319,13 +345,13 @@ export class DeleteWorkspaceModal extends Modal {
});
await this.plugin.workspaceManager.deleteWorkspace(
this.workspace.id
this.workspace.id,
);
new Notice(
t('Workspace "{{name}}" deleted', {
interpolation: {name: this.workspace.name},
})
interpolation: { name: this.workspace.name },
}),
);
this.onDeleted();
@ -339,7 +365,7 @@ export class DeleteWorkspaceModal extends Modal {
}
onClose() {
const {contentEl} = this;
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -135,13 +135,14 @@ export class DataflowOrchestrator {
}
// Initialize debounced restore handler (default ON) - trailing only
this.restoreByFilterDebounced = debounce(
() => {
void this.restoreByFilter();
},
500,
false
);
this.restoreByFilterDebounced = debounce(() => {
this.restoreByFilter().catch((error) => {
console.error(
"[DataflowOrchestrator] restoreByFilter failed:",
error
);
});
}, 500, false);
// Initialize worker orchestrator with settings
const taskWorkerManager = new TaskWorkerManager(vault, metadataCache, {
@ -373,6 +374,10 @@ export class DataflowOrchestrator {
);
}
// Subscribe to file update events from ObsidianSource and ICS events
console.log("[DataflowOrchestrator] Subscribing to events...");
this.subscribeToEvents();
// Initialize ObsidianSource to start listening for events
console.log(
"[DataflowOrchestrator] Initializing ObsidianSource..."
@ -381,20 +386,30 @@ export class DataflowOrchestrator {
// Initialize IcsSource to start listening for calendar events
console.log("[DataflowOrchestrator] Initializing IcsSource...");
this.icsSource.initialize();
this.icsSource
.initialize()
.catch((error) => {
console.error(
"[DataflowOrchestrator] IcsSource initialization failed:",
error
);
});
// Initialize FileSource to start file recognition
if (this.fileSource) {
console.log(
"[DataflowOrchestrator] Initializing FileSource..."
);
this.fileSource.initialize();
this.fileSource
.initialize()
.catch((error) => {
console.error(
"[DataflowOrchestrator] FileSource initialization failed:",
error
);
});
}
// Subscribe to file update events from ObsidianSource and ICS events
console.log("[DataflowOrchestrator] Subscribing to events...");
this.subscribeToEvents();
// Emit initial ready event
emit(this.app, Events.CACHE_READY, {
initial: true,
@ -904,7 +919,14 @@ export class DataflowOrchestrator {
settings.fileSource,
this.fileFilterManager
);
this.fileSource.initialize();
this.fileSource
.initialize()
.catch((error) => {
console.error(
"[DataflowOrchestrator] FileSource initialization failed:",
error
);
});
// Sync status mapping from Task Status settings on creation
try {
if (settings?.taskStatuses) {
@ -1002,7 +1024,12 @@ export class DataflowOrchestrator {
console.log("[TG Index Filter] action", {
action: "PRUNE_THEN_RESTORE",
});
void this.pruneByFilter();
this.pruneByFilter().catch((error) => {
console.error(
"[DataflowOrchestrator] pruneByFilter failed:",
error
);
});
this.restoreByFilterDebounced?.();
}
}

View file

@ -68,7 +68,7 @@ export class FileSource {
/**
* Initialize FileSource and start listening for events
*/
initialize(): void {
async initialize(): Promise<void> {
if (this.isInitialized) return;
if (!this.config.isEnabled()) return;
@ -82,14 +82,15 @@ export class FileSource {
// Subscribe to file events
this.subscribeToFileEvents();
// Delay initial scan to ensure vault is fully loaded
setTimeout(() => {
this.performInitialScan();
}, 1000); // 1 second delay
this.isInitialized = true;
this.stats.initialized = true;
try {
await this.performInitialScan();
} catch (error) {
console.error("[FileSource] Initial scan failed", error);
}
console.log(
`[FileSource] Initialized with strategies: ${this.config
.getEnabledStrategies()
@ -1115,7 +1116,7 @@ export class FileSource {
if (newConfig.enabled && !this.isInitialized) {
// FileSource is being enabled
this.initialize();
void this.initialize();
return;
}

View file

@ -23,7 +23,7 @@ export class IcsSource {
/**
* Initialize the ICS source and start listening for calendar updates
*/
initialize(): void {
async initialize(): Promise<void> {
if (this.isInitialized) return;
console.log("[IcsSource] Initializing ICS event source...");
@ -31,28 +31,27 @@ export class IcsSource {
// Subscribe to ICS manager updates first so we don't miss early signals
this.subscribeToIcsUpdates();
// Initial load of ICS events (may be no-op if manager not ready yet)
this.loadAndEmitIcsEvents();
// Fallback: retry until ICS manager becomes available (up to ~30s)
this.ensureManagerAndLoad(0);
if (this.getIcsManager()) {
await this.loadAndEmitIcsEvents();
} else {
await this.ensureManagerAndLoad();
}
this.isInitialized = true;
}
/**
* Ensure ICS manager becomes available shortly after startup and then load
*/
private ensureManagerAndLoad(attempt: number): void {
private async ensureManagerAndLoad(): Promise<void> {
const maxAttempts = 30; // ~30s with 1s interval
if (this.getIcsManager()) {
this.loadAndEmitIcsEvents();
return;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
if (this.getIcsManager()) {
await this.loadAndEmitIcsEvents();
return;
}
await this.delay(1000);
}
if (attempt >= maxAttempts) {
console.warn("[IcsSource] ICS manager not available after retries");
return;
}
setTimeout(() => this.ensureManagerAndLoad(attempt + 1), 1000);
console.warn("[IcsSource] ICS manager not available after retries");
}
/**
@ -62,7 +61,7 @@ export class IcsSource {
// Listen for ICS cache updates
this.app.workspace.on("ics-cache-updated" as any, () => {
console.log("[IcsSource] ICS cache updated, reloading events...");
this.loadAndEmitIcsEvents();
void this.loadAndEmitIcsEvents();
});
// Listen for ICS configuration changes
@ -139,6 +138,10 @@ export class IcsSource {
await this.loadAndEmitIcsEvents();
}
private async delay(ms: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Get current statistics
*/

View file

@ -5,7 +5,10 @@ import "@/styles/fluent/fluent-main.css";
import "@/styles/fluent/fluent-secondary.css";
import "@/styles/fluent/fluent-content-header.css";
import "@/styles/fluent/fluent-project-popover.css";
import { TopNavigation, ViewMode } from "@/components/features/fluent/components/FluentTopNavigation";
import {
TopNavigation,
ViewMode,
} from "@/components/features/fluent/components/FluentTopNavigation";
import { FluentTaskViewState } from "@/types/fluent-types";
import {
onWorkspaceSwitched,
@ -116,7 +119,7 @@ export class FluentTaskView extends ItemView {
}
getDisplayText(): string {
return t("Task Genius Fluent");
return t("Task Genius");
}
getIcon(): string {
@ -127,7 +130,7 @@ export class FluentTaskView extends ItemView {
* Check if using workspace side leaves mode
*/
private useSideLeaves(): boolean {
return !!(this.plugin.settings.fluentView)?.useWorkspaceSideLeaves;
return !!this.plugin.settings.fluentView?.useWorkspaceSideLeaves;
}
/**
@ -143,14 +146,14 @@ export class FluentTaskView extends ItemView {
// ====================
if (this.DEBUG_MODE) {
console.log(
"[TG-V2] Initializing UI, managers, structure, and events..."
"[TG-V2] Initializing UI, managers, structure, and events...",
);
}
this.contentEl.empty();
this.contentEl.toggleClass(
["task-genius-fluent-view", "task-genius-view"],
true
true,
);
// Create root container (use exact same class as original)
@ -174,7 +177,7 @@ export class FluentTaskView extends ItemView {
if (this.DEBUG_MODE) {
console.log(
"[TG-V2] ✅ UI, managers, structure, and events initialized"
"[TG-V2] ✅ UI, managers, structure, and events initialized",
);
}
@ -192,7 +195,7 @@ export class FluentTaskView extends ItemView {
this.viewState.currentWorkspace = savedWorkspaceId;
if (this.DEBUG_MODE) {
console.log(
`[TG-V2] Restored workspace ID: ${savedWorkspaceId}`
`[TG-V2] Restored workspace ID: ${savedWorkspaceId}`,
);
}
}
@ -238,7 +241,7 @@ export class FluentTaskView extends ItemView {
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`
`[TG-V2] ✅ Loaded ${this.tasks.length} tasks, ${this.filteredTasks.length} after filters`,
);
// ====================
@ -263,7 +266,7 @@ export class FluentTaskView extends ItemView {
// ====================
if (this.DEBUG_MODE) {
console.log(
`[TG-V2] Finalizing (isInitializing was ${this.isInitializing})`
`[TG-V2] Finalizing (isInitializing was ${this.isInitializing})`,
);
}
@ -281,7 +284,6 @@ export class FluentTaskView extends ItemView {
* Initialize all managers with callbacks
*/
private initializeManagers() {
// 1. FluentDataManager - Data loading and filtering
this.dataManager = new FluentDataManager(
this.plugin,
@ -294,7 +296,7 @@ export class FluentTaskView extends ItemView {
searchQuery: this.viewState.searchQuery || "",
filterInputValue: this.viewState.filterInputValue || "",
}),
() => this.isInitializing
() => this.isInitializing,
);
this.dataManager.setCallbacks({
onTasksLoaded: (tasks, error) => {
@ -310,7 +312,7 @@ export class FluentTaskView extends ItemView {
this.isLoading = false;
// Apply filters immediately after loading
this.filteredTasks = this.dataManager.applyFilters(
this.tasks
this.tasks,
);
this.updateView();
}
@ -335,7 +337,7 @@ export class FluentTaskView extends ItemView {
this.app,
this.plugin,
() => this.workspaceId,
() => this.useSideLeaves()
() => this.useSideLeaves(),
);
this.actionHandlers.setCallbacks({
onTaskSelectionChanged: (task) => {
@ -351,7 +353,7 @@ export class FluentTaskView extends ItemView {
this.tasks[index] = updatedTask;
// Re-apply filters
this.filteredTasks = this.dataManager.applyFilters(
this.tasks
this.tasks,
);
this.updateView();
}
@ -410,7 +412,7 @@ export class FluentTaskView extends ItemView {
.map((g: any) => ({
...g,
filters: (g.filters || []).filter(
(f: any) => f.property !== "project"
(f: any) => f.property !== "project",
),
}))
.filter((g: any) => g.filters && g.filters.length > 0); // Remove empty groups
@ -436,23 +438,23 @@ export class FluentTaskView extends ItemView {
this.currentFilterState = nextState as any;
this.app.saveLocalStorage(
"task-genius-view-filter",
nextState
nextState,
);
// Broadcast so any open filter UI reacts and header button shows reset
// The filter-changed event listener will handle applyFilters and updateView
this.app.workspace.trigger(
"task-genius:filter-changed",
nextState
nextState,
);
} catch (e) {
console.warn(
"[TG-V2] Failed to project-sync filter UI state",
e
e,
);
// If filter sync fails, still update the view
this.filteredTasks = this.dataManager.applyFilters(
this.tasks
this.tasks,
);
this.updateView();
}
@ -486,7 +488,7 @@ export class FluentTaskView extends ItemView {
viewMode: this.viewState.viewMode,
}),
() => this.currentFilterState,
() => this.liveFilterState
() => this.liveFilterState,
);
this.addChild(this.workspaceStateManager);
@ -546,7 +548,7 @@ export class FluentTaskView extends ItemView {
this.rootContainerEl,
this.headerEl, // Obsidian's view header
this.titleEl, // Obsidian's view title element
() => this.filteredTasks.length
() => this.filteredTasks.length,
);
this.layoutManager.setOnSidebarNavigate((viewId) => {
this.actionHandlers.handleNavigate(viewId);
@ -564,7 +566,7 @@ export class FluentTaskView extends ItemView {
onTaskUpdate: async (originalTask, updatedTask) => {
await this.actionHandlers.handleTaskUpdate(
originalTask,
updatedTask
updatedTask,
);
},
});
@ -584,7 +586,7 @@ export class FluentTaskView extends ItemView {
} else {
sidebarEl.hide();
console.log(
"[TG-V2] Using workspace side leaves: skip in-view sidebar"
"[TG-V2] Using workspace side leaves: skip in-view sidebar",
);
}
@ -603,7 +605,7 @@ export class FluentTaskView extends ItemView {
// Sort click
// TODO: Implement sort
},
() => this.actionHandlers.handleSettingsClick()
() => this.actionHandlers.handleSettingsClick(),
);
this.addChild(this.topNavigation);
@ -624,7 +626,7 @@ export class FluentTaskView extends ItemView {
onTaskUpdate: async (originalTask, updatedTask) => {
await this.actionHandlers.handleTaskUpdate(
originalTask,
updatedTask
updatedTask,
);
},
onTaskContextMenu: (event, task) => {
@ -635,11 +637,11 @@ export class FluentTaskView extends ItemView {
if (task) {
this.actionHandlers.handleKanbanTaskStatusUpdate(
task,
newStatusMark
newStatusMark,
);
}
},
}
},
);
this.addChild(this.componentManager);
this.componentManager.initializeViewComponents();
@ -730,7 +732,7 @@ export class FluentTaskView extends ItemView {
// Reload tasks
await this.dataManager.loadTasks();
}
})
}),
);
// Workspace overrides saved event
@ -740,7 +742,7 @@ export class FluentTaskView extends ItemView {
await this.workspaceStateManager.applyWorkspaceSettings();
await this.dataManager.loadTasks();
}
})
}),
);
// Settings changed event (skip for filter state changes)
@ -748,7 +750,7 @@ export class FluentTaskView extends ItemView {
on(this.app, Events.SETTINGS_CHANGED, async () => {
// Reload on settings change (unless caused by filter state save)
await this.dataManager.loadTasks();
})
}),
);
}
@ -764,7 +766,7 @@ export class FluentTaskView extends ItemView {
leafId !== "global-filter"
) {
console.log(
"[TG-V2] Filter changed from live component"
"[TG-V2] Filter changed from live component",
);
this.liveFilterState = filterState;
this.currentFilterState = filterState;
@ -799,7 +801,7 @@ export class FluentTaskView extends ItemView {
} catch (e) {
console.warn(
"[TG-V2] Failed to sync selectedProject from filter state",
e
e,
);
}
@ -809,11 +811,11 @@ export class FluentTaskView extends ItemView {
// Apply filters and update view
this.filteredTasks = this.dataManager.applyFilters(
this.tasks
this.tasks,
);
this.updateView();
}
)
},
),
);
// Sidebar selection changed (when using side leaves)
@ -834,11 +836,11 @@ export class FluentTaskView extends ItemView {
) {
// Use the full project selection logic via actionHandlers
this.actionHandlers.handleProjectSelect(
payload.selectionId || ""
payload.selectionId || "",
);
}
}
})
}),
);
}
@ -860,7 +862,7 @@ export class FluentTaskView extends ItemView {
`tasks=${this.tasks.length}, ` +
`filtered=${this.filteredTasks.length}, ` +
`isLoading=${this.isLoading}, ` +
`hasError=${!!this.loadError}`
`hasError=${!!this.loadError}`,
);
if (this.isInitializing) {
@ -869,7 +871,7 @@ export class FluentTaskView extends ItemView {
}
console.log(
`[TG-V2] ▶️ Proceeding with view update for ${this.currentViewId}`
`[TG-V2] ▶️ Proceeding with view update for ${this.currentViewId}`,
);
// Update task count
@ -905,7 +907,7 @@ export class FluentTaskView extends ItemView {
this.filteredTasks,
this.currentFilterState,
this.viewState.viewMode,
this.viewState.selectedProject
this.viewState.selectedProject,
);
}