@memberjunction/ng-task-graph-editor 6.1.0-edge.1 → 6.1.0-edge.2
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/lib/task-graph-canvas-adapter.d.ts +102 -5
- package/dist/lib/task-graph-canvas-adapter.d.ts.map +1 -1
- package/dist/lib/task-graph-canvas-adapter.js +154 -18
- package/dist/lib/task-graph-canvas-adapter.js.map +1 -1
- package/dist/lib/task-graph-editor.component.d.ts +106 -4
- package/dist/lib/task-graph-editor.component.d.ts.map +1 -1
- package/dist/lib/task-graph-editor.component.js +246 -24
- package/dist/lib/task-graph-editor.component.js.map +1 -1
- package/dist/lib/task-graph-editor.module.d.ts +6 -5
- package/dist/lib/task-graph-editor.module.d.ts.map +1 -1
- package/dist/lib/task-graph-editor.module.js +4 -3
- package/dist/lib/task-graph-editor.module.js.map +1 -1
- package/dist/lib/task-graph-properties-panel.component.d.ts +34 -8
- package/dist/lib/task-graph-properties-panel.component.d.ts.map +1 -1
- package/dist/lib/task-graph-properties-panel.component.js +164 -45
- package/dist/lib/task-graph-properties-panel.component.js.map +1 -1
- package/dist/lib/task-graph-run-view.component.d.ts +113 -0
- package/dist/lib/task-graph-run-view.component.d.ts.map +1 -0
- package/dist/lib/task-graph-run-view.component.js +289 -0
- package/dist/lib/task-graph-run-view.component.js.map +1 -0
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +9 -8
|
@@ -24,14 +24,16 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module @memberjunction/ng-task-graph-editor
|
|
26
26
|
*/
|
|
27
|
-
import { EventEmitter } from '@angular/core';
|
|
27
|
+
import { EventEmitter, OnDestroy } from '@angular/core';
|
|
28
28
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
29
29
|
import { type TaskGraphSpec, type TaskGraphSpecNode, type TaskGraphValidationError } from '@memberjunction/ai-core-plus';
|
|
30
|
-
import
|
|
30
|
+
import { FlowEditorComponent } from '@memberjunction/ng-flow-editor';
|
|
31
|
+
import type { FlowConnection, FlowConnectionCreatedEvent, FlowLayoutDirection, FlowNode, FlowNodeAddedEvent, FlowNodeMovedEvent, FlowNodeTypeConfig, FlowPosition } from '@memberjunction/ng-flow-editor';
|
|
31
32
|
import { type TaskGraphRuntimeStatus } from './task-graph-canvas-adapter';
|
|
33
|
+
import type { DependencyConditionChangeRequestedEventArgs, TaskPropertyChangeRequestedEventArgs } from './task-graph-properties-panel.component';
|
|
32
34
|
import { AfterDependencyAddedEventArgs, AfterDependencyRemovedEventArgs, AfterTaskAddedEventArgs, AfterTaskRemovedEventArgs, AfterTaskUpdatedEventArgs, AgentOpenRequestedEventArgs, BeforeDependencyAddedEventArgs, BeforeDependencyRemovedEventArgs, BeforeTaskAddedEventArgs, BeforeTaskRemovedEventArgs, BeforeTaskUpdatedEventArgs, RecordOpenRequestedEventArgs, TaskGraphSelectionChangedEventArgs, TaskGraphSpecChangedEventArgs, TaskGraphValidationChangedEventArgs } from './task-graph-editor-events';
|
|
33
35
|
import * as i0 from "@angular/core";
|
|
34
|
-
export declare class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
36
|
+
export declare class TaskGraphEditorComponent extends BaseAngularComponent implements OnDestroy {
|
|
35
37
|
/**
|
|
36
38
|
* The graph to show. Setter-based rather than `ngOnChanges` (repo convention): the reaction is
|
|
37
39
|
* explicit, runs only when this property changes, and costs nothing on other change-detection
|
|
@@ -46,6 +48,22 @@ export declare class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
46
48
|
*/
|
|
47
49
|
set RuntimeStatus(value: TaskGraphRuntimeStatus | null);
|
|
48
50
|
get RuntimeStatus(): TaskGraphRuntimeStatus | null;
|
|
51
|
+
/**
|
|
52
|
+
* Geometry for the nodes, keyed by `tempId`. Supplying it places the graph instead of laying it
|
|
53
|
+
* out.
|
|
54
|
+
*
|
|
55
|
+
* **Why a host must be able to supply this.** The spec has no layout field, so without it every
|
|
56
|
+
* node projects to the origin — all of them, stacked in one place — and the canvas is left to
|
|
57
|
+
* rescue the situation with a deferred Dagre pass. That pass is fine in the editor, where the
|
|
58
|
+
* author is present and can rearrange. It was not fine in the RUN views, which held the real
|
|
59
|
+
* geometry (a workflow's authored positions, or a computed layout) and had no way to hand it
|
|
60
|
+
* over: every run rendered its steps piled on the origin, and the zoom-to-fit that followed
|
|
61
|
+
* fitted a bounding box one node wide and blew the viewport up past 250%.
|
|
62
|
+
*
|
|
63
|
+
* Applied as the starting geometry only. Once the canvas reports positions of its own — a drag,
|
|
64
|
+
* an arrange — those win, because the person moving a node is the authority on where it goes.
|
|
65
|
+
*/
|
|
66
|
+
set NodePositions(value: ReadonlyMap<string, FlowPosition> | null);
|
|
49
67
|
/** Read-only mode. The same component is the viewer — there is no second, weaker renderer. */
|
|
50
68
|
ReadOnly: boolean;
|
|
51
69
|
ShowToolbar: boolean;
|
|
@@ -53,6 +71,22 @@ export declare class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
53
71
|
ShowMinimap: boolean;
|
|
54
72
|
ShowStatusBar: boolean;
|
|
55
73
|
AutoLayoutDirection: FlowLayoutDirection;
|
|
74
|
+
/**
|
|
75
|
+
* Whether the properties panel rides alongside the canvas.
|
|
76
|
+
*
|
|
77
|
+
* On by default, because without it a step added from the palette can never be named or
|
|
78
|
+
* assigned — the canvas draws structure, the panel supplies content, and one without the other
|
|
79
|
+
* is a graph the author can build but not finish. Hosts embedding the read-only viewer in a chat
|
|
80
|
+
* card turn it off.
|
|
81
|
+
*/
|
|
82
|
+
ShowProperties: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Agent names offered when assigning a step. Supplied by the host, which owns data access —
|
|
85
|
+
* this is a widgets-layer component and does not query.
|
|
86
|
+
*/
|
|
87
|
+
AvailableAgentNames: readonly string[];
|
|
88
|
+
/** Action names offered when assigning a step. Same ownership rule as `AvailableAgentNames`. */
|
|
89
|
+
AvailableActionNames: readonly string[];
|
|
56
90
|
/** Shown when there is nothing to draw yet. */
|
|
57
91
|
EmptyStateMessage: string;
|
|
58
92
|
BeforeTaskAdded: EventEmitter<BeforeTaskAddedEventArgs>;
|
|
@@ -78,6 +112,7 @@ export declare class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
78
112
|
SelectedTask: TaskGraphSpecNode | null;
|
|
79
113
|
ValidationErrors: readonly TaskGraphValidationError[];
|
|
80
114
|
IsValid: boolean;
|
|
115
|
+
protected canvas: FlowEditorComponent | undefined;
|
|
81
116
|
private currentSpec;
|
|
82
117
|
private currentRuntime;
|
|
83
118
|
get IsEmpty(): boolean;
|
|
@@ -113,6 +148,25 @@ export declare class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
113
148
|
/** Asks the host to open a record the graph references. */
|
|
114
149
|
RequestRecordOpen(entityName: string, recordID: string): void;
|
|
115
150
|
OnNodeSelected(node: FlowNode | null): void;
|
|
151
|
+
/**
|
|
152
|
+
* A palette entry was clicked or dragged onto the canvas.
|
|
153
|
+
*
|
|
154
|
+
* **This binding is the bug.** The canvas has always emitted `NodeAdded` for a palette drop, and
|
|
155
|
+
* this component simply never listened — so the node the canvas announced was thrown away, the
|
|
156
|
+
* spec never gained a task, and the author was told "a task graph must contain at least one
|
|
157
|
+
* task" no matter how many times they tried to add one. The canvas does not mutate its own
|
|
158
|
+
* `Nodes` on purpose (the host owns the model); an unheard event is therefore a silent no-op
|
|
159
|
+
* rather than a visible failure, which is why it survived.
|
|
160
|
+
*
|
|
161
|
+
* The new step is selected immediately: it lands unnamed and, for an agent or action step with
|
|
162
|
+
* nothing available to default to, unassigned — so the properties panel is where the author has
|
|
163
|
+
* to go next, and putting them there beats making them find it.
|
|
164
|
+
*/
|
|
165
|
+
OnNodeAdded(event: FlowNodeAddedEvent): void;
|
|
166
|
+
/** Applies a properties-panel edit through the same vetoable path a canvas edit takes. */
|
|
167
|
+
OnTaskPropertyChangeRequested(args: TaskPropertyChangeRequestedEventArgs): void;
|
|
168
|
+
/** Applies a properties-panel edge-condition edit. */
|
|
169
|
+
OnDependencyConditionChangeRequested(args: DependencyConditionChangeRequestedEventArgs): void;
|
|
116
170
|
OnConnectionCreated(event: FlowConnectionCreatedEvent): void;
|
|
117
171
|
OnConnectionRemoved(connection: FlowConnection): void;
|
|
118
172
|
OnNodeRemoved(node: FlowNode): void;
|
|
@@ -122,7 +176,55 @@ export declare class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
122
176
|
private commit;
|
|
123
177
|
/** Re-derives the canvas from the spec. Validation rides along so the two never disagree. */
|
|
124
178
|
private project;
|
|
179
|
+
/**
|
|
180
|
+
* Lays the graph out ONCE — when it arrives with no geometry of its own.
|
|
181
|
+
*
|
|
182
|
+
* A `TaskGraphSpec` carries no positions, so a spec opened for the first time projects with
|
|
183
|
+
* every node at the origin and needs Dagre to make it readable. After that the author's layout
|
|
184
|
+
* is the layout: `knownPositions` carries it across re-projections, and re-arranging again would
|
|
185
|
+
* throw away the arrangement they just made.
|
|
186
|
+
*
|
|
187
|
+
* It must also not run on every edit, because `AutoArrange` ends in `ZoomToFit` — so arranging
|
|
188
|
+
* per change meant the viewport snapped to fit after every added step and every drawn
|
|
189
|
+
* connection, which on a one-node graph zooms to maximum. That is the behaviour being fixed
|
|
190
|
+
* here; the rule mirrors the Flow Agent editor's (`flow-agent-editor.component.ts`), which has
|
|
191
|
+
* always arranged only when every node sits at the origin.
|
|
192
|
+
*/
|
|
193
|
+
private arrangeIfNeverLaidOut;
|
|
194
|
+
/**
|
|
195
|
+
* Fits the viewport to the graph, once the canvas has drawn it.
|
|
196
|
+
*
|
|
197
|
+
* Deferred for the same reason the layout pass is: `fitToScreen` measures the rendered nodes, so
|
|
198
|
+
* calling it in the same turn as the projection fits whatever was on screen a moment ago. With
|
|
199
|
+
* every node still at the origin that bounding box is a single node wide, and "fit" means zoom
|
|
200
|
+
* to ~265% — the symptom that made a four-step workflow look like one enormous box.
|
|
201
|
+
*/
|
|
202
|
+
private zoomToFitSoon;
|
|
203
|
+
/**
|
|
204
|
+
* The canvas is the authority on geometry, so remember what it reports.
|
|
205
|
+
*
|
|
206
|
+
* Without this the spec — which has no geometry field — is the only survivor of a re-projection,
|
|
207
|
+
* and every edit silently moved every node back to the origin. That is what forced a re-arrange
|
|
208
|
+
* (and therefore a re-zoom) on each change.
|
|
209
|
+
*/
|
|
210
|
+
OnNodesChanged(nodes: FlowNode[]): void;
|
|
211
|
+
/** A single node was dragged. Same authority, narrower event. */
|
|
212
|
+
OnNodeMoved(event: FlowNodeMovedEvent): void;
|
|
213
|
+
ngOnDestroy(): void;
|
|
214
|
+
/** The topology the current layout was computed for; '' when nothing has been laid out. */
|
|
215
|
+
/**
|
|
216
|
+
* Node geometry, which the spec cannot hold.
|
|
217
|
+
*
|
|
218
|
+
* `TaskGraphSpec` is an execution contract with no layout field, so a re-projection would
|
|
219
|
+
* otherwise return every node to the origin. Keyed by `tempId`; written from the canvas
|
|
220
|
+
* (`NodesChanged` / `NodeMoved`) and from the drop position of a newly added node, and read back
|
|
221
|
+
* by `SpecToNodes` on every projection.
|
|
222
|
+
*/
|
|
223
|
+
private readonly knownPositions;
|
|
224
|
+
/** Whether the one-time Dagre pass has run (or been made unnecessary by a hand-placed node). */
|
|
225
|
+
private hasLaidOut;
|
|
226
|
+
private pendingLayout;
|
|
125
227
|
static ɵfac: i0.ɵɵFactoryDeclaration<TaskGraphEditorComponent, never>;
|
|
126
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<TaskGraphEditorComponent, "mj-task-graph-editor", never, { "Spec": { "alias": "Spec"; "required": false; }; "RuntimeStatus": { "alias": "RuntimeStatus"; "required": false; }; "ReadOnly": { "alias": "ReadOnly"; "required": false; }; "ShowToolbar": { "alias": "ShowToolbar"; "required": false; }; "ShowPalette": { "alias": "ShowPalette"; "required": false; }; "ShowMinimap": { "alias": "ShowMinimap"; "required": false; }; "ShowStatusBar": { "alias": "ShowStatusBar"; "required": false; }; "AutoLayoutDirection": { "alias": "AutoLayoutDirection"; "required": false; }; "EmptyStateMessage": { "alias": "EmptyStateMessage"; "required": false; }; }, { "BeforeTaskAdded": "BeforeTaskAdded"; "AfterTaskAdded": "AfterTaskAdded"; "BeforeTaskRemoved": "BeforeTaskRemoved"; "AfterTaskRemoved": "AfterTaskRemoved"; "BeforeTaskUpdated": "BeforeTaskUpdated"; "AfterTaskUpdated": "AfterTaskUpdated"; "BeforeDependencyAdded": "BeforeDependencyAdded"; "AfterDependencyAdded": "AfterDependencyAdded"; "BeforeDependencyRemoved": "BeforeDependencyRemoved"; "AfterDependencyRemoved": "AfterDependencyRemoved"; "SpecChanged": "SpecChanged"; "SelectionChanged": "SelectionChanged"; "ValidationChanged": "ValidationChanged"; "AgentOpenRequested": "AgentOpenRequested"; "RecordOpenRequested": "RecordOpenRequested"; }, never, never, false, never>;
|
|
228
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<TaskGraphEditorComponent, "mj-task-graph-editor", never, { "Spec": { "alias": "Spec"; "required": false; }; "RuntimeStatus": { "alias": "RuntimeStatus"; "required": false; }; "NodePositions": { "alias": "NodePositions"; "required": false; }; "ReadOnly": { "alias": "ReadOnly"; "required": false; }; "ShowToolbar": { "alias": "ShowToolbar"; "required": false; }; "ShowPalette": { "alias": "ShowPalette"; "required": false; }; "ShowMinimap": { "alias": "ShowMinimap"; "required": false; }; "ShowStatusBar": { "alias": "ShowStatusBar"; "required": false; }; "AutoLayoutDirection": { "alias": "AutoLayoutDirection"; "required": false; }; "ShowProperties": { "alias": "ShowProperties"; "required": false; }; "AvailableAgentNames": { "alias": "AvailableAgentNames"; "required": false; }; "AvailableActionNames": { "alias": "AvailableActionNames"; "required": false; }; "EmptyStateMessage": { "alias": "EmptyStateMessage"; "required": false; }; }, { "BeforeTaskAdded": "BeforeTaskAdded"; "AfterTaskAdded": "AfterTaskAdded"; "BeforeTaskRemoved": "BeforeTaskRemoved"; "AfterTaskRemoved": "AfterTaskRemoved"; "BeforeTaskUpdated": "BeforeTaskUpdated"; "AfterTaskUpdated": "AfterTaskUpdated"; "BeforeDependencyAdded": "BeforeDependencyAdded"; "AfterDependencyAdded": "AfterDependencyAdded"; "BeforeDependencyRemoved": "BeforeDependencyRemoved"; "AfterDependencyRemoved": "AfterDependencyRemoved"; "SpecChanged": "SpecChanged"; "SelectionChanged": "SelectionChanged"; "ValidationChanged": "ValidationChanged"; "AgentOpenRequested": "AgentOpenRequested"; "RecordOpenRequested": "RecordOpenRequested"; }, never, never, false, never>;
|
|
127
229
|
}
|
|
128
230
|
//# sourceMappingURL=task-graph-editor.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-graph-editor.component.d.ts","sourceRoot":"","sources":["../../src/lib/task-graph-editor.component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAa,YAAY,
|
|
1
|
+
{"version":3,"file":"task-graph-editor.component.d.ts","sourceRoot":"","sources":["../../src/lib/task-graph-editor.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,14 +24,16 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module @memberjunction/ng-task-graph-editor
|
|
26
26
|
*/
|
|
27
|
-
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
27
|
+
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
|
28
28
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
29
|
-
import { ValidateTaskGraphSpec, } from '@memberjunction/ai-core-plus';
|
|
30
|
-
import {
|
|
29
|
+
import { ConfigOf, ValidateTaskGraphSpec, } from '@memberjunction/ai-core-plus';
|
|
30
|
+
import { FlowEditorComponent } from '@memberjunction/ng-flow-editor';
|
|
31
|
+
import { AddDependency, AddTask, GetDependents, GetNodeTypeConfig, IsAuthorableNodeType, NewTaskFromNodeType, NextTempId, RemoveDependency, RemoveTask, SpecToConnections, SpecToNodes, TASK_GRAPH_NODE_TYPES, UpdateTask, WouldCreateCycle, } from './task-graph-canvas-adapter';
|
|
31
32
|
import { AfterDependencyAddedEventArgs, AfterDependencyRemovedEventArgs, AfterTaskAddedEventArgs, AfterTaskRemovedEventArgs, AfterTaskUpdatedEventArgs, AgentOpenRequestedEventArgs, BeforeDependencyAddedEventArgs, BeforeDependencyRemovedEventArgs, BeforeTaskAddedEventArgs, BeforeTaskRemovedEventArgs, BeforeTaskUpdatedEventArgs, RecordOpenRequestedEventArgs, TaskGraphSelectionChangedEventArgs, TaskGraphSpecChangedEventArgs, TaskGraphValidationChangedEventArgs, } from './task-graph-editor-events';
|
|
32
33
|
import * as i0 from "@angular/core";
|
|
33
34
|
import * as i1 from "@memberjunction/ng-flow-editor";
|
|
34
35
|
import * as i2 from "@memberjunction/ng-ui-components";
|
|
36
|
+
import * as i3 from "./task-graph-properties-panel.component";
|
|
35
37
|
const _forTrack0 = ($index, $item) => $item.Code + ($item.TempId ?? "");
|
|
36
38
|
function TaskGraphEditorComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
37
39
|
i0.ɵɵelement(0, "mj-empty-state", 1);
|
|
@@ -39,7 +41,13 @@ function TaskGraphEditorComponent_Conditional_1_Template(rf, ctx) { if (rf & 1)
|
|
|
39
41
|
const ctx_r0 = i0.ɵɵnextContext();
|
|
40
42
|
i0.ɵɵproperty("Message", ctx_r0.EmptyStateMessage);
|
|
41
43
|
} }
|
|
42
|
-
function
|
|
44
|
+
function TaskGraphEditorComponent_Conditional_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
45
|
+
i0.ɵɵelement(0, "mj-alert", 2);
|
|
46
|
+
} if (rf & 2) {
|
|
47
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
48
|
+
i0.ɵɵproperty("Message", ctx_r0.EmptyStateMessage);
|
|
49
|
+
} }
|
|
50
|
+
function TaskGraphEditorComponent_Conditional_2_Conditional_1_For_5_Template(rf, ctx) { if (rf & 1) {
|
|
43
51
|
i0.ɵɵelementStart(0, "li");
|
|
44
52
|
i0.ɵɵtext(1);
|
|
45
53
|
i0.ɵɵelementEnd();
|
|
@@ -48,12 +56,12 @@ function TaskGraphEditorComponent_Conditional_2_Conditional_0_For_5_Template(rf,
|
|
|
48
56
|
i0.ɵɵadvance();
|
|
49
57
|
i0.ɵɵtextInterpolate(err_r3.Message);
|
|
50
58
|
} }
|
|
51
|
-
function
|
|
52
|
-
i0.ɵɵelementStart(0, "mj-alert",
|
|
59
|
+
function TaskGraphEditorComponent_Conditional_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
60
|
+
i0.ɵɵelementStart(0, "mj-alert", 3)(1, "strong");
|
|
53
61
|
i0.ɵɵtext(2);
|
|
54
62
|
i0.ɵɵelementEnd();
|
|
55
|
-
i0.ɵɵelementStart(3, "ul",
|
|
56
|
-
i0.ɵɵrepeaterCreate(4,
|
|
63
|
+
i0.ɵɵelementStart(3, "ul", 7);
|
|
64
|
+
i0.ɵɵrepeaterCreate(4, TaskGraphEditorComponent_Conditional_2_Conditional_1_For_5_Template, 2, 1, "li", null, _forTrack0);
|
|
57
65
|
i0.ɵɵelementEnd()();
|
|
58
66
|
} if (rf & 2) {
|
|
59
67
|
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
@@ -62,17 +70,33 @@ function TaskGraphEditorComponent_Conditional_2_Conditional_0_Template(rf, ctx)
|
|
|
62
70
|
i0.ɵɵadvance(2);
|
|
63
71
|
i0.ɵɵrepeater(ctx_r0.ValidationErrors);
|
|
64
72
|
} }
|
|
73
|
+
function TaskGraphEditorComponent_Conditional_2_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
74
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
75
|
+
i0.ɵɵelementStart(0, "mj-task-graph-properties", 8);
|
|
76
|
+
i0.ɵɵlistener("TaskPropertyChangeRequested", function TaskGraphEditorComponent_Conditional_2_Conditional_4_Template_mj_task_graph_properties_TaskPropertyChangeRequested_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnTaskPropertyChangeRequested($event)); })("DependencyConditionChangeRequested", function TaskGraphEditorComponent_Conditional_2_Conditional_4_Template_mj_task_graph_properties_DependencyConditionChangeRequested_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnDependencyConditionChangeRequested($event)); });
|
|
77
|
+
i0.ɵɵelementEnd();
|
|
78
|
+
} if (rf & 2) {
|
|
79
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
80
|
+
i0.ɵɵproperty("Task", ctx_r0.SelectedTask)("Spec", ctx_r0.Spec)("ReadOnly", ctx_r0.ReadOnly)("AvailableAgentNames", ctx_r0.AvailableAgentNames)("AvailableActionNames", ctx_r0.AvailableActionNames);
|
|
81
|
+
} }
|
|
65
82
|
function TaskGraphEditorComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
66
83
|
const _r2 = i0.ɵɵgetCurrentView();
|
|
67
|
-
i0.ɵɵconditionalCreate(0, TaskGraphEditorComponent_Conditional_2_Conditional_0_Template,
|
|
68
|
-
i0.ɵɵ
|
|
69
|
-
i0.ɵɵ
|
|
84
|
+
i0.ɵɵconditionalCreate(0, TaskGraphEditorComponent_Conditional_2_Conditional_0_Template, 1, 1, "mj-alert", 2);
|
|
85
|
+
i0.ɵɵconditionalCreate(1, TaskGraphEditorComponent_Conditional_2_Conditional_1_Template, 6, 2, "mj-alert", 3);
|
|
86
|
+
i0.ɵɵelementStart(2, "div", 4)(3, "mj-flow-editor", 5);
|
|
87
|
+
i0.ɵɵlistener("NodeSelected", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeSelected_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeSelected($event)); })("NodeAdded", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeAdded_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeAdded($event)); })("NodeRemoved", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeRemoved_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeRemoved($event)); })("NodesChanged", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodesChanged_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodesChanged($event)); })("NodeMoved", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeMoved_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeMoved($event)); })("ConnectionCreated", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_ConnectionCreated_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnConnectionCreated($event)); })("ConnectionRemoved", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_ConnectionRemoved_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnConnectionRemoved($event)); });
|
|
88
|
+
i0.ɵɵelementEnd();
|
|
89
|
+
i0.ɵɵconditionalCreate(4, TaskGraphEditorComponent_Conditional_2_Conditional_4_Template, 1, 5, "mj-task-graph-properties", 6);
|
|
70
90
|
i0.ɵɵelementEnd();
|
|
71
91
|
} if (rf & 2) {
|
|
72
92
|
const ctx_r0 = i0.ɵɵnextContext();
|
|
73
|
-
i0.ɵɵconditional(
|
|
93
|
+
i0.ɵɵconditional(ctx_r0.IsEmpty ? 0 : -1);
|
|
74
94
|
i0.ɵɵadvance();
|
|
95
|
+
i0.ɵɵconditional(!ctx_r0.IsValid && ctx_r0.ValidationErrors.length > 0 ? 1 : -1);
|
|
96
|
+
i0.ɵɵadvance(2);
|
|
75
97
|
i0.ɵɵproperty("Nodes", ctx_r0.Nodes)("Connections", ctx_r0.Connections)("NodeTypes", ctx_r0.NodeTypes)("ReadOnly", ctx_r0.ReadOnly)("ShowToolbar", ctx_r0.ShowToolbar)("ShowPalette", ctx_r0.ShowPalette && !ctx_r0.ReadOnly)("ShowMinimap", ctx_r0.ShowMinimap)("ShowStatusBar", ctx_r0.ShowStatusBar)("AutoLayoutDirection", ctx_r0.AutoLayoutDirection);
|
|
98
|
+
i0.ɵɵadvance();
|
|
99
|
+
i0.ɵɵconditional(ctx_r0.ShowProperties && !ctx_r0.ReadOnly ? 4 : -1);
|
|
76
100
|
} }
|
|
77
101
|
export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
78
102
|
// ── Inputs ───────────────────────────────────────────────────────────────
|
|
@@ -100,6 +124,31 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
100
124
|
get RuntimeStatus() {
|
|
101
125
|
return this.currentRuntime;
|
|
102
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Geometry for the nodes, keyed by `tempId`. Supplying it places the graph instead of laying it
|
|
129
|
+
* out.
|
|
130
|
+
*
|
|
131
|
+
* **Why a host must be able to supply this.** The spec has no layout field, so without it every
|
|
132
|
+
* node projects to the origin — all of them, stacked in one place — and the canvas is left to
|
|
133
|
+
* rescue the situation with a deferred Dagre pass. That pass is fine in the editor, where the
|
|
134
|
+
* author is present and can rearrange. It was not fine in the RUN views, which held the real
|
|
135
|
+
* geometry (a workflow's authored positions, or a computed layout) and had no way to hand it
|
|
136
|
+
* over: every run rendered its steps piled on the origin, and the zoom-to-fit that followed
|
|
137
|
+
* fitted a bounding box one node wide and blew the viewport up past 250%.
|
|
138
|
+
*
|
|
139
|
+
* Applied as the starting geometry only. Once the canvas reports positions of its own — a drag,
|
|
140
|
+
* an arrange — those win, because the person moving a node is the authority on where it goes.
|
|
141
|
+
*/
|
|
142
|
+
set NodePositions(value) {
|
|
143
|
+
if (!value || value.size === 0)
|
|
144
|
+
return;
|
|
145
|
+
for (const [id, position] of value)
|
|
146
|
+
this.knownPositions.set(id, { ...position });
|
|
147
|
+
// Real geometry means the one-time Dagre pass has nothing to rescue.
|
|
148
|
+
this.hasLaidOut = true;
|
|
149
|
+
this.project();
|
|
150
|
+
this.zoomToFitSoon();
|
|
151
|
+
}
|
|
103
152
|
/** Read-only mode. The same component is the viewer — there is no second, weaker renderer. */
|
|
104
153
|
ReadOnly = false;
|
|
105
154
|
ShowToolbar = true;
|
|
@@ -107,6 +156,22 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
107
156
|
ShowMinimap = true;
|
|
108
157
|
ShowStatusBar = true;
|
|
109
158
|
AutoLayoutDirection = 'vertical';
|
|
159
|
+
/**
|
|
160
|
+
* Whether the properties panel rides alongside the canvas.
|
|
161
|
+
*
|
|
162
|
+
* On by default, because without it a step added from the palette can never be named or
|
|
163
|
+
* assigned — the canvas draws structure, the panel supplies content, and one without the other
|
|
164
|
+
* is a graph the author can build but not finish. Hosts embedding the read-only viewer in a chat
|
|
165
|
+
* card turn it off.
|
|
166
|
+
*/
|
|
167
|
+
ShowProperties = true;
|
|
168
|
+
/**
|
|
169
|
+
* Agent names offered when assigning a step. Supplied by the host, which owns data access —
|
|
170
|
+
* this is a widgets-layer component and does not query.
|
|
171
|
+
*/
|
|
172
|
+
AvailableAgentNames = [];
|
|
173
|
+
/** Action names offered when assigning a step. Same ownership rule as `AvailableAgentNames`. */
|
|
174
|
+
AvailableActionNames = [];
|
|
110
175
|
/** Shown when there is nothing to draw yet. */
|
|
111
176
|
EmptyStateMessage = 'No steps yet. Add one to start building this workflow.';
|
|
112
177
|
// ── Outputs ──────────────────────────────────────────────────────────────
|
|
@@ -130,10 +195,11 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
130
195
|
// ── Rendered state ───────────────────────────────────────────────────────
|
|
131
196
|
Nodes = [];
|
|
132
197
|
Connections = [];
|
|
133
|
-
NodeTypes = TASK_GRAPH_NODE_TYPES;
|
|
198
|
+
NodeTypes = [...TASK_GRAPH_NODE_TYPES];
|
|
134
199
|
SelectedTask = null;
|
|
135
200
|
ValidationErrors = [];
|
|
136
201
|
IsValid = true;
|
|
202
|
+
canvas;
|
|
137
203
|
currentSpec = null;
|
|
138
204
|
currentRuntime = null;
|
|
139
205
|
get IsEmpty() {
|
|
@@ -160,13 +226,18 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
160
226
|
AddTask(partial = {}) {
|
|
161
227
|
if (this.ReadOnly || !this.currentSpec)
|
|
162
228
|
return null;
|
|
229
|
+
// Kind and configuration travel together — a partial that supplies one without the other
|
|
230
|
+
// would be a node the engine cannot run, so an unspecified partial defaults to an unassigned
|
|
231
|
+
// Agent step and the validator says so immediately.
|
|
163
232
|
const task = {
|
|
164
233
|
tempId: partial.tempId ?? NextTempId(this.currentSpec),
|
|
165
234
|
name: partial.name ?? 'New step',
|
|
166
235
|
description: partial.description ?? '',
|
|
167
|
-
|
|
168
|
-
|
|
236
|
+
kind: partial.kind ?? 'Agent',
|
|
237
|
+
configuration: partial.configuration ?? { agentName: '' },
|
|
169
238
|
dependsOn: partial.dependsOn ?? [],
|
|
239
|
+
policy: partial.policy,
|
|
240
|
+
layout: partial.layout,
|
|
170
241
|
inputPayload: partial.inputPayload,
|
|
171
242
|
};
|
|
172
243
|
const before = new BeforeTaskAddedEventArgs(task);
|
|
@@ -261,8 +332,9 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
261
332
|
}
|
|
262
333
|
/** Asks the host to open the agent behind a task. */
|
|
263
334
|
RequestAgentOpen(task) {
|
|
264
|
-
|
|
265
|
-
|
|
335
|
+
const agentName = ConfigOf(task, 'Agent')?.agentName;
|
|
336
|
+
if (agentName) {
|
|
337
|
+
this.AgentOpenRequested.emit(new AgentOpenRequestedEventArgs(agentName, task));
|
|
266
338
|
}
|
|
267
339
|
}
|
|
268
340
|
/** Asks the host to open a record the graph references. */
|
|
@@ -273,6 +345,51 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
273
345
|
OnNodeSelected(node) {
|
|
274
346
|
this.selectTask(node ? this.findTask(node.ID) : null);
|
|
275
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* A palette entry was clicked or dragged onto the canvas.
|
|
350
|
+
*
|
|
351
|
+
* **This binding is the bug.** The canvas has always emitted `NodeAdded` for a palette drop, and
|
|
352
|
+
* this component simply never listened — so the node the canvas announced was thrown away, the
|
|
353
|
+
* spec never gained a task, and the author was told "a task graph must contain at least one
|
|
354
|
+
* task" no matter how many times they tried to add one. The canvas does not mutate its own
|
|
355
|
+
* `Nodes` on purpose (the host owns the model); an unheard event is therefore a silent no-op
|
|
356
|
+
* rather than a visible failure, which is why it survived.
|
|
357
|
+
*
|
|
358
|
+
* The new step is selected immediately: it lands unnamed and, for an agent or action step with
|
|
359
|
+
* nothing available to default to, unassigned — so the properties panel is where the author has
|
|
360
|
+
* to go next, and putting them there beats making them find it.
|
|
361
|
+
*/
|
|
362
|
+
OnNodeAdded(event) {
|
|
363
|
+
if (this.ReadOnly || !this.currentSpec)
|
|
364
|
+
return;
|
|
365
|
+
const type = GetNodeTypeConfig(event.Node.Type)?.Type;
|
|
366
|
+
// Only an authorable shape can be dropped from the palette. The render set is wider, and a
|
|
367
|
+
// display-only kind arriving here would mean the palette offered something with no editor.
|
|
368
|
+
if (!type || !IsAuthorableNodeType(type))
|
|
369
|
+
return;
|
|
370
|
+
const added = this.AddTask(NewTaskFromNodeType(this.currentSpec, type, {
|
|
371
|
+
agentName: this.AvailableAgentNames[0],
|
|
372
|
+
actionName: this.AvailableActionNames[0],
|
|
373
|
+
}));
|
|
374
|
+
if (!added)
|
|
375
|
+
return;
|
|
376
|
+
// Remember where the canvas put it BEFORE anything re-projects. The spec has no geometry
|
|
377
|
+
// field, so this map is the only record that the author dropped (or clicked) it here — and
|
|
378
|
+
// without it the node would snap back to the origin on the very next edit.
|
|
379
|
+
this.knownPositions.set(added.tempId, { ...event.Node.Position });
|
|
380
|
+
// A graph that has received a hand-placed node is laid out, by definition. Marking it here
|
|
381
|
+
// stops the one-time Dagre pass from firing later and discarding that placement.
|
|
382
|
+
this.hasLaidOut = true;
|
|
383
|
+
this.selectTask(added);
|
|
384
|
+
}
|
|
385
|
+
/** Applies a properties-panel edit through the same vetoable path a canvas edit takes. */
|
|
386
|
+
OnTaskPropertyChangeRequested(args) {
|
|
387
|
+
this.UpdateTask(args.TempId, args.Next);
|
|
388
|
+
}
|
|
389
|
+
/** Applies a properties-panel edge-condition edit. */
|
|
390
|
+
OnDependencyConditionChangeRequested(args) {
|
|
391
|
+
this.SetDependencyCondition(args.FromTempId, args.ToTempId, args.Condition);
|
|
392
|
+
}
|
|
276
393
|
OnConnectionCreated(event) {
|
|
277
394
|
this.AddDependency(event.SourceNodeID, event.TargetNodeID);
|
|
278
395
|
}
|
|
@@ -303,29 +420,125 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
303
420
|
this.Connections = [];
|
|
304
421
|
this.ValidationErrors = [];
|
|
305
422
|
this.IsValid = true;
|
|
423
|
+
this.hasLaidOut = false;
|
|
424
|
+
this.knownPositions.clear();
|
|
306
425
|
return;
|
|
307
426
|
}
|
|
308
|
-
this.Nodes = SpecToNodes(this.currentSpec, this.currentRuntime ?? undefined);
|
|
427
|
+
this.Nodes = SpecToNodes(this.currentSpec, this.currentRuntime ?? undefined, this.knownPositions);
|
|
309
428
|
this.Connections = SpecToConnections(this.currentSpec);
|
|
310
429
|
this.Validate();
|
|
430
|
+
this.arrangeIfNeverLaidOut();
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Lays the graph out ONCE — when it arrives with no geometry of its own.
|
|
434
|
+
*
|
|
435
|
+
* A `TaskGraphSpec` carries no positions, so a spec opened for the first time projects with
|
|
436
|
+
* every node at the origin and needs Dagre to make it readable. After that the author's layout
|
|
437
|
+
* is the layout: `knownPositions` carries it across re-projections, and re-arranging again would
|
|
438
|
+
* throw away the arrangement they just made.
|
|
439
|
+
*
|
|
440
|
+
* It must also not run on every edit, because `AutoArrange` ends in `ZoomToFit` — so arranging
|
|
441
|
+
* per change meant the viewport snapped to fit after every added step and every drawn
|
|
442
|
+
* connection, which on a one-node graph zooms to maximum. That is the behaviour being fixed
|
|
443
|
+
* here; the rule mirrors the Flow Agent editor's (`flow-agent-editor.component.ts`), which has
|
|
444
|
+
* always arranged only when every node sits at the origin.
|
|
445
|
+
*/
|
|
446
|
+
arrangeIfNeverLaidOut() {
|
|
447
|
+
if (this.Nodes.length === 0)
|
|
448
|
+
return;
|
|
449
|
+
if (this.hasLaidOut)
|
|
450
|
+
return;
|
|
451
|
+
// Nothing to rescue a layout from: a spec whose nodes all sit at the origin has never been
|
|
452
|
+
// arranged. One node at the origin is the legitimate starting case too.
|
|
453
|
+
const allAtOrigin = this.Nodes.every((n) => n.Position.X === 0 && n.Position.Y === 0);
|
|
454
|
+
if (!allAtOrigin) {
|
|
455
|
+
this.hasLaidOut = true;
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.hasLaidOut = true;
|
|
459
|
+
// Deferred one turn: the canvas has to render the nodes before Dagre can measure them.
|
|
460
|
+
// Cleared on destroy so a pending layout cannot run against a torn-down view.
|
|
461
|
+
if (this.pendingLayout !== null)
|
|
462
|
+
clearTimeout(this.pendingLayout);
|
|
463
|
+
this.pendingLayout = setTimeout(() => {
|
|
464
|
+
this.pendingLayout = null;
|
|
465
|
+
this.canvas?.AutoArrange(this.AutoLayoutDirection);
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Fits the viewport to the graph, once the canvas has drawn it.
|
|
470
|
+
*
|
|
471
|
+
* Deferred for the same reason the layout pass is: `fitToScreen` measures the rendered nodes, so
|
|
472
|
+
* calling it in the same turn as the projection fits whatever was on screen a moment ago. With
|
|
473
|
+
* every node still at the origin that bounding box is a single node wide, and "fit" means zoom
|
|
474
|
+
* to ~265% — the symptom that made a four-step workflow look like one enormous box.
|
|
475
|
+
*/
|
|
476
|
+
zoomToFitSoon() {
|
|
477
|
+
if (this.pendingLayout !== null)
|
|
478
|
+
clearTimeout(this.pendingLayout);
|
|
479
|
+
this.pendingLayout = setTimeout(() => {
|
|
480
|
+
this.pendingLayout = null;
|
|
481
|
+
this.canvas?.ZoomToFit();
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* The canvas is the authority on geometry, so remember what it reports.
|
|
486
|
+
*
|
|
487
|
+
* Without this the spec — which has no geometry field — is the only survivor of a re-projection,
|
|
488
|
+
* and every edit silently moved every node back to the origin. That is what forced a re-arrange
|
|
489
|
+
* (and therefore a re-zoom) on each change.
|
|
490
|
+
*/
|
|
491
|
+
OnNodesChanged(nodes) {
|
|
492
|
+
for (const n of nodes)
|
|
493
|
+
this.knownPositions.set(n.ID, { ...n.Position });
|
|
494
|
+
}
|
|
495
|
+
/** A single node was dragged. Same authority, narrower event. */
|
|
496
|
+
OnNodeMoved(event) {
|
|
497
|
+
this.knownPositions.set(event.NodeID, { ...event.NewPosition });
|
|
498
|
+
}
|
|
499
|
+
ngOnDestroy() {
|
|
500
|
+
if (this.pendingLayout !== null) {
|
|
501
|
+
clearTimeout(this.pendingLayout);
|
|
502
|
+
this.pendingLayout = null;
|
|
503
|
+
}
|
|
311
504
|
}
|
|
505
|
+
/** The topology the current layout was computed for; '' when nothing has been laid out. */
|
|
506
|
+
/**
|
|
507
|
+
* Node geometry, which the spec cannot hold.
|
|
508
|
+
*
|
|
509
|
+
* `TaskGraphSpec` is an execution contract with no layout field, so a re-projection would
|
|
510
|
+
* otherwise return every node to the origin. Keyed by `tempId`; written from the canvas
|
|
511
|
+
* (`NodesChanged` / `NodeMoved`) and from the drop position of a newly added node, and read back
|
|
512
|
+
* by `SpecToNodes` on every projection.
|
|
513
|
+
*/
|
|
514
|
+
knownPositions = new Map();
|
|
515
|
+
/** Whether the one-time Dagre pass has run (or been made unnecessary by a hand-placed node). */
|
|
516
|
+
hasLaidOut = false;
|
|
517
|
+
pendingLayout = null;
|
|
312
518
|
static ɵfac = /*@__PURE__*/ (() => { let ɵTaskGraphEditorComponent_BaseFactory; return function TaskGraphEditorComponent_Factory(__ngFactoryType__) { return (ɵTaskGraphEditorComponent_BaseFactory || (ɵTaskGraphEditorComponent_BaseFactory = i0.ɵɵgetInheritedFactory(TaskGraphEditorComponent)))(__ngFactoryType__ || TaskGraphEditorComponent); }; })();
|
|
313
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TaskGraphEditorComponent, selectors: [["mj-task-graph-editor"]],
|
|
519
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TaskGraphEditorComponent, selectors: [["mj-task-graph-editor"]], viewQuery: function TaskGraphEditorComponent_Query(rf, ctx) { if (rf & 1) {
|
|
520
|
+
i0.ɵɵviewQuery(FlowEditorComponent, 5);
|
|
521
|
+
} if (rf & 2) {
|
|
522
|
+
let _t;
|
|
523
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.canvas = _t.first);
|
|
524
|
+
} }, inputs: { Spec: "Spec", RuntimeStatus: "RuntimeStatus", NodePositions: "NodePositions", ReadOnly: "ReadOnly", ShowToolbar: "ShowToolbar", ShowPalette: "ShowPalette", ShowMinimap: "ShowMinimap", ShowStatusBar: "ShowStatusBar", AutoLayoutDirection: "AutoLayoutDirection", ShowProperties: "ShowProperties", AvailableAgentNames: "AvailableAgentNames", AvailableActionNames: "AvailableActionNames", EmptyStateMessage: "EmptyStateMessage" }, outputs: { BeforeTaskAdded: "BeforeTaskAdded", AfterTaskAdded: "AfterTaskAdded", BeforeTaskRemoved: "BeforeTaskRemoved", AfterTaskRemoved: "AfterTaskRemoved", BeforeTaskUpdated: "BeforeTaskUpdated", AfterTaskUpdated: "AfterTaskUpdated", BeforeDependencyAdded: "BeforeDependencyAdded", AfterDependencyAdded: "AfterDependencyAdded", BeforeDependencyRemoved: "BeforeDependencyRemoved", AfterDependencyRemoved: "AfterDependencyRemoved", SpecChanged: "SpecChanged", SelectionChanged: "SelectionChanged", ValidationChanged: "ValidationChanged", AgentOpenRequested: "AgentOpenRequested", RecordOpenRequested: "RecordOpenRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 1, consts: [[1, "mj-tge"], ["Icon", "fa-diagram-project", "Title", "Nothing here yet", 3, "Message"], ["Variant", "info", 1, "mj-tge__empty-hint", 3, "Message"], ["Variant", "warning", 1, "mj-tge__validation"], [1, "mj-tge__workspace"], [1, "mj-tge__canvas", 3, "NodeSelected", "NodeAdded", "NodeRemoved", "NodesChanged", "NodeMoved", "ConnectionCreated", "ConnectionRemoved", "Nodes", "Connections", "NodeTypes", "ReadOnly", "ShowToolbar", "ShowPalette", "ShowMinimap", "ShowStatusBar", "AutoLayoutDirection"], [1, "mj-tge__properties", 3, "Task", "Spec", "ReadOnly", "AvailableAgentNames", "AvailableActionNames"], [1, "mj-tge__validation-list"], [1, "mj-tge__properties", 3, "TaskPropertyChangeRequested", "DependencyConditionChangeRequested", "Task", "Spec", "ReadOnly", "AvailableAgentNames", "AvailableActionNames"]], template: function TaskGraphEditorComponent_Template(rf, ctx) { if (rf & 1) {
|
|
314
525
|
i0.ɵɵelementStart(0, "div", 0);
|
|
315
|
-
i0.ɵɵconditionalCreate(1, TaskGraphEditorComponent_Conditional_1_Template, 1, 1, "mj-empty-state", 1)(2, TaskGraphEditorComponent_Conditional_2_Template,
|
|
526
|
+
i0.ɵɵconditionalCreate(1, TaskGraphEditorComponent_Conditional_1_Template, 1, 1, "mj-empty-state", 1)(2, TaskGraphEditorComponent_Conditional_2_Template, 5, 12);
|
|
316
527
|
i0.ɵɵelementEnd();
|
|
317
528
|
} if (rf & 2) {
|
|
318
529
|
i0.ɵɵadvance();
|
|
319
|
-
i0.ɵɵconditional(ctx.IsEmpty ? 1 : 2);
|
|
320
|
-
} }, dependencies: [i1.FlowEditorComponent, i2.MJEmptyStateComponent, i2.MJAlertComponent], styles: ["\n\n.mj-tge[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-page);\n}\n\n.mj-tge__validation[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n margin: var(--mj-space-sm, 0.5rem);\n}\n\n.mj-tge__validation-list[_ngcontent-%COMP%] {\n margin: var(--mj-space-xs, 0.25rem) 0 0;\n padding-inline-start: 1.25rem;\n}\n\n\n\n.mj-tge__canvas[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n}"] });
|
|
530
|
+
i0.ɵɵconditional(ctx.IsEmpty && ctx.ReadOnly ? 1 : 2);
|
|
531
|
+
} }, dependencies: [i1.FlowEditorComponent, i2.MJEmptyStateComponent, i2.MJAlertComponent, i3.TaskGraphPropertiesPanelComponent], styles: ["\n\n.mj-tge[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-page);\n}\n\n.mj-tge__validation[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n margin: var(--mj-space-sm, 0.5rem);\n}\n\n.mj-tge__validation-list[_ngcontent-%COMP%] {\n margin: var(--mj-space-xs, 0.25rem) 0 0;\n padding-inline-start: 1.25rem;\n}\n\n\n\n.mj-tge__workspace[_ngcontent-%COMP%] {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n\n\n.mj-tge__canvas[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n}\n\n.mj-tge__properties[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n width: 300px;\n min-height: 0;\n overflow-y: auto;\n border-left: 1px solid var(--mj-border-default);\n}\n\n@media (max-width: 900px) {\n \n\n .mj-tge__workspace[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .mj-tge__properties[_ngcontent-%COMP%] {\n width: 100%;\n max-height: 40%;\n border-left: 0;\n border-top: 1px solid var(--mj-border-default);\n }\n}\n\n\n\n.mj-tge__empty-hint[_ngcontent-%COMP%] {\n margin: var(--mj-space-2) var(--mj-space-3) 0;\n}"] });
|
|
321
532
|
}
|
|
322
533
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TaskGraphEditorComponent, [{
|
|
323
534
|
type: Component,
|
|
324
|
-
args: [{ standalone: false, selector: 'mj-task-graph-editor', template: "<div class=\"mj-tge\">\n @if (IsEmpty) {\n <mj-empty-state\n Icon=\"fa-diagram-project\"\n Title=\"Nothing here yet\"\n [Message]=\"EmptyStateMessage\">\n </mj-empty-state>\n } @else {\n @if (!IsValid && ValidationErrors.length > 0) {\n <!-- Author-time validation comes from the SAME function the engine runs at submission, so a\n graph that looks fine here cannot be rejected later for a reason the canvas never showed. -->\n <mj-alert Variant=\"warning\" class=\"mj-tge__validation\">\n <strong>{{ ValidationErrors.length }} problem{{ ValidationErrors.length === 1 ? '' : 's' }} to fix</strong>\n <ul class=\"mj-tge__validation-list\">\n @for (err of ValidationErrors; track err.Code + (err.TempId ?? '')) {\n <li>{{ err.Message }}</li>\n }\n </ul>\n </mj-alert>\n }\n\n <mj-flow-editor\n
|
|
535
|
+
args: [{ standalone: false, selector: 'mj-task-graph-editor', template: "<div class=\"mj-tge\">\n <!-- An EDITABLE empty canvas must still render the editor, because the palette is the only way to\n add a step \u2014 showing a bare empty state instead made \"Add one to start building this workflow\"\n an instruction the user could not follow. The hint stays, as a banner above a usable canvas. -->\n @if (IsEmpty && ReadOnly) {\n <mj-empty-state\n Icon=\"fa-diagram-project\"\n Title=\"Nothing here yet\"\n [Message]=\"EmptyStateMessage\">\n </mj-empty-state>\n } @else {\n @if (IsEmpty) {\n <mj-alert Variant=\"info\" class=\"mj-tge__empty-hint\" [Message]=\"EmptyStateMessage\"></mj-alert>\n }\n @if (!IsValid && ValidationErrors.length > 0) {\n <!-- Author-time validation comes from the SAME function the engine runs at submission, so a\n graph that looks fine here cannot be rejected later for a reason the canvas never showed. -->\n <mj-alert Variant=\"warning\" class=\"mj-tge__validation\">\n <strong>{{ ValidationErrors.length }} problem{{ ValidationErrors.length === 1 ? '' : 's' }} to fix</strong>\n <ul class=\"mj-tge__validation-list\">\n @for (err of ValidationErrors; track err.Code + (err.TempId ?? '')) {\n <li>{{ err.Message }}</li>\n }\n </ul>\n </mj-alert>\n }\n\n <div class=\"mj-tge__workspace\">\n <mj-flow-editor\n class=\"mj-tge__canvas\"\n [Nodes]=\"Nodes\"\n [Connections]=\"Connections\"\n [NodeTypes]=\"NodeTypes\"\n [ReadOnly]=\"ReadOnly\"\n [ShowToolbar]=\"ShowToolbar\"\n [ShowPalette]=\"ShowPalette && !ReadOnly\"\n [ShowMinimap]=\"ShowMinimap\"\n [ShowStatusBar]=\"ShowStatusBar\"\n [AutoLayoutDirection]=\"AutoLayoutDirection\"\n (NodeSelected)=\"OnNodeSelected($event)\"\n (NodeAdded)=\"OnNodeAdded($event)\"\n (NodeRemoved)=\"OnNodeRemoved($event)\"\n (NodesChanged)=\"OnNodesChanged($event)\"\n (NodeMoved)=\"OnNodeMoved($event)\"\n (ConnectionCreated)=\"OnConnectionCreated($event)\"\n (ConnectionRemoved)=\"OnConnectionRemoved($event)\">\n </mj-flow-editor>\n\n <!-- The canvas draws structure; this supplies content. A step added from the palette arrives\n unnamed and (with nothing to default to) unassigned, so without this panel the author can\n create a step but never finish one. -->\n @if (ShowProperties && !ReadOnly) {\n <mj-task-graph-properties\n class=\"mj-tge__properties\"\n [Task]=\"SelectedTask\"\n [Spec]=\"Spec\"\n [ReadOnly]=\"ReadOnly\"\n [AvailableAgentNames]=\"AvailableAgentNames\"\n [AvailableActionNames]=\"AvailableActionNames\"\n (TaskPropertyChangeRequested)=\"OnTaskPropertyChangeRequested($event)\"\n (DependencyConditionChangeRequested)=\"OnDependencyConditionChangeRequested($event)\">\n </mj-task-graph-properties>\n }\n </div>\n }\n</div>\n", styles: ["/* Design tokens only \u2014 no hardcoded colors (see .claude/rules/design-tokens.md). */\n.mj-tge {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-page);\n}\n\n.mj-tge__validation {\n flex: 0 0 auto;\n margin: var(--mj-space-sm, 0.5rem);\n}\n\n.mj-tge__validation-list {\n margin: var(--mj-space-xs, 0.25rem) 0 0;\n padding-inline-start: 1.25rem;\n}\n\n/* Canvas + properties side by side; the row takes the remaining height. */\n.mj-tge__workspace {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The canvas takes the remaining width; min-width:0 is what lets it actually shrink inside flex. */\n.mj-tge__canvas {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n}\n\n.mj-tge__properties {\n flex: 0 0 auto;\n width: 300px;\n min-height: 0;\n overflow-y: auto;\n border-left: 1px solid var(--mj-border-default);\n}\n\n@media (max-width: 900px) {\n /* Below this the two side by side leave neither usable; the panel drops under the canvas. */\n .mj-tge__workspace {\n flex-direction: column;\n }\n\n .mj-tge__properties {\n width: 100%;\n max-height: 40%;\n border-left: 0;\n border-top: 1px solid var(--mj-border-default);\n }\n}\n\n/* Shown above a usable, empty canvas \u2014 the palette stays reachable so the hint's advice is followable. */\n.mj-tge__empty-hint {\n margin: var(--mj-space-2) var(--mj-space-3) 0;\n}\n"] }]
|
|
325
536
|
}], null, { Spec: [{
|
|
326
537
|
type: Input
|
|
327
538
|
}], RuntimeStatus: [{
|
|
328
539
|
type: Input
|
|
540
|
+
}], NodePositions: [{
|
|
541
|
+
type: Input
|
|
329
542
|
}], ReadOnly: [{
|
|
330
543
|
type: Input
|
|
331
544
|
}], ShowToolbar: [{
|
|
@@ -338,6 +551,12 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
338
551
|
type: Input
|
|
339
552
|
}], AutoLayoutDirection: [{
|
|
340
553
|
type: Input
|
|
554
|
+
}], ShowProperties: [{
|
|
555
|
+
type: Input
|
|
556
|
+
}], AvailableAgentNames: [{
|
|
557
|
+
type: Input
|
|
558
|
+
}], AvailableActionNames: [{
|
|
559
|
+
type: Input
|
|
341
560
|
}], EmptyStateMessage: [{
|
|
342
561
|
type: Input
|
|
343
562
|
}], BeforeTaskAdded: [{
|
|
@@ -370,6 +589,9 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
|
|
|
370
589
|
type: Output
|
|
371
590
|
}], RecordOpenRequested: [{
|
|
372
591
|
type: Output
|
|
592
|
+
}], canvas: [{
|
|
593
|
+
type: ViewChild,
|
|
594
|
+
args: [FlowEditorComponent]
|
|
373
595
|
}] }); })();
|
|
374
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TaskGraphEditorComponent, { className: "TaskGraphEditorComponent", filePath: "src/lib/task-graph-editor.component.ts", lineNumber:
|
|
596
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TaskGraphEditorComponent, { className: "TaskGraphEditorComponent", filePath: "src/lib/task-graph-editor.component.ts", lineNumber: 92 }); })();
|
|
375
597
|
//# sourceMappingURL=task-graph-editor.component.js.map
|