@parallelworks/ui 0.37.0 → 0.39.0
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/README.md +1 -1
- package/dist/ai/components/ChatInput.js +8 -23
- package/dist/ai/components/ChatSidebar.js +7 -4
- package/dist/ai/components/chatDraft.d.ts +7 -0
- package/dist/ai/components/chatDraft.js +66 -0
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.js +2 -2
- package/dist/ai/ui/sidebar.d.ts +6 -0
- package/dist/ai/ui/sidebar.js +15 -1
- package/dist/editing.d.ts +2 -0
- package/dist/editor/Monaco.js +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/editor/lintContext.js +15 -3
- package/dist/form/FieldWrapper.js +1 -0
- package/dist/form/Form.js +64 -23
- package/dist/form/Wizard/WizardContainer.js +66 -6
- package/dist/form/Wizard/WizardStepContent.js +23 -10
- package/dist/form/Wizard/wizardPages.d.ts +12 -0
- package/dist/form/Wizard/wizardPages.js +5 -0
- package/dist/form/formEditing.d.ts +25 -0
- package/dist/form/formEditing.js +20 -0
- package/dist/graph/DependencyGraph.d.ts +8 -1
- package/dist/graph/DependencyGraph.js +433 -204
- package/dist/graph/GraphEditor.d.ts +27 -0
- package/dist/graph/GraphEditor.js +1543 -0
- package/dist/graph/GraphEditorDialogs.d.ts +87 -0
- package/dist/graph/GraphEditorDialogs.js +1808 -0
- package/dist/graph/InputDialog.js +1568 -54
- package/dist/graph/InputsEditor.d.ts +12 -0
- package/dist/graph/InputsEditor.js +1594 -0
- package/dist/graph/JobSummary.d.ts +3 -0
- package/dist/graph/JobSummary.js +34 -21
- package/dist/graph/MatrixGroup.d.ts +2 -1
- package/dist/graph/MatrixGroup.js +10 -3
- package/dist/graph/ShortcutsButton.js +56 -0
- package/dist/graph/SuggestionInput.js +29 -0
- package/dist/graph/WorkflowSettingsDialog.d.ts +21 -0
- package/dist/graph/WorkflowSettingsDialog.js +670 -0
- package/dist/graph/editorApi.js +8 -0
- package/dist/graph/editorChrome.js +75 -0
- package/dist/graph/editorFields.js +535 -0
- package/dist/graph/editorPrimitives.js +95 -4
- package/dist/graph/editorRows.d.ts +58 -0
- package/dist/graph/editorRows.js +460 -0
- package/dist/graph/editorStrings.d.ts +1 -0
- package/dist/graph/editorStrings.js +10 -1
- package/dist/graph/expressionRefs.js +39 -0
- package/dist/graph/fieldProblems.js +111 -0
- package/dist/graph/graphEditorState.d.ts +191 -0
- package/dist/graph/graphEditorState.js +74 -0
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +6 -2
- package/dist/graph/inputLayout.js +337 -0
- package/dist/graph/inputPages.d.ts +33 -0
- package/dist/graph/inputPages.js +186 -0
- package/dist/graph/inputRefs.js +388 -0
- package/dist/graph/inputRows.d.ts +43 -0
- package/dist/graph/inputRows.js +65 -0
- package/dist/graph/refusalText.js +13 -0
- package/dist/graph/settingsViews.js +62 -0
- package/dist/graph/stepWith.d.ts +38 -0
- package/dist/graph/stepWith.js +536 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,61 @@
|
|
|
1
|
-
import "classnames";
|
|
2
|
-
import "react";
|
|
3
|
-
import "react/jsx-runtime";
|
|
1
|
+
import cx from "classnames";
|
|
2
|
+
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/graph/editorPrimitives.tsx
|
|
5
|
+
/** A small store for an editor's view state, so a change redraws only what reads it. */
|
|
6
|
+
function createStore(initial) {
|
|
7
|
+
let state = initial;
|
|
8
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
9
|
+
return {
|
|
10
|
+
get: () => state,
|
|
11
|
+
set(patch) {
|
|
12
|
+
state = {
|
|
13
|
+
...state,
|
|
14
|
+
...patch
|
|
15
|
+
};
|
|
16
|
+
for (const listener of listeners) listener();
|
|
17
|
+
},
|
|
18
|
+
subscribe(listener) {
|
|
19
|
+
listeners.add(listener);
|
|
20
|
+
return () => {
|
|
21
|
+
listeners.delete(listener);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function useStore(store, select) {
|
|
27
|
+
return useSyncExternalStore(store.subscribe, () => select(store.get()));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Gives `container` focus back once it has fallen to the page, as when the row, menu or dialog
|
|
31
|
+
* holding it goes away, so the editor's shortcuts, Cmd+Z among them, keep reaching it.
|
|
32
|
+
*/
|
|
33
|
+
function reclaimFocus(container) {
|
|
34
|
+
window.setTimeout(() => {
|
|
35
|
+
const active = document.activeElement;
|
|
36
|
+
if (container?.isConnected && (!active || active === document.body)) container.focus({ preventScroll: true });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** Reclaims focus for the editor's container each time its dialog closes. */
|
|
40
|
+
function useFocusAfterDialog(store, container) {
|
|
41
|
+
const latest = useRef(container);
|
|
42
|
+
latest.current = container;
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
let open = store.get().dialog !== null;
|
|
45
|
+
return store.subscribe(() => {
|
|
46
|
+
const now = store.get().dialog !== null;
|
|
47
|
+
if (open && !now) reclaimFocus(latest.current());
|
|
48
|
+
open = now;
|
|
49
|
+
});
|
|
50
|
+
}, [store]);
|
|
51
|
+
}
|
|
52
|
+
/** `openMenu` whose menus reclaim focus for the editor when they close. */
|
|
53
|
+
function returningFocus(openMenu, container) {
|
|
54
|
+
return (x, y, items, onClose, search) => openMenu(x, y, items, () => {
|
|
55
|
+
onClose?.();
|
|
56
|
+
reclaimFocus(container());
|
|
57
|
+
}, search);
|
|
58
|
+
}
|
|
5
59
|
var DRAG_THRESHOLD = 4;
|
|
6
60
|
function suppressNextClick() {
|
|
7
61
|
const swallow = (e) => {
|
|
@@ -55,5 +109,42 @@ function trackDrag(start, { onMove, onEnd, onCancel, escape: cancelOnEscape = fa
|
|
|
55
109
|
window.addEventListener("pointercancel", cancel);
|
|
56
110
|
if (cancelOnEscape) window.addEventListener("keydown", key, true);
|
|
57
111
|
}
|
|
112
|
+
function place(box) {
|
|
113
|
+
return {
|
|
114
|
+
left: box.left,
|
|
115
|
+
top: box.top,
|
|
116
|
+
width: box.right - box.left,
|
|
117
|
+
height: box.bottom - box.top
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** The box a shift + drag draws, in its layer's coordinates. */
|
|
121
|
+
function MarqueeBox({ box, className }) {
|
|
122
|
+
return /* @__PURE__ */ jsx("div", {
|
|
123
|
+
className: cx("absolute rounded-md border-2 border-dashed border-(--theme-element) bg-(--theme-element)/10", className),
|
|
124
|
+
style: place(box)
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
var OPENED = "[role=\"menu\"], [role=\"dialog\"], [role=\"listbox\"]";
|
|
128
|
+
/** Calls `clear` when a press lands outside the editor and everything it has open. */
|
|
129
|
+
function usePressOutside(container, clear) {
|
|
130
|
+
const latest = useRef({
|
|
131
|
+
container,
|
|
132
|
+
clear
|
|
133
|
+
});
|
|
134
|
+
latest.current = {
|
|
135
|
+
container,
|
|
136
|
+
clear
|
|
137
|
+
};
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
const onDown = (e) => {
|
|
140
|
+
const root = latest.current.container();
|
|
141
|
+
const target = e.target;
|
|
142
|
+
if (!root || !(target instanceof Element) || root.contains(target) || target.closest(OPENED) || target.querySelector(`:scope > :is(${OPENED})`)) return;
|
|
143
|
+
latest.current.clear();
|
|
144
|
+
};
|
|
145
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
146
|
+
return () => document.removeEventListener("pointerdown", onDown, true);
|
|
147
|
+
}, []);
|
|
148
|
+
}
|
|
58
149
|
//#endregion
|
|
59
|
-
export { trackDrag };
|
|
150
|
+
export { MarqueeBox, createStore, place, reclaimFocus, returningFocus, trackDrag, useFocusAfterDialog, usePressOutside, useStore };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type Point } from './editorPrimitives.js';
|
|
3
|
+
import { type EdgeSelection } from './graphEditorState.js';
|
|
4
|
+
/** Drop indicators, the connection line and the selection, drawn in the graph's own coordinates. */
|
|
5
|
+
export declare function GraphEditorCanvas(): import("react").JSX.Element | null;
|
|
6
|
+
/** A multi-job box's handle for moving all its jobs at once. */
|
|
7
|
+
export declare function BoxGrip({ jobs }: {
|
|
8
|
+
jobs: string[];
|
|
9
|
+
}): import("react").JSX.Element | null;
|
|
10
|
+
/** A job row made draggable, with its menu and matrix badge; shift-click selects it. */
|
|
11
|
+
export declare function EditableJobRow({ job, label, badge, children, }: {
|
|
12
|
+
job: string;
|
|
13
|
+
label: string;
|
|
14
|
+
badge?: boolean;
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* A node's connector circles, where the graph draws its connector dots. Drag from the
|
|
19
|
+
* right one to add jobs that depend on the node, from the left one to add what it needs.
|
|
20
|
+
*/
|
|
21
|
+
export declare function NodePorts({ jobs, left, right, drawLeft, drawRight, zIndex, }: {
|
|
22
|
+
jobs: string[];
|
|
23
|
+
left: Point;
|
|
24
|
+
right: Point;
|
|
25
|
+
drawLeft: boolean;
|
|
26
|
+
drawRight: boolean;
|
|
27
|
+
zIndex: number;
|
|
28
|
+
}): import("react").JSX.Element | null;
|
|
29
|
+
/** A step row made draggable within its job, with its menu. */
|
|
30
|
+
export declare function EditableStepRow({ job, index, label, children, }: {
|
|
31
|
+
job: string;
|
|
32
|
+
index: number;
|
|
33
|
+
label: string;
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
}): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
|
|
36
|
+
export declare function AddStepButton({ job }: {
|
|
37
|
+
job: string;
|
|
38
|
+
}): import("react").JSX.Element | null;
|
|
39
|
+
/** Connector hit area: a click selects the dependency, shift adds it, a right-click opens its menu. */
|
|
40
|
+
export declare function EdgeHitPath({ d, edge }: {
|
|
41
|
+
d: string;
|
|
42
|
+
edge: EdgeSelection;
|
|
43
|
+
}): import("react").JSX.Element | null;
|
|
44
|
+
/** A selected connector's two ends; dragging one onto another job or node moves that end there. */
|
|
45
|
+
export declare function EdgeEnds({ edge, start, end, zIndex, }: {
|
|
46
|
+
edge: EdgeSelection;
|
|
47
|
+
start: Point;
|
|
48
|
+
end: Point;
|
|
49
|
+
zIndex: number;
|
|
50
|
+
}): import("react").JSX.Element | null;
|
|
51
|
+
/** Shown in place of the graph when the workflow has no jobs yet. */
|
|
52
|
+
/** The canvas of a graph with no jobs, which takes the graph's keys, so undo and paste still work. */
|
|
53
|
+
export declare function EmptyGraphEditor({ containerRef, overlays, height, removeBorder, }: {
|
|
54
|
+
containerRef: (el: HTMLDivElement | null) => void;
|
|
55
|
+
overlays: ReactNode;
|
|
56
|
+
height: string;
|
|
57
|
+
removeBorder?: boolean | undefined;
|
|
58
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { BsThreeDots } from "../icons/bs/BsThreeDots.js";
|
|
2
|
+
import { FaPlus } from "../icons/fa/FaPlus.js";
|
|
3
|
+
import { MdDragHandle } from "../icons/md/MdDragHandle.js";
|
|
4
|
+
import { TbAlertTriangle } from "../icons/tb/TbAlertTriangle.js";
|
|
5
|
+
import { TOOLTIP_ID } from "../components/Tooltip.js";
|
|
6
|
+
import { MarqueeBox, place } from "./editorPrimitives.js";
|
|
7
|
+
import { EDITOR_HANDLE_CLASS, NO_SELECTION, labelsOf, problemsIn, sameEdge, sameStep, selectionBounds, useGraphEditor, useSelectedEdges, useUi } from "./graphEditorState.js";
|
|
8
|
+
import cx from "classnames";
|
|
9
|
+
import { useEffect, useRef } from "react";
|
|
10
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
//#region src/graph/editorRows.tsx
|
|
12
|
+
/** Drop indicators, the connection line and the selection, drawn in the graph's own coordinates. */
|
|
13
|
+
function GraphEditorCanvas() {
|
|
14
|
+
const api = useGraphEditor();
|
|
15
|
+
const drag = useUi(api, (state) => state.drag, null);
|
|
16
|
+
const selection = useUi(api, (state) => state.selection, NO_SELECTION);
|
|
17
|
+
const marquee = useUi(api, (state) => state.marquee, null);
|
|
18
|
+
if (!api || !drag && !marquee && selection.length === 0) return null;
|
|
19
|
+
const target = drag?.target ?? null;
|
|
20
|
+
const indicator = target?.kind === "slot" || target?.kind === "steps" && !target.around ? target.indicator : null;
|
|
21
|
+
const outline = target?.kind === "steps" && target.around ? target.indicator : null;
|
|
22
|
+
const refused = target?.kind === "slot" && target.blocked || target?.kind === "steps" && !!target.problem;
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
24
|
+
className: "pointer-events-none absolute inset-0",
|
|
25
|
+
style: { zIndex: 60 },
|
|
26
|
+
children: [
|
|
27
|
+
marquee && /* @__PURE__ */ jsx(MarqueeBox, { box: marquee }),
|
|
28
|
+
!marquee && drag?.payload.kind !== "jobs" && /* @__PURE__ */ jsx(SelectionBox, { api }),
|
|
29
|
+
indicator && /* @__PURE__ */ jsx("div", {
|
|
30
|
+
className: cx("absolute rounded-full", refused ? "bg-(--theme-error)" : "bg-(--theme-element)"),
|
|
31
|
+
style: place(indicator)
|
|
32
|
+
}),
|
|
33
|
+
outline && /* @__PURE__ */ jsx("div", {
|
|
34
|
+
className: cx("absolute rounded-md border-4", refused ? "border-(--theme-error)" : "border-(--theme-element)"),
|
|
35
|
+
style: {
|
|
36
|
+
left: outline.left - 8,
|
|
37
|
+
top: outline.top - 4,
|
|
38
|
+
width: outline.right - outline.left + 16,
|
|
39
|
+
height: outline.bottom - outline.top + 8
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
(target?.kind === "connect" || target?.kind === "merge") && /* @__PURE__ */ jsx("div", {
|
|
43
|
+
className: cx("absolute rounded-md border-4", (target.kind === "connect" ? target.edits : target.valid) ? "border-(--theme-element)" : "border-(--theme-error)"),
|
|
44
|
+
style: {
|
|
45
|
+
left: target.box.left - 8,
|
|
46
|
+
top: target.box.top - 4,
|
|
47
|
+
width: target.box.right - target.box.left + 16,
|
|
48
|
+
height: target.box.bottom - target.box.top + 8
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
drag?.payload.kind === "connect" && drag.origin && drag.content && /* @__PURE__ */ jsx("svg", {
|
|
52
|
+
"aria-hidden": "true",
|
|
53
|
+
overflow: "visible",
|
|
54
|
+
className: "absolute left-0 top-0",
|
|
55
|
+
children: /* @__PURE__ */ jsx("line", {
|
|
56
|
+
x1: drag.origin.x,
|
|
57
|
+
y1: drag.origin.y,
|
|
58
|
+
x2: drag.content.x,
|
|
59
|
+
y2: drag.content.y,
|
|
60
|
+
stroke: target?.kind === "connect" && !target.edits ? "var(--theme-error)" : "var(--theme-element)",
|
|
61
|
+
strokeWidth: 6,
|
|
62
|
+
strokeDasharray: "16 10"
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
]
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** One outline around the selected jobs, with a grip to move them all and circles to connect them. */
|
|
69
|
+
function SelectionBox({ api }) {
|
|
70
|
+
const t = api.t;
|
|
71
|
+
const grid = api.grid();
|
|
72
|
+
const jobs = api.selected();
|
|
73
|
+
const bounds = grid ? selectionBounds(grid.wrapper, jobs) : null;
|
|
74
|
+
if (!grid || !bounds) return null;
|
|
75
|
+
const anchor = grid.cols.flat(2).find((job) => jobs.includes(job));
|
|
76
|
+
const y = bounds.connectorY;
|
|
77
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
78
|
+
/* @__PURE__ */ jsx("div", {
|
|
79
|
+
className: "absolute rounded-xl border-2 border-dashed border-(--theme-element)",
|
|
80
|
+
style: place(bounds)
|
|
81
|
+
}),
|
|
82
|
+
/* @__PURE__ */ jsx(PortCircle, {
|
|
83
|
+
side: "in",
|
|
84
|
+
at: {
|
|
85
|
+
x: bounds.left,
|
|
86
|
+
y
|
|
87
|
+
},
|
|
88
|
+
draw: true,
|
|
89
|
+
jobs,
|
|
90
|
+
port: "selection",
|
|
91
|
+
hint: t.selectionInHint
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ jsx(PortCircle, {
|
|
94
|
+
side: "out",
|
|
95
|
+
at: {
|
|
96
|
+
x: bounds.right,
|
|
97
|
+
y
|
|
98
|
+
},
|
|
99
|
+
draw: true,
|
|
100
|
+
jobs,
|
|
101
|
+
port: "selection",
|
|
102
|
+
hint: t.selectionOutHint
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ jsx("button", {
|
|
105
|
+
type: "button",
|
|
106
|
+
"aria-label": t.moveSelectionHint,
|
|
107
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
108
|
+
"data-tooltip-content": t.moveSelectionHint,
|
|
109
|
+
className: cx(EDITOR_HANDLE_CLASS, "pointer-events-auto absolute flex h-8 w-8 cursor-grab items-center justify-center rounded-full border-4 border-(--theme-element) bg-(--theme-panel-bg)"),
|
|
110
|
+
style: {
|
|
111
|
+
left: bounds.left - 16,
|
|
112
|
+
top: bounds.top - 16
|
|
113
|
+
},
|
|
114
|
+
onPointerDown: (e) => api.startDrag(e, {
|
|
115
|
+
kind: "jobs",
|
|
116
|
+
jobs,
|
|
117
|
+
label: labelsOf(jobs),
|
|
118
|
+
...anchor ? { anchor } : {}
|
|
119
|
+
}),
|
|
120
|
+
children: /* @__PURE__ */ jsx(MdDragHandle, { className: "h-5 w-5" })
|
|
121
|
+
})
|
|
122
|
+
] });
|
|
123
|
+
}
|
|
124
|
+
/** A multi-job box's handle for moving all its jobs at once. */
|
|
125
|
+
function BoxGrip({ jobs }) {
|
|
126
|
+
const api = useGraphEditor();
|
|
127
|
+
if (!api) return null;
|
|
128
|
+
const t = api.t;
|
|
129
|
+
return /* @__PURE__ */ jsx("button", {
|
|
130
|
+
type: "button",
|
|
131
|
+
"aria-label": t.moveBoxHint,
|
|
132
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
133
|
+
"data-tooltip-content": t.moveBoxHint,
|
|
134
|
+
className: cx(EDITOR_HANDLE_CLASS, "absolute -left-[18px] -top-[18px] flex h-8 w-8 cursor-grab items-center justify-center rounded-full border-4 theme-border bg-(--theme-panel-bg)"),
|
|
135
|
+
onPointerDown: (e) => api.startDrag(e, {
|
|
136
|
+
kind: "jobs",
|
|
137
|
+
jobs,
|
|
138
|
+
label: labelsOf(jobs)
|
|
139
|
+
}),
|
|
140
|
+
children: /* @__PURE__ */ jsx(MdDragHandle, { className: "h-5 w-5" })
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
var onBadge = (target) => target instanceof Element && !!target.closest("[data-problem-badge]");
|
|
144
|
+
/** A step's problems, or a job's with its steps', listed on hover; a click shows the first one's line. */
|
|
145
|
+
function ProblemBadge({ api, job, step }) {
|
|
146
|
+
const t = api.t;
|
|
147
|
+
const mine = (problems) => step === void 0 ? problems.filter((problem) => problem.job === job) : problemsIn(problems, job, step);
|
|
148
|
+
const messages = useUi(api, (state) => mine(state.problems).map((problem) => problem.message).join("\n"), "");
|
|
149
|
+
const line = useUi(api, (state) => mine(state.problems)[0]?.line ?? 0, 0);
|
|
150
|
+
if (!messages) return null;
|
|
151
|
+
return /* @__PURE__ */ jsx("button", {
|
|
152
|
+
type: "button",
|
|
153
|
+
"data-problem-badge": "",
|
|
154
|
+
"aria-label": t.problemCount(messages.split("\n").length),
|
|
155
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
156
|
+
"data-tooltip-content": messages,
|
|
157
|
+
className: "cursor-pointer rounded p-0.5 text-(--theme-error) hover:bg-(--theme-muted-panel-bg)",
|
|
158
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
159
|
+
onClick: (e) => {
|
|
160
|
+
e.stopPropagation();
|
|
161
|
+
api.reveal({ line });
|
|
162
|
+
},
|
|
163
|
+
children: /* @__PURE__ */ jsx(TbAlertTriangle, { className: "h-[0.9em] w-[0.9em]" })
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/** What a job or step row does in the editor: drag, shift-click to select, a menu, its problems. */
|
|
167
|
+
function EditableRow({ api, selected, flagged, data, className, drag, toggle, reveal, openMenu, menuLabel, menuClassName, iconClassName, badges, children }) {
|
|
168
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
169
|
+
role: "none",
|
|
170
|
+
...data,
|
|
171
|
+
...selected ? { "data-selected": "" } : {},
|
|
172
|
+
className: cx(EDITOR_HANDLE_CLASS, "-mx-0.5 px-0.5", className, selected && "bg-(--theme-element)/15", flagged ? "ring-2 ring-(--theme-error)" : selected && "ring-2 ring-(--theme-element)"),
|
|
173
|
+
onPointerDown: (e) => {
|
|
174
|
+
if (e.shiftKey) e.preventDefault();
|
|
175
|
+
api.startDrag(e, drag());
|
|
176
|
+
},
|
|
177
|
+
onClickCapture: (e) => {
|
|
178
|
+
if (onBadge(e.target)) return;
|
|
179
|
+
if (e.shiftKey) {
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
e.stopPropagation();
|
|
182
|
+
if (!toggle()) return;
|
|
183
|
+
}
|
|
184
|
+
reveal();
|
|
185
|
+
},
|
|
186
|
+
onContextMenu: (e) => {
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
e.stopPropagation();
|
|
189
|
+
openMenu(e.clientX, e.clientY);
|
|
190
|
+
},
|
|
191
|
+
children: [
|
|
192
|
+
children,
|
|
193
|
+
badges,
|
|
194
|
+
/* @__PURE__ */ jsx("button", {
|
|
195
|
+
type: "button",
|
|
196
|
+
"aria-label": menuLabel,
|
|
197
|
+
className: cx("cursor-pointer rounded theme-muted-text opacity-0 transition-opacity hover:bg-(--theme-muted-panel-bg) focus-visible:opacity-100", menuClassName),
|
|
198
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
199
|
+
onClick: (e) => {
|
|
200
|
+
e.stopPropagation();
|
|
201
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
202
|
+
openMenu(r.right, r.bottom);
|
|
203
|
+
},
|
|
204
|
+
children: /* @__PURE__ */ jsx(BsThreeDots, { className: iconClassName })
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/** A job row made draggable, with its menu and matrix badge; shift-click selects it. */
|
|
210
|
+
function EditableJobRow({ job, label, badge = true, children }) {
|
|
211
|
+
const api = useGraphEditor();
|
|
212
|
+
const selected = useUi(api, (state) => state.selection.includes(job), false);
|
|
213
|
+
const flagged = useUi(api, (state) => problemsIn(state.problems, job).length > 0, false);
|
|
214
|
+
if (!api) return children;
|
|
215
|
+
const t = api.t;
|
|
216
|
+
return /* @__PURE__ */ jsx(EditableRow, {
|
|
217
|
+
api,
|
|
218
|
+
selected,
|
|
219
|
+
flagged,
|
|
220
|
+
data: { "data-dag-job": job },
|
|
221
|
+
className: "group/row relative flex cursor-grab items-center gap-x-2 rounded-md [&>button:first-child]:w-auto [&>button:first-child]:flex-1",
|
|
222
|
+
drag: () => {
|
|
223
|
+
const selection = api.selected();
|
|
224
|
+
return selection.length > 1 && selection.includes(job) ? {
|
|
225
|
+
kind: "jobs",
|
|
226
|
+
jobs: selection,
|
|
227
|
+
anchor: job,
|
|
228
|
+
label: labelsOf(selection)
|
|
229
|
+
} : {
|
|
230
|
+
kind: "jobs",
|
|
231
|
+
jobs: [job],
|
|
232
|
+
label
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
toggle: () => {
|
|
236
|
+
api.toggleSelected(job);
|
|
237
|
+
return api.store.get().selection.includes(job);
|
|
238
|
+
},
|
|
239
|
+
reveal: () => api.reveal({ job }),
|
|
240
|
+
openMenu: (x, y) => api.openJobMenu(x, y, job),
|
|
241
|
+
menuLabel: t.jobActions,
|
|
242
|
+
menuClassName: "p-1 group-hover/row:opacity-100",
|
|
243
|
+
iconClassName: "h-[1em] w-[1em]",
|
|
244
|
+
badges: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ProblemBadge, {
|
|
245
|
+
api,
|
|
246
|
+
job
|
|
247
|
+
}), badge && api.isMatrixJob(job) && /* @__PURE__ */ jsx("span", {
|
|
248
|
+
className: "rounded-full border-2 theme-border px-2 text-[1rem] theme-muted-text",
|
|
249
|
+
children: t.matrixBadge
|
|
250
|
+
})] }),
|
|
251
|
+
children
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/** One connector circle: dragging it connects `jobs`, dropping on it connects to them. */
|
|
255
|
+
function PortCircle({ side, at, draw, jobs, port, hint, zIndex }) {
|
|
256
|
+
const api = useGraphEditor();
|
|
257
|
+
const drag = useUi(api, (state) => state.drag, null);
|
|
258
|
+
if (!api) return null;
|
|
259
|
+
const dragging = drag?.payload.kind === "connect" && drag.payload.port === port && drag.payload.side === side;
|
|
260
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
261
|
+
role: "none",
|
|
262
|
+
"data-dag-port": side,
|
|
263
|
+
...port === "selection" ? { "data-dag-selection": "" } : { "data-dag-node": jobs[0] },
|
|
264
|
+
...drag ? {} : { "data-tooltip-id": TOOLTIP_ID },
|
|
265
|
+
"data-tooltip-content": hint,
|
|
266
|
+
className: cx(EDITOR_HANDLE_CLASS, "group/port pointer-events-auto absolute flex h-8 w-8 cursor-crosshair items-center justify-center"),
|
|
267
|
+
style: {
|
|
268
|
+
left: at.x - 16,
|
|
269
|
+
top: at.y - 16,
|
|
270
|
+
...zIndex === void 0 ? {} : { zIndex }
|
|
271
|
+
},
|
|
272
|
+
onPointerDown: (e) => api.startDrag(e, {
|
|
273
|
+
kind: "connect",
|
|
274
|
+
from: jobs,
|
|
275
|
+
side,
|
|
276
|
+
label: labelsOf(jobs),
|
|
277
|
+
port
|
|
278
|
+
}),
|
|
279
|
+
children: [draw && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsx("span", { className: cx("relative h-4 w-4 rounded-full group-hover/port:bg-(--theme-element)", dragging ? "bg-(--theme-element)" : draw && "bg-(--theme-border)") })]
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* A node's connector circles, where the graph draws its connector dots. Drag from the
|
|
284
|
+
* right one to add jobs that depend on the node, from the left one to add what it needs.
|
|
285
|
+
*/
|
|
286
|
+
function NodePorts({ jobs, left, right, drawLeft, drawRight, zIndex }) {
|
|
287
|
+
const api = useGraphEditor();
|
|
288
|
+
if (!api) return null;
|
|
289
|
+
const t = api.t;
|
|
290
|
+
const port = `node:${jobs[0]}`;
|
|
291
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PortCircle, {
|
|
292
|
+
side: "in",
|
|
293
|
+
at: left,
|
|
294
|
+
draw: drawLeft,
|
|
295
|
+
jobs,
|
|
296
|
+
port,
|
|
297
|
+
hint: t.connectInHint,
|
|
298
|
+
zIndex
|
|
299
|
+
}), /* @__PURE__ */ jsx(PortCircle, {
|
|
300
|
+
side: "out",
|
|
301
|
+
at: right,
|
|
302
|
+
draw: drawRight,
|
|
303
|
+
jobs,
|
|
304
|
+
port,
|
|
305
|
+
hint: t.connectOutHint,
|
|
306
|
+
zIndex
|
|
307
|
+
})] });
|
|
308
|
+
}
|
|
309
|
+
/** A step row made draggable within its job, with its menu. */
|
|
310
|
+
function EditableStepRow({ job, index, label, children }) {
|
|
311
|
+
const api = useGraphEditor();
|
|
312
|
+
const selected = useUi(api, (state) => state.steps.some((ref) => ref.job === job && ref.index === index), false);
|
|
313
|
+
const flagged = useUi(api, (state) => problemsIn(state.problems, job, index).length > 0, false);
|
|
314
|
+
if (!api) return children;
|
|
315
|
+
const t = api.t;
|
|
316
|
+
return /* @__PURE__ */ jsx(EditableRow, {
|
|
317
|
+
api,
|
|
318
|
+
selected,
|
|
319
|
+
flagged,
|
|
320
|
+
data: {
|
|
321
|
+
"data-dag-step-job": job,
|
|
322
|
+
"data-dag-step": index
|
|
323
|
+
},
|
|
324
|
+
className: "group/step flex cursor-grab items-center gap-x-1 rounded",
|
|
325
|
+
drag: () => {
|
|
326
|
+
const steps = api.selectedSteps();
|
|
327
|
+
return selected && steps.length > 1 ? {
|
|
328
|
+
kind: "steps",
|
|
329
|
+
steps,
|
|
330
|
+
label: t.stepCount(steps.length)
|
|
331
|
+
} : {
|
|
332
|
+
kind: "steps",
|
|
333
|
+
steps: [{
|
|
334
|
+
job,
|
|
335
|
+
index
|
|
336
|
+
}],
|
|
337
|
+
label
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
toggle: () => {
|
|
341
|
+
api.toggleStep({
|
|
342
|
+
job,
|
|
343
|
+
index
|
|
344
|
+
});
|
|
345
|
+
return api.store.get().steps.some((ref) => sameStep(ref, {
|
|
346
|
+
job,
|
|
347
|
+
index
|
|
348
|
+
}));
|
|
349
|
+
},
|
|
350
|
+
reveal: () => api.reveal({
|
|
351
|
+
job,
|
|
352
|
+
step: index
|
|
353
|
+
}),
|
|
354
|
+
openMenu: (x, y) => api.openStepMenu(x, y, job, index),
|
|
355
|
+
menuLabel: t.stepActions,
|
|
356
|
+
menuClassName: "p-0.5 group-hover/step:opacity-100",
|
|
357
|
+
iconClassName: "h-[0.9em] w-[0.9em]",
|
|
358
|
+
badges: /* @__PURE__ */ jsx(ProblemBadge, {
|
|
359
|
+
api,
|
|
360
|
+
job,
|
|
361
|
+
step: index
|
|
362
|
+
}),
|
|
363
|
+
children
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function AddStepButton({ job }) {
|
|
367
|
+
const api = useGraphEditor();
|
|
368
|
+
if (!api) return null;
|
|
369
|
+
const t = api.t;
|
|
370
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
371
|
+
type: "button",
|
|
372
|
+
className: "ml-1.5 flex cursor-pointer items-center gap-x-1.5 text-left theme-muted-text hover:text-(--theme-link)",
|
|
373
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
374
|
+
onClick: (e) => {
|
|
375
|
+
e.stopPropagation();
|
|
376
|
+
api.addStep(job);
|
|
377
|
+
},
|
|
378
|
+
children: [/* @__PURE__ */ jsx(FaPlus, { className: "h-[0.8em] w-[0.8em]" }), t.addStep]
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/** Connector hit area: a click selects the dependency, shift adds it, a right-click opens its menu. */
|
|
382
|
+
function EdgeHitPath({ d, edge }) {
|
|
383
|
+
const api = useGraphEditor();
|
|
384
|
+
const selected = useSelectedEdges(api).some((other) => sameEdge(other, edge));
|
|
385
|
+
if (!api) return null;
|
|
386
|
+
const t = api.t;
|
|
387
|
+
return /* @__PURE__ */ jsx("path", {
|
|
388
|
+
role: "button",
|
|
389
|
+
tabIndex: 0,
|
|
390
|
+
"aria-label": t.dependencyOf(edge.from, edge.to),
|
|
391
|
+
"aria-pressed": selected,
|
|
392
|
+
"data-dag-edge-from": edge.from,
|
|
393
|
+
"data-dag-edge-to": edge.to,
|
|
394
|
+
d,
|
|
395
|
+
stroke: "transparent",
|
|
396
|
+
strokeWidth: 28,
|
|
397
|
+
fill: "none",
|
|
398
|
+
className: cx(EDITOR_HANDLE_CLASS, "outline-none focus-visible:[stroke:color-mix(in_srgb,var(--theme-element)_35%,transparent)]"),
|
|
399
|
+
style: {
|
|
400
|
+
pointerEvents: "stroke",
|
|
401
|
+
cursor: "pointer"
|
|
402
|
+
},
|
|
403
|
+
onClick: (e) => api.selectEdge(edge, e.shiftKey),
|
|
404
|
+
onKeyDown: (e) => {
|
|
405
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
406
|
+
e.preventDefault();
|
|
407
|
+
api.selectEdge(edge, e.shiftKey);
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
onContextMenu: (e) => {
|
|
411
|
+
e.preventDefault();
|
|
412
|
+
api.openEdgeMenu(e.clientX, e.clientY, edge);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
/** A selected connector's two ends; dragging one onto another job or node moves that end there. */
|
|
417
|
+
function EdgeEnds({ edge, start, end, zIndex }) {
|
|
418
|
+
const api = useGraphEditor();
|
|
419
|
+
const drag = useUi(api, (state) => state.drag, null);
|
|
420
|
+
if (!api) return null;
|
|
421
|
+
const t = api.t;
|
|
422
|
+
const inUse = api.edgeInUse(edge);
|
|
423
|
+
const handle = (side, at, fixed) => /* @__PURE__ */ jsx("span", {
|
|
424
|
+
role: "none",
|
|
425
|
+
"data-dag-edge-end": side,
|
|
426
|
+
...drag ? {} : { "data-tooltip-id": TOOLTIP_ID },
|
|
427
|
+
"data-tooltip-content": inUse ? t.dependencyInUse : t.moveDependencyEnd,
|
|
428
|
+
className: cx(EDITOR_HANDLE_CLASS, "pointer-events-auto absolute flex h-8 w-8 items-center justify-center", inUse ? "cursor-not-allowed" : "cursor-grab"),
|
|
429
|
+
style: {
|
|
430
|
+
left: at.x - 16,
|
|
431
|
+
top: at.y - 16,
|
|
432
|
+
zIndex
|
|
433
|
+
},
|
|
434
|
+
onPointerDown: (e) => {
|
|
435
|
+
if (!inUse) api.startEdgeEnd(e, edge, side, fixed);
|
|
436
|
+
},
|
|
437
|
+
children: /* @__PURE__ */ jsx("span", { className: "h-5 w-5 rounded-full border-4 border-(--theme-element) bg-(--theme-panel-bg)" })
|
|
438
|
+
}, side);
|
|
439
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [handle("from", start, end), handle("to", end, start)] });
|
|
440
|
+
}
|
|
441
|
+
/** Shown in place of the graph when the workflow has no jobs yet. */
|
|
442
|
+
/** The canvas of a graph with no jobs, which takes the graph's keys, so undo and paste still work. */
|
|
443
|
+
function EmptyGraphEditor({ containerRef, overlays, height, removeBorder }) {
|
|
444
|
+
const own = useRef(null);
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
if (document.activeElement === document.body) own.current?.focus({ preventScroll: true });
|
|
447
|
+
}, []);
|
|
448
|
+
return /* @__PURE__ */ jsx("div", {
|
|
449
|
+
ref: (el) => {
|
|
450
|
+
own.current = el;
|
|
451
|
+
containerRef(el);
|
|
452
|
+
},
|
|
453
|
+
tabIndex: -1,
|
|
454
|
+
className: cx("panel relative w-full overflow-hidden outline-none", removeBorder ? "border-none" : "border"),
|
|
455
|
+
style: { height },
|
|
456
|
+
children: overlays
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
//#endregion
|
|
460
|
+
export { AddStepButton, BoxGrip, EdgeEnds, EdgeHitPath, EditableJobRow, EditableStepRow, EmptyGraphEditor, GraphEditorCanvas, NodePorts };
|
|
@@ -243,6 +243,7 @@ var GRAPH_EDITOR_STRINGS = {
|
|
|
243
243
|
otherInputs: "Other inputs",
|
|
244
244
|
viewForm: "Form",
|
|
245
245
|
viewYaml: "YAML",
|
|
246
|
+
fixFieldsFirst: "Fix the fields with errors first, so the YAML shows everything you entered.",
|
|
246
247
|
yamlSyntax: (message) => `The YAML doesn’t parse: ${message}`,
|
|
247
248
|
yamlNotMap: "Write the settings as keys and values, such as run: make.",
|
|
248
249
|
fields: {
|
|
@@ -695,5 +696,13 @@ function useGraphEditorStrings() {
|
|
|
695
696
|
...overrides
|
|
696
697
|
}), [overrides]);
|
|
697
698
|
}
|
|
699
|
+
/** The input form editor's strings: the host's translations over the English defaults. */
|
|
700
|
+
function useInputsEditorStrings() {
|
|
701
|
+
const overrides = useStrings().inputsEditor;
|
|
702
|
+
return useMemo(() => ({
|
|
703
|
+
...INPUTS_EDITOR_STRINGS,
|
|
704
|
+
...overrides
|
|
705
|
+
}), [overrides]);
|
|
706
|
+
}
|
|
698
707
|
//#endregion
|
|
699
|
-
export { GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS, useGraphEditorStrings };
|
|
708
|
+
export { GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS, useGraphEditorStrings, useInputsEditorStrings };
|