@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,337 @@
|
|
|
1
|
+
import { inputWidth } from "../form/lib.js";
|
|
2
|
+
import { asRecord } from "./records.js";
|
|
3
|
+
import { resolveFormLayout, responsiveLayoutValues } from "../form/layout.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 {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { inputLayout } from "./inputLayout.js";
|
|
2
|
+
//#region src/graph/inputPages.ts
|
|
3
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
4
|
+
var isStep = (definition) => asRecord(definition)["type"] === "step";
|
|
5
|
+
var inputNames = (list) => Object.keys(list).filter((name) => !name.startsWith("$"));
|
|
6
|
+
/**
|
|
7
|
+
* The wizard settings a list split into pages gets: what it had, and otherwise pages that can be
|
|
8
|
+
* jumped between and, for the form itself, a Submit button.
|
|
9
|
+
*/
|
|
10
|
+
function wizardSettings(list, submitLabel) {
|
|
11
|
+
const wizard = asRecord(asRecord(asRecord(list)["$meta"])["wizard"]);
|
|
12
|
+
return {
|
|
13
|
+
...wizard,
|
|
14
|
+
mode: "wizard",
|
|
15
|
+
navigation: {
|
|
16
|
+
allowJump: true,
|
|
17
|
+
...asRecord(wizard["navigation"])
|
|
18
|
+
},
|
|
19
|
+
...submitLabel && wizard["submitLabel"] === void 0 ? { submitLabel } : {}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What splitting the form into pages adds besides its `$meta`: a wizard draws only its pages, so the
|
|
24
|
+
* inputs outside one go into a first page, and a form with no pages gets one to show.
|
|
25
|
+
*/
|
|
26
|
+
function firstPageEdits(editing, inputs, title) {
|
|
27
|
+
const names = inputNames(asRecord(inputs));
|
|
28
|
+
const loose = names.filter((name) => !isStep(asRecord(inputs)[name]));
|
|
29
|
+
if (loose.length === 0 && names.length > 0) return [];
|
|
30
|
+
const page = editing.nextName(names, "step_1");
|
|
31
|
+
const layout = asRecord(asRecord(inputs)["$meta"])["layout"];
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
type: "addInput",
|
|
35
|
+
parent: [],
|
|
36
|
+
index: 0,
|
|
37
|
+
name: page,
|
|
38
|
+
definition: newStep(title)
|
|
39
|
+
},
|
|
40
|
+
...loose.length > 0 ? [{
|
|
41
|
+
type: "moveInputs",
|
|
42
|
+
paths: loose.map((name) => [name]),
|
|
43
|
+
parent: [page],
|
|
44
|
+
index: 0
|
|
45
|
+
}] : [],
|
|
46
|
+
...layout !== void 0 ? [{
|
|
47
|
+
type: "updateInput",
|
|
48
|
+
path: [page],
|
|
49
|
+
set: { options: {
|
|
50
|
+
...Object.fromEntries(loose.map((name) => [name, asRecord(inputs)[name]])),
|
|
51
|
+
$meta: { layout }
|
|
52
|
+
} }
|
|
53
|
+
}] : []
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The edits that split a list of inputs into pages: the form's (`parent` is `[]`), or those the group
|
|
58
|
+
* or list at `parent` holds under `childKey`. Only the form's `$meta` is edited on its own, so a group's
|
|
59
|
+
* or list's inputs are written whole.
|
|
60
|
+
*/
|
|
61
|
+
function splitIntoPagesEdits(editing, list, parent, childKey, title, submitLabel) {
|
|
62
|
+
if (parent.length === 0) return [{
|
|
63
|
+
type: "updateWorkflow",
|
|
64
|
+
inputsMeta: {
|
|
65
|
+
set: { wizard: wizardSettings(list, submitLabel) },
|
|
66
|
+
unset: ["layout"]
|
|
67
|
+
}
|
|
68
|
+
}, ...firstPageEdits(editing, list, title)];
|
|
69
|
+
const record = asRecord(list);
|
|
70
|
+
const names = inputNames(record);
|
|
71
|
+
const loose = names.filter((name) => !isStep(record[name]));
|
|
72
|
+
const pages = Object.fromEntries(names.filter((name) => isStep(record[name])).map((name) => [name, record[name]]));
|
|
73
|
+
const first = loose.length > 0 || names.length === 0 ? { [editing.nextName(names, "step_1")]: {
|
|
74
|
+
...newStep(title),
|
|
75
|
+
options: {
|
|
76
|
+
...Object.fromEntries(loose.map((name) => [name, record[name]])),
|
|
77
|
+
...asRecord(record["$meta"])["layout"] !== void 0 ? { $meta: { layout: asRecord(record["$meta"])["layout"] } } : {}
|
|
78
|
+
}
|
|
79
|
+
} } : {};
|
|
80
|
+
const { layout: _layout, ...previousMeta } = asRecord(record["$meta"]);
|
|
81
|
+
const meta = {
|
|
82
|
+
...previousMeta,
|
|
83
|
+
wizard: wizardSettings(record)
|
|
84
|
+
};
|
|
85
|
+
return [{
|
|
86
|
+
type: "updateInput",
|
|
87
|
+
path: parent,
|
|
88
|
+
set: { [childKey ?? "items"]: {
|
|
89
|
+
$meta: meta,
|
|
90
|
+
...first,
|
|
91
|
+
...pages
|
|
92
|
+
} }
|
|
93
|
+
}];
|
|
94
|
+
}
|
|
95
|
+
/** A page added after a wizard's last, titled by how many it then has. */
|
|
96
|
+
function addPageEdit(editing, list, parent, title) {
|
|
97
|
+
const names = inputNames(asRecord(list));
|
|
98
|
+
const count = names.filter((name) => isStep(asRecord(list)[name])).length + 1;
|
|
99
|
+
return {
|
|
100
|
+
type: "addInput",
|
|
101
|
+
parent,
|
|
102
|
+
name: editing.nextName(names, `step_${count}`),
|
|
103
|
+
definition: newStep(title(count))
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* What undoing pages adds besides dropping the wizard from `$meta`: each page's inputs go back where
|
|
108
|
+
* the page was, in order, and the pages go. Moving them one by one refuses a name two pages share.
|
|
109
|
+
*/
|
|
110
|
+
function pagesBackEdits(list, parent) {
|
|
111
|
+
const record = asRecord(list);
|
|
112
|
+
const order = inputNames(record);
|
|
113
|
+
const edits = [];
|
|
114
|
+
for (const page of order.filter((name) => isStep(record[name]))) {
|
|
115
|
+
const fields = inputNames(asRecord(asRecord(record[page])["options"]));
|
|
116
|
+
const at = order.indexOf(page);
|
|
117
|
+
if (fields.length > 0) edits.push({
|
|
118
|
+
type: "moveInputs",
|
|
119
|
+
paths: fields.map((name) => [
|
|
120
|
+
...parent,
|
|
121
|
+
page,
|
|
122
|
+
name
|
|
123
|
+
]),
|
|
124
|
+
parent,
|
|
125
|
+
index: at
|
|
126
|
+
});
|
|
127
|
+
order.splice(at, 1, ...fields);
|
|
128
|
+
edits.push({
|
|
129
|
+
type: "deleteInput",
|
|
130
|
+
path: [...parent, page]
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return edits;
|
|
134
|
+
}
|
|
135
|
+
/** The edits that turn a wizard back into a list of inputs drawn all at once. */
|
|
136
|
+
function unsplitPagesEdits(list, parent, childKey) {
|
|
137
|
+
const record = asRecord(list);
|
|
138
|
+
const pageLayouts = inputNames(record).filter((name) => isStep(record[name])).map((name) => asRecord(asRecord(record[name])["options"]));
|
|
139
|
+
const layout = pageLayouts.some((options) => asRecord(options["$meta"])["layout"] !== void 0) ? {
|
|
140
|
+
type: "stack",
|
|
141
|
+
children: pageLayouts.map(inputLayout)
|
|
142
|
+
} : void 0;
|
|
143
|
+
if (parent.length === 0) return [...pagesBackEdits(list, []), {
|
|
144
|
+
type: "updateWorkflow",
|
|
145
|
+
inputsMeta: {
|
|
146
|
+
unset: ["wizard"],
|
|
147
|
+
...layout ? { set: { layout } } : {}
|
|
148
|
+
}
|
|
149
|
+
}];
|
|
150
|
+
const { wizard: _wizard, ...meta } = asRecord(record["$meta"]);
|
|
151
|
+
if (layout) meta["layout"] = layout;
|
|
152
|
+
const unpaged = Object.keys(meta).length > 0 ? { $meta: meta } : {};
|
|
153
|
+
for (const name of inputNames(record)) if (isStep(record[name])) {
|
|
154
|
+
const options = asRecord(asRecord(record[name])["options"]);
|
|
155
|
+
for (const field of inputNames(options)) unpaged[field] = options[field];
|
|
156
|
+
} else unpaged[name] = record[name];
|
|
157
|
+
return [...pagesBackEdits(list, parent), {
|
|
158
|
+
type: "updateInput",
|
|
159
|
+
path: parent,
|
|
160
|
+
set: { [childKey ?? "items"]: unpaged }
|
|
161
|
+
}];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A wizard step added where the inputs aren't a wizard comes as a wizard of its own: a group whose
|
|
165
|
+
* fields are read as the form's own, holding the step.
|
|
166
|
+
*/
|
|
167
|
+
function wizardGroupDefinition(label, title) {
|
|
168
|
+
return {
|
|
169
|
+
type: "group",
|
|
170
|
+
label,
|
|
171
|
+
flatten: true,
|
|
172
|
+
items: {
|
|
173
|
+
$meta: { wizard: wizardSettings(void 0) },
|
|
174
|
+
step_1: newStep(title)
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function newStep(title) {
|
|
179
|
+
return {
|
|
180
|
+
type: "step",
|
|
181
|
+
title,
|
|
182
|
+
options: {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
export { addPageEdit, firstPageEdits, pagesBackEdits, splitIntoPagesEdits, unsplitPagesEdits, wizardGroupDefinition, wizardSettings };
|