mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
feat(graph-view): implement task deletion and dependency management enhancements
- Added onDeleteTask functionality to allow task deletion from both board and graph views. - Integrated delete options for dependencies in the graph view, enhancing user interaction. - Updated ancestor context section to clarify the role of parent tasks in task descriptions. - Improved layout handling in graph view to preserve node positions during updates. This update enhances task management capabilities and improves user experience in the graph view.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import dagre from 'dagre';
|
||||
import { Node, Edge, useReactFlow } from '@xyflow/react';
|
||||
import { TaskNode, DependencyEdge } from './use-graph-nodes';
|
||||
@@ -18,6 +18,10 @@ interface UseGraphLayoutProps {
|
||||
export function useGraphLayout({ nodes, edges }: UseGraphLayoutProps) {
|
||||
const { fitView, setNodes } = useReactFlow();
|
||||
|
||||
// Cache the last computed positions to avoid recalculating layout
|
||||
const positionCache = useRef<Map<string, { x: number; y: number }>>(new Map());
|
||||
const lastStructureKey = useRef<string>('');
|
||||
|
||||
const getLayoutedElements = useCallback(
|
||||
(
|
||||
inputNodes: TaskNode[],
|
||||
@@ -48,12 +52,15 @@ export function useGraphLayout({ nodes, edges }: UseGraphLayoutProps) {
|
||||
|
||||
const layoutedNodes = inputNodes.map((node) => {
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
const position = {
|
||||
x: nodeWithPosition.x - NODE_WIDTH / 2,
|
||||
y: nodeWithPosition.y - NODE_HEIGHT / 2,
|
||||
};
|
||||
// Update cache
|
||||
positionCache.current.set(node.id, position);
|
||||
return {
|
||||
...node,
|
||||
position: {
|
||||
x: nodeWithPosition.x - NODE_WIDTH / 2,
|
||||
y: nodeWithPosition.y - NODE_HEIGHT / 2,
|
||||
},
|
||||
position,
|
||||
targetPosition: isHorizontal ? 'left' : 'top',
|
||||
sourcePosition: isHorizontal ? 'right' : 'bottom',
|
||||
} as TaskNode;
|
||||
@@ -64,13 +71,45 @@ export function useGraphLayout({ nodes, edges }: UseGraphLayoutProps) {
|
||||
[]
|
||||
);
|
||||
|
||||
// Initial layout
|
||||
// Create a stable structure key based only on node IDs (not edge changes)
|
||||
// Edges changing shouldn't trigger re-layout
|
||||
const structureKey = useMemo(() => {
|
||||
const nodeIds = nodes
|
||||
.map((n) => n.id)
|
||||
.sort()
|
||||
.join(',');
|
||||
return nodeIds;
|
||||
}, [nodes]);
|
||||
|
||||
// Initial layout - only recalculate when node structure changes (new nodes added/removed)
|
||||
const layoutedElements = useMemo(() => {
|
||||
if (nodes.length === 0) {
|
||||
positionCache.current.clear();
|
||||
lastStructureKey.current = '';
|
||||
return { nodes: [], edges: [] };
|
||||
}
|
||||
return getLayoutedElements(nodes, edges, 'LR');
|
||||
}, [nodes, edges, getLayoutedElements]);
|
||||
|
||||
// Check if structure changed (new nodes added or removed)
|
||||
const structureChanged = structureKey !== lastStructureKey.current;
|
||||
|
||||
if (structureChanged) {
|
||||
// Structure changed - run full layout
|
||||
lastStructureKey.current = structureKey;
|
||||
return getLayoutedElements(nodes, edges, 'LR');
|
||||
} else {
|
||||
// Structure unchanged - preserve cached positions, just update node data
|
||||
const layoutedNodes = nodes.map((node) => {
|
||||
const cachedPosition = positionCache.current.get(node.id);
|
||||
return {
|
||||
...node,
|
||||
position: cachedPosition || { x: 0, y: 0 },
|
||||
targetPosition: 'left',
|
||||
sourcePosition: 'right',
|
||||
} as TaskNode;
|
||||
});
|
||||
return { nodes: layoutedNodes, edges };
|
||||
}
|
||||
}, [nodes, edges, structureKey, getLayoutedElements]);
|
||||
|
||||
// Manual re-layout function
|
||||
const runLayout = useCallback(
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface TaskNodeData extends Feature {
|
||||
onStopTask?: () => void;
|
||||
onResumeTask?: () => void;
|
||||
onSpawnTask?: () => void;
|
||||
onDeleteTask?: () => void;
|
||||
}
|
||||
|
||||
export type TaskNode = Node<TaskNodeData, 'task'>;
|
||||
@@ -33,6 +34,7 @@ export type DependencyEdge = Edge<{
|
||||
targetStatus: Feature['status'];
|
||||
isHighlighted?: boolean;
|
||||
isDimmed?: boolean;
|
||||
onDeleteDependency?: (sourceId: string, targetId: string) => void;
|
||||
}>;
|
||||
|
||||
export interface NodeActionCallbacks {
|
||||
@@ -42,6 +44,8 @@ export interface NodeActionCallbacks {
|
||||
onStopTask?: (featureId: string) => void;
|
||||
onResumeTask?: (featureId: string) => void;
|
||||
onSpawnTask?: (featureId: string) => void;
|
||||
onDeleteTask?: (featureId: string) => void;
|
||||
onDeleteDependency?: (sourceId: string, targetId: string) => void;
|
||||
}
|
||||
|
||||
interface UseGraphNodesProps {
|
||||
@@ -117,6 +121,9 @@ export function useGraphNodes({
|
||||
onSpawnTask: actionCallbacks?.onSpawnTask
|
||||
? () => actionCallbacks.onSpawnTask!(feature.id)
|
||||
: undefined,
|
||||
onDeleteTask: actionCallbacks?.onDeleteTask
|
||||
? () => actionCallbacks.onDeleteTask!(feature.id)
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -146,6 +153,7 @@ export function useGraphNodes({
|
||||
targetStatus: feature.status,
|
||||
isHighlighted: edgeIsHighlighted,
|
||||
isDimmed: edgeIsDimmed,
|
||||
onDeleteDependency: actionCallbacks?.onDeleteDependency,
|
||||
},
|
||||
};
|
||||
edgeList.push(edge);
|
||||
|
||||
Reference in New Issue
Block a user