mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
refactor(workspace): improve state management and persistence reliability
- Add snapshot-based filter state capture to prevent state corruption - Implement immediate save for critical workspace operations - Add recursive save protection with isSaving flag - Introduce WORKSPACE_ONLY_KEYS for keys that never merge globally - Add component visibility controls for sidebar sections - Improve cache management with targeted invalidation - Add activeViewId tracking for better view restoration - Enhance error handling with user-facing notices - Add backward compatibility for legacy sidebar component IDs
This commit is contained in:
parent
7d41359c6e
commit
422fdf04c3
6 changed files with 563 additions and 268 deletions
|
|
@ -125,75 +125,94 @@ export class FluentSidebar extends Component {
|
|||
this.renderNavigationItems(primarySection, this.primaryItems);
|
||||
|
||||
// Projects section
|
||||
const projectsSection = content.createDiv({
|
||||
cls: "fluent-sidebar-section",
|
||||
});
|
||||
const projectHeader = projectsSection.createDiv({
|
||||
cls: "fluent-section-header",
|
||||
});
|
||||
|
||||
projectHeader.createSpan({ text: t("Projects") });
|
||||
|
||||
// Button container for tree toggle and sort
|
||||
const buttonContainer = projectHeader.createDiv({
|
||||
cls: "fluent-project-header-buttons",
|
||||
});
|
||||
|
||||
// Tree/List toggle button
|
||||
const treeToggleBtn = buttonContainer.createDiv({
|
||||
cls: "fluent-tree-toggle-btn",
|
||||
attr: { "aria-label": t("Toggle tree/list view") },
|
||||
});
|
||||
// Load saved view mode preference
|
||||
this.isTreeView =
|
||||
this.plugin.app.loadLocalStorage(
|
||||
"task-genius-project-view-mode",
|
||||
) === "tree";
|
||||
setIcon(treeToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
|
||||
this.registerDomEvent(treeToggleBtn, "click", () => {
|
||||
this.isTreeView = !this.isTreeView;
|
||||
setIcon(treeToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
// Save preference
|
||||
this.plugin.app.saveLocalStorage(
|
||||
"task-genius-project-view-mode",
|
||||
this.isTreeView ? "tree" : "list",
|
||||
const isProjectsHidden =
|
||||
this.plugin.workspaceManager?.isSidebarComponentHidden(
|
||||
"projects-list",
|
||||
);
|
||||
// Update project list view mode
|
||||
if (this.projectList) {
|
||||
(this.projectList as ProjectList).setViewMode?.(
|
||||
this.isTreeView,
|
||||
|
||||
if (!isProjectsHidden) {
|
||||
const projectsSection = content.createDiv({
|
||||
cls: "fluent-sidebar-section",
|
||||
});
|
||||
const projectHeader = projectsSection.createDiv({
|
||||
cls: "fluent-section-header",
|
||||
});
|
||||
|
||||
projectHeader.createSpan({ text: t("Projects") });
|
||||
|
||||
// Button container for tree toggle and sort
|
||||
const buttonContainer = projectHeader.createDiv({
|
||||
cls: "fluent-project-header-buttons",
|
||||
});
|
||||
|
||||
// Tree/List toggle button
|
||||
const treeToggleBtn = buttonContainer.createDiv({
|
||||
cls: "fluent-tree-toggle-btn",
|
||||
attr: { "aria-label": t("Toggle tree/list view") },
|
||||
});
|
||||
// Load saved view mode preference
|
||||
this.isTreeView =
|
||||
this.plugin.app.loadLocalStorage(
|
||||
"task-genius-project-view-mode",
|
||||
) === "tree";
|
||||
setIcon(treeToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
|
||||
this.registerDomEvent(treeToggleBtn, "click", () => {
|
||||
this.isTreeView = !this.isTreeView;
|
||||
setIcon(
|
||||
treeToggleBtn,
|
||||
this.isTreeView ? "git-branch" : "list",
|
||||
);
|
||||
}
|
||||
});
|
||||
// Save preference
|
||||
this.plugin.app.saveLocalStorage(
|
||||
"task-genius-project-view-mode",
|
||||
this.isTreeView ? "tree" : "list",
|
||||
);
|
||||
// Update project list view mode
|
||||
if (this.projectList) {
|
||||
(this.projectList as ProjectList).setViewMode?.(
|
||||
this.isTreeView,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort button
|
||||
const sortProjectBtn = buttonContainer.createDiv({
|
||||
cls: "fluent-sort-project-btn",
|
||||
attr: { "aria-label": t("Sort projects") },
|
||||
});
|
||||
setIcon(sortProjectBtn, "arrow-up-down");
|
||||
// Sort button
|
||||
const sortProjectBtn = buttonContainer.createDiv({
|
||||
cls: "fluent-sort-project-btn",
|
||||
attr: { "aria-label": t("Sort projects") },
|
||||
});
|
||||
setIcon(sortProjectBtn, "arrow-up-down");
|
||||
|
||||
// Pass sort button to project list for menu handling
|
||||
this.registerDomEvent(sortProjectBtn, "click", () => {
|
||||
(this.projectList as ProjectList).showSortMenu?.(sortProjectBtn);
|
||||
});
|
||||
// Pass sort button to project list for menu handling
|
||||
this.registerDomEvent(sortProjectBtn, "click", () => {
|
||||
(this.projectList as ProjectList).showSortMenu?.(
|
||||
sortProjectBtn,
|
||||
);
|
||||
});
|
||||
|
||||
const projectListEl = projectsSection.createDiv();
|
||||
this.projectList = new ProjectList(
|
||||
projectListEl,
|
||||
this.plugin,
|
||||
this.onProjectSelect,
|
||||
this.isTreeView,
|
||||
);
|
||||
// Add ProjectList as a child component
|
||||
this.addChild(this.projectList);
|
||||
const projectListEl = projectsSection.createDiv();
|
||||
this.projectList = new ProjectList(
|
||||
projectListEl,
|
||||
this.plugin,
|
||||
this.onProjectSelect,
|
||||
this.isTreeView,
|
||||
);
|
||||
// Add ProjectList as a child component
|
||||
this.addChild(this.projectList);
|
||||
}
|
||||
|
||||
// Other views section
|
||||
this.otherViewsSection = content.createDiv({
|
||||
cls: "fluent-sidebar-section",
|
||||
});
|
||||
this.renderOtherViewsSection();
|
||||
const isOtherViewsHidden =
|
||||
this.plugin.workspaceManager?.isSidebarComponentHidden(
|
||||
"other-views",
|
||||
);
|
||||
|
||||
if (!isOtherViewsHidden) {
|
||||
this.otherViewsSection = content.createDiv({
|
||||
cls: "fluent-sidebar-section",
|
||||
});
|
||||
this.renderOtherViewsSection();
|
||||
}
|
||||
}
|
||||
|
||||
private renderRailMode() {
|
||||
|
|
@ -232,53 +251,66 @@ export class FluentSidebar extends Component {
|
|||
});
|
||||
|
||||
// Other view icons with overflow menu when > 5
|
||||
const allOtherItems = this.computeOtherItems();
|
||||
const visibleCount =
|
||||
this.plugin?.settings?.fluentView?.fluentConfig
|
||||
?.maxOtherViewsBeforeOverflow ?? 5;
|
||||
const displayedOther: FluentTaskNavigationItem[] = allOtherItems.slice(
|
||||
0,
|
||||
visibleCount,
|
||||
);
|
||||
const remainingOther: FluentTaskNavigationItem[] =
|
||||
allOtherItems.slice(visibleCount);
|
||||
if (
|
||||
!this.plugin.workspaceManager?.isSidebarComponentHidden(
|
||||
"other-views",
|
||||
)
|
||||
) {
|
||||
const allOtherItems = this.computeOtherItems();
|
||||
const visibleCount =
|
||||
this.plugin?.settings?.fluentView?.fluentConfig
|
||||
?.maxOtherViewsBeforeOverflow ?? 5;
|
||||
const displayedOther: FluentTaskNavigationItem[] =
|
||||
allOtherItems.slice(0, visibleCount);
|
||||
const remainingOther: FluentTaskNavigationItem[] =
|
||||
allOtherItems.slice(visibleCount);
|
||||
|
||||
displayedOther.forEach((item: FluentTaskNavigationItem) => {
|
||||
const btn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": item.label, "data-view-id": item.id },
|
||||
displayedOther.forEach((item: FluentTaskNavigationItem) => {
|
||||
const btn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: {
|
||||
"aria-label": item.label,
|
||||
"data-view-id": item.id,
|
||||
},
|
||||
});
|
||||
setIcon(btn, item.icon);
|
||||
this.registerDomEvent(btn, "click", () => {
|
||||
this.setActiveItem(item.id);
|
||||
this.onNavigate(item.id);
|
||||
});
|
||||
// Add context menu handler for rail button
|
||||
this.registerDomEvent(btn, "contextmenu", (e) => {
|
||||
this.showViewContextMenu(e as MouseEvent, item.id);
|
||||
});
|
||||
});
|
||||
setIcon(btn, item.icon);
|
||||
this.registerDomEvent(btn, "click", () => {
|
||||
this.setActiveItem(item.id);
|
||||
this.onNavigate(item.id);
|
||||
});
|
||||
// Add context menu handler for rail button
|
||||
this.registerDomEvent(btn, "contextmenu", (e) => {
|
||||
this.showViewContextMenu(e as MouseEvent, item.id);
|
||||
});
|
||||
});
|
||||
|
||||
if (remainingOther.length > 0) {
|
||||
const moreBtn = this.railEl.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": t("More views") },
|
||||
});
|
||||
setIcon(moreBtn, "more-horizontal");
|
||||
this.registerDomEvent(moreBtn, "click", (e) =>
|
||||
this.showOtherViewsMenu(e as MouseEvent, remainingOther),
|
||||
);
|
||||
if (remainingOther.length > 0) {
|
||||
const moreBtn = this.railEl.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": t("More views") },
|
||||
});
|
||||
setIcon(moreBtn, "more-horizontal");
|
||||
this.registerDomEvent(moreBtn, "click", (e) =>
|
||||
this.showOtherViewsMenu(e as MouseEvent, remainingOther),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Projects menu button
|
||||
const projBtn = this.railEl.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": t("Projects") },
|
||||
});
|
||||
setIcon(projBtn, "folder");
|
||||
this.registerDomEvent(projBtn, "click", (e) =>
|
||||
this.showProjectMenu(e as MouseEvent),
|
||||
);
|
||||
if (
|
||||
!this.plugin.workspaceManager?.isSidebarComponentHidden(
|
||||
"projects-list",
|
||||
)
|
||||
) {
|
||||
const projBtn = this.railEl.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": t("Projects") },
|
||||
});
|
||||
setIcon(projBtn, "folder");
|
||||
this.registerDomEvent(projBtn, "click", (e) =>
|
||||
this.showProjectMenu(e as MouseEvent),
|
||||
);
|
||||
}
|
||||
|
||||
// Add (New Task) button
|
||||
const addBtn = this.railEl.createDiv({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { App, Component, debounce } from "obsidian";
|
||||
import { App, Component, Notice, debounce } from "obsidian";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { RootFilterState } from "@/components/features/task/filter/ViewTaskFilter";
|
||||
import { ViewMode } from "../components/FluentTopNavigation";
|
||||
|
|
@ -10,6 +10,7 @@ export interface WorkspaceFilterSnapshot {
|
|||
liveFilterState: RootFilterState | null;
|
||||
viewMode: ViewMode;
|
||||
shouldClearSearch: boolean;
|
||||
activeViewId?: string;
|
||||
}
|
||||
|
||||
export interface FilterSyncHandlers {
|
||||
|
|
@ -24,6 +25,17 @@ export interface FilterSyncHandlers {
|
|||
onAfterSync?: (snapshot: WorkspaceFilterSnapshot) => void;
|
||||
}
|
||||
|
||||
interface FilterStatePersistPayload {
|
||||
workspaceId: string;
|
||||
viewId: string;
|
||||
activeViewId: string;
|
||||
filters: any;
|
||||
selectedProject: string | undefined;
|
||||
viewMode: ViewMode;
|
||||
searchQuery: string;
|
||||
advancedFilter: RootFilterState | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* FluentWorkspaceStateManager - Manages workspace state persistence
|
||||
*
|
||||
|
|
@ -58,16 +70,14 @@ export class FluentWorkspaceStateManager extends Component {
|
|||
* Save workspace layout (filter state and preferences)
|
||||
*/
|
||||
saveWorkspaceLayout(): void {
|
||||
const workspaceId = this.getWorkspaceId();
|
||||
if (!workspaceId) return;
|
||||
const snapshot = this.captureFilterStateSnapshot();
|
||||
if (!snapshot) return;
|
||||
|
||||
// Save filter state
|
||||
this.saveFilterStateToWorkspace();
|
||||
this.saveFilterStateToWorkspace(snapshot);
|
||||
|
||||
// Save current workspace ID to localStorage for persistence
|
||||
localStorage.setItem(
|
||||
"task-genius-fluent-current-workspace",
|
||||
workspaceId
|
||||
snapshot.workspaceId
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -113,75 +123,210 @@ export class FluentWorkspaceStateManager extends Component {
|
|||
}
|
||||
|
||||
/**
|
||||
* Save filter state to workspace (debounced to avoid infinite loops)
|
||||
* Capture a point-in-time snapshot of the filter state for persistence.
|
||||
* @returns Snapshot payload or null if workspaceManager is unavailable
|
||||
*/
|
||||
saveFilterStateToWorkspace = debounce(
|
||||
() => {
|
||||
const workspaceId = this.getWorkspaceId();
|
||||
const viewId = this.getCurrentViewId();
|
||||
public captureFilterStateSnapshot(): FilterStatePersistPayload | null {
|
||||
const workspaceId = this.getWorkspaceId();
|
||||
const viewId = this.getCurrentViewId();
|
||||
|
||||
if (!this.plugin.workspaceManager || !workspaceId) return;
|
||||
if (!this.plugin.workspaceManager || !workspaceId || !viewId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const viewState = this.getViewState();
|
||||
const currentFilterState = this.getCurrentFilterState();
|
||||
|
||||
return {
|
||||
workspaceId,
|
||||
viewId,
|
||||
activeViewId: viewId,
|
||||
filters: structuredClone(viewState.filters || {}),
|
||||
selectedProject: viewState.selectedProject,
|
||||
viewMode: viewState.viewMode,
|
||||
searchQuery: viewState.searchQuery,
|
||||
advancedFilter: currentFilterState
|
||||
? structuredClone(currentFilterState)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
public getSavedActiveViewId(): string | null {
|
||||
const workspaceId = this.getWorkspaceId();
|
||||
|
||||
if (!this.plugin.workspaceManager || !workspaceId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const effective =
|
||||
this.plugin.workspaceManager.getEffectiveSettings(workspaceId);
|
||||
const active = (effective as any)?.fluentActiveViewId;
|
||||
return typeof active === "string" ? active : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounced worker that performs the actual save.
|
||||
*/
|
||||
private persistFilterState = debounce(
|
||||
(payload: FilterStatePersistPayload) => {
|
||||
if (!this.plugin.workspaceManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
workspaceId,
|
||||
viewId,
|
||||
activeViewId,
|
||||
filters,
|
||||
selectedProject,
|
||||
viewMode,
|
||||
searchQuery,
|
||||
advancedFilter,
|
||||
} = payload;
|
||||
|
||||
const effectiveSettings =
|
||||
this.plugin.workspaceManager.getEffectiveSettings(workspaceId);
|
||||
|
||||
// Save current filter state
|
||||
if (!effectiveSettings.fluentFilterState) {
|
||||
effectiveSettings.fluentFilterState = {};
|
||||
}
|
||||
|
||||
const viewState = this.getViewState();
|
||||
const currentFilterState = this.getCurrentFilterState();
|
||||
|
||||
// Build payload (do NOT persist ephemeral fields across workspaces)
|
||||
const payload = {
|
||||
filters: viewState.filters,
|
||||
selectedProject: viewState.selectedProject,
|
||||
advancedFilter: currentFilterState,
|
||||
viewMode: viewState.viewMode,
|
||||
effectiveSettings.fluentFilterState[viewId] = {
|
||||
filters,
|
||||
selectedProject,
|
||||
advancedFilter,
|
||||
viewMode,
|
||||
};
|
||||
effectiveSettings.fluentFilterState[viewId] = payload;
|
||||
if (activeViewId) {
|
||||
effectiveSettings.fluentActiveViewId = activeViewId;
|
||||
}
|
||||
|
||||
console.log("[FluentWorkspace] saveFilterStateToWorkspace", {
|
||||
workspaceId: workspaceId,
|
||||
viewId: viewId,
|
||||
searchQuery: viewState.searchQuery,
|
||||
selectedProject: viewState.selectedProject,
|
||||
hasAdvanced: !!currentFilterState,
|
||||
groups: (currentFilterState as any)?.filterGroups?.length ?? 0,
|
||||
workspaceId,
|
||||
viewId,
|
||||
activeViewId,
|
||||
searchQuery,
|
||||
selectedProject,
|
||||
hasAdvanced: !!advancedFilter,
|
||||
groups: (advancedFilter as any)?.filterGroups?.length ?? 0,
|
||||
});
|
||||
|
||||
// Use saveOverridesQuietly to avoid triggering SETTINGS_CHANGED event
|
||||
this.plugin.workspaceManager
|
||||
.saveOverridesQuietly(workspaceId, effectiveSettings)
|
||||
.then(() =>
|
||||
console.log("[FluentWorkspace] overrides saved quietly", {
|
||||
workspaceId: workspaceId,
|
||||
viewId: viewId,
|
||||
workspaceId,
|
||||
viewId,
|
||||
})
|
||||
)
|
||||
.catch((e) =>
|
||||
console.warn(
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
"[FluentWorkspace] failed to save overrides",
|
||||
e
|
||||
)
|
||||
);
|
||||
);
|
||||
new Notice(
|
||||
"Failed to save workspace state. Recent changes may be lost."
|
||||
);
|
||||
});
|
||||
},
|
||||
500,
|
||||
true
|
||||
500
|
||||
);
|
||||
|
||||
/**
|
||||
* Save filter state to workspace (debounced to avoid infinite loops)
|
||||
*/
|
||||
public saveFilterStateToWorkspace(
|
||||
payload?: FilterStatePersistPayload,
|
||||
): void {
|
||||
const snapshot = payload ?? this.captureFilterStateSnapshot();
|
||||
if (!snapshot) return;
|
||||
|
||||
this.persistFilterState(snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save filter state immediately without debouncing.
|
||||
* Used for critical saves like workspace switching.
|
||||
* @param payload Optional snapshot to save, or captures current state if not provided
|
||||
*/
|
||||
public async saveFilterStateImmediately(
|
||||
payload?: FilterStatePersistPayload,
|
||||
): Promise<void> {
|
||||
const snapshot = payload ?? this.captureFilterStateSnapshot();
|
||||
if (!snapshot || !this.plugin.workspaceManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
workspaceId,
|
||||
viewId,
|
||||
activeViewId,
|
||||
filters,
|
||||
selectedProject,
|
||||
viewMode,
|
||||
advancedFilter,
|
||||
} = snapshot;
|
||||
|
||||
const effectiveSettings =
|
||||
this.plugin.workspaceManager.getEffectiveSettings(workspaceId);
|
||||
|
||||
if (!effectiveSettings.fluentFilterState) {
|
||||
effectiveSettings.fluentFilterState = {};
|
||||
}
|
||||
|
||||
effectiveSettings.fluentFilterState[viewId] = {
|
||||
filters,
|
||||
selectedProject,
|
||||
advancedFilter,
|
||||
viewMode,
|
||||
};
|
||||
if (activeViewId) {
|
||||
effectiveSettings.fluentActiveViewId = activeViewId;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[FluentWorkspace] saveFilterStateImmediately",
|
||||
{
|
||||
workspaceId,
|
||||
viewId,
|
||||
activeViewId,
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await this.plugin.workspaceManager.saveOverridesQuietly(
|
||||
workspaceId,
|
||||
effectiveSettings
|
||||
);
|
||||
console.log(
|
||||
"[FluentWorkspace] immediate save completed",
|
||||
{ workspaceId, viewId }
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"[FluentWorkspace] immediate save failed",
|
||||
e
|
||||
);
|
||||
new Notice(
|
||||
"Failed to save workspace state. Recent changes may be lost."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore filter state from workspace
|
||||
*/
|
||||
restoreFilterStateFromWorkspace(): WorkspaceFilterSnapshot | null {
|
||||
const workspaceId = this.getWorkspaceId();
|
||||
const viewId = this.getCurrentViewId();
|
||||
|
||||
if (!this.plugin.workspaceManager || !workspaceId) return null;
|
||||
|
||||
const effectiveSettings =
|
||||
this.plugin.workspaceManager.getEffectiveSettings(workspaceId);
|
||||
const activeViewId =
|
||||
((effectiveSettings as any)?.fluentActiveViewId as
|
||||
| string
|
||||
| undefined) ?? this.getCurrentViewId();
|
||||
const viewId = activeViewId || this.getCurrentViewId();
|
||||
|
||||
const saved = effectiveSettings.fluentFilterState?.[viewId] ?? null;
|
||||
|
||||
|
|
@ -195,6 +340,7 @@ export class FluentWorkspaceStateManager extends Component {
|
|||
liveFilterState: savedState.advancedFilter || null,
|
||||
viewMode: savedState.viewMode || "list",
|
||||
shouldClearSearch: true, // Always clear searchQuery on workspace restore
|
||||
activeViewId: viewId,
|
||||
};
|
||||
} else {
|
||||
// No saved state for this view in this workspace
|
||||
|
|
@ -205,6 +351,7 @@ export class FluentWorkspaceStateManager extends Component {
|
|||
liveFilterState: null,
|
||||
viewMode: "list",
|
||||
shouldClearSearch: true,
|
||||
activeViewId: viewId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { App, Notice } from "obsidian";
|
||||
import {
|
||||
AnySidebarComponentType,
|
||||
EffectiveSettings,
|
||||
HiddenModulesConfig,
|
||||
SidebarComponentType,
|
||||
WORKSPACE_ONLY_KEYS,
|
||||
WORKSPACE_SCOPED_KEYS,
|
||||
WorkspaceData,
|
||||
WorkspaceOverrides,
|
||||
|
|
@ -24,6 +27,7 @@ export class WorkspaceManager {
|
|||
private app: App;
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private effectiveCache: Map<string, EffectiveSettings> = new Map();
|
||||
private isSaving = false;
|
||||
|
||||
constructor(plugin: TaskProgressBarPlugin) {
|
||||
this.plugin = plugin;
|
||||
|
|
@ -35,6 +39,7 @@ export class WorkspaceManager {
|
|||
if (!this.plugin.settings.workspaces) {
|
||||
return this.initializeWorkspaces();
|
||||
}
|
||||
|
||||
return this.plugin.settings.workspaces;
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +63,11 @@ export class WorkspaceManager {
|
|||
return this.plugin.settings.workspaces;
|
||||
}
|
||||
|
||||
// Check if a key is workspace-only (never merged into global settings)
|
||||
private isWorkspaceOnlyKey(key: string): boolean {
|
||||
return WORKSPACE_ONLY_KEYS.includes(key as any);
|
||||
}
|
||||
|
||||
// Ensure default workspace invariants
|
||||
public ensureDefaultWorkspaceInvariant(): void {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
|
@ -83,9 +93,19 @@ export class WorkspaceManager {
|
|||
// Ensure default workspace has no overrides
|
||||
const defaultWs = config.byId[config.defaultWorkspaceId];
|
||||
if (defaultWs.settings && Object.keys(defaultWs.settings).length > 0) {
|
||||
// Merge any overrides into global settings and clear
|
||||
const workspaceOnlyOverrides = Object.fromEntries(
|
||||
Object.entries(defaultWs.settings).filter(([key]) =>
|
||||
this.isWorkspaceOnlyKey(key),
|
||||
),
|
||||
) as WorkspaceOverrides;
|
||||
|
||||
// Merge any overrides into global settings while preserving workspace-only entries
|
||||
this.mergeIntoGlobal(defaultWs.settings);
|
||||
defaultWs.settings = {};
|
||||
|
||||
defaultWs.settings =
|
||||
Object.keys(workspaceOnlyOverrides).length > 0
|
||||
? workspaceOnlyOverrides
|
||||
: {};
|
||||
}
|
||||
|
||||
// Ensure active workspace exists
|
||||
|
|
@ -100,7 +120,10 @@ export class WorkspaceManager {
|
|||
// Merge workspace overrides into global settings
|
||||
private mergeIntoGlobal(overrides: WorkspaceOverrides): void {
|
||||
for (const key of Object.keys(overrides)) {
|
||||
if (WORKSPACE_SCOPED_KEYS.includes(key as any)) {
|
||||
if (
|
||||
WORKSPACE_SCOPED_KEYS.includes(key as any) &&
|
||||
!this.isWorkspaceOnlyKey(key)
|
||||
) {
|
||||
(this.plugin.settings as any)[key] = structuredClone(
|
||||
overrides[key as keyof WorkspaceOverrides],
|
||||
);
|
||||
|
|
@ -141,6 +164,7 @@ export class WorkspaceManager {
|
|||
const effective: EffectiveSettings = { ...this.plugin.settings };
|
||||
// Explicitly drop any global fluentFilterState to avoid cross-workspace leakage
|
||||
(effective as any).fluentFilterState = undefined;
|
||||
(effective as any).fluentActiveViewId = undefined;
|
||||
|
||||
// Apply workspace overrides if not default
|
||||
if (id !== config.defaultWorkspaceId && workspace.settings) {
|
||||
|
|
@ -160,6 +184,14 @@ export class WorkspaceManager {
|
|||
workspace.settings.fluentFilterState,
|
||||
);
|
||||
}
|
||||
if (
|
||||
workspace.settings &&
|
||||
workspace.settings.fluentActiveViewId !== undefined
|
||||
) {
|
||||
effective.fluentActiveViewId = structuredClone(
|
||||
workspace.settings.fluentActiveViewId,
|
||||
);
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
this.effectiveCache.set(id, effective);
|
||||
|
|
@ -174,8 +206,8 @@ export class WorkspaceManager {
|
|||
const effValue = (effective as any)[key];
|
||||
const globalValue = (this.plugin.settings as any)[key];
|
||||
|
||||
// fluentFilterState is workspace-only. Always persist it per-workspace when defined.
|
||||
if (key === "fluentFilterState") {
|
||||
// Workspace-only keys: always persist when defined
|
||||
if (this.isWorkspaceOnlyKey(key)) {
|
||||
if (effValue !== undefined) {
|
||||
overrides[key] = structuredClone(effValue);
|
||||
}
|
||||
|
|
@ -215,7 +247,15 @@ export class WorkspaceManager {
|
|||
}
|
||||
|
||||
// Clear the effective cache
|
||||
public clearCache(): void {
|
||||
public clearCache(...workspaceIds: Array<string | undefined>): void {
|
||||
if (workspaceIds.length > 0) {
|
||||
for (const id of workspaceIds) {
|
||||
if (!id) continue;
|
||||
this.effectiveCache.delete(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.effectiveCache.clear();
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +277,10 @@ export class WorkspaceManager {
|
|||
public getActiveWorkspace(): WorkspaceData {
|
||||
const config = this.getWorkspacesConfig();
|
||||
const activeId = config.activeWorkspaceId || config.defaultWorkspaceId;
|
||||
return config.byId[activeId] || config.byId[config.defaultWorkspaceId];
|
||||
const workspace =
|
||||
config.byId[activeId] || config.byId[config.defaultWorkspaceId];
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
// Set active workspace
|
||||
|
|
@ -248,6 +291,7 @@ export class WorkspaceManager {
|
|||
});
|
||||
|
||||
const config = this.getWorkspacesConfig();
|
||||
const previousId = config.activeWorkspaceId;
|
||||
|
||||
if (!config.byId[workspaceId]) {
|
||||
new Notice(`Workspace not found. Using default workspace.`);
|
||||
|
|
@ -263,7 +307,7 @@ export class WorkspaceManager {
|
|||
}
|
||||
|
||||
config.activeWorkspaceId = workspaceId;
|
||||
this.clearCache();
|
||||
this.clearCache(previousId, workspaceId);
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
|
|
@ -400,70 +444,93 @@ export class WorkspaceManager {
|
|||
workspaceId: string,
|
||||
effective: EffectiveSettings,
|
||||
): Promise<void> {
|
||||
const config = this.getWorkspacesConfig();
|
||||
// Prevent recursive calls that could cause infinite loops
|
||||
if (this.isSaving) {
|
||||
console.warn(
|
||||
"[TG-WORKSPACE] Recursive saveOverrides detected, skipping to prevent loop",
|
||||
{ workspaceId }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cannot save overrides to default workspace
|
||||
if (workspaceId === config.defaultWorkspaceId) {
|
||||
// For default, write directly to global settings EXCEPT fluentFilterState which is workspace-only
|
||||
const changedKeys: string[] = [];
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
if (
|
||||
effective[key] !== undefined &&
|
||||
key !== "fluentFilterState"
|
||||
) {
|
||||
(this.plugin.settings as any)[key] = structuredClone(
|
||||
effective[key],
|
||||
);
|
||||
changedKeys.push(key);
|
||||
this.isSaving = true;
|
||||
try {
|
||||
const config = this.getWorkspacesConfig();
|
||||
|
||||
// Cannot save overrides to default workspace
|
||||
if (workspaceId === config.defaultWorkspaceId) {
|
||||
// For default, write directly to global settings EXCEPT workspace-only keys
|
||||
const changedKeys: string[] = [];
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
if (
|
||||
effective[key] !== undefined &&
|
||||
!this.isWorkspaceOnlyKey(key)
|
||||
) {
|
||||
(this.plugin.settings as any)[key] = structuredClone(
|
||||
effective[key],
|
||||
);
|
||||
changedKeys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle fluentFilterState specially for default workspace
|
||||
if (effective.fluentFilterState !== undefined) {
|
||||
const ws = config.byId[workspaceId];
|
||||
ws.settings = (ws.settings || {}) as any;
|
||||
(ws.settings as any).fluentFilterState = structuredClone(
|
||||
effective.fluentFilterState,
|
||||
// Handle fluentFilterState specially for default workspace
|
||||
if (effective.fluentFilterState !== undefined) {
|
||||
const ws = config.byId[workspaceId];
|
||||
ws.settings = (ws.settings || {}) as any;
|
||||
(ws.settings as any).fluentFilterState = structuredClone(
|
||||
effective.fluentFilterState,
|
||||
);
|
||||
ws.updatedAt = Date.now();
|
||||
changedKeys.push("fluentFilterState");
|
||||
}
|
||||
if (effective.fluentActiveViewId !== undefined) {
|
||||
const ws = config.byId[workspaceId];
|
||||
ws.settings = (ws.settings || {}) as any;
|
||||
(ws.settings as any).fluentActiveViewId = structuredClone(
|
||||
effective.fluentActiveViewId,
|
||||
);
|
||||
ws.updatedAt = Date.now();
|
||||
changedKeys.push("fluentActiveViewId");
|
||||
}
|
||||
console.log("[TG-WORKSPACE] saveOverrides(default)", {
|
||||
workspaceId,
|
||||
changedKeys,
|
||||
});
|
||||
this.clearCache();
|
||||
await this.plugin.saveSettings();
|
||||
// Emit overrides saved for UI to react; also emit SETTINGS_CHANGED for global changes
|
||||
emitWorkspaceOverridesSaved(
|
||||
this.app,
|
||||
workspaceId,
|
||||
changedKeys.length ? changedKeys : undefined,
|
||||
);
|
||||
ws.updatedAt = Date.now();
|
||||
changedKeys.push("fluentFilterState");
|
||||
emit(this.app, Events.SETTINGS_CHANGED);
|
||||
return;
|
||||
}
|
||||
console.log("[TG-WORKSPACE] saveOverrides(default)", {
|
||||
|
||||
const workspace = config.byId[workspaceId];
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate overrides
|
||||
const overrides = this.toOverrides(effective);
|
||||
const changedKeys = Object.keys(overrides);
|
||||
|
||||
console.log("[TG-WORKSPACE] saveOverrides", {
|
||||
workspaceId,
|
||||
changedKeys,
|
||||
});
|
||||
workspace.settings = overrides;
|
||||
workspace.updatedAt = Date.now();
|
||||
|
||||
this.clearCache();
|
||||
await this.plugin.saveSettings();
|
||||
// Emit overrides saved for UI to react; also emit SETTINGS_CHANGED for global changes
|
||||
emitWorkspaceOverridesSaved(
|
||||
this.app,
|
||||
workspaceId,
|
||||
changedKeys.length ? changedKeys : undefined,
|
||||
);
|
||||
|
||||
emitWorkspaceOverridesSaved(this.app, workspaceId, changedKeys);
|
||||
emit(this.app, Events.SETTINGS_CHANGED);
|
||||
return;
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
|
||||
const workspace = config.byId[workspaceId];
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate overrides
|
||||
const overrides = this.toOverrides(effective);
|
||||
const changedKeys = Object.keys(overrides);
|
||||
|
||||
console.log("[TG-WORKSPACE] saveOverrides", {
|
||||
workspaceId,
|
||||
changedKeys,
|
||||
});
|
||||
workspace.settings = overrides;
|
||||
workspace.updatedAt = Date.now();
|
||||
|
||||
this.clearCache();
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
emitWorkspaceOverridesSaved(this.app, workspaceId, changedKeys);
|
||||
emit(this.app, Events.SETTINGS_CHANGED);
|
||||
}
|
||||
|
||||
// Save overrides quietly without triggering SETTINGS_CHANGED event
|
||||
|
|
@ -475,11 +542,11 @@ export class WorkspaceManager {
|
|||
|
||||
// Cannot save overrides to default workspace
|
||||
if (workspaceId === config.defaultWorkspaceId) {
|
||||
// For default, write directly to global settings EXCEPT fluentFilterState which is workspace-only
|
||||
// For default, write directly to global settings EXCEPT workspace-only keys
|
||||
for (const key of WORKSPACE_SCOPED_KEYS) {
|
||||
if (
|
||||
effective[key] !== undefined &&
|
||||
key !== "fluentFilterState"
|
||||
!this.isWorkspaceOnlyKey(key)
|
||||
) {
|
||||
(this.plugin.settings as any)[key] = structuredClone(
|
||||
effective[key],
|
||||
|
|
@ -495,6 +562,14 @@ export class WorkspaceManager {
|
|||
);
|
||||
ws.updatedAt = Date.now();
|
||||
}
|
||||
if (effective.fluentActiveViewId !== undefined) {
|
||||
const ws = config.byId[workspaceId];
|
||||
ws.settings = (ws.settings || {}) as any;
|
||||
(ws.settings as any).fluentActiveViewId = structuredClone(
|
||||
effective.fluentActiveViewId,
|
||||
);
|
||||
ws.updatedAt = Date.now();
|
||||
}
|
||||
console.log("[TG-WORKSPACE] saveOverridesQuietly(default)", {
|
||||
workspaceId,
|
||||
keys: WORKSPACE_SCOPED_KEYS.filter(
|
||||
|
|
@ -722,19 +797,35 @@ export class WorkspaceManager {
|
|||
return workspace.settings.hiddenModules.views.includes(viewId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize sidebar component ID for backward compatibility
|
||||
* Maps legacy component IDs to current ones
|
||||
* @param componentId - The sidebar component ID to normalize
|
||||
* @returns Normalized component ID, or null if the component doesn't exist
|
||||
*/
|
||||
private normalizeSidebarComponentId(
|
||||
componentId: AnySidebarComponentType,
|
||||
): SidebarComponentType | null {
|
||||
// Mapping from legacy/any ID to current ID
|
||||
const mapping: Record<string, SidebarComponentType | null> = {
|
||||
"projects-list": "projects-list",
|
||||
"other-views": "other-views",
|
||||
"view-switcher": "other-views", // Legacy name mapped to new name
|
||||
"tags-list": null, // Never implemented, ignore
|
||||
"top-views": null, // Only for old TaskView (not implemented), ignore
|
||||
"bottom-views": null, // Only for old TaskView (not implemented), ignore
|
||||
};
|
||||
return mapping[componentId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a sidebar component is hidden in the specified workspace
|
||||
* @param componentId - The sidebar component ID to check
|
||||
* @param componentId - The sidebar component ID to check (accepts legacy IDs)
|
||||
* @param workspaceId - Optional workspace ID, defaults to active workspace
|
||||
* @returns true if the sidebar component is hidden
|
||||
*/
|
||||
public isSidebarComponentHidden(
|
||||
componentId:
|
||||
| "projects-list"
|
||||
| "tags-list"
|
||||
| "view-switcher"
|
||||
| "top-views"
|
||||
| "bottom-views",
|
||||
componentId: AnySidebarComponentType,
|
||||
workspaceId?: string,
|
||||
): boolean {
|
||||
const workspace = workspaceId
|
||||
|
|
@ -745,8 +836,15 @@ export class WorkspaceManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Normalize the component ID to handle legacy names
|
||||
const normalizedId = this.normalizeSidebarComponentId(componentId);
|
||||
if (normalizedId === null) {
|
||||
// Legacy component that doesn't exist, treat as not hidden
|
||||
return false;
|
||||
}
|
||||
|
||||
return workspace.settings.hiddenModules.sidebarComponents.includes(
|
||||
componentId,
|
||||
normalizedId,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -857,17 +955,11 @@ export class WorkspaceManager {
|
|||
|
||||
/**
|
||||
* Set hidden sidebar components for a workspace
|
||||
* @param componentIds - Array of sidebar component IDs to hide
|
||||
* @param componentIds - Array of sidebar component IDs to hide (accepts legacy IDs)
|
||||
* @param workspaceId - Optional workspace ID, defaults to active workspace
|
||||
*/
|
||||
public async setHiddenSidebarComponents(
|
||||
componentIds: Array<
|
||||
| "projects-list"
|
||||
| "tags-list"
|
||||
| "view-switcher"
|
||||
| "top-views"
|
||||
| "bottom-views"
|
||||
>,
|
||||
componentIds: Array<AnySidebarComponentType>,
|
||||
workspaceId?: string,
|
||||
): Promise<void> {
|
||||
const workspace = workspaceId
|
||||
|
|
@ -876,9 +968,14 @@ export class WorkspaceManager {
|
|||
|
||||
if (!workspace) return;
|
||||
|
||||
// Normalize component IDs and filter out invalid ones
|
||||
const normalizedIds = componentIds
|
||||
.map((id) => this.normalizeSidebarComponentId(id))
|
||||
.filter((id): id is SidebarComponentType => id !== null);
|
||||
|
||||
// Ensure complete initialization and get the initialized object
|
||||
const hiddenModules = this.ensureHiddenModulesInitialized(workspace);
|
||||
hiddenModules.sidebarComponents = [...componentIds];
|
||||
hiddenModules.sidebarComponents = normalizedIds;
|
||||
|
||||
workspace.updatedAt = Date.now();
|
||||
this.clearCache();
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export function renderWorkspaceSettingsTab(
|
|||
});
|
||||
}
|
||||
})
|
||||
.addButton((button) => {
|
||||
.addExtraButton((button) => {
|
||||
button
|
||||
.setIcon("settings-2")
|
||||
.setTooltip(t("Configure hidden modules"))
|
||||
|
|
@ -140,7 +140,7 @@ export function renderWorkspaceSettingsTab(
|
|||
);
|
||||
});
|
||||
})
|
||||
.addButton((button) => {
|
||||
.addExtraButton((button) => {
|
||||
button
|
||||
.setIcon("edit")
|
||||
.setTooltip(t("Rename"))
|
||||
|
|
@ -148,7 +148,7 @@ export function renderWorkspaceSettingsTab(
|
|||
showRenameWorkspaceDialog(settingTab, workspace);
|
||||
});
|
||||
})
|
||||
.addButton((button) => {
|
||||
.addExtraButton((button) => {
|
||||
if (isDefaultWs) {
|
||||
button
|
||||
.setIcon("trash")
|
||||
|
|
@ -262,7 +262,7 @@ function getAvailableModules(plugin: TaskProgressBarPlugin): {
|
|||
}),
|
||||
);
|
||||
|
||||
// Define sidebar component modules
|
||||
// Define sidebar component modules (Fluent interface only)
|
||||
const sidebarComponents: ModuleDefinition[] = [
|
||||
{
|
||||
id: "projects-list",
|
||||
|
|
@ -271,29 +271,11 @@ function getAvailableModules(plugin: TaskProgressBarPlugin): {
|
|||
type: "sidebar" as const,
|
||||
},
|
||||
{
|
||||
id: "tags-list",
|
||||
name: t("Tags List"),
|
||||
icon: "tag",
|
||||
type: "sidebar" as const,
|
||||
},
|
||||
{
|
||||
id: "view-switcher",
|
||||
name: t("View Switcher"),
|
||||
id: "other-views",
|
||||
name: t("Other Views"),
|
||||
icon: "layout-list",
|
||||
type: "sidebar" as const,
|
||||
},
|
||||
{
|
||||
id: "top-views",
|
||||
name: t("Top Views Area"),
|
||||
icon: "layout-top",
|
||||
type: "sidebar" as const,
|
||||
},
|
||||
{
|
||||
id: "bottom-views",
|
||||
name: t("Bottom Views Area"),
|
||||
icon: "layout-bottom",
|
||||
type: "sidebar" as const,
|
||||
},
|
||||
];
|
||||
|
||||
// Define feature component modules
|
||||
|
|
@ -475,8 +457,9 @@ function renderHiddenModulesConfig(
|
|||
break;
|
||||
}
|
||||
|
||||
const refreshed =
|
||||
plugin.workspaceManager?.getWorkspace(workspace.id);
|
||||
const refreshed = plugin.workspaceManager?.getWorkspace(
|
||||
workspace.id,
|
||||
);
|
||||
if (refreshed?.settings?.hiddenModules) {
|
||||
hiddenModules = refreshed.settings.hiddenModules;
|
||||
} else {
|
||||
|
|
@ -487,18 +470,16 @@ function renderHiddenModulesConfig(
|
|||
};
|
||||
}
|
||||
|
||||
hiddenList = newHiddenList;
|
||||
// Update local state from the refreshed data (after normalization)
|
||||
switch (moduleType) {
|
||||
case "views":
|
||||
hiddenModules.views = newHiddenList;
|
||||
hiddenList = hiddenModules.views || [];
|
||||
break;
|
||||
case "sidebarComponents":
|
||||
hiddenModules.sidebarComponents =
|
||||
newHiddenList as SidebarComponentType[];
|
||||
hiddenList = hiddenModules.sidebarComponents || [];
|
||||
break;
|
||||
case "features":
|
||||
hiddenModules.features =
|
||||
newHiddenList as FeatureComponentType[];
|
||||
hiddenList = hiddenModules.features || [];
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -512,7 +493,7 @@ function renderHiddenModulesConfig(
|
|||
"{{hidden}}/{{total}} hidden",
|
||||
{
|
||||
interpolation: {
|
||||
hidden: newHiddenList.length.toString(),
|
||||
hidden: hiddenList.length.toString(),
|
||||
total: totalCount.toString(),
|
||||
},
|
||||
},
|
||||
|
|
@ -530,12 +511,12 @@ function renderHiddenModulesConfig(
|
|||
};
|
||||
|
||||
checkbox.addEventListener("change", () => {
|
||||
void toggleVisibility();
|
||||
toggleVisibility();
|
||||
});
|
||||
itemEl.addEventListener("click", (e) => {
|
||||
if (e.target !== checkbox) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
void toggleVisibility();
|
||||
toggleVisibility();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -270,6 +270,9 @@ export class FluentTaskView extends ItemView {
|
|||
await this.workspaceStateManager.applyWorkspaceSettings();
|
||||
const restored =
|
||||
this.workspaceStateManager.restoreFilterStateFromWorkspace();
|
||||
if (restored?.activeViewId) {
|
||||
this.currentViewId = restored.activeViewId;
|
||||
}
|
||||
this.workspaceStateManager.syncFilterState(restored, {
|
||||
setLiveFilterState: (state) => {
|
||||
this.liveFilterState = state;
|
||||
|
|
@ -848,8 +851,14 @@ export class FluentTaskView extends ItemView {
|
|||
this.registerEvent(
|
||||
onWorkspaceSwitched(this.app, async (payload) => {
|
||||
if (payload.workspaceId !== this.workspaceId) {
|
||||
// Save current workspace state
|
||||
this.workspaceStateManager.saveWorkspaceLayout();
|
||||
// Save current workspace state immediately (before switching)
|
||||
const snapshot =
|
||||
this.workspaceStateManager.captureFilterStateSnapshot();
|
||||
if (snapshot) {
|
||||
await this.workspaceStateManager.saveFilterStateImmediately(
|
||||
snapshot
|
||||
);
|
||||
}
|
||||
|
||||
// Switch to new workspace
|
||||
this.workspaceId = payload.workspaceId;
|
||||
|
|
@ -858,9 +867,12 @@ export class FluentTaskView extends ItemView {
|
|||
// Apply new workspace settings
|
||||
await this.workspaceStateManager.applyWorkspaceSettings();
|
||||
|
||||
// Restore filter state
|
||||
// Restore filter state (includes activeViewId)
|
||||
const restored =
|
||||
this.workspaceStateManager.restoreFilterStateFromWorkspace();
|
||||
if (restored?.activeViewId) {
|
||||
this.currentViewId = restored.activeViewId;
|
||||
}
|
||||
this.workspaceStateManager.syncFilterState(restored, {
|
||||
setLiveFilterState: (state) => {
|
||||
this.liveFilterState = state;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export interface WorkspaceOverrides {
|
|||
viewMode?: string;
|
||||
}
|
||||
>;
|
||||
fluentActiveViewId?: string;
|
||||
|
||||
// Hidden modules configuration
|
||||
hiddenModules?: HiddenModulesConfig;
|
||||
|
|
@ -61,13 +62,28 @@ export interface HiddenModulesConfig {
|
|||
features?: FeatureComponentType[];
|
||||
}
|
||||
|
||||
/** Sidebar component types that can be hidden */
|
||||
/** Sidebar component types that can be hidden (Fluent interface) */
|
||||
export type SidebarComponentType =
|
||||
| "projects-list"
|
||||
| "tags-list"
|
||||
| "view-switcher"
|
||||
| "top-views"
|
||||
| "bottom-views";
|
||||
| "projects-list" // Fluent: Projects list section
|
||||
| "other-views"; // Fluent: Other views section
|
||||
|
||||
/**
|
||||
* Legacy sidebar component types (deprecated, kept for backward compatibility)
|
||||
* These will be silently ignored or mapped when reading old workspace settings
|
||||
*/
|
||||
export type LegacySidebarComponentType =
|
||||
| "tags-list" // Never implemented
|
||||
| "view-switcher" // Legacy name, replaced by "other-views"
|
||||
| "top-views" // Only for old TaskView (not implemented)
|
||||
| "bottom-views"; // Only for old TaskView (not implemented)
|
||||
|
||||
/**
|
||||
* All sidebar component types including legacy ones
|
||||
* Used internally for reading old settings
|
||||
*/
|
||||
export type AnySidebarComponentType =
|
||||
| SidebarComponentType
|
||||
| LegacySidebarComponentType;
|
||||
|
||||
/** Feature component types that can be hidden */
|
||||
export type FeatureComponentType =
|
||||
|
|
@ -115,11 +131,21 @@ export const WORKSPACE_SCOPED_KEYS = [
|
|||
"customProjectGroupsAndNames",
|
||||
"tagCustomOrder",
|
||||
"fluentFilterState",
|
||||
"fluentActiveViewId",
|
||||
"hiddenModules",
|
||||
] as const;
|
||||
|
||||
export type WorkspaceScopedKey = (typeof WORKSPACE_SCOPED_KEYS)[number];
|
||||
|
||||
// Workspace-only keys (stored per-workspace, never merged into global settings)
|
||||
export const WORKSPACE_ONLY_KEYS = [
|
||||
"fluentFilterState",
|
||||
"fluentActiveViewId",
|
||||
"hiddenModules",
|
||||
] as const;
|
||||
|
||||
export type WorkspaceOnlyKey = (typeof WORKSPACE_ONLY_KEYS)[number];
|
||||
|
||||
// Global-only keys (cannot be overridden)
|
||||
export const GLOBAL_ONLY_KEYS = [
|
||||
"autoRun",
|
||||
|
|
|
|||
Loading…
Reference in a new issue