mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
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:
parent
b475615592
commit
dee954193f
8 changed files with 254 additions and 148 deletions
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?.();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue