@parallelworks/ui 0.36.0 → 0.38.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 +16 -0
- package/dist/ai/agent/permissions.d.ts +2 -2
- package/dist/ai/agent/permissions.js +3 -1
- package/dist/ai/components/ChatSidebar.js +7 -4
- package/dist/ai/components/agent/PermissionPicker.js +1 -0
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.js +2 -2
- package/dist/ai/strings.d.ts +2 -0
- package/dist/ai/strings.js +2 -0
- package/dist/ai/ui/sidebar.d.ts +6 -0
- package/dist/ai/ui/sidebar.js +15 -1
- package/dist/components/Dropdown.d.ts +2 -0
- package/dist/components/Dropdown.js +7 -2
- package/dist/components/Input.d.ts +11 -6
- package/dist/components/Input.js +93 -74
- package/dist/components/Provider.d.ts +31 -0
- package/dist/components/Provider.js +50 -2
- package/dist/editing.d.ts +404 -0
- package/dist/editor/EditorField.js +1 -1
- package/dist/editor/Monaco.d.ts +15 -1
- package/dist/editor/Monaco.js +117 -13
- package/dist/editor/editorModel.d.ts +5 -0
- package/dist/editor/editorModel.js +23 -0
- package/dist/editor/index.d.ts +4 -0
- package/dist/editor/index.js +5 -1
- package/dist/editor/lintContext.d.ts +21 -0
- package/dist/editor/lintContext.js +136 -0
- package/dist/editor/lintOwner.d.ts +2 -0
- package/dist/editor/lintOwner.js +5 -0
- package/dist/editor/nestedText.d.ts +13 -0
- package/dist/editor/nestedText.js +15 -0
- package/dist/editor/reveal.d.ts +4 -0
- package/dist/editor/reveal.js +15 -0
- package/dist/editor/settingsYaml.d.ts +12 -0
- package/dist/editor/settingsYaml.js +41 -0
- package/dist/form/FieldWrapper.js +1 -0
- package/dist/form/Fields/DropdownField.js +1 -1
- package/dist/form/Fields/DurationField.js +1 -1
- package/dist/form/Form.js +65 -24
- 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/fieldRegistry.js +1 -1
- package/dist/form/formEditing.d.ts +25 -0
- package/dist/form/formEditing.js +20 -0
- package/dist/form/index.js +5 -5
- package/dist/graph/InputDialog.d.ts +80 -0
- package/dist/graph/InputDialog.js +2110 -0
- package/dist/graph/InputsEditor.d.ts +12 -0
- package/dist/graph/InputsEditor.js +1594 -0
- package/dist/graph/ProblemsButton.d.ts +9 -0
- package/dist/graph/ProblemsButton.js +61 -0
- package/dist/graph/ShortcutsButton.d.ts +15 -0
- package/dist/graph/ShortcutsButton.js +56 -0
- package/dist/graph/SuggestionInput.d.ts +18 -0
- package/dist/graph/SuggestionInput.js +29 -0
- package/dist/graph/ToolbarPopover.d.ts +8 -0
- package/dist/graph/ToolbarPopover.js +71 -0
- package/dist/graph/editorApi.d.ts +51 -0
- package/dist/graph/editorApi.js +8 -0
- package/dist/graph/editorChrome.d.ts +27 -0
- package/dist/graph/editorChrome.js +75 -0
- package/dist/graph/editorFields.d.ts +180 -0
- package/dist/graph/editorFields.js +394 -0
- package/dist/graph/editorPrimitives.d.ts +60 -0
- package/dist/graph/editorPrimitives.js +150 -0
- package/dist/graph/editorStrings.d.ts +695 -0
- package/dist/graph/editorStrings.js +707 -0
- package/dist/graph/expressionRefs.d.ts +12 -0
- package/dist/graph/fieldProblems.d.ts +32 -0
- package/dist/graph/fieldProblems.js +77 -0
- package/dist/graph/index.d.ts +7 -0
- package/dist/graph/index.js +6 -1
- package/dist/graph/inputLayout.d.ts +25 -0
- 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.d.ts +103 -0
- package/dist/graph/inputRefs.js +196 -0
- package/dist/graph/inputRows.d.ts +43 -0
- package/dist/graph/inputRows.js +65 -0
- package/dist/graph/records.d.ts +3 -0
- package/dist/graph/records.js +7 -0
- package/dist/graph/refusalText.d.ts +6 -0
- package/dist/graph/settingsViews.d.ts +26 -0
- package/dist/graph/settingsViews.js +62 -0
- package/dist/graph/usesInputs.d.ts +17 -0
- package/dist/graph/usesInputs.js +40 -0
- package/dist/icons/bi/BiRedo.js +16 -0
- package/dist/icons/bi/BiUndo.js +16 -0
- package/dist/icons/lu/LuBraces.js +27 -0
- package/dist/icons/lu/LuCode.js +27 -0
- package/dist/icons/lu/LuKeyboard.js +71 -0
- package/dist/icons/lu/LuNetwork.js +63 -0
- package/dist/icons/lu/LuTextCursorInput.js +45 -0
- package/dist/icons.d.ts +2 -2
- package/dist/icons.js +8 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/list/ActionToolbar.d.ts +2 -0
- package/dist/list/ActionToolbar.js +18 -5
- package/dist/list/RowContextMenu.d.ts +10 -1
- package/dist/list/RowContextMenu.js +185 -30
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WorkflowEditing } from '../editing.js';
|
|
2
|
+
import { type Json } from './editorFields.js';
|
|
3
|
+
export type RefGroup = 'inputs' | 'outputs' | 'matrix' | 'variables' | 'sessions' | 'env';
|
|
4
|
+
/** Something a `${{ }}` expression can read, as the parser names it. */
|
|
5
|
+
export interface ExpressionRef {
|
|
6
|
+
group: RefGroup;
|
|
7
|
+
/** The name the parser reads, such as needs.build.outputs.version. */
|
|
8
|
+
label: string;
|
|
9
|
+
expression: string;
|
|
10
|
+
}
|
|
11
|
+
/** Everything an expression in `job` can read; without a job, only what the whole workflow has. */
|
|
12
|
+
export declare function expressionRefs(editing: WorkflowEditing, workflow: Json | undefined, job?: string): ExpressionRef[];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { LintFix } from '../editing.js';
|
|
3
|
+
/** A linter problem inside what a dialog edits; `at` is its path from there, such as ['run']. */
|
|
4
|
+
export interface ScopedProblem {
|
|
5
|
+
at: (string | number)[];
|
|
6
|
+
message: string;
|
|
7
|
+
fix?: LintFix | undefined;
|
|
8
|
+
}
|
|
9
|
+
/** Hands a form's fields the problems at the keys they set. */
|
|
10
|
+
export declare function FieldProblems({ problems, children, }: {
|
|
11
|
+
problems: ScopedProblem[];
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
/** Where the fields inside sit, such as `ssh` for a remote host's fields. */
|
|
15
|
+
export declare function LintScope({ at, children }: {
|
|
16
|
+
at: string;
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
|
+
/** The problems at the field with id `id`, which sets `key`; the dialog's list then leads to it. */
|
|
20
|
+
export declare function useFieldProblems(key: string | undefined, id: string): ScopedProblem[];
|
|
21
|
+
/** A field's problems as one message, and the buttons that make their likely fixes. */
|
|
22
|
+
export declare function useFieldLint(key: string | undefined, id: string, value: string, onChange: (value: string) => void): {
|
|
23
|
+
message: string | undefined;
|
|
24
|
+
fixes: ReactNode;
|
|
25
|
+
};
|
|
26
|
+
export declare function FixButtons({ fixes, onFix }: {
|
|
27
|
+
fixes: LintFix[];
|
|
28
|
+
onFix: (fix: LintFix) => void;
|
|
29
|
+
}): import("react").JSX.Element | null;
|
|
30
|
+
export declare function ProblemList({ problems }: {
|
|
31
|
+
problems: ScopedProblem[];
|
|
32
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useWorkflowEditing } from "../components/Provider.js";
|
|
2
|
+
import { TbAlertTriangle } from "../icons/tb/TbAlertTriangle.js";
|
|
3
|
+
import { keyedByContent } from "../components/keys.js";
|
|
4
|
+
import { useGraphEditorStrings } from "./editorStrings.js";
|
|
5
|
+
import { createContext, useContext, useEffect } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/graph/fieldProblems.tsx
|
|
8
|
+
var FieldProblemsContext = createContext(null);
|
|
9
|
+
var keyOf = (at) => at.join(".");
|
|
10
|
+
var within = (problem, key) => keyOf(problem.at) === key || keyOf(problem.at).startsWith(`${key}.`);
|
|
11
|
+
var LintScopeContext = createContext("");
|
|
12
|
+
/** The problems at the field with id `id`, which sets `key`; the dialog's list then leads to it. */
|
|
13
|
+
function useFieldProblems(key, id) {
|
|
14
|
+
const context = useContext(FieldProblemsContext);
|
|
15
|
+
const scope = useContext(LintScopeContext);
|
|
16
|
+
const full = key === void 0 ? void 0 : scope ? `${scope}.${key}` : key;
|
|
17
|
+
const register = context?.register;
|
|
18
|
+
useEffect(() => full && register ? register(full, id) : void 0, [
|
|
19
|
+
full,
|
|
20
|
+
id,
|
|
21
|
+
register
|
|
22
|
+
]);
|
|
23
|
+
return full && context ? context.problems.filter((problem) => within(problem, full)) : [];
|
|
24
|
+
}
|
|
25
|
+
/** A field's problems as one message, and the buttons that make their likely fixes. */
|
|
26
|
+
function useFieldLint(key, id, value, onChange) {
|
|
27
|
+
const { replaceReference } = useWorkflowEditing();
|
|
28
|
+
const problems = useFieldProblems(key, id);
|
|
29
|
+
const fixes = problems.flatMap(({ fix }) => fix && !fix.key && replaceReference(value, fix.find, fix.replace) !== value ? [fix] : []);
|
|
30
|
+
return {
|
|
31
|
+
message: problems.length > 0 ? problems.map((problem) => problem.message).join(" ") : void 0,
|
|
32
|
+
fixes: /* @__PURE__ */ jsx(FixButtons, {
|
|
33
|
+
fixes,
|
|
34
|
+
onFix: (fix) => onChange(replaceReference(value, fix.find, fix.replace))
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function FixButtons({ fixes, onFix }) {
|
|
39
|
+
const t = useGraphEditorStrings();
|
|
40
|
+
if (fixes.length === 0) return null;
|
|
41
|
+
return /* @__PURE__ */ jsx("div", {
|
|
42
|
+
className: "flex flex-wrap gap-2",
|
|
43
|
+
children: fixes.map((fix) => /* @__PURE__ */ jsx("button", {
|
|
44
|
+
type: "button",
|
|
45
|
+
className: "cursor-pointer text-left text-xs font-medium text-(--theme-link) hover:underline",
|
|
46
|
+
onClick: () => onFix(fix),
|
|
47
|
+
children: t.applyFix(fix.replace)
|
|
48
|
+
}, `${fix.find}>${fix.replace}`))
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function reveal(id) {
|
|
52
|
+
const element = document.getElementById(id);
|
|
53
|
+
for (let section = element?.closest("details"); section; section = section.parentElement?.closest("details")) section.open = true;
|
|
54
|
+
element?.focus();
|
|
55
|
+
}
|
|
56
|
+
function ProblemList({ problems }) {
|
|
57
|
+
const fields = useContext(FieldProblemsContext)?.fields;
|
|
58
|
+
if (problems.length === 0) return null;
|
|
59
|
+
const fieldOf = (problem) => [...fields ?? []].filter(([key]) => within(problem, key)).sort(([a], [b]) => b.length - a.length)[0]?.[1];
|
|
60
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
61
|
+
className: "flex flex-col gap-1 text-sm text-(--theme-error)",
|
|
62
|
+
children: keyedByContent(problems, (problem) => `${problem.at.join(".")}:${problem.message}`).map(({ key, item: problem }) => {
|
|
63
|
+
const field = fieldOf(problem);
|
|
64
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
65
|
+
className: "flex gap-1.5",
|
|
66
|
+
children: [/* @__PURE__ */ jsx(TbAlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), field ? /* @__PURE__ */ jsx("button", {
|
|
67
|
+
type: "button",
|
|
68
|
+
className: "cursor-pointer text-left hover:underline",
|
|
69
|
+
onClick: () => reveal(field),
|
|
70
|
+
children: problem.message
|
|
71
|
+
}) : problem.message]
|
|
72
|
+
}, key);
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { FixButtons, ProblemList, useFieldLint, useFieldProblems };
|
package/dist/graph/index.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
export { DependencyGraphPreview, default as DependencyGraph, type ViewMode, } from './DependencyGraph.js';
|
|
2
|
+
export type { DependencyGraphEditor, EditorProblem, RevealTarget, } from './editorApi.js';
|
|
3
|
+
export { trackDrag } from './editorPrimitives.js';
|
|
4
|
+
export { GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS } from './editorStrings.js';
|
|
5
|
+
export { inputTypes, offeredInputKeys } from './InputDialog.js';
|
|
6
|
+
export { InputsFormEditor } from './InputsEditor.js';
|
|
7
|
+
export { type ListedProblem, ProblemsButton } from './ProblemsButton.js';
|
|
8
|
+
export type { SettingsView } from './settingsViews.js';
|
|
2
9
|
export type { RunLink } from './types.js';
|
package/dist/graph/index.js
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import DependencyGraph, { DependencyGraphPreview } from "./DependencyGraph.js";
|
|
2
|
-
|
|
2
|
+
import { trackDrag } from "./editorPrimitives.js";
|
|
3
|
+
import { GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS } from "./editorStrings.js";
|
|
4
|
+
import { inputTypes, offeredInputKeys } from "./InputDialog.js";
|
|
5
|
+
import { ProblemsButton } from "./ProblemsButton.js";
|
|
6
|
+
import { InputsFormEditor } from "./InputsEditor.js";
|
|
7
|
+
export { DependencyGraph, DependencyGraphPreview, GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS, InputsFormEditor, ProblemsButton, inputTypes, offeredInputKeys, trackDrag };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { GraphEdit, InputPath, WorkflowEditing } from '../editing.js';
|
|
2
|
+
import { type FormLayoutNode } from '../form/layout.js';
|
|
3
|
+
import { type Json } from './records.js';
|
|
4
|
+
interface Drop {
|
|
5
|
+
parent: InputPath;
|
|
6
|
+
index: number;
|
|
7
|
+
beside?: {
|
|
8
|
+
path: InputPath;
|
|
9
|
+
side: 'left' | 'right';
|
|
10
|
+
};
|
|
11
|
+
stack?: {
|
|
12
|
+
path: InputPath;
|
|
13
|
+
side: 'above' | 'below';
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
type Node = FormLayoutNode;
|
|
17
|
+
/** Whether a list of inputs is drawn as a wizard, one step at a time. */
|
|
18
|
+
export declare function isWizard(list: unknown): boolean;
|
|
19
|
+
export declare function layoutList(editing: WorkflowEditing, inputs: unknown, path: InputPath): Json;
|
|
20
|
+
export declare function inputLayout(list: Json): Node;
|
|
21
|
+
export declare function layoutEdit(editing: WorkflowEditing, inputs: unknown, parent: InputPath, layout: Node | undefined): GraphEdit;
|
|
22
|
+
export declare function dropLayoutEdits(editing: WorkflowEditing, inputs: unknown, moving: InputPath[], drop: Drop, move: GraphEdit): GraphEdit[];
|
|
23
|
+
export declare function resizeLayout(editing: WorkflowEditing, inputs: unknown, left: InputPath, right: InputPath, split: [number, number]): GraphEdit | undefined;
|
|
24
|
+
export declare function withInputLayouts(editing: WorkflowEditing, inputs: unknown, edit: GraphEdit): GraphEdit;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { inputWidth } from "../form/lib.js";
|
|
2
|
+
import { resolveFormLayout, responsiveLayoutValues } from "../form/layout.js";
|
|
3
|
+
import { asRecord } from "./records.js";
|
|
4
|
+
//#region src/graph/inputLayout.ts
|
|
5
|
+
var namesIn = (list) => Object.keys(list).filter((name) => !name.startsWith("$"));
|
|
6
|
+
var field = (name) => ({
|
|
7
|
+
type: "field",
|
|
8
|
+
field: name
|
|
9
|
+
});
|
|
10
|
+
var stack = (children) => ({
|
|
11
|
+
type: "stack",
|
|
12
|
+
children
|
|
13
|
+
});
|
|
14
|
+
var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
15
|
+
var legacyKeys = ["width", "anchor-below"];
|
|
16
|
+
/** Whether a list of inputs is drawn as a wizard, one step at a time. */
|
|
17
|
+
function isWizard(list) {
|
|
18
|
+
return asRecord(asRecord(asRecord(list)["$meta"])["wizard"])["mode"] === "wizard";
|
|
19
|
+
}
|
|
20
|
+
function layoutList(editing, inputs, path) {
|
|
21
|
+
let list = asRecord(inputs);
|
|
22
|
+
for (const name of path) {
|
|
23
|
+
const definition = asRecord(list[name]);
|
|
24
|
+
list = asRecord(definition[editing.inputChildrenKey(definition["type"]) ?? "options"]);
|
|
25
|
+
}
|
|
26
|
+
return list;
|
|
27
|
+
}
|
|
28
|
+
function inputLayout(list) {
|
|
29
|
+
const resolved = resolveFormLayout(asRecord(list["$meta"])["layout"], namesIn(list));
|
|
30
|
+
if (resolved.layout) {
|
|
31
|
+
const layout = structuredClone(resolved.layout);
|
|
32
|
+
return resolved.remaining.length ? stack([layout, ...resolved.remaining.map(field)]) : layout;
|
|
33
|
+
}
|
|
34
|
+
const columns = [];
|
|
35
|
+
const hidden = [];
|
|
36
|
+
for (const name of namesIn(list)) {
|
|
37
|
+
const definition = asRecord(list[name]);
|
|
38
|
+
if (definition["hidden"] === true || definition["ignore"] === true) {
|
|
39
|
+
hidden.push(field(name));
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const width = inputWidth(definition["width"]);
|
|
43
|
+
const last = columns.at(-1);
|
|
44
|
+
if (definition["anchor-below"] === true && last) {
|
|
45
|
+
last.members.push(member(name, width, last.width));
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
columns.push({
|
|
49
|
+
head: name,
|
|
50
|
+
width,
|
|
51
|
+
members: []
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const rows = [];
|
|
55
|
+
let row = [];
|
|
56
|
+
let shares = [];
|
|
57
|
+
const flush = () => {
|
|
58
|
+
if (row.length) rows.push(within(row.length === 1 ? row[0] ?? stack([]) : {
|
|
59
|
+
type: "grid",
|
|
60
|
+
columns: {
|
|
61
|
+
base: 1,
|
|
62
|
+
sm: shares
|
|
63
|
+
},
|
|
64
|
+
children: row
|
|
65
|
+
}, shares.reduce((a, b) => a + b, 0), false));
|
|
66
|
+
row = [];
|
|
67
|
+
shares = [];
|
|
68
|
+
};
|
|
69
|
+
for (const { head, width, members } of columns) {
|
|
70
|
+
const pixels = width?.endsWith("px") ? width : void 0;
|
|
71
|
+
const node = members.length ? stack([sized(field(head), pixels), ...members]) : sized(field(head), pixels);
|
|
72
|
+
if (pixels) {
|
|
73
|
+
flush();
|
|
74
|
+
rows.push(node);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const share = width ? Number.parseFloat(width) : 100;
|
|
78
|
+
if (shares.reduce((a, b) => a + b, 0) + share > 100 || row.length >= 12) flush();
|
|
79
|
+
row.push(node);
|
|
80
|
+
shares.push(share);
|
|
81
|
+
}
|
|
82
|
+
flush();
|
|
83
|
+
return stack([...rows, ...hidden]);
|
|
84
|
+
}
|
|
85
|
+
function within(node, share, wraps) {
|
|
86
|
+
if (share >= 99) return node;
|
|
87
|
+
const tracks = [Math.round(share * 100) / 100, Math.round((100 - share) * 100) / 100];
|
|
88
|
+
return {
|
|
89
|
+
type: "grid",
|
|
90
|
+
columns: wraps ? {
|
|
91
|
+
base: 1,
|
|
92
|
+
sm: tracks
|
|
93
|
+
} : tracks,
|
|
94
|
+
children: [node]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function member(name, own, column) {
|
|
98
|
+
if (column?.endsWith("px")) return sized(field(name), own?.endsWith("px") && Number.parseFloat(own) < Number.parseFloat(column) ? own : column);
|
|
99
|
+
if (!own || own.endsWith("px")) return sized(field(name), own);
|
|
100
|
+
const share = Number.parseFloat(own) / (column ? Number.parseFloat(column) : 100) * 100;
|
|
101
|
+
return within(field(name), share, true);
|
|
102
|
+
}
|
|
103
|
+
function sized(node, pixels) {
|
|
104
|
+
return pixels ? {
|
|
105
|
+
...node,
|
|
106
|
+
css: `width: ${Math.min(Number.parseFloat(pixels) / 16, 256)}rem;`
|
|
107
|
+
} : node;
|
|
108
|
+
}
|
|
109
|
+
function trail(node, name, parents = []) {
|
|
110
|
+
if (node.type === "field") return node.field === name ? [...parents, node] : void 0;
|
|
111
|
+
for (const child of node.children) {
|
|
112
|
+
const found = trail(child, name, [...parents, node]);
|
|
113
|
+
if (found) return found;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function retrack(columns, count, change, spare = false) {
|
|
117
|
+
const responsive = typeof columns === "object" && !Array.isArray(columns);
|
|
118
|
+
const resize = (tracks, key) => {
|
|
119
|
+
const weights = Array.isArray(tracks) ? tracks : Array.from({ length: tracks }, () => 1);
|
|
120
|
+
if (spare ? weights.length < count : weights.length !== count) return tracks;
|
|
121
|
+
if (tracks === 1 && key === "base" && responsive && Object.keys(columns).length > 1) return tracks;
|
|
122
|
+
const changed = change(weights);
|
|
123
|
+
return Array.isArray(tracks) || changed.some((weight) => weight !== changed[0]) ? changed : changed.length;
|
|
124
|
+
};
|
|
125
|
+
return responsive ? Object.fromEntries(Object.entries(columns).map(([key, tracks]) => [key, resize(tracks, key)])) : resize(columns);
|
|
126
|
+
}
|
|
127
|
+
function remove(node, names) {
|
|
128
|
+
if (node.type === "field") return names.has(node.field) ? void 0 : node;
|
|
129
|
+
const previous = node.children;
|
|
130
|
+
const retained = [];
|
|
131
|
+
node.children = node.children.flatMap((child, index) => {
|
|
132
|
+
const kept = remove(child, names);
|
|
133
|
+
if (kept) retained.push(index);
|
|
134
|
+
return kept ? [kept] : [];
|
|
135
|
+
});
|
|
136
|
+
if (node.type === "grid" && previous.length !== node.children.length && node.children.length) node.columns = retrack(node.columns, previous.length, (weights) => retained.map((index) => weights[index] ?? 1));
|
|
137
|
+
return node.children.length || node.type === "section" || node.css ? node : void 0;
|
|
138
|
+
}
|
|
139
|
+
function insert(root, nodes, anchor, side) {
|
|
140
|
+
const path = anchor ? trail(root, anchor) : void 0;
|
|
141
|
+
if (!path) return root.type === "stack" ? {
|
|
142
|
+
...root,
|
|
143
|
+
children: [...root.children, ...nodes]
|
|
144
|
+
} : stack([root, ...nodes]);
|
|
145
|
+
const beside = side === "left" || side === "right";
|
|
146
|
+
let target = path.at(-1);
|
|
147
|
+
if (!target) return stack(nodes);
|
|
148
|
+
let parent = path.at(-2);
|
|
149
|
+
if (beside) for (let i = path.length - 2; i >= 0; i--) {
|
|
150
|
+
const candidate = path[i];
|
|
151
|
+
if (!candidate) continue;
|
|
152
|
+
if (candidate.type === "grid" && candidate.align !== "rows" && candidate.children.length + nodes.length <= 12) {
|
|
153
|
+
parent = candidate;
|
|
154
|
+
target = path[i + 1] ?? target;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const before = side === "above" || side === "left";
|
|
159
|
+
if (parent && (beside && parent.type === "grid" && parent.align !== "rows" && parent.children.length + nodes.length <= 12 || !beside && parent.type === "stack")) {
|
|
160
|
+
const index = parent.children.indexOf(target) + (before ? 0 : 1);
|
|
161
|
+
const count = parent.children.length;
|
|
162
|
+
if (parent.type === "grid") parent.columns = retrack(parent.columns, count, (weights) => {
|
|
163
|
+
const used = weights.slice(0, count);
|
|
164
|
+
const spare = weights.slice(count);
|
|
165
|
+
const room = spare.reduce((a, b) => a + b, 0);
|
|
166
|
+
const share = used.reduce((a, b) => a + b, 0) / used.length;
|
|
167
|
+
const fits = nodes.length <= spare.length;
|
|
168
|
+
const added = fits ? spare.slice(0, nodes.length) : nodes.map(() => spare.length ? room / nodes.length : share);
|
|
169
|
+
return [
|
|
170
|
+
...used.slice(0, index),
|
|
171
|
+
...added,
|
|
172
|
+
...used.slice(index),
|
|
173
|
+
...fits ? spare.slice(nodes.length) : []
|
|
174
|
+
];
|
|
175
|
+
}, true);
|
|
176
|
+
parent.children.splice(index, 0, ...nodes);
|
|
177
|
+
return root;
|
|
178
|
+
}
|
|
179
|
+
const children = before ? [...nodes, target] : [target, ...nodes];
|
|
180
|
+
const replacement = beside ? {
|
|
181
|
+
type: "grid",
|
|
182
|
+
columns: {
|
|
183
|
+
base: 1,
|
|
184
|
+
sm: Math.min(12, children.length)
|
|
185
|
+
},
|
|
186
|
+
children
|
|
187
|
+
} : stack(children);
|
|
188
|
+
if (!parent) return replacement;
|
|
189
|
+
parent.children[parent.children.indexOf(target)] = replacement;
|
|
190
|
+
return root;
|
|
191
|
+
}
|
|
192
|
+
function layoutEdit(editing, inputs, parent, layout) {
|
|
193
|
+
const list = layoutList(editing, inputs, parent);
|
|
194
|
+
const legacy = layout ? namesIn(list).filter((name) => legacyKeys.some((key) => key in asRecord(list[name]))) : [];
|
|
195
|
+
if (!parent.length) {
|
|
196
|
+
const meta = {
|
|
197
|
+
type: "updateWorkflow",
|
|
198
|
+
inputsMeta: layout ? { set: { layout } } : { unset: ["layout"] }
|
|
199
|
+
};
|
|
200
|
+
return legacy.length ? {
|
|
201
|
+
type: "batch",
|
|
202
|
+
edits: [meta, ...legacy.map((name) => ({
|
|
203
|
+
type: "updateInput",
|
|
204
|
+
path: [name],
|
|
205
|
+
unset: legacyKeys
|
|
206
|
+
}))]
|
|
207
|
+
} : meta;
|
|
208
|
+
}
|
|
209
|
+
const definition = asRecord(layoutList(editing, inputs, parent.slice(0, -1))[parent.at(-1) ?? ""]);
|
|
210
|
+
const { layout: _layout, ...meta } = asRecord(list["$meta"]);
|
|
211
|
+
const fields = { ...list };
|
|
212
|
+
for (const name of legacy) fields[name] = Object.fromEntries(Object.entries(asRecord(list[name])).filter(([key]) => !legacyKeys.includes(key)));
|
|
213
|
+
return {
|
|
214
|
+
type: "updateInput",
|
|
215
|
+
path: parent,
|
|
216
|
+
set: { [editing.inputChildrenKey(definition["type"]) ?? "options"]: {
|
|
217
|
+
...fields,
|
|
218
|
+
$meta: {
|
|
219
|
+
...meta,
|
|
220
|
+
...layout ? { layout } : {}
|
|
221
|
+
}
|
|
222
|
+
} }
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function inputsAfter(editing, inputs, edit) {
|
|
226
|
+
const source = editing.dumpYaml({
|
|
227
|
+
on: { execute: { inputs } },
|
|
228
|
+
jobs: {}
|
|
229
|
+
});
|
|
230
|
+
const result = editing.applyGraphEdit({
|
|
231
|
+
yml: source,
|
|
232
|
+
layout: void 0
|
|
233
|
+
}, edit);
|
|
234
|
+
return asRecord(asRecord(asRecord(asRecord(editing.loadYaml(result.yml))["on"])["execute"])["inputs"]);
|
|
235
|
+
}
|
|
236
|
+
function dropLayoutEdits(editing, inputs, moving, drop, move) {
|
|
237
|
+
const after = inputsAfter(editing, inputs, move);
|
|
238
|
+
const destination = layoutList(editing, inputs, drop.parent);
|
|
239
|
+
const names = moving.map((path) => path.at(-1) ?? "");
|
|
240
|
+
const nodes = moving.map((path) => {
|
|
241
|
+
return trail(inputLayout(layoutList(editing, inputs, path.slice(0, -1))), path.at(-1) ?? "")?.at(-1) ?? field(path.at(-1) ?? "");
|
|
242
|
+
});
|
|
243
|
+
const parents = [...new Map(moving.map((path) => [JSON.stringify(path.slice(0, -1)), path.slice(0, -1)])).values()];
|
|
244
|
+
const edits = [];
|
|
245
|
+
for (const parent of parents.filter((parent) => !same(parent, drop.parent))) {
|
|
246
|
+
const list = layoutList(editing, inputs, parent);
|
|
247
|
+
if (asRecord(list["$meta"])["layout"] !== void 0) edits.push(layoutEdit(editing, after, parent, remove(inputLayout(list), new Set(moving.filter((path) => same(path.slice(0, -1), parent)).map((path) => path.at(-1) ?? ""))) ?? stack([])));
|
|
248
|
+
}
|
|
249
|
+
const root = remove(inputLayout(destination), new Set(names)) ?? stack([]);
|
|
250
|
+
const anchor = drop.beside?.path.at(-1) ?? drop.stack?.path.at(-1) ?? namesIn(destination).slice(drop.index).find((name) => !names.includes(name));
|
|
251
|
+
if (isWizard(destination)) return edits;
|
|
252
|
+
const layout = insert(root, nodes, anchor, drop.beside?.side ?? drop.stack?.side ?? "above");
|
|
253
|
+
edits.push(layoutEdit(editing, after, drop.parent, layout));
|
|
254
|
+
return edits;
|
|
255
|
+
}
|
|
256
|
+
function resizeLayout(editing, inputs, left, right, split) {
|
|
257
|
+
const parent = left.slice(0, -1);
|
|
258
|
+
const layout = inputLayout(layoutList(editing, inputs, parent));
|
|
259
|
+
const a = trail(layout, left.at(-1) ?? "");
|
|
260
|
+
const b = trail(layout, right.at(-1) ?? "");
|
|
261
|
+
if (!a || !b) return void 0;
|
|
262
|
+
const grid = [...a].reverse().find((node) => node.type === "grid" && b.includes(node));
|
|
263
|
+
if (grid?.type !== "grid") return void 0;
|
|
264
|
+
const i = grid.children.indexOf(a[a.indexOf(grid) + 1] ?? grid);
|
|
265
|
+
const j = grid.children.indexOf(b[b.indexOf(grid) + 1] ?? grid);
|
|
266
|
+
if (i === j || i < 0 || j < 0) return void 0;
|
|
267
|
+
const responsive = responsiveLayoutValues(grid.columns, 1);
|
|
268
|
+
grid.columns = Object.fromEntries(Object.entries(responsive).map(([breakpoint, tracks]) => {
|
|
269
|
+
const count = Array.isArray(tracks) ? tracks.length : tracks;
|
|
270
|
+
if (count !== grid.children.length) return [breakpoint, tracks];
|
|
271
|
+
const weights = Array.isArray(tracks) ? [...tracks] : Array.from({ length: count }, () => 1);
|
|
272
|
+
const total = (weights[i] ?? 1) + (weights[j] ?? 1);
|
|
273
|
+
const shares = split[0] + split[1];
|
|
274
|
+
weights[i] = total * split[0] / shares;
|
|
275
|
+
weights[j] = total * split[1] / shares;
|
|
276
|
+
return [breakpoint, weights];
|
|
277
|
+
}));
|
|
278
|
+
return layoutEdit(editing, inputs, parent, layout);
|
|
279
|
+
}
|
|
280
|
+
/** Keeps layouts valid even with hosts whose editing engine predates layout metadata. */
|
|
281
|
+
function inputLayouts(editing, inputs, edit) {
|
|
282
|
+
if (edit.type === "batch") {
|
|
283
|
+
let current = inputs;
|
|
284
|
+
return {
|
|
285
|
+
type: "batch",
|
|
286
|
+
edits: edit.edits.map((item) => {
|
|
287
|
+
const next = withInputLayouts(editing, current, item);
|
|
288
|
+
current = inputsAfter(editing, current, next);
|
|
289
|
+
return next;
|
|
290
|
+
})
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (edit.type === "moveInputs") return [...edit.paths.map((path) => path.slice(0, -1)), edit.parent].some((parent) => asRecord(layoutList(editing, inputs, parent)["$meta"])["layout"] !== void 0) ? {
|
|
294
|
+
type: "batch",
|
|
295
|
+
edits: [edit, ...dropLayoutEdits(editing, inputs, edit.paths, {
|
|
296
|
+
parent: edit.parent,
|
|
297
|
+
index: edit.index
|
|
298
|
+
}, edit)]
|
|
299
|
+
} : edit;
|
|
300
|
+
if (edit.type !== "updateInput" && edit.type !== "deleteInput" && edit.type !== "duplicateInput") return edit;
|
|
301
|
+
if (edit.type === "updateInput" && (!edit.name || edit.name === edit.path.at(-1))) return edit;
|
|
302
|
+
const parent = edit.path.slice(0, -1);
|
|
303
|
+
const list = layoutList(editing, inputs, parent);
|
|
304
|
+
const layout = resolveFormLayout(asRecord(list["$meta"])["layout"], namesIn(list)).layout;
|
|
305
|
+
if (!layout) return edit;
|
|
306
|
+
const after = inputsAfter(editing, inputs, edit);
|
|
307
|
+
let updated = structuredClone(layout);
|
|
308
|
+
const name = edit.path.at(-1) ?? "";
|
|
309
|
+
if (edit.type === "deleteInput") updated = remove(updated, /* @__PURE__ */ new Set([name])) ?? stack([]);
|
|
310
|
+
else if (edit.type === "updateInput") {
|
|
311
|
+
const node = trail(updated, name)?.at(-1);
|
|
312
|
+
if (node?.type === "field") node.field = edit.name ?? name;
|
|
313
|
+
} else {
|
|
314
|
+
const added = namesIn(layoutList(editing, after, parent)).find((key) => !Object.hasOwn(list, key));
|
|
315
|
+
if (added) {
|
|
316
|
+
const original = trail(updated, name)?.at(-1);
|
|
317
|
+
updated = insert(updated, [{
|
|
318
|
+
...original?.type === "field" ? original : {},
|
|
319
|
+
type: "field",
|
|
320
|
+
field: added
|
|
321
|
+
}], name, "below");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
type: "batch",
|
|
326
|
+
edits: [edit, layoutEdit(editing, after, parent, updated)]
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function withInputLayouts(editing, inputs, edit) {
|
|
330
|
+
try {
|
|
331
|
+
return inputLayouts(editing, inputs, edit);
|
|
332
|
+
} catch {
|
|
333
|
+
return edit;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//#endregion
|
|
337
|
+
export { dropLayoutEdits, inputLayout, isWizard, layoutEdit, layoutList, resizeLayout, withInputLayouts };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { GraphEdit, InputPath, WorkflowEditing } from '../editing.js';
|
|
2
|
+
type Json = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* The wizard settings a list split into pages gets: what it had, and otherwise pages that can be
|
|
5
|
+
* jumped between and, for the form itself, a Submit button.
|
|
6
|
+
*/
|
|
7
|
+
export declare function wizardSettings(list: unknown, submitLabel?: string): Json;
|
|
8
|
+
/**
|
|
9
|
+
* What splitting the form into pages adds besides its `$meta`: a wizard draws only its pages, so the
|
|
10
|
+
* inputs outside one go into a first page, and a form with no pages gets one to show.
|
|
11
|
+
*/
|
|
12
|
+
export declare function firstPageEdits(editing: WorkflowEditing, inputs: unknown, title: string): GraphEdit[];
|
|
13
|
+
/**
|
|
14
|
+
* The edits that split a list of inputs into pages: the form's (`parent` is `[]`), or those the group
|
|
15
|
+
* or list at `parent` holds under `childKey`. Only the form's `$meta` is edited on its own, so a group's
|
|
16
|
+
* or list's inputs are written whole.
|
|
17
|
+
*/
|
|
18
|
+
export declare function splitIntoPagesEdits(editing: WorkflowEditing, list: unknown, parent: InputPath, childKey: string | undefined, title: string, submitLabel?: string): GraphEdit[];
|
|
19
|
+
/** A page added after a wizard's last, titled by how many it then has. */
|
|
20
|
+
export declare function addPageEdit(editing: WorkflowEditing, list: unknown, parent: InputPath, title: (count: number) => string): GraphEdit;
|
|
21
|
+
/**
|
|
22
|
+
* What undoing pages adds besides dropping the wizard from `$meta`: each page's inputs go back where
|
|
23
|
+
* the page was, in order, and the pages go. Moving them one by one refuses a name two pages share.
|
|
24
|
+
*/
|
|
25
|
+
export declare function pagesBackEdits(list: unknown, parent: InputPath): GraphEdit[];
|
|
26
|
+
/** The edits that turn a wizard back into a list of inputs drawn all at once. */
|
|
27
|
+
export declare function unsplitPagesEdits(list: unknown, parent: InputPath, childKey: string | undefined): GraphEdit[];
|
|
28
|
+
/**
|
|
29
|
+
* A wizard step added where the inputs aren't a wizard comes as a wizard of its own: a group whose
|
|
30
|
+
* fields are read as the form's own, holding the step.
|
|
31
|
+
*/
|
|
32
|
+
export declare function wizardGroupDefinition(label: string, title: string): Json;
|
|
33
|
+
export {};
|