@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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { BiRedo } from "../icons/bi/BiRedo.js";
|
|
2
|
+
import { BiUndo } from "../icons/bi/BiUndo.js";
|
|
3
|
+
import { IoSettingsOutline } from "../icons/io5/IoSettingsOutline.js";
|
|
4
|
+
import { TOOLTIP_ID } from "../components/Tooltip.js";
|
|
5
|
+
import { IconButton } from "../components/IconButton.js";
|
|
6
|
+
import { useGraphEditorStrings } from "./editorStrings.js";
|
|
7
|
+
import { ProblemsButton } from "./ProblemsButton.js";
|
|
8
|
+
import { ShortcutsButton } from "./ShortcutsButton.js";
|
|
9
|
+
import cx from "classnames";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
//#region src/graph/editorChrome.tsx
|
|
12
|
+
/** What a drag carries, beside the pointer at `at` in its layer's coordinates. */
|
|
13
|
+
function DragLabel({ at, text, refused = false }) {
|
|
14
|
+
return /* @__PURE__ */ jsx("div", {
|
|
15
|
+
className: cx("pointer-events-none absolute z-30 rounded-md border bg-(--theme-panel-bg) px-2 py-1 text-xs shadow", refused ? "border-(--theme-error) text-(--theme-error)" : "theme-border"),
|
|
16
|
+
style: {
|
|
17
|
+
left: at.x + 12,
|
|
18
|
+
top: at.y + 12
|
|
19
|
+
},
|
|
20
|
+
children: text
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function BarDivider() {
|
|
24
|
+
return /* @__PURE__ */ jsx("div", { className: "mx-0.5 h-5 w-px bg-(--theme-border)" });
|
|
25
|
+
}
|
|
26
|
+
/** A toolbar chip that adds something: click to add it, or, given `onPointerDown`, drag it to where it goes. */
|
|
27
|
+
function AddChip({ icon, label, hint, className, onPointerDown, onClick }) {
|
|
28
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
29
|
+
type: "button",
|
|
30
|
+
className: cx("flex h-7 items-center gap-1 whitespace-nowrap rounded px-2 text-xs theme-hover", onPointerDown ? "cursor-grab" : "cursor-pointer", className),
|
|
31
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
32
|
+
"data-tooltip-content": hint,
|
|
33
|
+
onPointerDown,
|
|
34
|
+
onClick,
|
|
35
|
+
children: [icon, label]
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** The bar both editors draw: problems, undo and redo, the editor's own controls, settings, shortcuts. */
|
|
39
|
+
function EditorBar({ editor, groups, className, children }) {
|
|
40
|
+
const t = useGraphEditorStrings();
|
|
41
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
42
|
+
className: cx("flex max-w-full flex-wrap items-center justify-end gap-0.5 rounded-md border theme-border bg-(--theme-panel-bg) p-0.5", className),
|
|
43
|
+
children: [
|
|
44
|
+
!!editor.listedProblems?.length && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ProblemsButton, { problems: editor.listedProblems }), /* @__PURE__ */ jsx(BarDivider, {})] }),
|
|
45
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
46
|
+
icon: /* @__PURE__ */ jsx(BiUndo, { className: "h-4 w-4" }),
|
|
47
|
+
label: t.undo,
|
|
48
|
+
size: "sm",
|
|
49
|
+
variant: "ghost",
|
|
50
|
+
disabled: !editor.canUndo,
|
|
51
|
+
onClick: editor.onUndo
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
54
|
+
icon: /* @__PURE__ */ jsx(BiRedo, { className: "h-4 w-4" }),
|
|
55
|
+
label: t.redo,
|
|
56
|
+
size: "sm",
|
|
57
|
+
variant: "ghost",
|
|
58
|
+
disabled: !editor.canRedo,
|
|
59
|
+
onClick: editor.onRedo
|
|
60
|
+
}),
|
|
61
|
+
/* @__PURE__ */ jsx(BarDivider, {}),
|
|
62
|
+
children,
|
|
63
|
+
editor.onOpenSettings && /* @__PURE__ */ jsx(IconButton, {
|
|
64
|
+
icon: /* @__PURE__ */ jsx(IoSettingsOutline, { className: "h-4 w-4" }),
|
|
65
|
+
label: t.workflowSettings,
|
|
66
|
+
size: "sm",
|
|
67
|
+
variant: "ghost",
|
|
68
|
+
onClick: editor.onOpenSettings
|
|
69
|
+
}),
|
|
70
|
+
/* @__PURE__ */ jsx(ShortcutsButton, { groups })
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { AddChip, BarDivider, DragLabel, EditorBar };
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { FaPlus } from "../icons/fa/FaPlus.js";
|
|
2
|
+
import { IoMdClose } from "../icons/io/IoMdClose.js";
|
|
3
|
+
import { LuBraces } from "../icons/lu/LuBraces.js";
|
|
4
|
+
import { LuChevronRight } from "../icons/lu/LuChevronRight.js";
|
|
5
|
+
import { withPositionKeys } from "../components/keys.js";
|
|
6
|
+
import { TOOLTIP_ID } from "../components/Tooltip.js";
|
|
7
|
+
import { BareModal, modalPanelClasses } from "../components/BareModal.js";
|
|
8
|
+
import { Button } from "../components/Button.js";
|
|
9
|
+
import { ghostButtonClasses, primaryButtonClasses } from "../components/ghostButton.js";
|
|
10
|
+
import { FilterPill } from "../components/FilterPill.js";
|
|
11
|
+
import { IconButton } from "../components/IconButton.js";
|
|
12
|
+
import { Input, Textarea } from "../components/Input.js";
|
|
13
|
+
import { Toggle } from "../components/Toggle.js";
|
|
14
|
+
import { useGraphEditorStrings } from "./editorStrings.js";
|
|
15
|
+
import { asRecord } from "./records.js";
|
|
16
|
+
import { useFieldLint } from "./fieldProblems.js";
|
|
17
|
+
import { SuggestionInput } from "./SuggestionInput.js";
|
|
18
|
+
import cx from "classnames";
|
|
19
|
+
import { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
20
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
21
|
+
//#region src/graph/editorFields.tsx
|
|
22
|
+
var EXPRESSION = /^\$\{\{.*\}\}$/;
|
|
23
|
+
var ENV_KEY = /^[a-zA-Z0-9_]+$/;
|
|
24
|
+
var EMPTY_EXPRESSION = /^\$\{\{\s*\}\}$/;
|
|
25
|
+
var DURATION = /^(\d+)(d|h|m|s)?$/;
|
|
26
|
+
function withoutUndefined(value) {
|
|
27
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
|
|
28
|
+
}
|
|
29
|
+
function text(value) {
|
|
30
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
31
|
+
}
|
|
32
|
+
function isScalar(value) {
|
|
33
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
34
|
+
}
|
|
35
|
+
function parseScalar(value) {
|
|
36
|
+
if (/^-?\d+(\.\d+)?$/.test(value) && String(Number(value)) === value) return Number(value);
|
|
37
|
+
if (value === "true" || value === "false") return value === "true";
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function sameValue(a, b) {
|
|
41
|
+
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, i) => sameValue(item, b[i]));
|
|
42
|
+
if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
|
|
43
|
+
const ak = Object.keys(a);
|
|
44
|
+
const bk = Object.keys(b);
|
|
45
|
+
return ak.length === bk.length && ak.every((k) => sameValue(a[k], b[k]));
|
|
46
|
+
}
|
|
47
|
+
return a === b;
|
|
48
|
+
}
|
|
49
|
+
/** Keys whose value changed: undefined removes a key, anything else writes it. */
|
|
50
|
+
function diffPatch(original, next) {
|
|
51
|
+
const set = {};
|
|
52
|
+
const unset = [];
|
|
53
|
+
for (const [key, value] of Object.entries(next)) if (value === void 0) {
|
|
54
|
+
if (Object.hasOwn(original, key)) unset.push(key);
|
|
55
|
+
} else if (!sameValue(original[key], value)) set[key] = value;
|
|
56
|
+
return {
|
|
57
|
+
set,
|
|
58
|
+
unset
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function isEmptyPatch(patch) {
|
|
62
|
+
return Object.keys(patch.set).length === 0 && patch.unset.length === 0;
|
|
63
|
+
}
|
|
64
|
+
function conditionText(value) {
|
|
65
|
+
return typeof value === "boolean" ? String(value) : text(value);
|
|
66
|
+
}
|
|
67
|
+
function parseCondition(value) {
|
|
68
|
+
const trimmed = value.trim();
|
|
69
|
+
if (!trimmed) return;
|
|
70
|
+
if (trimmed === "true" || trimmed === "false") return trimmed === "true";
|
|
71
|
+
return trimmed;
|
|
72
|
+
}
|
|
73
|
+
function durationError(value, t, pattern = DURATION) {
|
|
74
|
+
const trimmed = value.trim();
|
|
75
|
+
return !trimmed || pattern.test(trimmed) || EXPRESSION.test(trimmed) ? void 0 : t.invalidDuration;
|
|
76
|
+
}
|
|
77
|
+
function expressionError(value, t) {
|
|
78
|
+
const trimmed = value.trim();
|
|
79
|
+
return !trimmed || EXPRESSION.test(trimmed) && !EMPTY_EXPRESSION.test(trimmed) ? void 0 : t.invalidExpressionValue;
|
|
80
|
+
}
|
|
81
|
+
function orUndefined(value) {
|
|
82
|
+
return value.trim() ? value.trim() : void 0;
|
|
83
|
+
}
|
|
84
|
+
function countError(value, min, message) {
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
if (!trimmed || EXPRESSION.test(trimmed)) return;
|
|
87
|
+
return /^\d+$/.test(trimmed) && Number(trimmed) >= min ? void 0 : message;
|
|
88
|
+
}
|
|
89
|
+
function parseCount(value) {
|
|
90
|
+
const trimmed = value.trim();
|
|
91
|
+
if (!trimmed) return;
|
|
92
|
+
return /^\d+$/.test(trimmed) ? Number(trimmed) : trimmed;
|
|
93
|
+
}
|
|
94
|
+
function parseJson(value) {
|
|
95
|
+
try {
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
value: JSON.parse(value)
|
|
99
|
+
};
|
|
100
|
+
} catch {
|
|
101
|
+
return { ok: false };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function unchangedValue(text, original, nested = false) {
|
|
105
|
+
return original !== void 0 && text === (nested ? JSON.stringify(original) : String(original));
|
|
106
|
+
}
|
|
107
|
+
/** Rows for a map; with `nested` false a non-scalar is shown as its JSON text and saved as a string. */
|
|
108
|
+
function rowsFrom(value, nested = true) {
|
|
109
|
+
return Object.entries(asRecord(value)).map(([key, item]) => isScalar(item) ? {
|
|
110
|
+
key,
|
|
111
|
+
value: String(item),
|
|
112
|
+
original: item
|
|
113
|
+
} : nested ? {
|
|
114
|
+
key,
|
|
115
|
+
value: JSON.stringify(item),
|
|
116
|
+
nested: true,
|
|
117
|
+
original: item
|
|
118
|
+
} : {
|
|
119
|
+
key,
|
|
120
|
+
value: JSON.stringify(item) ?? ""
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function rowsTo(rows, parse) {
|
|
124
|
+
if (rows.length === 0) return;
|
|
125
|
+
return Object.fromEntries(rows.map((row) => [row.key.trim(), unchangedValue(row.value, row.original, row.nested) ? row.original : row.nested ? parseJson(row.value).value : parse(row.value)]));
|
|
126
|
+
}
|
|
127
|
+
function rowsError(rows, keyPattern, keyMessage, t, valueError) {
|
|
128
|
+
const keys = rows.map((row) => row.key.trim());
|
|
129
|
+
for (const [i, row] of rows.entries()) {
|
|
130
|
+
const key = keys[i] ?? "";
|
|
131
|
+
if (!keyPattern.test(key)) return keyMessage;
|
|
132
|
+
if (keys.indexOf(key) !== i) return t.duplicateKey;
|
|
133
|
+
const error = row.nested ? parseJson(row.value).ok ? void 0 : t.invalidJson : valueError?.(row.value);
|
|
134
|
+
if (error) return error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function flagOf(value) {
|
|
138
|
+
return typeof value === "boolean" || typeof value === "string" ? value : void 0;
|
|
139
|
+
}
|
|
140
|
+
function flagError(value, t) {
|
|
141
|
+
return typeof value === "string" ? expressionError(value, t) : void 0;
|
|
142
|
+
}
|
|
143
|
+
function flagValue(checked, fallback, original) {
|
|
144
|
+
if (checked !== fallback) return checked;
|
|
145
|
+
return original === checked ? checked : void 0;
|
|
146
|
+
}
|
|
147
|
+
/** Whether each section of a dialog is open, kept while the dialog is up so switching views keeps it. */
|
|
148
|
+
var SectionMemory = createContext(null);
|
|
149
|
+
function useSectionMemory() {
|
|
150
|
+
return useState(() => /* @__PURE__ */ new Map())[0];
|
|
151
|
+
}
|
|
152
|
+
function Section({ title, description, open = false, alert = false, children }) {
|
|
153
|
+
const memory = useContext(SectionMemory);
|
|
154
|
+
const [initial] = useState(() => {
|
|
155
|
+
const decided = memory?.get(title) ?? open;
|
|
156
|
+
memory?.set(title, decided);
|
|
157
|
+
return decided;
|
|
158
|
+
});
|
|
159
|
+
const details = useRef(null);
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (alert && details.current && !details.current.open) details.current.open = true;
|
|
162
|
+
}, [alert]);
|
|
163
|
+
return /* @__PURE__ */ jsxs("details", {
|
|
164
|
+
ref: details,
|
|
165
|
+
open: initial,
|
|
166
|
+
onToggle: (e) => memory?.set(title, e.currentTarget.open),
|
|
167
|
+
className: "group border-t theme-border",
|
|
168
|
+
children: [/* @__PURE__ */ jsxs("summary", {
|
|
169
|
+
className: "flex cursor-pointer select-none list-none items-center gap-1.5 py-2.5 text-sm font-semibold [&::-webkit-details-marker]:hidden",
|
|
170
|
+
children: [/* @__PURE__ */ jsx(LuChevronRight, {
|
|
171
|
+
"aria-hidden": "true",
|
|
172
|
+
className: "h-3.5 w-3.5 shrink-0 theme-muted-text transition-transform group-open:rotate-90 motion-reduce:transition-none"
|
|
173
|
+
}), title]
|
|
174
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
175
|
+
className: "flex flex-col gap-3 pb-4",
|
|
176
|
+
children: [description && /* @__PURE__ */ jsx("div", {
|
|
177
|
+
className: "text-xs theme-muted-text",
|
|
178
|
+
children: description
|
|
179
|
+
}), children]
|
|
180
|
+
})]
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function FieldError({ message }) {
|
|
184
|
+
return message ? /* @__PURE__ */ jsx("div", {
|
|
185
|
+
className: "text-xs text-(--theme-error)",
|
|
186
|
+
children: message
|
|
187
|
+
}) : null;
|
|
188
|
+
}
|
|
189
|
+
/** The YAML key a field sets, beside its name; left out where the name already says it. */
|
|
190
|
+
function KeyHint({ label, yamlKey }) {
|
|
191
|
+
return label.trim().toLowerCase() === yamlKey.toLowerCase() ? null : /* @__PURE__ */ jsx("code", {
|
|
192
|
+
"aria-hidden": "true",
|
|
193
|
+
className: "font-mono text-xs theme-muted-text",
|
|
194
|
+
children: yamlKey
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/** Label props for an Input or Textarea that sets `yamlKey`. */
|
|
198
|
+
function labelled(label, yamlKey) {
|
|
199
|
+
return {
|
|
200
|
+
label,
|
|
201
|
+
labelHint: /* @__PURE__ */ jsx(KeyHint, {
|
|
202
|
+
label,
|
|
203
|
+
yamlKey
|
|
204
|
+
})
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** A field's name and help text, for controls without their own label; `actions` sit at its right. */
|
|
208
|
+
function FieldLabel({ label, yamlKey, description, htmlFor, descriptionId, actions }) {
|
|
209
|
+
const nameClass = "text-[0.8125rem] font-medium";
|
|
210
|
+
const text = /* @__PURE__ */ jsxs("div", {
|
|
211
|
+
className: "flex min-w-0 flex-col",
|
|
212
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
213
|
+
className: "flex flex-wrap items-baseline gap-x-1.5",
|
|
214
|
+
children: [htmlFor ? /* @__PURE__ */ jsx("label", {
|
|
215
|
+
htmlFor,
|
|
216
|
+
className: nameClass,
|
|
217
|
+
children: label
|
|
218
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
219
|
+
className: nameClass,
|
|
220
|
+
children: label
|
|
221
|
+
}), yamlKey && /* @__PURE__ */ jsx(KeyHint, {
|
|
222
|
+
label,
|
|
223
|
+
yamlKey
|
|
224
|
+
})]
|
|
225
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
226
|
+
id: descriptionId,
|
|
227
|
+
className: "text-xs theme-muted-text",
|
|
228
|
+
children: description
|
|
229
|
+
})]
|
|
230
|
+
});
|
|
231
|
+
return actions ? /* @__PURE__ */ jsxs("div", {
|
|
232
|
+
className: "flex items-start justify-between gap-3",
|
|
233
|
+
children: [text, /* @__PURE__ */ jsx("div", {
|
|
234
|
+
className: "flex shrink-0 items-center gap-1",
|
|
235
|
+
children: actions
|
|
236
|
+
})]
|
|
237
|
+
}) : text;
|
|
238
|
+
}
|
|
239
|
+
/** A field's label and description above the editor that sets it. */
|
|
240
|
+
function LabelledField({ children, ...label }) {
|
|
241
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
242
|
+
className: "flex flex-col gap-1.5",
|
|
243
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, { ...label }), children]
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Switches a field between its own control and a typed expression, where the schema takes one. Its
|
|
248
|
+
* name says what pressing it does, so it isn't also announced as pressed.
|
|
249
|
+
*/
|
|
250
|
+
function ExpressionToggle({ active, onChange }) {
|
|
251
|
+
const t = useGraphEditorStrings();
|
|
252
|
+
return /* @__PURE__ */ jsx(IconButton, {
|
|
253
|
+
icon: /* @__PURE__ */ jsx(LuBraces, { className: "h-4 w-4" }),
|
|
254
|
+
label: active ? t.stopExpression : t.useExpression,
|
|
255
|
+
size: "sm",
|
|
256
|
+
variant: "ghost",
|
|
257
|
+
active,
|
|
258
|
+
onClick: () => onChange(!active)
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function ToggleField({ label, yamlKey, description, checked, onChange, disabled }) {
|
|
262
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
263
|
+
className: "flex items-center justify-between gap-3",
|
|
264
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
265
|
+
label,
|
|
266
|
+
yamlKey,
|
|
267
|
+
description
|
|
268
|
+
}), /* @__PURE__ */ jsx(Toggle, {
|
|
269
|
+
checked,
|
|
270
|
+
onChange,
|
|
271
|
+
label,
|
|
272
|
+
...disabled ? { disabled } : {}
|
|
273
|
+
})]
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function ChoiceButtons({ options, value, onChange }) {
|
|
277
|
+
return /* @__PURE__ */ jsx("div", {
|
|
278
|
+
className: "flex flex-wrap gap-1",
|
|
279
|
+
children: options.map((option) => /* @__PURE__ */ jsx(FilterPill, {
|
|
280
|
+
active: value === option.value,
|
|
281
|
+
onClick: () => onChange(option.value),
|
|
282
|
+
children: option.label
|
|
283
|
+
}, option.value))
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function AddRowButton({ label, onClick }) {
|
|
287
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
|
|
288
|
+
type: "button",
|
|
289
|
+
variant: "ghost",
|
|
290
|
+
size: "sm",
|
|
291
|
+
fullWidth: false,
|
|
292
|
+
icon: /* @__PURE__ */ jsx(FaPlus, { className: "h-3 w-3" }),
|
|
293
|
+
iconPosition: "left",
|
|
294
|
+
onClick,
|
|
295
|
+
children: label
|
|
296
|
+
}) });
|
|
297
|
+
}
|
|
298
|
+
function KeyValueEditor({ rows, onChange, error, valuePlaceholder, keyPlaceholder, addLabel, valueSuggestions = [], lint = false }) {
|
|
299
|
+
const t = useGraphEditorStrings();
|
|
300
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
301
|
+
className: "flex flex-col gap-2",
|
|
302
|
+
children: [
|
|
303
|
+
withPositionKeys(rows).map(({ key, item: row }, i) => /* @__PURE__ */ jsx(KeyValueRow, {
|
|
304
|
+
row,
|
|
305
|
+
onChange: (next) => onChange(updateAt(rows, i, next)),
|
|
306
|
+
onRemove: () => onChange(removeAt(rows, i)),
|
|
307
|
+
keyPlaceholder: keyPlaceholder ?? t.key,
|
|
308
|
+
valuePlaceholder: valuePlaceholder ?? t.value,
|
|
309
|
+
valueSuggestions,
|
|
310
|
+
lint
|
|
311
|
+
}, key)),
|
|
312
|
+
/* @__PURE__ */ jsx(FieldError, { message: error }),
|
|
313
|
+
/* @__PURE__ */ jsx(AddRowButton, {
|
|
314
|
+
label: addLabel ?? t.addRow,
|
|
315
|
+
onClick: () => onChange([...rows, {
|
|
316
|
+
key: "",
|
|
317
|
+
value: ""
|
|
318
|
+
}])
|
|
319
|
+
})
|
|
320
|
+
]
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function KeyValueRow({ row, onChange, onRemove, keyPlaceholder, valuePlaceholder, valueSuggestions, lint: linted }) {
|
|
324
|
+
const t = useGraphEditorStrings();
|
|
325
|
+
const id = useId();
|
|
326
|
+
const lint = useFieldLint(linted ? row.key.trim() || void 0 : void 0, id, row.value, (value) => onChange({
|
|
327
|
+
...row,
|
|
328
|
+
value
|
|
329
|
+
}));
|
|
330
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
331
|
+
className: "flex flex-col gap-1.5",
|
|
332
|
+
children: [
|
|
333
|
+
/* @__PURE__ */ jsxs("div", {
|
|
334
|
+
className: "flex items-center gap-2",
|
|
335
|
+
children: [
|
|
336
|
+
/* @__PURE__ */ jsx("div", {
|
|
337
|
+
className: "w-2/5",
|
|
338
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
339
|
+
mono: true,
|
|
340
|
+
"aria-label": t.key,
|
|
341
|
+
placeholder: keyPlaceholder,
|
|
342
|
+
value: row.key,
|
|
343
|
+
onChange: (e) => onChange({
|
|
344
|
+
...row,
|
|
345
|
+
key: e.target.value
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
}),
|
|
349
|
+
/* @__PURE__ */ jsx("div", {
|
|
350
|
+
className: "flex-1",
|
|
351
|
+
title: row.nested ? t.jsonValue : void 0,
|
|
352
|
+
children: /* @__PURE__ */ jsx(SuggestionInput, {
|
|
353
|
+
id,
|
|
354
|
+
ariaLabel: t.value,
|
|
355
|
+
placeholder: valuePlaceholder,
|
|
356
|
+
value: row.value,
|
|
357
|
+
suggestions: valueSuggestions.map((value) => ({ value })),
|
|
358
|
+
onChange: (value) => onChange({
|
|
359
|
+
...row,
|
|
360
|
+
value
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
}),
|
|
364
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
365
|
+
icon: /* @__PURE__ */ jsx(IoMdClose, { className: "h-4 w-4" }),
|
|
366
|
+
label: t.removeRow,
|
|
367
|
+
variant: "ghost",
|
|
368
|
+
size: "sm",
|
|
369
|
+
onClick: onRemove
|
|
370
|
+
})
|
|
371
|
+
]
|
|
372
|
+
}),
|
|
373
|
+
/* @__PURE__ */ jsx(FieldError, { message: lint.message }),
|
|
374
|
+
lint.fixes
|
|
375
|
+
]
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function updateAt(items, index, item) {
|
|
379
|
+
return items.map((current, i) => i === index ? item : current);
|
|
380
|
+
}
|
|
381
|
+
function removeAt(items, index) {
|
|
382
|
+
return items.filter((_, i) => i !== index);
|
|
383
|
+
}
|
|
384
|
+
/** A list of single values, such as permissions or variable names. */
|
|
385
|
+
function StringListEditor({ label, values, onChange, addLabel, placeholder, error, suggestions = [] }) {
|
|
386
|
+
const t = useGraphEditorStrings();
|
|
387
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
388
|
+
className: "flex flex-col gap-2",
|
|
389
|
+
children: [
|
|
390
|
+
withPositionKeys(values).map(({ key, item: value }, i) => /* @__PURE__ */ jsxs("div", {
|
|
391
|
+
className: "flex items-center gap-2",
|
|
392
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
393
|
+
className: "flex-1",
|
|
394
|
+
children: /* @__PURE__ */ jsx(SuggestionInput, {
|
|
395
|
+
ariaLabel: t.listItem(label, i + 1),
|
|
396
|
+
value,
|
|
397
|
+
placeholder,
|
|
398
|
+
suggestions: suggestions.map((option) => ({ value: option })),
|
|
399
|
+
onChange: (next) => onChange(updateAt(values, i, next))
|
|
400
|
+
})
|
|
401
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
402
|
+
icon: /* @__PURE__ */ jsx(IoMdClose, { className: "h-4 w-4" }),
|
|
403
|
+
label: t.removeRow,
|
|
404
|
+
variant: "ghost",
|
|
405
|
+
size: "sm",
|
|
406
|
+
onClick: () => onChange(removeAt(values, i))
|
|
407
|
+
})]
|
|
408
|
+
}, key)),
|
|
409
|
+
/* @__PURE__ */ jsx(FieldError, { message: error }),
|
|
410
|
+
/* @__PURE__ */ jsx(AddRowButton, {
|
|
411
|
+
label: addLabel,
|
|
412
|
+
onClick: () => onChange([...values, ""])
|
|
413
|
+
})
|
|
414
|
+
]
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
function SuggestedInput({ label, yamlKey, description, value, onChange, suggestions, error, placeholder }) {
|
|
418
|
+
const id = useId();
|
|
419
|
+
const lint = useFieldLint(yamlKey, id, value, onChange);
|
|
420
|
+
return /* @__PURE__ */ jsxs(LabelledField, {
|
|
421
|
+
label,
|
|
422
|
+
yamlKey,
|
|
423
|
+
description: description ?? "",
|
|
424
|
+
htmlFor: id,
|
|
425
|
+
...description ? { descriptionId: `${id}-help` } : {},
|
|
426
|
+
children: [
|
|
427
|
+
/* @__PURE__ */ jsx(SuggestionInput, {
|
|
428
|
+
id,
|
|
429
|
+
"aria-describedby": description ? `${id}-help` : void 0,
|
|
430
|
+
value,
|
|
431
|
+
placeholder,
|
|
432
|
+
suggestions: suggestions.map((option) => ({ value: option })),
|
|
433
|
+
onChange
|
|
434
|
+
}),
|
|
435
|
+
/* @__PURE__ */ jsx(FieldError, { message: error ?? lint.message }),
|
|
436
|
+
lint.fixes
|
|
437
|
+
]
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/** A text area that starts one line tall and grows with its text. */
|
|
441
|
+
function useGrowingArea(value) {
|
|
442
|
+
const ref = useRef(null);
|
|
443
|
+
useLayoutEffect(() => {
|
|
444
|
+
const area = ref.current;
|
|
445
|
+
if (area) {
|
|
446
|
+
area.style.height = "auto";
|
|
447
|
+
area.style.height = `${area.scrollHeight + area.offsetHeight - area.clientHeight}px`;
|
|
448
|
+
}
|
|
449
|
+
}, [value]);
|
|
450
|
+
return ref;
|
|
451
|
+
}
|
|
452
|
+
var GROWING_AREA = "!min-h-0 !resize-none overflow-hidden";
|
|
453
|
+
function GrowingTextarea(props) {
|
|
454
|
+
const ref = useGrowingArea(props.value);
|
|
455
|
+
return /* @__PURE__ */ jsx(Textarea, {
|
|
456
|
+
...props,
|
|
457
|
+
ref,
|
|
458
|
+
rows: 1,
|
|
459
|
+
className: GROWING_AREA
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
/** The dialogs' "open when adding" switch, when the host keeps the setting. */
|
|
463
|
+
function openOnAddOf(editor) {
|
|
464
|
+
return editor?.onOpenOnAddChange ? {
|
|
465
|
+
open: editor.openOnAdd !== false,
|
|
466
|
+
onChange: editor.onOpenOnAddChange
|
|
467
|
+
} : void 0;
|
|
468
|
+
}
|
|
469
|
+
function OpenOnAddSwitch({ open, onChange }) {
|
|
470
|
+
const t = useGraphEditorStrings();
|
|
471
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
472
|
+
className: "flex cursor-pointer items-center gap-2 text-xs theme-muted-text",
|
|
473
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
474
|
+
"data-tooltip-content": t.openOnAddHelp,
|
|
475
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
476
|
+
type: "checkbox",
|
|
477
|
+
className: "h-4 w-4 cursor-pointer accent-(--theme-element)",
|
|
478
|
+
checked: open,
|
|
479
|
+
onChange: (e) => onChange(e.target.checked)
|
|
480
|
+
}), t.openOnAdd]
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
function DialogShell({ title, onClose, onSubmit, saveDisabled, dirty, locked = false, footerStart, headerEnd, openOnAdd, children }) {
|
|
484
|
+
const t = useGraphEditorStrings();
|
|
485
|
+
const submit = () => {
|
|
486
|
+
if (!saveDisabled) onSubmit();
|
|
487
|
+
};
|
|
488
|
+
return /* @__PURE__ */ jsx(BareModal, {
|
|
489
|
+
open: true,
|
|
490
|
+
onClose,
|
|
491
|
+
ariaLabel: title,
|
|
492
|
+
align: "center",
|
|
493
|
+
preventClose: dirty || locked,
|
|
494
|
+
className: cx(modalPanelClasses, "flex max-h-[85vh] w-full max-w-2xl flex-col"),
|
|
495
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
496
|
+
className: "flex min-h-0 flex-col",
|
|
497
|
+
children: [
|
|
498
|
+
/* @__PURE__ */ jsxs("div", {
|
|
499
|
+
className: "flex items-center gap-3 border-b theme-border px-5 py-4",
|
|
500
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
501
|
+
className: "flex-1 text-[1rem] font-semibold",
|
|
502
|
+
children: title
|
|
503
|
+
}), headerEnd]
|
|
504
|
+
}),
|
|
505
|
+
/* @__PURE__ */ jsx("div", {
|
|
506
|
+
className: "flex min-h-0 flex-col gap-3 overflow-y-auto px-5 py-4",
|
|
507
|
+
children
|
|
508
|
+
}),
|
|
509
|
+
/* @__PURE__ */ jsxs("div", {
|
|
510
|
+
className: "flex items-center gap-2 border-t theme-border px-5 py-3",
|
|
511
|
+
children: [
|
|
512
|
+
footerStart,
|
|
513
|
+
openOnAdd && /* @__PURE__ */ jsx(OpenOnAddSwitch, { ...openOnAdd }),
|
|
514
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
515
|
+
/* @__PURE__ */ jsx("button", {
|
|
516
|
+
type: "button",
|
|
517
|
+
className: ghostButtonClasses,
|
|
518
|
+
onClick: onClose,
|
|
519
|
+
children: t.cancel
|
|
520
|
+
}),
|
|
521
|
+
/* @__PURE__ */ jsx("button", {
|
|
522
|
+
type: "button",
|
|
523
|
+
disabled: saveDisabled,
|
|
524
|
+
onClick: submit,
|
|
525
|
+
className: cx(primaryButtonClasses, "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
526
|
+
children: t.save
|
|
527
|
+
})
|
|
528
|
+
]
|
|
529
|
+
})
|
|
530
|
+
]
|
|
531
|
+
})
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
//#endregion
|
|
535
|
+
export { AddRowButton, ChoiceButtons, DURATION, DialogShell, ENV_KEY, EXPRESSION, ExpressionToggle, FieldError, FieldLabel, GROWING_AREA, GrowingTextarea, KeyHint, KeyValueEditor, LabelledField, Section, SectionMemory, StringListEditor, SuggestedInput, ToggleField, asRecord, conditionText, countError, diffPatch, durationError, expressionError, flagError, flagOf, flagValue, isEmptyPatch, isScalar, labelled, openOnAddOf, orUndefined, parseCondition, parseCount, parseJson, parseScalar, removeAt, rowsError, rowsFrom, rowsTo, sameValue, text, unchangedValue, updateAt, useGrowingArea, useSectionMemory, withoutUndefined };
|