@parhelia/core 0.1.12477 → 0.1.12485
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/config.js +22 -3
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.js.map +1 -1
- package/dist/editor/ConfirmationDialog.js +20 -4
- package/dist/editor/ConfirmationDialog.js.map +1 -1
- package/dist/editor/ContentTree.js +19 -7
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/PictureCropper.js +45 -41
- package/dist/editor/PictureCropper.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.js +97 -8
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AgentTerminalStatusBar.js +65 -0
- package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
- package/dist/editor/ai/Agents.js +19 -0
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.js +5 -0
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ContentInspectorPopover.d.ts +1 -0
- package/dist/editor/ai/ContentInspectorPopover.js +22 -8
- package/dist/editor/ai/ContentInspectorPopover.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.d.ts +2 -0
- package/dist/editor/ai/ToolCallDisplay.js +54 -11
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/dialogs/AgentDialogHandler.js +32 -3
- package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.js +55 -20
- package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
- package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +7 -4
- package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
- package/dist/editor/ai/types.d.ts +2 -0
- package/dist/editor/client/EditorShell.js +55 -10
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +2 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.d.ts +0 -2
- package/dist/editor/client/ui/EditorChrome.js +2 -13
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +8 -1
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
- package/dist/editor/page-viewer/PageViewer.js +23 -4
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +3 -4
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +27 -0
- package/dist/editor/services/agentService.js +11 -2
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.js +54 -3
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/serviceHelper.js +5 -2
- package/dist/editor/services/serviceHelper.js.map +1 -1
- package/dist/editor/settings/About.js +40 -4
- package/dist/editor/settings/About.js.map +1 -1
- package/dist/editor/settings/panels/PersistentLogsPanel.d.ts +2 -0
- package/dist/editor/settings/panels/PersistentLogsPanel.js +244 -0
- package/dist/editor/settings/panels/PersistentLogsPanel.js.map +1 -0
- package/dist/editor/settings/panels/ProjectTemplateAgentPanel.d.ts +7 -1
- package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js +3 -3
- package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js.map +1 -1
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js +165 -84
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
- package/dist/editor/settings/panels/index.d.ts +1 -0
- package/dist/editor/settings/panels/index.js +1 -0
- package/dist/editor/settings/panels/index.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
- package/dist/editor/sidebar/ComponentTree.js +23 -15
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/NavigationPanelItem.js +1 -1
- package/dist/editor/sidebar/NavigationPanelItem.js.map +1 -1
- package/dist/editor/sidebar/WorkspaceButton.js +1 -1
- package/dist/editor/sidebar/WorkspaceButton.js.map +1 -1
- package/dist/editor/tree-indicators/GutterColumns.js +24 -4
- package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
- package/dist/editor/tree-indicators/types.d.ts +10 -0
- package/dist/editor/ui/Splitter.d.ts +1 -0
- package/dist/editor/ui/Splitter.js +7 -1
- package/dist/editor/ui/Splitter.js.map +1 -1
- package/dist/editor/utils.js +16 -4
- package/dist/editor/utils.js.map +1 -1
- package/dist/editor/views/CompareView.d.ts +3 -1
- package/dist/editor/views/CompareView.js +3 -3
- package/dist/editor/views/CompareView.js.map +1 -1
- package/dist/editor/views/EditorSlot.js +7 -6
- package/dist/editor/views/EditorSlot.js.map +1 -1
- package/dist/editor/views/SingleEditView.d.ts +2 -1
- package/dist/editor/views/SingleEditView.js +8 -8
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/licensing/LicenseContext.js +40 -4
- package/dist/licensing/LicenseContext.js.map +1 -1
- package/dist/licensing/LicenseOverlay.js +1 -1
- package/dist/licensing/LicenseOverlay.js.map +1 -1
- package/dist/licensing/types.d.ts +3 -1
- package/dist/licensing/types.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/task-board/TaskBoardWorkspace.js +165 -354
- package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
- package/dist/task-board/assigneeDisplay.js +1 -3
- package/dist/task-board/assigneeDisplay.js.map +1 -1
- package/dist/task-board/components/CreateProjectDialog.js +2 -1
- package/dist/task-board/components/CreateProjectDialog.js.map +1 -1
- package/dist/task-board/components/ProjectDashboard.js +2 -1
- package/dist/task-board/components/ProjectDashboard.js.map +1 -1
- package/dist/task-board/components/ProjectExecutionUserPicker.d.ts +14 -0
- package/dist/task-board/components/ProjectExecutionUserPicker.js +65 -0
- package/dist/task-board/components/ProjectExecutionUserPicker.js.map +1 -0
- package/dist/task-board/components/ProjectSettingsDialog.d.ts +1 -0
- package/dist/task-board/components/ProjectSettingsDialog.js +146 -12
- package/dist/task-board/components/ProjectSettingsDialog.js.map +1 -1
- package/dist/task-board/components/TaskAgentPanel.d.ts +1 -0
- package/dist/task-board/components/TaskAgentPanel.js +2 -2
- package/dist/task-board/components/TaskAgentPanel.js.map +1 -1
- package/dist/task-board/components/TaskAssigneePicker.js +2 -2
- package/dist/task-board/components/TaskAssigneePicker.js.map +1 -1
- package/dist/task-board/components/TaskBoardMyTasksSidebar.js +1 -1
- package/dist/task-board/components/TaskBoardMyTasksSidebar.js.map +1 -1
- package/dist/task-board/components/TaskDetailDialog.d.ts +1 -0
- package/dist/task-board/components/TaskDetailDialog.js +2 -2
- package/dist/task-board/components/TaskDetailDialog.js.map +1 -1
- package/dist/task-board/components/TaskDetailPanel.d.ts +1 -0
- package/dist/task-board/components/TaskDetailPanel.js +23 -8
- package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
- package/dist/task-board/components/TaskRow.js +3 -2
- package/dist/task-board/components/TaskRow.js.map +1 -1
- package/dist/task-board/components/TaskboardPersistentLogPanel.d.ts +11 -0
- package/dist/task-board/components/TaskboardPersistentLogPanel.js +141 -0
- package/dist/task-board/components/TaskboardPersistentLogPanel.js.map +1 -0
- package/dist/task-board/components/WizardTaskDetailsPanel.js +2 -1
- package/dist/task-board/components/WizardTaskDetailsPanel.js.map +1 -1
- package/dist/task-board/persistentLogCopy.d.ts +7 -0
- package/dist/task-board/persistentLogCopy.js +80 -0
- package/dist/task-board/persistentLogCopy.js.map +1 -0
- package/dist/task-board/persistentLogLabels.d.ts +38 -0
- package/dist/task-board/persistentLogLabels.js +189 -0
- package/dist/task-board/persistentLogLabels.js.map +1 -0
- package/dist/task-board/services/taskService.d.ts +17 -1
- package/dist/task-board/services/taskService.js +56 -0
- package/dist/task-board/services/taskService.js.map +1 -1
- package/dist/task-board/taskExecutionRecords.js +2 -0
- package/dist/task-board/taskExecutionRecords.js.map +1 -1
- package/dist/task-board/types.d.ts +78 -1
- package/dist/task-board/useTaskBoardAgentPanelState.d.ts +34 -0
- package/dist/task-board/useTaskBoardAgentPanelState.js +283 -0
- package/dist/task-board/useTaskBoardAgentPanelState.js.map +1 -0
- package/dist/task-board/utils/taskDependencyOrdering.d.ts +6 -0
- package/dist/task-board/utils/taskDependencyOrdering.js +138 -1
- package/dist/task-board/utils/taskDependencyOrdering.js.map +1 -1
- package/dist/task-board/views/DependencyGraphView.d.ts +5 -2
- package/dist/task-board/views/DependencyGraphView.js +261 -69
- package/dist/task-board/views/DependencyGraphView.js.map +1 -1
- package/dist/task-board/views/KanbanView.js +8 -1
- package/dist/task-board/views/KanbanView.js.map +1 -1
- package/dist/task-board/views/ListView.js +1 -1
- package/dist/task-board/views/ListView.js.map +1 -1
- package/dist/task-board/views/WizardView.js +30 -24
- package/dist/task-board/views/WizardView.js.map +1 -1
- package/dist/tour/Tour.js +8 -2
- package/dist/tour/Tour.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { ReactFlow, Controls, MiniMap, useNodesState, useEdgesState, Handle, Position, MarkerType, } from "@xyflow/react";
|
|
4
4
|
import Dagre from "@dagrejs/dagre";
|
|
5
5
|
import "@xyflow/react/dist/style.css";
|
|
@@ -10,7 +10,7 @@ import { cn } from "../../lib/utils";
|
|
|
10
10
|
import { saveGraphLayout } from "../services/taskService";
|
|
11
11
|
import { Button } from "../../components/ui/button";
|
|
12
12
|
import { Badge } from "../../components/ui/badge";
|
|
13
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip";
|
|
13
|
+
import { Tooltip, TooltipContent, TooltipTrigger, } from "../../components/ui/tooltip";
|
|
14
14
|
import { Circle, PlayCircle, Clock3, Eye, CheckCircle2, ArrowUp, ArrowDown, Minus, Flame, GitBranch, RotateCcw, Loader2, Plus, Trash2, } from "lucide-react";
|
|
15
15
|
const NODE_WIDTH = 220;
|
|
16
16
|
const NODE_HEIGHT = 80;
|
|
@@ -46,24 +46,37 @@ function TaskGraphNode({ data }) {
|
|
|
46
46
|
const priorityCfg = data.priority ? PRIORITY_CONFIG[data.priority] : null;
|
|
47
47
|
const PriorityIcon = priorityCfg?.icon;
|
|
48
48
|
const actionButtonClass = "nodrag nopan absolute z-20 flex h-5 w-5 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 shadow-sm transition-colors hover:border-blue-200 hover:bg-blue-50 hover:text-blue-600";
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const dependentButtonClass = data.dependencySourceHandle === "right"
|
|
50
|
+
? "top-1/2 right-0 translate-x-[calc(100%+6px)] -translate-y-1/2"
|
|
51
|
+
: "bottom-0 left-1/2 -translate-x-1/2 translate-y-[calc(100%+6px)]";
|
|
52
|
+
const dependentTooltipSide = data.dependencySourceHandle === "right" ? "right" : "bottom";
|
|
53
|
+
const subtaskButtonClass = data.hierarchySourceHandle === "right"
|
|
54
|
+
? "top-1/2 right-0 translate-x-[calc(100%+6px)] -translate-y-1/2"
|
|
55
|
+
: "bottom-0 left-1/2 -translate-x-1/2 translate-y-[calc(100%+6px)]";
|
|
56
|
+
const subtaskTooltipSide = data.hierarchySourceHandle === "right" ? "right" : "bottom";
|
|
57
|
+
const nodeSurfaceClass = data.isSelected
|
|
58
|
+
? "ring-primary border-primary/50 ring-2 bg-white"
|
|
59
|
+
: data.isBlocked
|
|
60
|
+
? "border-red-200 bg-red-50/70"
|
|
61
|
+
: data.status === "Done"
|
|
62
|
+
? "border-emerald-200 bg-emerald-50/80"
|
|
63
|
+
: "border-slate-200 bg-white hover:border-slate-300 hover:shadow-md";
|
|
64
|
+
return (_jsxs("div", { className: cn("relative cursor-pointer rounded-lg border px-3 py-2 shadow-sm transition-all", nodeSurfaceClass), style: { width: NODE_WIDTH }, "data-testid": `dependency-graph-node-${data.taskId}`, children: [_jsx(Handle, { type: "target", position: Position.Left, id: "left", className: HANDLE_CLASS, "data-testid": `dependency-graph-target-left-${data.taskId}` }), _jsx(Handle, { type: "target", position: Position.Top, id: "top", className: HANDLE_CLASS, "data-testid": `dependency-graph-target-top-${data.taskId}` }), _jsxs("div", { className: "flex items-start justify-between gap-1", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center justify-between gap-1", children: [_jsx("span", { className: "truncate text-[10px] font-medium text-slate-400", children: data.taskKey || data.taskId.slice(0, 8).toUpperCase() }), PriorityIcon && (_jsx(PriorityIcon, { className: cn("h-3 w-3 shrink-0", priorityCfg.color) }))] }), data.onRemoveTask && (_jsx("button", { type: "button", className: "-mt-px -mr-0.5 shrink-0 rounded p-0.5 text-slate-400 transition-colors hover:bg-red-50 hover:text-red-600", "aria-label": "Remove task", "data-testid": `dependency-graph-remove-task-${data.taskId}`, onClick: (event) => {
|
|
52
65
|
event.stopPropagation();
|
|
53
66
|
data.onRemoveTask?.(data.taskId);
|
|
54
|
-
}, children: _jsx(Trash2, { className: "h-2.5 w-2.5", strokeWidth: 1.5 }) }))] }), _jsx("div", { className: "mt-0.5 truncate text-xs font-medium text-slate-800", children: data.title }), _jsxs("div", { className: "mt-1 flex items-center gap-1.5", children: [executionDisplay ? (_jsxs(Badge, { variant: executionDisplay.variant, className: cn("h-5 max-w-full px-1.5 text-[10px] font-bold
|
|
67
|
+
}, children: _jsx(Trash2, { className: "h-2.5 w-2.5", strokeWidth: 1.5 }) }))] }), _jsx("div", { className: "mt-0.5 truncate text-xs font-medium text-slate-800", children: data.title }), _jsxs("div", { className: "mt-1 flex items-center gap-1.5", children: [executionDisplay ? (_jsxs(Badge, { variant: executionDisplay.variant, className: cn("h-5 max-w-full px-1.5 text-[10px] font-bold tracking-wider uppercase", executionDisplay.className), children: [executionDisplay.showSpinner && (_jsx(Loader2, { className: "mr-1 h-2.5 w-2.5 animate-spin" })), executionDisplay.label] })) : (_jsxs("span", { className: cn("inline-flex items-center gap-0.5 rounded-full px-1.5 py-0 text-[10px] font-medium", statusStyle.bg, statusStyle.text), children: [_jsx(StatusIcon, { className: "h-2.5 w-2.5" }), getTaskStatusLabel(status)] })), data.assigneeDisplayName && (_jsxs("span", { className: "truncate text-[10px] text-slate-400", children: ["@", data.assigneeDisplayName] }))] }), data.onAddDependentTaskFromNode && (_jsxs(Tooltip, { delayDuration: 250, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: cn(actionButtonClass, dependentButtonClass), "aria-label": "Add dependent task", "data-testid": `dependency-graph-add-dependent-task-${data.taskId}`, onClick: (event) => {
|
|
55
68
|
event.stopPropagation();
|
|
56
69
|
data.onSelect(data.taskId);
|
|
57
70
|
data.onAddDependentTaskFromNode?.(data.taskId);
|
|
58
71
|
}, onMouseDown: (event) => {
|
|
59
72
|
event.stopPropagation();
|
|
60
|
-
}, children: _jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { side:
|
|
73
|
+
}, children: _jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { side: dependentTooltipSide, sideOffset: 8, children: "Add dependent task" })] })), data.onAddChildTaskFromNode && (_jsxs(Tooltip, { delayDuration: 250, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: cn(actionButtonClass, subtaskButtonClass), "aria-label": "Add subtask", "data-testid": `dependency-graph-add-subtask-${data.taskId}`, onClick: (event) => {
|
|
61
74
|
event.stopPropagation();
|
|
62
75
|
data.onSelect(data.taskId);
|
|
63
76
|
data.onAddChildTaskFromNode?.(data.taskId);
|
|
64
77
|
}, onMouseDown: (event) => {
|
|
65
78
|
event.stopPropagation();
|
|
66
|
-
}, children: _jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { side:
|
|
79
|
+
}, children: _jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { side: subtaskTooltipSide, sideOffset: 8, children: "Add subtask" })] })), _jsx(Handle, { type: "source", position: Position.Right, id: "right", className: HANDLE_CLASS, "data-testid": `dependency-graph-source-right-${data.taskId}` }), _jsx(Handle, { type: "source", position: Position.Bottom, id: "bottom", className: HANDLE_CLASS, "data-testid": `dependency-graph-source-bottom-${data.taskId}` })] }));
|
|
67
80
|
}
|
|
68
81
|
const nodeTypes = {
|
|
69
82
|
taskNode: TaskGraphNode,
|
|
@@ -71,13 +84,30 @@ const nodeTypes = {
|
|
|
71
84
|
const HIERARCHY_CHILD_INDENT = 24;
|
|
72
85
|
const HIERARCHY_CHILD_GAP = 32;
|
|
73
86
|
const HIERARCHY_SUBTREE_GAP = 80;
|
|
87
|
+
function normalizeGraphOrientation(orientation) {
|
|
88
|
+
return orientation === "vertical" ? "vertical" : "horizontal";
|
|
89
|
+
}
|
|
90
|
+
function getDependencyHandles(orientation) {
|
|
91
|
+
return orientation === "horizontal"
|
|
92
|
+
? { source: "right", target: "left" }
|
|
93
|
+
: { source: "bottom", target: "top" };
|
|
94
|
+
}
|
|
95
|
+
function getHierarchyHandles(orientation, autoLayoutStrategy) {
|
|
96
|
+
if (autoLayoutStrategy !== "hierarchy") {
|
|
97
|
+
return getDependencyHandles(orientation);
|
|
98
|
+
}
|
|
99
|
+
return orientation === "horizontal"
|
|
100
|
+
? { source: "bottom", target: "left" }
|
|
101
|
+
: { source: "right", target: "left" };
|
|
102
|
+
}
|
|
74
103
|
/**
|
|
75
104
|
* Compound layout for the hierarchy strategy: each parent–children group is
|
|
76
105
|
* laid out vertically (parent on top, children stacked below with a slight
|
|
77
106
|
* indent), and then the resulting subtree "super-nodes" are arranged
|
|
78
|
-
*
|
|
107
|
+
* in the selected orientation via Dagre using the dependency edges between
|
|
108
|
+
* them.
|
|
79
109
|
*/
|
|
80
|
-
function layoutGraphHierarchy(nodes, edges) {
|
|
110
|
+
function layoutGraphHierarchy(nodes, edges, orientation) {
|
|
81
111
|
if (nodes.length === 0)
|
|
82
112
|
return { nodes, edges };
|
|
83
113
|
const childrenOf = new Map();
|
|
@@ -122,7 +152,11 @@ function layoutGraphHierarchy(nodes, edges) {
|
|
|
122
152
|
}
|
|
123
153
|
}
|
|
124
154
|
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
|
125
|
-
g.setGraph({
|
|
155
|
+
g.setGraph({
|
|
156
|
+
rankdir: orientation === "horizontal" ? "LR" : "TB",
|
|
157
|
+
nodesep: 40,
|
|
158
|
+
ranksep: HIERARCHY_SUBTREE_GAP,
|
|
159
|
+
});
|
|
126
160
|
for (const [rootId, data] of subtrees) {
|
|
127
161
|
g.setNode(rootId, { width: data.width, height: data.height });
|
|
128
162
|
}
|
|
@@ -164,7 +198,7 @@ function layoutGraphHierarchy(nodes, edges) {
|
|
|
164
198
|
function layoutGraph(nodes, edges, orientation, autoLayoutStrategy) {
|
|
165
199
|
if (autoLayoutStrategy === "hierarchy" &&
|
|
166
200
|
edges.some((e) => e.data?.relationship === "hierarchy")) {
|
|
167
|
-
return layoutGraphHierarchy(nodes, edges);
|
|
201
|
+
return layoutGraphHierarchy(nodes, edges, orientation);
|
|
168
202
|
}
|
|
169
203
|
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
|
170
204
|
g.setGraph({
|
|
@@ -199,6 +233,8 @@ function buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, b
|
|
|
199
233
|
const childRelationshipKeys = new Set(visibleTasks
|
|
200
234
|
.filter((task) => !!task.parentTaskId)
|
|
201
235
|
.map((task) => `${task.parentTaskId}->${task.taskId}`));
|
|
236
|
+
const dependencyHandles = getDependencyHandles(orientation);
|
|
237
|
+
const hierarchyHandles = getHierarchyHandles(orientation, autoLayoutStrategy);
|
|
202
238
|
const nodes = visibleTasks.map((task) => ({
|
|
203
239
|
id: task.taskId,
|
|
204
240
|
type: "taskNode",
|
|
@@ -215,26 +251,27 @@ function buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, b
|
|
|
215
251
|
isSelected: task.taskId === selectedTaskId,
|
|
216
252
|
isBlocked: blockedTaskIds.has(task.taskId),
|
|
217
253
|
orientation,
|
|
254
|
+
dependencySourceHandle: dependencyHandles.source,
|
|
255
|
+
hierarchySourceHandle: hierarchyHandles.source,
|
|
218
256
|
onSelect,
|
|
219
257
|
onAddDependentTaskFromNode,
|
|
220
258
|
onAddChildTaskFromNode,
|
|
221
259
|
onRemoveTask,
|
|
222
260
|
},
|
|
223
261
|
}));
|
|
224
|
-
const depSourceHandle =
|
|
225
|
-
const depTargetHandle =
|
|
226
|
-
const hierSourceHandle =
|
|
227
|
-
const hierTargetHandle =
|
|
262
|
+
const depSourceHandle = dependencyHandles.source;
|
|
263
|
+
const depTargetHandle = dependencyHandles.target;
|
|
264
|
+
const hierSourceHandle = hierarchyHandles.source;
|
|
265
|
+
const hierTargetHandle = hierarchyHandles.target;
|
|
228
266
|
const dependencyEdges = dependencies
|
|
229
267
|
.filter((dep) => taskMap.has(dep.taskId) &&
|
|
230
268
|
taskMap.has(dep.dependsOnTaskId) &&
|
|
231
269
|
!childRelationshipKeys.has(`${dep.dependsOnTaskId}->${dep.taskId}`))
|
|
232
270
|
.map((dep) => {
|
|
233
271
|
const isBlockedBy = dep.dependencyType === "BlockedBy";
|
|
234
|
-
const strokeColor = isBlockedBy
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const isSelected = dep.taskId === selectedTaskId && dep.dependsOnTaskId === selectedDependencyId;
|
|
272
|
+
const strokeColor = isBlockedBy ? EDGE_BLOCKED_BY : EDGE_RELATED;
|
|
273
|
+
const isSelected = dep.taskId === selectedTaskId &&
|
|
274
|
+
dep.dependsOnTaskId === selectedDependencyId;
|
|
238
275
|
return {
|
|
239
276
|
id: dep.dependencyId,
|
|
240
277
|
source: dep.dependsOnTaskId,
|
|
@@ -264,8 +301,7 @@ function buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, b
|
|
|
264
301
|
};
|
|
265
302
|
});
|
|
266
303
|
const hierarchyEdges = visibleTasks
|
|
267
|
-
.filter((task) => !!task.parentTaskId &&
|
|
268
|
-
taskMap.has(task.parentTaskId))
|
|
304
|
+
.filter((task) => !!task.parentTaskId && taskMap.has(task.parentTaskId))
|
|
269
305
|
.map((task) => ({
|
|
270
306
|
id: `parent:${task.parentTaskId}:${task.taskId}`,
|
|
271
307
|
source: task.parentTaskId,
|
|
@@ -320,12 +356,15 @@ function buildChildRelationshipKeys(tasks) {
|
|
|
320
356
|
.filter((task) => !!task.parentTaskId)
|
|
321
357
|
.map((task) => `${task.parentTaskId}->${task.taskId}`));
|
|
322
358
|
}
|
|
323
|
-
function isHierarchyConnectionValid(connection, tasks) {
|
|
359
|
+
function isHierarchyConnectionValid(connection, tasks, orientation, autoLayoutStrategy) {
|
|
360
|
+
if (autoLayoutStrategy !== "hierarchy") {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
324
363
|
const { source, sourceHandle, target, targetHandle } = connection;
|
|
325
364
|
if (!source || !target || source === target) {
|
|
326
365
|
return false;
|
|
327
366
|
}
|
|
328
|
-
if (sourceHandle !==
|
|
367
|
+
if (sourceHandle !== getHierarchyHandles(orientation, autoLayoutStrategy).source) {
|
|
329
368
|
return false;
|
|
330
369
|
}
|
|
331
370
|
if (targetHandle && targetHandle !== "left" && targetHandle !== "top") {
|
|
@@ -345,12 +384,12 @@ function isHierarchyConnectionValid(connection, tasks) {
|
|
|
345
384
|
}
|
|
346
385
|
return !buildChildRelationshipKeys(tasks).has(`${source}->${target}`);
|
|
347
386
|
}
|
|
348
|
-
function isDependencyConnectionValid(connection, tasks, dependencies) {
|
|
387
|
+
function isDependencyConnectionValid(connection, tasks, dependencies, orientation) {
|
|
349
388
|
const { source, sourceHandle, target, targetHandle } = connection;
|
|
350
389
|
if (!source || !target || source === target) {
|
|
351
390
|
return false;
|
|
352
391
|
}
|
|
353
|
-
if (sourceHandle !==
|
|
392
|
+
if (sourceHandle !== getDependencyHandles(orientation).source) {
|
|
354
393
|
return false;
|
|
355
394
|
}
|
|
356
395
|
if (targetHandle && targetHandle !== "left" && targetHandle !== "top") {
|
|
@@ -365,15 +404,18 @@ function isDependencyConnectionValid(connection, tasks, dependencies) {
|
|
|
365
404
|
}
|
|
366
405
|
return !buildChildRelationshipKeys(tasks).has(`${source}->${target}`);
|
|
367
406
|
}
|
|
368
|
-
function isGraphConnectionValid(connection, tasks, dependencies) {
|
|
369
|
-
|
|
370
|
-
|
|
407
|
+
function isGraphConnectionValid(connection, tasks, dependencies, orientation, autoLayoutStrategy) {
|
|
408
|
+
const hierarchySourceHandle = getHierarchyHandles(orientation, "hierarchy").source;
|
|
409
|
+
if (autoLayoutStrategy === "hierarchy" &&
|
|
410
|
+
connection.sourceHandle === hierarchySourceHandle) {
|
|
411
|
+
return isHierarchyConnectionValid(connection, tasks, orientation, autoLayoutStrategy);
|
|
371
412
|
}
|
|
372
|
-
return isDependencyConnectionValid(connection, tasks, dependencies);
|
|
413
|
+
return isDependencyConnectionValid(connection, tasks, dependencies, orientation);
|
|
373
414
|
}
|
|
374
|
-
function createLayoutSignature(layoutKey, nodes, viewport) {
|
|
415
|
+
function createLayoutSignature(layoutKey, nodes, viewport, orientation) {
|
|
375
416
|
return JSON.stringify({
|
|
376
417
|
layoutKey,
|
|
418
|
+
orientation,
|
|
377
419
|
nodes: nodes
|
|
378
420
|
.map((node) => ({
|
|
379
421
|
taskId: node.id,
|
|
@@ -390,12 +432,43 @@ function createLayoutSignature(layoutKey, nodes, viewport) {
|
|
|
390
432
|
: null,
|
|
391
433
|
});
|
|
392
434
|
}
|
|
435
|
+
function createGraphSyncSignature(nodes, edges) {
|
|
436
|
+
return JSON.stringify({
|
|
437
|
+
nodes: nodes
|
|
438
|
+
.map((node) => ({
|
|
439
|
+
id: node.id,
|
|
440
|
+
x: node.position.x,
|
|
441
|
+
y: node.position.y,
|
|
442
|
+
selected: node.data.isSelected,
|
|
443
|
+
status: node.data.status,
|
|
444
|
+
executionState: node.data.executionState ?? null,
|
|
445
|
+
blocked: node.data.isBlocked,
|
|
446
|
+
assignee: node.data.assigneeDisplayName ?? null,
|
|
447
|
+
}))
|
|
448
|
+
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
449
|
+
edges: edges
|
|
450
|
+
.map((edge) => ({
|
|
451
|
+
id: edge.id,
|
|
452
|
+
source: edge.source,
|
|
453
|
+
target: edge.target,
|
|
454
|
+
sourceHandle: edge.sourceHandle ?? null,
|
|
455
|
+
targetHandle: edge.targetHandle ?? null,
|
|
456
|
+
}))
|
|
457
|
+
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
458
|
+
});
|
|
459
|
+
}
|
|
393
460
|
export function DependencyGraphView(props) {
|
|
394
|
-
const { projectId, layoutKey, tasks, dependencies, savedLayout, selectedTaskId, selectedDependencyId = null, onSelectTask, onSelectDependency, onClearDependencySelection, permission, canPersistLayout: canPersistLayoutOverride, onPersistLayout, onLayoutSaved, highlightBlockedTasks = true, showExecutionStateBadges = true, emptyStateTitle = "No tasks to visualize", emptyStateDescription = "Create tasks in this project to see them in the dependency graph.", orientation = "vertical", autoLayoutStrategy = "allEdges", layoutSaveDebounceMs = SAVE_DEBOUNCE_MS, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask, allowDependencyConnect = false, onCreateDependency, onCreateChildRelationship, miniMapWidth, miniMapHeight, } = props;
|
|
461
|
+
const { projectId, layoutKey, tasks, dependencies, savedLayout, selectedTaskId, selectedDependencyId = null, onSelectTask, onSelectDependency, onClearDependencySelection, permission, canPersistLayout: canPersistLayoutOverride, onPersistLayout, onLayoutSaved, highlightBlockedTasks = true, showExecutionStateBadges = true, emptyStateTitle = "No tasks to visualize", emptyStateDescription = "Create tasks in this project to see them in the dependency graph.", orientation = "vertical", autoLayoutStrategy = "allEdges", showOrientationToggle = false, layoutSaveDebounceMs = SAVE_DEBOUNCE_MS, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask, allowDependencyConnect = false, onCreateDependency, onCreateChildRelationship, miniMapWidth, miniMapHeight, showMiniMap = true, } = props;
|
|
395
462
|
const canPersistLayout = typeof canPersistLayoutOverride === "boolean"
|
|
396
463
|
? canPersistLayoutOverride
|
|
397
464
|
: permission === "Owner" || permission === "Editor";
|
|
398
465
|
const effectiveLayoutKey = layoutKey ?? projectId ?? "graph";
|
|
466
|
+
const fallbackOrientation = normalizeGraphOrientation(orientation);
|
|
467
|
+
const savedLayoutOrientation = savedLayout
|
|
468
|
+
? normalizeGraphOrientation(savedLayout.orientation ?? fallbackOrientation)
|
|
469
|
+
: null;
|
|
470
|
+
const [transientOrientation, setTransientOrientation] = useState(null);
|
|
471
|
+
const effectiveOrientation = transientOrientation ?? savedLayoutOrientation ?? fallbackOrientation;
|
|
399
472
|
const taskMap = useMemo(() => {
|
|
400
473
|
const m = new Map();
|
|
401
474
|
for (const t of tasks)
|
|
@@ -407,6 +480,11 @@ export function DependencyGraphView(props) {
|
|
|
407
480
|
return new Set();
|
|
408
481
|
}
|
|
409
482
|
const blocked = new Set();
|
|
483
|
+
for (const task of tasks) {
|
|
484
|
+
if ((task.executionBlockedByProjectIds?.length ?? 0) > 0) {
|
|
485
|
+
blocked.add(task.taskId);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
410
488
|
for (const dep of dependencies) {
|
|
411
489
|
if (dep.dependencyType !== "BlockedBy")
|
|
412
490
|
continue;
|
|
@@ -419,13 +497,13 @@ export function DependencyGraphView(props) {
|
|
|
419
497
|
return blocked;
|
|
420
498
|
}, [dependencies, highlightBlockedTasks, taskMap]);
|
|
421
499
|
const onSelect = useCallback((taskId) => onSelectTask(taskId), [onSelectTask]);
|
|
422
|
-
const { nodes: initialNodes, edges: initialEdges } = useMemo(() => buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds,
|
|
500
|
+
const { nodes: initialNodes, edges: initialEdges } = useMemo(() => buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds, effectiveOrientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask), [
|
|
423
501
|
tasks,
|
|
424
502
|
dependencies,
|
|
425
503
|
selectedTaskId,
|
|
426
504
|
selectedDependencyId,
|
|
427
505
|
blockedTaskIds,
|
|
428
|
-
|
|
506
|
+
effectiveOrientation,
|
|
429
507
|
showExecutionStateBadges,
|
|
430
508
|
autoLayoutStrategy,
|
|
431
509
|
onSelect,
|
|
@@ -436,6 +514,7 @@ export function DependencyGraphView(props) {
|
|
|
436
514
|
const [nodes, setNodes, onNodesChangeBase] = useNodesState(initialNodes);
|
|
437
515
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
438
516
|
const nodesRef = useRef(nodes);
|
|
517
|
+
const suppressNodeSelectionUntilRef = useRef(0);
|
|
439
518
|
const onNodesChange = useCallback((changes) => {
|
|
440
519
|
onNodesChangeBase(changes);
|
|
441
520
|
const hasPositionChange = changes.some((c) => c.type === "position" && c.position);
|
|
@@ -453,22 +532,36 @@ export function DependencyGraphView(props) {
|
|
|
453
532
|
const pendingViewportRef = useRef(null);
|
|
454
533
|
const fitViewOnNextSyncRef = useRef(false);
|
|
455
534
|
const skipNextMoveEndSaveRef = useRef(false);
|
|
456
|
-
const
|
|
535
|
+
const pendingSaveOrientationRef = useRef(null);
|
|
457
536
|
const lastSavedSignatureRef = useRef(null);
|
|
458
537
|
const lastAppliedLayoutKeyRef = useRef(null);
|
|
459
538
|
const lastAppliedLayoutSignatureRef = useRef(null);
|
|
539
|
+
const lastAppliedGraphSignatureRef = useRef(null);
|
|
540
|
+
const lastAppliedNodeIdSignatureRef = useRef(null);
|
|
460
541
|
const savedLayoutSignature = useMemo(() => savedLayout
|
|
461
542
|
? JSON.stringify({
|
|
543
|
+
orientation: savedLayoutOrientation,
|
|
462
544
|
updatedAt: savedLayout.updatedAt ?? null,
|
|
463
545
|
nodes: savedLayout.nodes ?? [],
|
|
464
546
|
viewport: savedLayout.viewport ?? null,
|
|
465
547
|
})
|
|
466
|
-
: "null", [savedLayout]);
|
|
467
|
-
|
|
548
|
+
: "null", [savedLayout, savedLayoutOrientation]);
|
|
549
|
+
useEffect(() => {
|
|
550
|
+
setTransientOrientation(null);
|
|
551
|
+
pendingSaveOrientationRef.current = null;
|
|
552
|
+
}, [effectiveLayoutKey]);
|
|
553
|
+
useEffect(() => {
|
|
554
|
+
if (transientOrientation &&
|
|
555
|
+
savedLayoutOrientation &&
|
|
556
|
+
transientOrientation === savedLayoutOrientation) {
|
|
557
|
+
setTransientOrientation(null);
|
|
558
|
+
}
|
|
559
|
+
}, [savedLayoutOrientation, transientOrientation]);
|
|
560
|
+
const persistLayout = useCallback(async (nextNodes, viewport, nextOrientation) => {
|
|
468
561
|
if (!canPersistLayout) {
|
|
469
562
|
return;
|
|
470
563
|
}
|
|
471
|
-
const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport);
|
|
564
|
+
const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport, nextOrientation);
|
|
472
565
|
if (signature === lastSavedSignatureRef.current) {
|
|
473
566
|
return;
|
|
474
567
|
}
|
|
@@ -479,6 +572,7 @@ export function DependencyGraphView(props) {
|
|
|
479
572
|
x: node.position.x,
|
|
480
573
|
y: node.position.y,
|
|
481
574
|
})),
|
|
575
|
+
orientation: nextOrientation,
|
|
482
576
|
viewport: viewport == null
|
|
483
577
|
? null
|
|
484
578
|
: {
|
|
@@ -495,6 +589,7 @@ export function DependencyGraphView(props) {
|
|
|
495
589
|
projectId,
|
|
496
590
|
nodes: layoutPayload.nodes,
|
|
497
591
|
viewport: layoutPayload.viewport,
|
|
592
|
+
orientation: layoutPayload.orientation,
|
|
498
593
|
});
|
|
499
594
|
if (result.type !== "success") {
|
|
500
595
|
toast.error(result.summary || "Failed to save graph layout");
|
|
@@ -506,6 +601,7 @@ export function DependencyGraphView(props) {
|
|
|
506
601
|
lastSavedSignatureRef.current = signature;
|
|
507
602
|
if (nextLayout) {
|
|
508
603
|
const incomingSignature = JSON.stringify({
|
|
604
|
+
orientation: normalizeGraphOrientation(nextLayout.orientation ?? nextOrientation),
|
|
509
605
|
updatedAt: nextLayout.updatedAt ?? null,
|
|
510
606
|
nodes: nextLayout.nodes ?? [],
|
|
511
607
|
viewport: nextLayout.viewport ?? null,
|
|
@@ -522,15 +618,16 @@ export function DependencyGraphView(props) {
|
|
|
522
618
|
}, [
|
|
523
619
|
canPersistLayout,
|
|
524
620
|
effectiveLayoutKey,
|
|
621
|
+
effectiveOrientation,
|
|
525
622
|
onLayoutSaved,
|
|
526
623
|
onPersistLayout,
|
|
527
624
|
projectId,
|
|
528
625
|
]);
|
|
529
|
-
const queueLayoutSave = useCallback((nextNodes, viewport) => {
|
|
626
|
+
const queueLayoutSave = useCallback((nextNodes, viewport, nextOrientation = effectiveOrientation) => {
|
|
530
627
|
if (!canPersistLayout) {
|
|
531
628
|
return;
|
|
532
629
|
}
|
|
533
|
-
const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport);
|
|
630
|
+
const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport, nextOrientation);
|
|
534
631
|
if (signature === lastSavedSignatureRef.current) {
|
|
535
632
|
return;
|
|
536
633
|
}
|
|
@@ -539,14 +636,20 @@ export function DependencyGraphView(props) {
|
|
|
539
636
|
saveTimeoutRef.current = null;
|
|
540
637
|
}
|
|
541
638
|
if (layoutSaveDebounceMs === 0) {
|
|
542
|
-
void persistLayout(nextNodes, viewport);
|
|
639
|
+
void persistLayout(nextNodes, viewport, nextOrientation);
|
|
543
640
|
return;
|
|
544
641
|
}
|
|
545
642
|
saveTimeoutRef.current = window.setTimeout(() => {
|
|
546
643
|
saveTimeoutRef.current = null;
|
|
547
|
-
void persistLayout(nextNodes, viewport);
|
|
644
|
+
void persistLayout(nextNodes, viewport, nextOrientation);
|
|
548
645
|
}, layoutSaveDebounceMs);
|
|
549
|
-
}, [
|
|
646
|
+
}, [
|
|
647
|
+
canPersistLayout,
|
|
648
|
+
effectiveLayoutKey,
|
|
649
|
+
effectiveOrientation,
|
|
650
|
+
layoutSaveDebounceMs,
|
|
651
|
+
persistLayout,
|
|
652
|
+
]);
|
|
550
653
|
const restoreViewport = useCallback(() => {
|
|
551
654
|
const instance = reactFlowRef.current;
|
|
552
655
|
if (!instance || nodesRef.current.length === 0) {
|
|
@@ -558,7 +661,7 @@ export function DependencyGraphView(props) {
|
|
|
558
661
|
skipNextMoveEndSaveRef.current = true;
|
|
559
662
|
window.requestAnimationFrame(() => {
|
|
560
663
|
reactFlowRef.current?.setViewport(viewport, { duration: 0 });
|
|
561
|
-
lastSavedSignatureRef.current = createLayoutSignature(effectiveLayoutKey, nodesRef.current, viewport);
|
|
664
|
+
lastSavedSignatureRef.current = createLayoutSignature(effectiveLayoutKey, nodesRef.current, viewport, effectiveOrientation);
|
|
562
665
|
});
|
|
563
666
|
return;
|
|
564
667
|
}
|
|
@@ -569,39 +672,73 @@ export function DependencyGraphView(props) {
|
|
|
569
672
|
skipNextMoveEndSaveRef.current = true;
|
|
570
673
|
window.requestAnimationFrame(() => {
|
|
571
674
|
reactFlowRef.current?.fitView({ padding: 0.2 });
|
|
572
|
-
if (!
|
|
675
|
+
if (!pendingSaveOrientationRef.current) {
|
|
573
676
|
return;
|
|
574
677
|
}
|
|
575
|
-
|
|
678
|
+
const nextOrientation = pendingSaveOrientationRef.current;
|
|
679
|
+
pendingSaveOrientationRef.current = null;
|
|
576
680
|
const viewport = reactFlowRef.current?.getViewport() ?? null;
|
|
577
|
-
queueLayoutSave(nodesRef.current, viewport);
|
|
681
|
+
queueLayoutSave(nodesRef.current, viewport, nextOrientation);
|
|
578
682
|
});
|
|
579
|
-
}, [effectiveLayoutKey, queueLayoutSave]);
|
|
683
|
+
}, [effectiveLayoutKey, effectiveOrientation, queueLayoutSave]);
|
|
580
684
|
useEffect(() => {
|
|
581
685
|
const shouldApplySavedLayout = lastAppliedLayoutKeyRef.current !== effectiveLayoutKey ||
|
|
582
686
|
lastAppliedLayoutSignatureRef.current !== savedLayoutSignature;
|
|
687
|
+
const nextNodeIdSignature = initialNodes.map((node) => node.id).join("|");
|
|
688
|
+
const savedLayoutNodeCount = savedLayout?.nodes?.length ?? 0;
|
|
689
|
+
const shouldAutoLayoutForHierarchy = autoLayoutStrategy === "hierarchy" &&
|
|
690
|
+
(savedLayoutNodeCount < initialNodes.length ||
|
|
691
|
+
(lastAppliedNodeIdSignatureRef.current !== null &&
|
|
692
|
+
lastAppliedNodeIdSignatureRef.current !== nextNodeIdSignature));
|
|
583
693
|
const nextNodes = shouldApplySavedLayout
|
|
584
|
-
?
|
|
585
|
-
|
|
694
|
+
? shouldAutoLayoutForHierarchy
|
|
695
|
+
? initialNodes
|
|
696
|
+
: applySavedLayout(initialNodes, savedLayout)
|
|
697
|
+
: shouldAutoLayoutForHierarchy
|
|
698
|
+
? initialNodes
|
|
699
|
+
: preserveNodePositions(nodesRef.current, initialNodes);
|
|
700
|
+
const nextGraphSignature = createGraphSyncSignature(nextNodes, initialEdges);
|
|
701
|
+
if (!shouldApplySavedLayout &&
|
|
702
|
+
!shouldAutoLayoutForHierarchy &&
|
|
703
|
+
nextGraphSignature === lastAppliedGraphSignatureRef.current) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
586
706
|
setNodes(nextNodes);
|
|
587
707
|
setEdges(initialEdges);
|
|
588
708
|
nodesRef.current = nextNodes;
|
|
709
|
+
lastAppliedGraphSignatureRef.current = nextGraphSignature;
|
|
710
|
+
lastAppliedNodeIdSignatureRef.current = nextNodeIdSignature;
|
|
589
711
|
if (shouldApplySavedLayout) {
|
|
590
712
|
lastAppliedLayoutKeyRef.current = effectiveLayoutKey;
|
|
591
713
|
lastAppliedLayoutSignatureRef.current = savedLayoutSignature;
|
|
592
|
-
pendingViewportRef.current =
|
|
593
|
-
|
|
594
|
-
|
|
714
|
+
pendingViewportRef.current =
|
|
715
|
+
shouldAutoLayoutForHierarchy ? null : (savedLayout?.viewport ?? null);
|
|
716
|
+
fitViewOnNextSyncRef.current =
|
|
717
|
+
shouldAutoLayoutForHierarchy || !savedLayout?.viewport;
|
|
718
|
+
if (!savedLayout?.viewport || shouldAutoLayoutForHierarchy) {
|
|
595
719
|
lastSavedSignatureRef.current = savedLayout?.nodes?.length
|
|
596
|
-
? createLayoutSignature(effectiveLayoutKey, nextNodes, null)
|
|
720
|
+
? createLayoutSignature(effectiveLayoutKey, nextNodes, null, effectiveOrientation)
|
|
597
721
|
: null;
|
|
598
722
|
}
|
|
723
|
+
if (shouldAutoLayoutForHierarchy) {
|
|
724
|
+
pendingSaveOrientationRef.current = effectiveOrientation;
|
|
725
|
+
}
|
|
726
|
+
restoreViewport();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (shouldAutoLayoutForHierarchy) {
|
|
730
|
+
pendingViewportRef.current = null;
|
|
731
|
+
fitViewOnNextSyncRef.current = true;
|
|
732
|
+
pendingSaveOrientationRef.current = effectiveOrientation;
|
|
599
733
|
restoreViewport();
|
|
600
734
|
}
|
|
601
735
|
}, [
|
|
736
|
+
autoLayoutStrategy,
|
|
602
737
|
initialEdges,
|
|
603
738
|
initialNodes,
|
|
604
739
|
effectiveLayoutKey,
|
|
740
|
+
effectiveOrientation,
|
|
741
|
+
projectId,
|
|
605
742
|
restoreViewport,
|
|
606
743
|
savedLayout,
|
|
607
744
|
savedLayoutSignature,
|
|
@@ -618,18 +755,18 @@ export function DependencyGraphView(props) {
|
|
|
618
755
|
}, []);
|
|
619
756
|
const hasVisibleNodes = initialNodes.length > 0;
|
|
620
757
|
const handleAutoLayout = useCallback(() => {
|
|
621
|
-
const autoLayout = buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds,
|
|
758
|
+
const autoLayout = buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds, effectiveOrientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask);
|
|
622
759
|
setNodes(autoLayout.nodes);
|
|
623
760
|
setEdges(autoLayout.edges);
|
|
624
761
|
nodesRef.current = autoLayout.nodes;
|
|
625
762
|
fitViewOnNextSyncRef.current = true;
|
|
626
|
-
|
|
763
|
+
pendingSaveOrientationRef.current = effectiveOrientation;
|
|
627
764
|
restoreViewport();
|
|
628
765
|
}, [
|
|
629
766
|
blockedTaskIds,
|
|
630
767
|
dependencies,
|
|
768
|
+
effectiveOrientation,
|
|
631
769
|
onSelect,
|
|
632
|
-
orientation,
|
|
633
770
|
showExecutionStateBadges,
|
|
634
771
|
autoLayoutStrategy,
|
|
635
772
|
onRemoveTask,
|
|
@@ -643,8 +780,14 @@ export function DependencyGraphView(props) {
|
|
|
643
780
|
tasks,
|
|
644
781
|
]);
|
|
645
782
|
const isValidConnection = useCallback((connection) => allowDependencyConnect
|
|
646
|
-
? isGraphConnectionValid(connection, tasks, dependencies)
|
|
647
|
-
: false, [
|
|
783
|
+
? isGraphConnectionValid(connection, tasks, dependencies, effectiveOrientation, autoLayoutStrategy)
|
|
784
|
+
: false, [
|
|
785
|
+
allowDependencyConnect,
|
|
786
|
+
autoLayoutStrategy,
|
|
787
|
+
dependencies,
|
|
788
|
+
effectiveOrientation,
|
|
789
|
+
tasks,
|
|
790
|
+
]);
|
|
648
791
|
const handleConnect = useCallback((connection) => {
|
|
649
792
|
if (!allowDependencyConnect) {
|
|
650
793
|
return;
|
|
@@ -653,24 +796,64 @@ export function DependencyGraphView(props) {
|
|
|
653
796
|
if (!source || !target) {
|
|
654
797
|
return;
|
|
655
798
|
}
|
|
656
|
-
if (!isGraphConnectionValid(connection, tasks, dependencies)) {
|
|
799
|
+
if (!isGraphConnectionValid(connection, tasks, dependencies, effectiveOrientation, autoLayoutStrategy)) {
|
|
657
800
|
return;
|
|
658
801
|
}
|
|
659
|
-
|
|
660
|
-
if (
|
|
802
|
+
suppressNodeSelectionUntilRef.current = performance.now() + 150;
|
|
803
|
+
if (autoLayoutStrategy === "hierarchy" &&
|
|
804
|
+
sourceHandle === getHierarchyHandles(effectiveOrientation, "hierarchy").source) {
|
|
661
805
|
onCreateChildRelationship?.(source, target);
|
|
662
|
-
return;
|
|
663
806
|
}
|
|
664
|
-
|
|
807
|
+
else {
|
|
808
|
+
onCreateDependency?.(source, target);
|
|
809
|
+
}
|
|
810
|
+
window.requestAnimationFrame(() => {
|
|
811
|
+
onSelectTask(target);
|
|
812
|
+
});
|
|
665
813
|
}, [
|
|
666
814
|
allowDependencyConnect,
|
|
815
|
+
autoLayoutStrategy,
|
|
667
816
|
dependencies,
|
|
817
|
+
effectiveOrientation,
|
|
668
818
|
onCreateChildRelationship,
|
|
669
819
|
onCreateDependency,
|
|
670
820
|
onSelectTask,
|
|
671
821
|
tasks,
|
|
672
822
|
]);
|
|
673
|
-
|
|
823
|
+
const handleOrientationChange = useCallback((nextOrientation) => {
|
|
824
|
+
if (nextOrientation === effectiveOrientation) {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
setTransientOrientation(nextOrientation);
|
|
828
|
+
const autoLayout = buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds, nextOrientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask);
|
|
829
|
+
setNodes(autoLayout.nodes);
|
|
830
|
+
setEdges(autoLayout.edges);
|
|
831
|
+
nodesRef.current = autoLayout.nodes;
|
|
832
|
+
fitViewOnNextSyncRef.current = true;
|
|
833
|
+
pendingSaveOrientationRef.current = nextOrientation;
|
|
834
|
+
restoreViewport();
|
|
835
|
+
}, [
|
|
836
|
+
autoLayoutStrategy,
|
|
837
|
+
blockedTaskIds,
|
|
838
|
+
dependencies,
|
|
839
|
+
effectiveOrientation,
|
|
840
|
+
onAddChildTaskFromNode,
|
|
841
|
+
onAddDependentTaskFromNode,
|
|
842
|
+
onRemoveTask,
|
|
843
|
+
onSelect,
|
|
844
|
+
restoreViewport,
|
|
845
|
+
selectedDependencyId,
|
|
846
|
+
selectedTaskId,
|
|
847
|
+
setEdges,
|
|
848
|
+
setNodes,
|
|
849
|
+
showExecutionStateBadges,
|
|
850
|
+
tasks,
|
|
851
|
+
]);
|
|
852
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "flex shrink-0 items-center justify-between gap-3 px-3 pt-2", children: [hasVisibleNodes && highlightBlockedTasks ? (_jsxs("div", { className: "flex items-center gap-2 text-[11px] text-slate-400", children: [_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "inline-block h-0.5 w-4 rounded-sm bg-orange-400" }), "Blocked by"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "inline-block h-0.5 w-4 rounded-sm bg-slate-400" }), "Related"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "inline-block h-0.5 w-4 rounded-sm bg-blue-500" }), "Child task"] })] })) : (_jsx("div", {})), hasVisibleNodes && (_jsxs("div", { className: "flex items-center gap-2", children: [showOrientationToggle && (_jsxs("div", { className: "inline-flex rounded-md border border-slate-200 bg-white p-0.5", "data-testid": "dependency-graph-orientation-toggle", children: [_jsx("button", { type: "button", className: cn("rounded px-2.5 py-1 text-xs font-medium transition-colors", effectiveOrientation === "horizontal"
|
|
853
|
+
? "bg-slate-900 text-white"
|
|
854
|
+
: "text-slate-500 hover:bg-slate-100 hover:text-slate-700"), "data-testid": "dependency-graph-orientation-horizontal", onClick: () => handleOrientationChange("horizontal"), children: "Horizontal" }), _jsx("button", { type: "button", className: cn("rounded px-2.5 py-1 text-xs font-medium transition-colors", effectiveOrientation === "vertical"
|
|
855
|
+
? "bg-slate-900 text-white"
|
|
856
|
+
: "text-slate-500 hover:bg-slate-100 hover:text-slate-700"), "data-testid": "dependency-graph-orientation-vertical", onClick: () => handleOrientationChange("vertical"), children: "Vertical" })] })), canPersistLayout && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8 px-3", onClick: handleAutoLayout, children: [_jsx(RotateCcw, { className: "mr-1.5 h-4 w-4" }), "Auto layout"] }))] }))] }), _jsx("div", { className: "min-h-0 flex-1", children: !hasVisibleNodes ? (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex max-w-sm flex-col items-center gap-3 text-center", children: [_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-slate-100", children: _jsx(GitBranch, { className: "h-6 w-6 text-slate-400" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-slate-600", children: emptyStateTitle }), _jsx("p", { className: "mt-1 text-xs text-slate-400", children: emptyStateDescription })] })] }) })) : (_jsxs(ReactFlow, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: handleConnect, onPaneClick: () => {
|
|
674
857
|
onClearDependencySelection?.();
|
|
675
858
|
}, onEdgeClick: (_event, edge) => {
|
|
676
859
|
if (edge.data?.relationship === "dependency" &&
|
|
@@ -679,9 +862,15 @@ export function DependencyGraphView(props) {
|
|
|
679
862
|
onSelectDependency?.(edge.data.taskId, edge.data.dependsOnTaskId);
|
|
680
863
|
}
|
|
681
864
|
}, onNodeClick: (_event, node) => {
|
|
865
|
+
if (performance.now() < suppressNodeSelectionUntilRef.current) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
682
868
|
onClearDependencySelection?.();
|
|
683
869
|
onSelectTask(node.id);
|
|
684
870
|
}, onNodeDragStart: (_event, node) => {
|
|
871
|
+
if (performance.now() < suppressNodeSelectionUntilRef.current) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
685
874
|
onClearDependencySelection?.();
|
|
686
875
|
onSelectTask(node.id);
|
|
687
876
|
}, onInit: (instance) => {
|
|
@@ -697,13 +886,16 @@ export function DependencyGraphView(props) {
|
|
|
697
886
|
return;
|
|
698
887
|
}
|
|
699
888
|
queueLayoutSave(nodesRef.current, viewport);
|
|
700
|
-
}, nodeTypes: nodeTypes, minZoom: 0.2, maxZoom: 2, proOptions: { hideAttribution: true }, nodesDraggable: canPersistLayout, nodesConnectable: allowDependencyConnect, isValidConnection: isValidConnection, elementsSelectable: true, children: [_jsx(Controls, { showInteractive: false, className: "rounded-lg! border-slate-200! shadow-md!" }), _jsx(MiniMap, { ...(miniMapWidth != null && miniMapHeight != null
|
|
889
|
+
}, nodeTypes: nodeTypes, minZoom: 0.2, maxZoom: 2, proOptions: { hideAttribution: true }, nodesDraggable: canPersistLayout, nodesConnectable: allowDependencyConnect, isValidConnection: isValidConnection, elementsSelectable: true, children: [_jsx(Controls, { showInteractive: false, className: "rounded-lg! border-slate-200! shadow-md!" }), showMiniMap ? (_jsx(MiniMap, { ...(miniMapWidth != null && miniMapHeight != null
|
|
701
890
|
? { width: miniMapWidth, height: miniMapHeight }
|
|
702
891
|
: {}), nodeColor: (node) => {
|
|
703
|
-
const
|
|
892
|
+
const d = node.data;
|
|
893
|
+
if (d?.isBlocked)
|
|
894
|
+
return "#fecaca";
|
|
895
|
+
const status = d?.status;
|
|
704
896
|
switch (status) {
|
|
705
897
|
case "Done":
|
|
706
|
-
return "#
|
|
898
|
+
return "#bbf7d0";
|
|
707
899
|
case "InProgress":
|
|
708
900
|
return "#93c5fd";
|
|
709
901
|
case "Review":
|
|
@@ -713,6 +905,6 @@ export function DependencyGraphView(props) {
|
|
|
713
905
|
default:
|
|
714
906
|
return "#cbd5e1";
|
|
715
907
|
}
|
|
716
|
-
}, maskColor: "rgba(0, 0, 0, 0.08)", className: "rounded-lg! border-slate-200! shadow-md!" })] })) })] }));
|
|
908
|
+
}, maskColor: "rgba(0, 0, 0, 0.08)", className: "rounded-lg! border-slate-200! shadow-md!" })) : null] })) })] }));
|
|
717
909
|
}
|
|
718
910
|
//# sourceMappingURL=DependencyGraphView.js.map
|