mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
fix(fluent): resolve filter state loading and flickering issues
Fix three critical issues in FluentTaskView filtering system: 1. Filter button state loading: Remove incorrect onLayoutReady wrapper that only fired once during initial layout. Now uses direct setTimeout to load filter state every time popover is opened. 2. Flickering during filtering: Add 150ms debounced handleFilterChanged method to prevent rapid re-renders. Previously filter changes triggered 2-3 consecutive updates, now consolidated to single update. 3. Projects view filtering: Fix dual-mode filtering where project list should show all projects (left sidebar) while task list respects global filter (right panel). Modified ProjectsComponent.setTasks to accept both tasks and filteredTasks parameters. Affected files: - FluentLayoutManager.ts: Fix popover state loading timing - FluentTaskView.ts: Add debounced filter handler - FluentComponentManager.ts: Pass both parameters to Projects view - projects.ts: Support external filtered tasks
This commit is contained in:
parent
0f3726d7d2
commit
8c89618e10
4 changed files with 111 additions and 78 deletions
|
|
@ -604,13 +604,21 @@ export class FluentComponentManager extends Component {
|
|||
// Set tasks on the component
|
||||
if (typeof targetComponent.setTasks === "function") {
|
||||
// Special handling for components that need only all tasks (single parameter)
|
||||
// Projects overview mode (no project selected) needs all tasks to build project list
|
||||
if (viewId === "review" || viewId === "tags" || (viewId === "projects" && !project)) {
|
||||
// Review and tags views need all tasks to build their indices
|
||||
if (viewId === "review" || viewId === "tags") {
|
||||
console.log(
|
||||
`[FluentComponent] Calling setTasks for ${viewId} with ALL tasks:`,
|
||||
tasks.length,
|
||||
);
|
||||
targetComponent.setTasks(tasks);
|
||||
} else if (viewId === "projects" && !project) {
|
||||
// Projects overview mode: pass ALL tasks to build project index
|
||||
// and FILTERED tasks to apply filter to project task lists
|
||||
// This ensures: left sidebar shows all projects, right panel shows filtered tasks
|
||||
console.log(
|
||||
`[FluentComponent] Calling setTasks for projects with ALL tasks (${tasks.length}) and FILTERED tasks (${filteredTasks.length})`,
|
||||
);
|
||||
targetComponent.setTasks(tasks, filteredTasks);
|
||||
} else {
|
||||
// Use filtered tasks
|
||||
let filteredTasksLocal = [...filteredTasks];
|
||||
|
|
|
|||
|
|
@ -564,17 +564,21 @@ export class FluentLayoutManager extends Component {
|
|||
this.plugin,
|
||||
);
|
||||
|
||||
// Set up filter state when opening
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
setTimeout(() => {
|
||||
const liveFilterState = this.getLiveFilterState?.();
|
||||
if (liveFilterState && popover.taskFilterComponent) {
|
||||
popover.taskFilterComponent.loadFilterState(
|
||||
liveFilterState,
|
||||
);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
// Set onClose callback for consistency (realtime event listeners handle the actual updates)
|
||||
popover.onClose = (filterState) => {
|
||||
// Realtime event listeners already handle filter changes
|
||||
// This callback is kept for potential future extensions
|
||||
};
|
||||
|
||||
// Load current filter state after popover is shown
|
||||
setTimeout(() => {
|
||||
const liveFilterState = this.getLiveFilterState?.();
|
||||
if (liveFilterState && popover.taskFilterComponent) {
|
||||
popover.taskFilterComponent.loadFilterState(
|
||||
liveFilterState,
|
||||
);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
popover.showAtPosition({ x: e.clientX, y: e.clientY });
|
||||
} else {
|
||||
|
|
@ -586,15 +590,15 @@ export class FluentLayoutManager extends Component {
|
|||
|
||||
modal.open();
|
||||
|
||||
// Set initial filter state
|
||||
const liveFilterState = this.getLiveFilterState?.();
|
||||
if (liveFilterState && modal.taskFilterComponent) {
|
||||
setTimeout(() => {
|
||||
// Load current filter state after modal is opened
|
||||
setTimeout(() => {
|
||||
const liveFilterState = this.getLiveFilterState?.();
|
||||
if (liveFilterState && modal.taskFilterComponent) {
|
||||
modal.taskFilterComponent.loadFilterState(
|
||||
liveFilterState,
|
||||
);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export class ProjectsComponent extends Component {
|
|||
// State
|
||||
private allTasks: Task[] = [];
|
||||
private filteredTasks: Task[] = [];
|
||||
private externalFilteredTasks: Task[] | null = null; // Externally filtered tasks from FluentView
|
||||
private selectedProjects: SelectedProjects = {
|
||||
projects: [],
|
||||
tasks: [],
|
||||
|
|
@ -290,8 +291,9 @@ export class ProjectsComponent extends Component {
|
|||
this.updateTgProjectPropsButton(null);
|
||||
}
|
||||
|
||||
public setTasks(tasks: Task[]) {
|
||||
public setTasks(tasks: Task[], filteredTasks?: Task[]) {
|
||||
this.allTasks = tasks;
|
||||
this.externalFilteredTasks = filteredTasks || null;
|
||||
this.allTasksMap = new Map(
|
||||
this.allTasks.map((task) => [task.id, task]),
|
||||
);
|
||||
|
|
@ -674,6 +676,17 @@ export class ProjectsComponent extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
// Apply external filter if provided (from FluentView filter system)
|
||||
// This ensures project tasks respect global filter conditions
|
||||
if (this.externalFilteredTasks) {
|
||||
const externalFilteredIds = new Set(
|
||||
this.externalFilteredTasks.map((t) => t.id),
|
||||
);
|
||||
this.filteredTasks = this.filteredTasks.filter((task) =>
|
||||
externalFilteredIds.has(task.id),
|
||||
);
|
||||
}
|
||||
|
||||
// Update the task list using the renderer
|
||||
this.renderTaskList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,67 @@ export class FluentTaskView extends ItemView {
|
|||
);
|
||||
}, 200);
|
||||
|
||||
/**
|
||||
* Debounced filter change handler to prevent rapid re-renders
|
||||
* Delay: 150ms to balance responsiveness and performance
|
||||
*/
|
||||
private handleFilterChanged = debounce(
|
||||
(filterState: RootFilterState, leafId?: string) => {
|
||||
// Only update if it's from a live filter component
|
||||
if (
|
||||
leafId &&
|
||||
!leafId.startsWith("view-config-") &&
|
||||
leafId !== "global-filter"
|
||||
) {
|
||||
console.log("[TG] Filter changed from live component");
|
||||
this.liveFilterState = filterState;
|
||||
this.currentFilterState = filterState;
|
||||
} else if (!leafId) {
|
||||
// No leafId means it's also a live filter change
|
||||
console.log("[TG] Filter changed (no leafId)");
|
||||
this.liveFilterState = filterState;
|
||||
this.currentFilterState = filterState;
|
||||
}
|
||||
|
||||
// Sync selectedProject with filter UI state (if project filter is present)
|
||||
try {
|
||||
const groups = filterState?.filterGroups || [];
|
||||
const projectFilters: string[] = [];
|
||||
for (const g of groups) {
|
||||
for (const f of g.filters || []) {
|
||||
if (
|
||||
f.property === "project" &&
|
||||
f.condition === "is" &&
|
||||
typeof f.value === "string" &&
|
||||
f.value.trim() !== ""
|
||||
) {
|
||||
projectFilters.push(f.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (projectFilters.length > 0) {
|
||||
this.viewState.selectedProject = projectFilters[0];
|
||||
} else {
|
||||
this.viewState.selectedProject = undefined;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"[TG] Failed to sync selectedProject from filter state",
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
// Persist and update header UI
|
||||
this.workspaceStateManager.saveFilterStateToWorkspace();
|
||||
this.layoutManager.updateActionButtons();
|
||||
|
||||
// Apply filters and update view
|
||||
this.filteredTasks = this.dataManager.applyFilters(this.tasks);
|
||||
this.updateView();
|
||||
},
|
||||
150,
|
||||
);
|
||||
|
||||
onResize(): void {
|
||||
this.updateWorkspaceLeafWidth();
|
||||
}
|
||||
|
|
@ -784,12 +845,11 @@ export class FluentTaskView extends ItemView {
|
|||
(query: string) => this.actionHandlers.handleSearch(query),
|
||||
(mode: ViewMode) => this.actionHandlers.handleViewModeChange(mode),
|
||||
() => {
|
||||
// Filter click - open filter modal/popover
|
||||
// TODO: Implement filter modal
|
||||
// Filter click callback (currently unused)
|
||||
// Note: Filter functionality is implemented in FluentLayoutManager via addAction
|
||||
},
|
||||
() => {
|
||||
// Sort click
|
||||
// TODO: Implement sort
|
||||
// Sort click callback (future extension point)
|
||||
},
|
||||
() => this.actionHandlers.handleSettingsClick(),
|
||||
);
|
||||
|
|
@ -970,64 +1030,12 @@ export class FluentTaskView extends ItemView {
|
|||
);
|
||||
}
|
||||
|
||||
// Listen for filter change events
|
||||
// Listen for filter change events (with debouncing to prevent flickering)
|
||||
this.registerEvent(
|
||||
this.app.workspace.on(
|
||||
"task-genius:filter-changed",
|
||||
(filterState: RootFilterState, leafId?: string) => {
|
||||
// Only update if it's from a live filter component
|
||||
if (
|
||||
leafId &&
|
||||
!leafId.startsWith("view-config-") &&
|
||||
leafId !== "global-filter"
|
||||
) {
|
||||
console.log("[TG] Filter changed from live component");
|
||||
this.liveFilterState = filterState;
|
||||
this.currentFilterState = filterState;
|
||||
} else if (!leafId) {
|
||||
// No leafId means it's also a live filter change
|
||||
console.log("[TG] Filter changed (no leafId)");
|
||||
this.liveFilterState = filterState;
|
||||
this.currentFilterState = filterState;
|
||||
}
|
||||
|
||||
// Sync selectedProject with filter UI state (if project filter is present)
|
||||
try {
|
||||
const groups = filterState?.filterGroups || [];
|
||||
const projectFilters: string[] = [];
|
||||
for (const g of groups) {
|
||||
for (const f of g.filters || []) {
|
||||
if (
|
||||
f.property === "project" &&
|
||||
f.condition === "is" &&
|
||||
typeof f.value === "string" &&
|
||||
f.value.trim() !== ""
|
||||
) {
|
||||
projectFilters.push(f.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (projectFilters.length > 0) {
|
||||
this.viewState.selectedProject = projectFilters[0];
|
||||
} else {
|
||||
this.viewState.selectedProject = undefined;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"[TG] Failed to sync selectedProject from filter state",
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
// Persist and update header UI
|
||||
this.workspaceStateManager.saveFilterStateToWorkspace();
|
||||
this.layoutManager.updateActionButtons();
|
||||
|
||||
// Apply filters and update view
|
||||
this.filteredTasks = this.dataManager.applyFilters(
|
||||
this.tasks,
|
||||
);
|
||||
this.updateView();
|
||||
this.handleFilterChanged(filterState, leafId);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue