@parallelworks/ui 0.1.0 → 0.2.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/dist/assets/editor.worker-CuBRIwi-.js +14776 -0
- package/dist/assets/json.worker-8ytS02vz.js +22161 -0
- package/dist/assets/yaml.worker-DJC-VUcv.js +48367 -0
- package/dist/chunks/Indicator-fF8AVbjQ.js +158 -0
- package/dist/chunks/Loader-DQiZ75fa.js +25 -0
- package/dist/chunks/MarkdownImpl-Bq4QhrtA.js +33 -0
- package/dist/chunks/Monaco-CP_cL9gp.js +745 -0
- package/dist/chunks/Provider-C2VtPnh5.js +151 -0
- package/dist/chunks/SwitchToggleSmall-FcsaYSTs.js +14 -0
- package/dist/chunks/Table-DWuRINhu.js +855 -0
- package/dist/chunks/Tooltip-WgbiC_nd.js +71 -0
- package/dist/chunks/lib-DTgL8klR.js +191 -0
- package/dist/chunks/workers-Cjfjb8E7.js +119 -0
- package/dist/components/CollapsiblePanel.d.ts +27 -0
- package/dist/components/CopyToClipboard.d.ts +17 -0
- package/dist/components/Dropdown.d.ts +24 -0
- package/dist/components/Indicator.d.ts +4 -0
- package/dist/components/Listbox.d.ts +18 -0
- package/dist/components/Markdown.d.ts +7 -0
- package/dist/components/MarkdownImpl.d.ts +2 -0
- package/dist/components/MultiSelect.d.ts +19 -0
- package/dist/components/Overview.d.ts +12 -0
- package/dist/components/Provider.d.ts +145 -0
- package/dist/components/SectionHeader.d.ts +6 -0
- package/dist/components/SwitchToggle.d.ts +12 -0
- package/dist/components/SwitchToggleSmall.d.ts +6 -0
- package/dist/components/dropdownUtils.d.ts +18 -0
- package/dist/editor/Editor.d.ts +2 -0
- package/dist/editor/EditorField.d.ts +11 -0
- package/dist/editor/FormCodePanel.d.ts +8 -0
- package/dist/editor/Monaco.d.ts +31 -0
- package/dist/editor/index.d.ts +6 -0
- package/dist/editor/index.js +219 -0
- package/dist/editor/themes.d.ts +4 -0
- package/dist/editor/workers.d.ts +6 -0
- package/dist/form/FieldWrapper.d.ts +10 -0
- package/dist/form/Fields/BooleanField.d.ts +12 -0
- package/dist/form/Fields/BucketReadOnlyField.d.ts +7 -0
- package/dist/form/Fields/CheckboxGroupField.d.ts +18 -0
- package/dist/form/Fields/ColorField.d.ts +8 -0
- package/dist/form/Fields/DropdownField.d.ts +24 -0
- package/dist/form/Fields/DurationField.d.ts +13 -0
- package/dist/form/Fields/LabelField.d.ts +9 -0
- package/dist/form/Fields/MultiDropdownField.d.ts +18 -0
- package/dist/form/Fields/NumberField.d.ts +13 -0
- package/dist/form/Fields/ObjectField.d.ts +8 -0
- package/dist/form/Fields/RadioField.d.ts +9 -0
- package/dist/form/Fields/RangeField.d.ts +11 -0
- package/dist/form/Fields/ResourceOptionIcon.d.ts +11 -0
- package/dist/form/Fields/StepField.d.ts +3 -0
- package/dist/form/Fields/StringField.d.ts +17 -0
- package/dist/form/Fields/TextAreaField.d.ts +8 -0
- package/dist/form/Fields/durationValidity.d.ts +2 -0
- package/dist/form/Form.d.ts +105 -0
- package/dist/form/SpecialTypes/DropdownWrapper.d.ts +14 -0
- package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +53 -0
- package/dist/form/SpecialTypes/FormikCustomInput.d.ts +17 -0
- package/dist/form/SpecialTypes/FormikMultiSelectDropdown.d.ts +13 -0
- package/dist/form/SpecialTypes/FormikNumericInput.d.ts +9 -0
- package/dist/form/SpecialTypes/index.d.ts +3 -0
- package/dist/form/Wizard/WizardContainer.d.ts +5 -0
- package/dist/form/Wizard/WizardNavigation.d.ts +2 -0
- package/dist/form/Wizard/WizardStepContent.d.ts +8 -0
- package/dist/form/Wizard/WizardStepIndicator.d.ts +2 -0
- package/dist/form/Wizard/types.d.ts +171 -0
- package/dist/form/Wizard/useWizardState.d.ts +28 -0
- package/dist/form/Wizard/utils.d.ts +2 -0
- package/dist/form/fieldRegistry.d.ts +7 -0
- package/dist/form/index.d.ts +16 -0
- package/dist/form/index.js +2317 -0
- package/dist/form/lib.d.ts +15 -0
- package/dist/form/types/fieldComponentTypes.d.ts +32 -0
- package/dist/form/types/fieldTypes.d.ts +80 -0
- package/dist/form/types/workflow-schema.d.ts +1107 -0
- package/dist/form/useParsedOpts.d.ts +5 -0
- package/dist/form/usePrevious.d.ts +1 -0
- package/dist/form/utils/checkShowIfObject.d.ts +1 -0
- package/dist/form/utils/getParentValue.d.ts +1 -0
- package/dist/form/utils/getPlaceholder.d.ts +2 -0
- package/dist/form/utils/getValueUsingPath.d.ts +1 -0
- package/dist/form/utils/pwResolver.d.ts +18 -0
- package/dist/form/utils/secondaryField.d.ts +8 -0
- package/dist/form/utils/slurmDuration.d.ts +15 -0
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +2 -2
- package/dist/index.d.ts +17 -0
- package/dist/index.js +107 -323
- package/dist/logviewer/LogViewer.d.ts +18 -0
- package/dist/logviewer/index.d.ts +1 -0
- package/dist/logviewer/index.js +534 -0
- package/dist/logviewer/logviewer.css +70 -0
- package/dist/styles.css +1 -1
- package/dist/workflow/AnnotationBanner.d.ts +5 -0
- package/dist/workflow/Collapse.d.ts +6 -0
- package/dist/workflow/DependencyGraph.d.ts +15 -0
- package/dist/workflow/JobSummary.d.ts +38 -0
- package/dist/workflow/MatrixGroup.d.ts +41 -0
- package/dist/workflow/Reveal.d.ts +7 -0
- package/dist/workflow/TreeView.d.ts +42 -0
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.js +2330 -0
- package/package.json +60 -6
|
@@ -0,0 +1,2317 @@
|
|
|
1
|
+
import { AngleRightIcon, EyeIcon, EyeOffIcon, SuccessCheckmark, TrashIcon } from "../icons.js";
|
|
2
|
+
import { a as useSlots, o as useStrings } from "../chunks/Provider-C2VtPnh5.js";
|
|
3
|
+
import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-WgbiC_nd.js";
|
|
4
|
+
import { a as StatusDot, d as CustomListbox, f as Dropdown, g as isCategory, l as UncontrolledCollapsiblePanel, n as Table, o as SectionHeader, r as SwitchToggle, s as ControlledCollapsiblePanel, u as MultiSelectDropdown } from "../chunks/Table-DWuRINhu.js";
|
|
5
|
+
import { t as Loader } from "../chunks/Loader-DQiZ75fa.js";
|
|
6
|
+
import { a as resolvedFlag, c as FieldWrapper, i as initializeValues, n as flattenGroups, o as applySecondaryField, r as impureSetValueFromPath, s as findSecondaryOption, t as enforceOneMustBeTrue } from "../chunks/lib-DTgL8klR.js";
|
|
7
|
+
import cx from "classnames";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import React, { Suspense, createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
+
import deepEqual from "fast-deep-equal";
|
|
11
|
+
import { callWhenParserInitialized, convertToDynamicForm, extractInputDeps, initializeParseStringsOfObj, parseStringsOfObj, parseStringsOfObjReady } from "@parallelworks/workflow-parser";
|
|
12
|
+
import { Duration } from "luxon";
|
|
13
|
+
import { Field, Form, Formik, getIn, useField, useFormikContext } from "formik";
|
|
14
|
+
import { NumericFormat } from "react-number-format";
|
|
15
|
+
//#region src/form/utils/getValueUsingPath.ts
|
|
16
|
+
function getValueUsingPath(obj, path) {
|
|
17
|
+
if (typeof path !== "string" || !path) return;
|
|
18
|
+
const keys = path.split(".");
|
|
19
|
+
let current = obj;
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
if (!current || typeof current !== "object") return;
|
|
22
|
+
if (key.includes("[")) {
|
|
23
|
+
const [arrayKey, indexStr] = key.split("[");
|
|
24
|
+
const index = parseInt(indexStr.slice(0, -1), 10);
|
|
25
|
+
if (!Array.isArray(current[arrayKey]) || isNaN(index)) return;
|
|
26
|
+
current = current[arrayKey][index];
|
|
27
|
+
} else {
|
|
28
|
+
if (!(key in current)) return;
|
|
29
|
+
current = current[key];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return current;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/form/utils/checkShowIfObject.ts
|
|
36
|
+
function checkShowIfObject(showIfObject, formValues, index) {
|
|
37
|
+
const valueList = [];
|
|
38
|
+
for (let [key, value] of Object.entries(showIfObject)) {
|
|
39
|
+
if (index !== void 0) key = key.replace("[index]", `[${index}]`);
|
|
40
|
+
const dependsOnValue = getValueUsingPath(formValues, key);
|
|
41
|
+
if (typeof value === "boolean" || typeof value === "string" || typeof value === "number") valueList.push(dependsOnValue?.toString() === value.toString());
|
|
42
|
+
else if (Array.isArray(value)) valueList.push(value.includes(dependsOnValue));
|
|
43
|
+
}
|
|
44
|
+
return valueList;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/form/utils/getParentValue.ts
|
|
48
|
+
function getParentValue(index, targetField, values, dependentPath, field) {
|
|
49
|
+
let parentValue;
|
|
50
|
+
if (typeof targetField === "object" && !Array.isArray(targetField)) parentValue = !checkShowIfObject(targetField, values, index).includes(false);
|
|
51
|
+
else if (field.depends_on) {
|
|
52
|
+
dependentPath = field.depends_on;
|
|
53
|
+
if (index !== void 0) dependentPath = field.depends_on.replace("[index]", `[${index}]`);
|
|
54
|
+
const dependsOnValue = getValueUsingPath(values, dependentPath);
|
|
55
|
+
parentValue = targetField.includes(dependsOnValue) || targetField.includes(dependsOnValue?.toString());
|
|
56
|
+
} else {
|
|
57
|
+
dependentPath = targetField;
|
|
58
|
+
if (index !== void 0) dependentPath = targetField.replace("[index]", `[${index}]`);
|
|
59
|
+
parentValue = getValueUsingPath(values, dependentPath);
|
|
60
|
+
}
|
|
61
|
+
return parentValue;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/form/useParsedOpts.ts
|
|
65
|
+
var EMPTY_VARS = {};
|
|
66
|
+
var EMPTY_KEYS = [];
|
|
67
|
+
/**
|
|
68
|
+
* useMemo won't work here — Formik values changes on every keystroke, so this hook extracts which inputs.* keys the schema references and re-parses only when those change. Defaults `userVars`/`secretKeys` to empty — callers in React scope should pass them from `useUserVars()` so the parser leaves `${{ var.SECRET }}` literal during template eval (otherwise the recursive substitution turns `"https://${{ var.PAT }}@host/"` into `"https://@host/"`).
|
|
69
|
+
*/
|
|
70
|
+
function useParsedOpts(rawOptions, values, organizationVariables, userVars = EMPTY_VARS, secretKeys = EMPTY_KEYS) {
|
|
71
|
+
const c = useRef({
|
|
72
|
+
rawOptions: null,
|
|
73
|
+
orgVars: void 0,
|
|
74
|
+
userVars: null,
|
|
75
|
+
secretKeys: null,
|
|
76
|
+
converted: null,
|
|
77
|
+
inputDeps: /* @__PURE__ */ new Set(),
|
|
78
|
+
hasExpressions: false,
|
|
79
|
+
relevantSnapshot: {},
|
|
80
|
+
result: null
|
|
81
|
+
}).current;
|
|
82
|
+
if (rawOptions !== c.rawOptions) {
|
|
83
|
+
c.rawOptions = rawOptions;
|
|
84
|
+
c.converted = convertToDynamicForm(rawOptions);
|
|
85
|
+
const { inputDeps, hasExpressions } = extractInputDeps(c.converted);
|
|
86
|
+
c.inputDeps = inputDeps;
|
|
87
|
+
c.hasExpressions = hasExpressions;
|
|
88
|
+
c.result = null;
|
|
89
|
+
}
|
|
90
|
+
if (organizationVariables !== c.orgVars) {
|
|
91
|
+
c.orgVars = organizationVariables;
|
|
92
|
+
c.result = null;
|
|
93
|
+
}
|
|
94
|
+
if (userVars !== c.userVars || secretKeys !== c.secretKeys) {
|
|
95
|
+
c.userVars = userVars;
|
|
96
|
+
c.secretKeys = secretKeys;
|
|
97
|
+
c.result = null;
|
|
98
|
+
}
|
|
99
|
+
if (c.result !== null) {
|
|
100
|
+
if (c.inputDeps.size === 0) return c.result;
|
|
101
|
+
let inputsChanged = false;
|
|
102
|
+
for (const key of c.inputDeps) if (!deepEqual(values[key], c.relevantSnapshot[key])) {
|
|
103
|
+
inputsChanged = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
if (!inputsChanged) return c.result;
|
|
107
|
+
}
|
|
108
|
+
c.result = parseStringsOfObj({
|
|
109
|
+
inputs: values,
|
|
110
|
+
obj: c.converted,
|
|
111
|
+
orgVars: organizationVariables,
|
|
112
|
+
userVars,
|
|
113
|
+
secretKeys
|
|
114
|
+
});
|
|
115
|
+
c.relevantSnapshot = {};
|
|
116
|
+
for (const key of c.inputDeps) c.relevantSnapshot[key] = values[key];
|
|
117
|
+
return c.result;
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/form/usePrevious.ts
|
|
121
|
+
function usePrevious(value) {
|
|
122
|
+
const ref = useRef(void 0);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
ref.current = value;
|
|
125
|
+
});
|
|
126
|
+
return ref.current;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/form/Wizard/WizardNavigation.tsx
|
|
130
|
+
function WizardNavigation({ currentStep, stepOrder, canGoToNext, canGoBack, isCurrentStepValid, nextLabel, prevLabel, submitLabel = "Submit", onNext, onPrevious, onSubmit }) {
|
|
131
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
132
|
+
const [error, setError] = useState(null);
|
|
133
|
+
const isLastStep = stepOrder.indexOf(currentStep) === stepOrder.length - 1;
|
|
134
|
+
const handleNext = async () => {
|
|
135
|
+
setIsLoading(true);
|
|
136
|
+
setError(null);
|
|
137
|
+
try {
|
|
138
|
+
if (!await onNext()) setError("Please fix errors before proceeding");
|
|
139
|
+
} catch (err) {
|
|
140
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
141
|
+
} finally {
|
|
142
|
+
setIsLoading(false);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const handleSubmit = async () => {
|
|
146
|
+
setIsLoading(true);
|
|
147
|
+
setError(null);
|
|
148
|
+
try {
|
|
149
|
+
await onSubmit();
|
|
150
|
+
} catch (err) {
|
|
151
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
152
|
+
} finally {
|
|
153
|
+
setIsLoading(false);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return /* @__PURE__ */ jsxs("div", { children: [error && /* @__PURE__ */ jsx("div", {
|
|
157
|
+
className: "mb-4 p-3 bg-red-50 border border-red-200 rounded text-sm text-red-700",
|
|
158
|
+
children: error
|
|
159
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
160
|
+
className: "flex gap-3 justify-between pt-4",
|
|
161
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
162
|
+
type: "button",
|
|
163
|
+
onClick: onPrevious,
|
|
164
|
+
disabled: !canGoBack || isLoading,
|
|
165
|
+
className: "px-6 py-3 rounded-lg font-medium transition-all duration-200 border flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
|
|
166
|
+
style: {
|
|
167
|
+
color: "var(--theme-app)",
|
|
168
|
+
borderColor: "var(--theme-border)",
|
|
169
|
+
backgroundColor: "var(--theme-app-bg)"
|
|
170
|
+
},
|
|
171
|
+
onMouseEnter: (e) => {
|
|
172
|
+
if (!(!canGoBack || isLoading)) e.currentTarget.style.backgroundColor = "var(--theme-hover)";
|
|
173
|
+
},
|
|
174
|
+
onMouseLeave: (e) => {
|
|
175
|
+
e.currentTarget.style.backgroundColor = "var(--theme-app-bg)";
|
|
176
|
+
},
|
|
177
|
+
"aria-label": "Go to previous step",
|
|
178
|
+
children: prevLabel || "← Previous"
|
|
179
|
+
}), isLastStep ? /* @__PURE__ */ jsxs("button", {
|
|
180
|
+
type: "button",
|
|
181
|
+
onClick: handleSubmit,
|
|
182
|
+
disabled: !isCurrentStepValid || isLoading,
|
|
183
|
+
className: "px-8 py-3 rounded-lg font-medium transition-all duration-200 text-white flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-md hover:shadow-lg",
|
|
184
|
+
style: { backgroundColor: "var(--theme-element)" },
|
|
185
|
+
onMouseEnter: (e) => {
|
|
186
|
+
if (!(!isCurrentStepValid || isLoading)) e.currentTarget.style.filter = "brightness(0.9)";
|
|
187
|
+
},
|
|
188
|
+
onMouseLeave: (e) => {
|
|
189
|
+
e.currentTarget.style.filter = "brightness(1)";
|
|
190
|
+
},
|
|
191
|
+
"aria-label": submitLabel,
|
|
192
|
+
children: [isLoading && /* @__PURE__ */ jsx(Loader, {
|
|
193
|
+
size: 16,
|
|
194
|
+
full: false
|
|
195
|
+
}), submitLabel]
|
|
196
|
+
}) : /* @__PURE__ */ jsxs("button", {
|
|
197
|
+
type: "button",
|
|
198
|
+
onClick: handleNext,
|
|
199
|
+
disabled: !canGoToNext || isLoading,
|
|
200
|
+
className: "px-8 py-3 rounded-lg font-medium transition-all duration-200 text-white flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-md hover:shadow-lg",
|
|
201
|
+
style: { backgroundColor: "var(--theme-element)" },
|
|
202
|
+
onMouseEnter: (e) => {
|
|
203
|
+
if (!(!canGoToNext || isLoading)) e.currentTarget.style.filter = "brightness(0.9)";
|
|
204
|
+
},
|
|
205
|
+
onMouseLeave: (e) => {
|
|
206
|
+
e.currentTarget.style.filter = "brightness(1)";
|
|
207
|
+
},
|
|
208
|
+
"aria-label": "Go to next step",
|
|
209
|
+
children: [isLoading && /* @__PURE__ */ jsx(Loader, {
|
|
210
|
+
size: 16,
|
|
211
|
+
full: false
|
|
212
|
+
}), nextLabel || "Next →"]
|
|
213
|
+
})]
|
|
214
|
+
})] });
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/form/Wizard/WizardStepContent.tsx
|
|
218
|
+
function WizardStepContent({ currentStep, stepConfig, flatten = true, values, onValuesChange, setFormDirty, setFieldValue, setFieldTouched, ...fieldsProps }) {
|
|
219
|
+
if (!stepConfig?.options) return null;
|
|
220
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
221
|
+
className: "mb-4 w-full",
|
|
222
|
+
children: [
|
|
223
|
+
stepConfig.title && /* @__PURE__ */ jsx("h2", {
|
|
224
|
+
className: "text-2xl font-bold mb-2",
|
|
225
|
+
style: { color: "var(--theme-app)" },
|
|
226
|
+
children: stepConfig.title
|
|
227
|
+
}),
|
|
228
|
+
stepConfig.description && /* @__PURE__ */ jsx("p", {
|
|
229
|
+
className: "mb-4 leading-relaxed",
|
|
230
|
+
style: { color: "var(--theme-muted-text-color)" },
|
|
231
|
+
children: stepConfig.description
|
|
232
|
+
}),
|
|
233
|
+
/* @__PURE__ */ jsx(FieldsFromOptions, {
|
|
234
|
+
options: stepConfig.options,
|
|
235
|
+
values,
|
|
236
|
+
setFormDirty,
|
|
237
|
+
setFieldValue,
|
|
238
|
+
setFieldTouched,
|
|
239
|
+
...flatten === false ? { parentInfo: {
|
|
240
|
+
parentName: currentStep,
|
|
241
|
+
fieldNamePrefix: `${currentStep}.`
|
|
242
|
+
} } : {},
|
|
243
|
+
...fieldsProps
|
|
244
|
+
}, currentStep)
|
|
245
|
+
]
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/form/Wizard/WizardStepIndicator.tsx
|
|
250
|
+
function WizardStepIndicator({ stepOrder, currentStep, steps, visitedSteps, invalidSteps, onStepClick, allowJump = false, hideStepNumbers = false }) {
|
|
251
|
+
const currentIndex = useMemo(() => stepOrder.indexOf(currentStep), [stepOrder, currentStep]);
|
|
252
|
+
const getStepStatus = (stepKey, index) => {
|
|
253
|
+
if (stepKey === currentStep) return "active";
|
|
254
|
+
if (index < currentIndex) return "completed";
|
|
255
|
+
return "upcoming";
|
|
256
|
+
};
|
|
257
|
+
const handleStepClick = (stepKey) => {
|
|
258
|
+
if (!allowJump || !visitedSteps.has(stepKey)) return;
|
|
259
|
+
onStepClick?.(stepKey);
|
|
260
|
+
};
|
|
261
|
+
const isClickable = (stepKey) => {
|
|
262
|
+
return allowJump && visitedSteps.has(stepKey) && stepKey !== currentStep;
|
|
263
|
+
};
|
|
264
|
+
return /* @__PURE__ */ jsx("div", {
|
|
265
|
+
className: "flex items-start justify-between w-full mb-2 gap-4 py-2",
|
|
266
|
+
children: stepOrder.map((stepKey, index) => {
|
|
267
|
+
const stepConfig = steps[stepKey];
|
|
268
|
+
const status = getStepStatus(stepKey, index);
|
|
269
|
+
const isInvalid = invalidSteps.has(stepKey);
|
|
270
|
+
const isClickable_ = isClickable(stepKey);
|
|
271
|
+
const isDotActive = status === "active";
|
|
272
|
+
const isDotCompleted = status === "completed";
|
|
273
|
+
const isDotUpcoming = status === "upcoming";
|
|
274
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
275
|
+
className: "flex flex-col items-center flex-1 relative",
|
|
276
|
+
children: [
|
|
277
|
+
/* @__PURE__ */ jsxs("button", {
|
|
278
|
+
type: "button",
|
|
279
|
+
onClick: () => handleStepClick(stepKey),
|
|
280
|
+
disabled: !isClickable_,
|
|
281
|
+
className: cx("w-12 h-12 rounded-full border-3 flex items-center justify-center font-bold text-base transition-all duration-300 flex-shrink-0 relative", {
|
|
282
|
+
"cursor-pointer": isClickable_,
|
|
283
|
+
"cursor-not-allowed": !isClickable_
|
|
284
|
+
}, {
|
|
285
|
+
"text-white": isDotActive,
|
|
286
|
+
"bg-green-500 border-green-600 text-white shadow-md": isDotCompleted && !isInvalid,
|
|
287
|
+
"bg-(--theme-border) theme-border theme-muted-text shadow-sm": isDotUpcoming && !isInvalid,
|
|
288
|
+
"bg-(--theme-muted-panel-bg) theme-border theme-muted-text shadow-sm": !isDotActive && !isDotCompleted && !isDotUpcoming && !isInvalid,
|
|
289
|
+
"bg-red-50 border-red-500 text-red-600 shadow-sm": isInvalid && status !== "active"
|
|
290
|
+
}),
|
|
291
|
+
style: isDotActive ? {
|
|
292
|
+
backgroundColor: "var(--theme-element)",
|
|
293
|
+
borderColor: "var(--theme-element)",
|
|
294
|
+
color: "var(--theme-element-text)",
|
|
295
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
296
|
+
zIndex: 10
|
|
297
|
+
} : { zIndex: 10 },
|
|
298
|
+
"aria-label": `Step ${index + 1}: ${stepConfig?.title}`,
|
|
299
|
+
title: stepConfig?.title,
|
|
300
|
+
children: [!hideStepNumbers && /* @__PURE__ */ jsx("span", {
|
|
301
|
+
className: cx({ hidden: status === "completed" }),
|
|
302
|
+
children: index + 1
|
|
303
|
+
}), status === "completed" && /* @__PURE__ */ jsx("svg", {
|
|
304
|
+
className: "w-4 h-4",
|
|
305
|
+
fill: "none",
|
|
306
|
+
stroke: "currentColor",
|
|
307
|
+
viewBox: "0 0 24 24",
|
|
308
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
309
|
+
strokeLinecap: "round",
|
|
310
|
+
strokeLinejoin: "round",
|
|
311
|
+
strokeWidth: 3,
|
|
312
|
+
d: "M5 13l4 4L19 7"
|
|
313
|
+
})
|
|
314
|
+
})]
|
|
315
|
+
}),
|
|
316
|
+
index < stepOrder.length - 1 && /* @__PURE__ */ jsx("div", {
|
|
317
|
+
className: cx("absolute h-1 transition-all duration-300", {
|
|
318
|
+
"bg-green-500": index < currentIndex,
|
|
319
|
+
"bg-(--theme-border)": index >= currentIndex,
|
|
320
|
+
"opacity-25": index >= currentIndex
|
|
321
|
+
}),
|
|
322
|
+
style: {
|
|
323
|
+
top: "24px",
|
|
324
|
+
left: "calc(50% + 24px)",
|
|
325
|
+
right: "-50%",
|
|
326
|
+
boxShadow: index < currentIndex ? "0 1px 2px rgba(34, 197, 94, 0.2)" : "none"
|
|
327
|
+
},
|
|
328
|
+
"aria-hidden": "true"
|
|
329
|
+
}),
|
|
330
|
+
/* @__PURE__ */ jsxs("div", {
|
|
331
|
+
className: "mt-2 text-center",
|
|
332
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
333
|
+
className: cx("text-sm text-center transition-colors duration-300 max-w-30", {
|
|
334
|
+
"font-bold text-green-600": isDotCompleted && !isInvalid,
|
|
335
|
+
"font-bold text-red-600": isInvalid && status !== "active",
|
|
336
|
+
"font-semibold theme-muted-text": !isDotActive && !isDotCompleted && !isInvalid,
|
|
337
|
+
"font-bold text-white": isDotActive
|
|
338
|
+
}),
|
|
339
|
+
style: isDotActive ? { color: "var(--theme-element)" } : {},
|
|
340
|
+
children: stepConfig?.title
|
|
341
|
+
}), stepConfig?.description && /* @__PURE__ */ jsx("div", {
|
|
342
|
+
className: "text-xs theme-muted-text mt-1 text-center max-w-30",
|
|
343
|
+
children: stepConfig.description
|
|
344
|
+
})]
|
|
345
|
+
})
|
|
346
|
+
]
|
|
347
|
+
}, stepKey);
|
|
348
|
+
})
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/form/Wizard/useWizardState.ts
|
|
353
|
+
function useWizardState({ stepOrder, steps, validateStep, initialStep }) {
|
|
354
|
+
const firstStep = initialStep || stepOrder[0];
|
|
355
|
+
const [currentStep, setCurrentStep] = useState(firstStep);
|
|
356
|
+
const [visitedSteps, setVisitedSteps] = useState(new Set([firstStep]));
|
|
357
|
+
const [invalidSteps, setInvalidSteps] = useState(/* @__PURE__ */ new Set());
|
|
358
|
+
const currentStepIndex = stepOrder.indexOf(currentStep);
|
|
359
|
+
const isLastStep = currentStepIndex === stepOrder.length - 1;
|
|
360
|
+
const canGoBack = currentStepIndex > 0;
|
|
361
|
+
/**
|
|
362
|
+
* Mark step as visited
|
|
363
|
+
*/
|
|
364
|
+
const markStepVisited = useCallback((stepKey) => {
|
|
365
|
+
setVisitedSteps((prev) => new Set([...prev, stepKey]));
|
|
366
|
+
}, []);
|
|
367
|
+
/**
|
|
368
|
+
* Mark step as invalid
|
|
369
|
+
*/
|
|
370
|
+
const markStepInvalid = useCallback((stepKey, invalid) => {
|
|
371
|
+
setInvalidSteps((prev) => {
|
|
372
|
+
const next = new Set(prev);
|
|
373
|
+
if (invalid) next.add(stepKey);
|
|
374
|
+
else next.delete(stepKey);
|
|
375
|
+
return next;
|
|
376
|
+
});
|
|
377
|
+
}, []);
|
|
378
|
+
const goToNext = useCallback(async () => {
|
|
379
|
+
if (isLastStep) return false;
|
|
380
|
+
if (steps[currentStep]?.validateOnNext !== false) {
|
|
381
|
+
if (!await validateStep(currentStep)) {
|
|
382
|
+
markStepInvalid(currentStep, true);
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
markStepInvalid(currentStep, false);
|
|
387
|
+
const nextStepKey = stepOrder[currentStepIndex + 1];
|
|
388
|
+
setCurrentStep(nextStepKey);
|
|
389
|
+
markStepVisited(nextStepKey);
|
|
390
|
+
return true;
|
|
391
|
+
}, [
|
|
392
|
+
currentStep,
|
|
393
|
+
currentStepIndex,
|
|
394
|
+
isLastStep,
|
|
395
|
+
steps,
|
|
396
|
+
stepOrder,
|
|
397
|
+
validateStep,
|
|
398
|
+
markStepInvalid,
|
|
399
|
+
markStepVisited
|
|
400
|
+
]);
|
|
401
|
+
const goToPrevious = useCallback(() => {
|
|
402
|
+
if (!canGoBack) return;
|
|
403
|
+
const prevStepKey = stepOrder[currentStepIndex - 1];
|
|
404
|
+
setCurrentStep(prevStepKey);
|
|
405
|
+
}, [
|
|
406
|
+
canGoBack,
|
|
407
|
+
currentStepIndex,
|
|
408
|
+
stepOrder
|
|
409
|
+
]);
|
|
410
|
+
const jumpToStep = useCallback((stepKey) => {
|
|
411
|
+
if (!stepOrder.includes(stepKey)) return false;
|
|
412
|
+
if (!visitedSteps.has(stepKey) && stepKey !== currentStep) return false;
|
|
413
|
+
setCurrentStep(stepKey);
|
|
414
|
+
return true;
|
|
415
|
+
}, [
|
|
416
|
+
stepOrder,
|
|
417
|
+
visitedSteps,
|
|
418
|
+
currentStep
|
|
419
|
+
]);
|
|
420
|
+
/**
|
|
421
|
+
* Reset wizard to initial state
|
|
422
|
+
*/
|
|
423
|
+
const reset = useCallback(() => {
|
|
424
|
+
setCurrentStep(firstStep);
|
|
425
|
+
setVisitedSteps(new Set([firstStep]));
|
|
426
|
+
setInvalidSteps(/* @__PURE__ */ new Set());
|
|
427
|
+
}, [firstStep]);
|
|
428
|
+
/**
|
|
429
|
+
* Get current wizard state
|
|
430
|
+
*/
|
|
431
|
+
const getState = useCallback(() => {
|
|
432
|
+
return {
|
|
433
|
+
currentStep,
|
|
434
|
+
visitedSteps,
|
|
435
|
+
invalidSteps,
|
|
436
|
+
stepOrder,
|
|
437
|
+
totalSteps: stepOrder.length,
|
|
438
|
+
isLastStep,
|
|
439
|
+
canProceedToNext: !isLastStep,
|
|
440
|
+
canGoBack
|
|
441
|
+
};
|
|
442
|
+
}, [
|
|
443
|
+
currentStep,
|
|
444
|
+
visitedSteps,
|
|
445
|
+
invalidSteps,
|
|
446
|
+
stepOrder,
|
|
447
|
+
isLastStep,
|
|
448
|
+
canGoBack
|
|
449
|
+
]);
|
|
450
|
+
return {
|
|
451
|
+
currentStep,
|
|
452
|
+
visitedSteps,
|
|
453
|
+
invalidSteps,
|
|
454
|
+
isLastStep,
|
|
455
|
+
canGoBack,
|
|
456
|
+
currentStepIndex,
|
|
457
|
+
totalSteps: stepOrder.length,
|
|
458
|
+
goToNext,
|
|
459
|
+
goToPrevious,
|
|
460
|
+
jumpToStep,
|
|
461
|
+
reset,
|
|
462
|
+
markStepVisited,
|
|
463
|
+
markStepInvalid,
|
|
464
|
+
getState
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/form/Wizard/WizardContainer.tsx
|
|
469
|
+
function WizardContainer({ wizardConfig, values, onChange, onSubmit, className = "", labelPosition = "left", missingFields = [], spaceCompact = false, workflowForm = false, setFieldValue, setFieldTouched }) {
|
|
470
|
+
const { validateForm, setTouched, touched } = useFormikContext();
|
|
471
|
+
const { config, steps, stepOrder } = wizardConfig;
|
|
472
|
+
const { currentStep, visitedSteps, invalidSteps, isLastStep, canGoBack, goToNext, goToPrevious, jumpToStep } = useWizardState({
|
|
473
|
+
stepOrder,
|
|
474
|
+
steps,
|
|
475
|
+
validateStep: useCallback(async (stepKey) => {
|
|
476
|
+
const stepConfig = steps[stepKey];
|
|
477
|
+
if (!stepConfig?.options) return true;
|
|
478
|
+
const stepFieldNames = Object.keys(stepConfig.options);
|
|
479
|
+
const shouldFlatten = config.flatten !== false;
|
|
480
|
+
const errors = await validateForm();
|
|
481
|
+
const hasStepErrors = shouldFlatten ? stepFieldNames.some((fieldName) => errors[fieldName]) : stepFieldNames.some((fieldName) => errors[stepKey]?.[fieldName]);
|
|
482
|
+
if (hasStepErrors) {
|
|
483
|
+
const touchedFields = stepFieldNames.reduce((acc, fieldName) => {
|
|
484
|
+
acc[fieldName] = true;
|
|
485
|
+
return acc;
|
|
486
|
+
}, {});
|
|
487
|
+
if (shouldFlatten) setTouched({
|
|
488
|
+
...touched,
|
|
489
|
+
...touchedFields
|
|
490
|
+
}, false);
|
|
491
|
+
else setTouched({
|
|
492
|
+
...touched,
|
|
493
|
+
[stepKey]: touchedFields
|
|
494
|
+
}, false);
|
|
495
|
+
}
|
|
496
|
+
return !hasStepErrors;
|
|
497
|
+
}, [
|
|
498
|
+
steps,
|
|
499
|
+
validateForm,
|
|
500
|
+
setTouched,
|
|
501
|
+
touched
|
|
502
|
+
])
|
|
503
|
+
});
|
|
504
|
+
const currentStepConfig = steps[currentStep];
|
|
505
|
+
const config_ = config.navigation || {};
|
|
506
|
+
const handleGoToNext = useCallback(async () => {
|
|
507
|
+
const success = await goToNext();
|
|
508
|
+
if (success) onChange?.(values);
|
|
509
|
+
return success;
|
|
510
|
+
}, [
|
|
511
|
+
goToNext,
|
|
512
|
+
values,
|
|
513
|
+
onChange
|
|
514
|
+
]);
|
|
515
|
+
const handleJumpToStep = useCallback((stepKey) => {
|
|
516
|
+
if (!config_.allowJump || !visitedSteps.has(stepKey)) return false;
|
|
517
|
+
return jumpToStep(stepKey);
|
|
518
|
+
}, [
|
|
519
|
+
jumpToStep,
|
|
520
|
+
config_.allowJump,
|
|
521
|
+
visitedSteps
|
|
522
|
+
]);
|
|
523
|
+
const handleSubmit = useCallback(async () => {
|
|
524
|
+
if (!isLastStep) return;
|
|
525
|
+
const errors = await validateForm();
|
|
526
|
+
if (Object.keys(errors).length > 0) {
|
|
527
|
+
const touchedFields = Object.keys(values).reduce((acc, fieldName) => {
|
|
528
|
+
acc[fieldName] = true;
|
|
529
|
+
return acc;
|
|
530
|
+
}, {});
|
|
531
|
+
setTouched({
|
|
532
|
+
...touched,
|
|
533
|
+
...touchedFields
|
|
534
|
+
}, false);
|
|
535
|
+
throw new Error("Please fix errors before submitting");
|
|
536
|
+
}
|
|
537
|
+
await onSubmit?.(values);
|
|
538
|
+
}, [
|
|
539
|
+
isLastStep,
|
|
540
|
+
validateForm,
|
|
541
|
+
values,
|
|
542
|
+
setTouched,
|
|
543
|
+
touched,
|
|
544
|
+
onSubmit
|
|
545
|
+
]);
|
|
546
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
547
|
+
className,
|
|
548
|
+
children: [
|
|
549
|
+
config_.showSteps !== false && /* @__PURE__ */ jsx(WizardStepIndicator, {
|
|
550
|
+
stepOrder,
|
|
551
|
+
currentStep,
|
|
552
|
+
steps,
|
|
553
|
+
visitedSteps,
|
|
554
|
+
invalidSteps,
|
|
555
|
+
onStepClick: handleJumpToStep,
|
|
556
|
+
allowJump: config_.allowJump,
|
|
557
|
+
hideStepNumbers: config_.hideStepNumbers
|
|
558
|
+
}),
|
|
559
|
+
currentStepConfig && /* @__PURE__ */ jsx("div", {
|
|
560
|
+
className: "mb-4",
|
|
561
|
+
children: /* @__PURE__ */ jsx(WizardStepContent, {
|
|
562
|
+
currentStep,
|
|
563
|
+
stepConfig: currentStepConfig,
|
|
564
|
+
flatten: config.flatten,
|
|
565
|
+
values,
|
|
566
|
+
onValuesChange: onChange,
|
|
567
|
+
labelPosition,
|
|
568
|
+
missingFields,
|
|
569
|
+
spaceCompact,
|
|
570
|
+
setFormDirty: () => onChange?.(values),
|
|
571
|
+
workflowForm,
|
|
572
|
+
setFieldValue,
|
|
573
|
+
setFieldTouched
|
|
574
|
+
})
|
|
575
|
+
}),
|
|
576
|
+
/* @__PURE__ */ jsx(WizardNavigation, {
|
|
577
|
+
currentStep,
|
|
578
|
+
stepOrder,
|
|
579
|
+
canGoToNext: !isLastStep,
|
|
580
|
+
canGoBack,
|
|
581
|
+
isCurrentStepValid: !invalidSteps.has(currentStep),
|
|
582
|
+
nextLabel: currentStepConfig?.nextLabel,
|
|
583
|
+
prevLabel: currentStepConfig?.prevLabel,
|
|
584
|
+
submitLabel: config.submitLabel || "Execute",
|
|
585
|
+
onNext: handleGoToNext,
|
|
586
|
+
onPrevious: goToPrevious,
|
|
587
|
+
onSubmit: handleSubmit
|
|
588
|
+
})
|
|
589
|
+
]
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/form/Wizard/utils.ts
|
|
594
|
+
function parseWizardConfig(options) {
|
|
595
|
+
const wizardConfig = options.$meta?.wizard;
|
|
596
|
+
if (!wizardConfig || wizardConfig.mode !== "wizard") return null;
|
|
597
|
+
const steps = {};
|
|
598
|
+
const stepOrder = [];
|
|
599
|
+
const otherFields = {};
|
|
600
|
+
Object.entries(options).forEach(([key, field]) => {
|
|
601
|
+
if (key === "$meta") return;
|
|
602
|
+
const fieldConfig = field;
|
|
603
|
+
if (fieldConfig?.type === "step") {
|
|
604
|
+
steps[key] = fieldConfig;
|
|
605
|
+
stepOrder.push(key);
|
|
606
|
+
} else otherFields[key] = field;
|
|
607
|
+
});
|
|
608
|
+
if (stepOrder.length === 0) {
|
|
609
|
+
console.warn("Wizard mode enabled but no steps found");
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
config: wizardConfig,
|
|
614
|
+
steps,
|
|
615
|
+
stepOrder,
|
|
616
|
+
otherFields
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
//#endregion
|
|
620
|
+
//#region src/form/SpecialTypes/FormikCustomDropdown.tsx
|
|
621
|
+
function toComparableOptions(options) {
|
|
622
|
+
return options?.map((option) => {
|
|
623
|
+
if (typeof option === "string") return option;
|
|
624
|
+
if (isCategory(option)) return {
|
|
625
|
+
category: option.category,
|
|
626
|
+
options: option.options.map((o) => {
|
|
627
|
+
const base = {
|
|
628
|
+
label: o.label,
|
|
629
|
+
value: o.value,
|
|
630
|
+
disabled: o.disabled
|
|
631
|
+
};
|
|
632
|
+
return o.description !== void 0 ? {
|
|
633
|
+
...base,
|
|
634
|
+
description: o.description
|
|
635
|
+
} : base;
|
|
636
|
+
})
|
|
637
|
+
};
|
|
638
|
+
const base = {
|
|
639
|
+
label: option.label,
|
|
640
|
+
value: option.value,
|
|
641
|
+
disabled: option.disabled
|
|
642
|
+
};
|
|
643
|
+
return option.description !== void 0 ? {
|
|
644
|
+
...base,
|
|
645
|
+
description: option.description
|
|
646
|
+
} : base;
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
function arePropsEqual(prev, next) {
|
|
650
|
+
for (const key of Object.keys(next)) if (key === "options") {
|
|
651
|
+
if (!deepEqual(toComparableOptions(prev.options), toComparableOptions(next.options))) return false;
|
|
652
|
+
} else if (prev[key] !== next[key]) return false;
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
var FormikCustomDropdown_default = memo(function FormikCustomDropdown({ name, ariaLabel, options, setFormDirty, onChange, parentValue, className, secondaryField, disabled = false, loading = false, invalid = false, autoselect, value, allowCustomValue = false, customValueLabel, placeholder, currentValue, setFieldValue, setFieldTouched }) {
|
|
656
|
+
const prevParentRef = useRef(parentValue);
|
|
657
|
+
const handleChange = (val) => {
|
|
658
|
+
if (secondaryField && typeof options[0] !== "string") {
|
|
659
|
+
const option = findSecondaryOption(options, val);
|
|
660
|
+
if (option) applySecondaryField(secondaryField, option.secondaryValue, setFieldValue);
|
|
661
|
+
}
|
|
662
|
+
if (onChange) onChange(val);
|
|
663
|
+
setFieldValue(name, val);
|
|
664
|
+
setFieldTouched(name, true);
|
|
665
|
+
setFormDirty(true);
|
|
666
|
+
};
|
|
667
|
+
useEffect(() => {
|
|
668
|
+
prevParentRef.current = parentValue;
|
|
669
|
+
});
|
|
670
|
+
const prevOptions = useRef(null);
|
|
671
|
+
useEffect(() => {
|
|
672
|
+
if (deepEqual(prevOptions.current, toComparableOptions(options))) return;
|
|
673
|
+
if (prevOptions.current === null && currentValue !== void 0 && currentValue !== null) {
|
|
674
|
+
prevOptions.current = toComparableOptions(options);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
prevOptions.current = toComparableOptions(options);
|
|
678
|
+
if (autoselect === false || !options?.length) return;
|
|
679
|
+
if (autoselect === void 0) {
|
|
680
|
+
if (options.length && isCategory(options[0])) {
|
|
681
|
+
let optionCount = 0;
|
|
682
|
+
for (const category of options) {
|
|
683
|
+
if (isCategory(category)) optionCount += category.options.length;
|
|
684
|
+
if (optionCount > 1) return;
|
|
685
|
+
}
|
|
686
|
+
} else if (options.length !== 1) return;
|
|
687
|
+
}
|
|
688
|
+
if (!options?.find((option) => {
|
|
689
|
+
if (typeof option === "string") return option === currentValue;
|
|
690
|
+
else if (isCategory(option)) return option.options.some((subOption) => {
|
|
691
|
+
return subOption.value === currentValue;
|
|
692
|
+
});
|
|
693
|
+
return option.value === currentValue;
|
|
694
|
+
}) || autoselect) {
|
|
695
|
+
let val;
|
|
696
|
+
for (const option of options) if (typeof option === "string") {
|
|
697
|
+
val = option;
|
|
698
|
+
break;
|
|
699
|
+
} else if (isCategory(option)) {
|
|
700
|
+
const enabled = option.options.find((o) => !o.disabled);
|
|
701
|
+
if (enabled) {
|
|
702
|
+
val = enabled.value;
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
} else if (!option.disabled) {
|
|
706
|
+
val = option.value;
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
if (val !== void 0) handleChange(val);
|
|
710
|
+
}
|
|
711
|
+
}, [
|
|
712
|
+
parentValue,
|
|
713
|
+
options,
|
|
714
|
+
autoselect
|
|
715
|
+
]);
|
|
716
|
+
return /* @__PURE__ */ jsx(Dropdown, {
|
|
717
|
+
textBoxClassName: cx(className, invalid && "invalid"),
|
|
718
|
+
ariaLabel,
|
|
719
|
+
onChange: (val) => handleChange(val),
|
|
720
|
+
value: options?.length === 0 && !allowCustomValue ? value : currentValue,
|
|
721
|
+
options,
|
|
722
|
+
placeholder,
|
|
723
|
+
disabled,
|
|
724
|
+
loading,
|
|
725
|
+
allowCustomValue,
|
|
726
|
+
customValueLabel
|
|
727
|
+
});
|
|
728
|
+
}, arePropsEqual);
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region src/form/SpecialTypes/FormikCustomInput.tsx
|
|
731
|
+
var DEBOUNCE_MS$2 = 500;
|
|
732
|
+
function FormikCustomInput({ name, type, step, placeholder, onChange, disabled, min, max, sanitize, invalid = false, lowercase = false, minLength, maxLength, defaultValue = "", value }) {
|
|
733
|
+
const [field] = useField(name);
|
|
734
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
735
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
736
|
+
const isPassword = type === "password";
|
|
737
|
+
const [localValue, setLocalValue] = useState(value ?? field.value);
|
|
738
|
+
const isFocused = useRef(false);
|
|
739
|
+
const debounceRef = useRef(null);
|
|
740
|
+
useEffect(() => {
|
|
741
|
+
if (!isFocused.current) setLocalValue(value ?? field.value);
|
|
742
|
+
}, [field.value, value]);
|
|
743
|
+
const sanitizeName = useCallback((val) => {
|
|
744
|
+
if (sanitize === void 0) return lowercase ? val.toLowerCase() : val;
|
|
745
|
+
try {
|
|
746
|
+
const regexp = new RegExp(sanitize);
|
|
747
|
+
if (lowercase) val = val.toLowerCase();
|
|
748
|
+
return val.replace(regexp, "");
|
|
749
|
+
} catch {
|
|
750
|
+
console.error("Invalid regex for sanitization:", sanitize);
|
|
751
|
+
}
|
|
752
|
+
return val;
|
|
753
|
+
}, [sanitize, lowercase]);
|
|
754
|
+
const flushToFormik = useCallback((val) => {
|
|
755
|
+
if (onChange) onChange(val);
|
|
756
|
+
setFieldTouched(name, true);
|
|
757
|
+
setFieldValue(name, val);
|
|
758
|
+
}, [
|
|
759
|
+
name,
|
|
760
|
+
onChange,
|
|
761
|
+
setFieldTouched,
|
|
762
|
+
setFieldValue
|
|
763
|
+
]);
|
|
764
|
+
const handleChange = (e) => {
|
|
765
|
+
let val = e.target.value;
|
|
766
|
+
if (sanitize) val = sanitizeName(val);
|
|
767
|
+
if (type === "number") {
|
|
768
|
+
const parsed = parseFloat(val);
|
|
769
|
+
val = isNaN(parsed) ? void 0 : parsed;
|
|
770
|
+
}
|
|
771
|
+
if (type === "password" && val === "") val = defaultValue;
|
|
772
|
+
setLocalValue(val);
|
|
773
|
+
clearTimeout(debounceRef.current ?? void 0);
|
|
774
|
+
debounceRef.current = setTimeout(() => flushToFormik(val), DEBOUNCE_MS$2);
|
|
775
|
+
};
|
|
776
|
+
const handleFocus = () => {
|
|
777
|
+
isFocused.current = true;
|
|
778
|
+
};
|
|
779
|
+
const handleBlur = () => {
|
|
780
|
+
isFocused.current = false;
|
|
781
|
+
clearTimeout(debounceRef.current ?? void 0);
|
|
782
|
+
const current = localValue;
|
|
783
|
+
if (current === void 0 || current === null) return;
|
|
784
|
+
let val = current.toString();
|
|
785
|
+
if (type === "number") {
|
|
786
|
+
const parsed = parseFloat(val);
|
|
787
|
+
if (Number.isNaN(parsed)) val = void 0;
|
|
788
|
+
else if (min !== void 0 && parsed < min) val = min;
|
|
789
|
+
else if (max !== void 0 && parsed > max) val = max;
|
|
790
|
+
else val = parsed;
|
|
791
|
+
}
|
|
792
|
+
setLocalValue(val);
|
|
793
|
+
flushToFormik(val);
|
|
794
|
+
};
|
|
795
|
+
const displayValue = isPassword && localValue === defaultValue ? "" : localValue ?? "";
|
|
796
|
+
const resolvedType = isPassword ? showPassword ? "text" : "password" : type === "string" ? "text" : type;
|
|
797
|
+
const input = /* @__PURE__ */ jsx("input", {
|
|
798
|
+
"data-1p-ignore": true,
|
|
799
|
+
className: cx("w-full", disabled && "disabled", invalid && "invalid", isPassword && "pr-8"),
|
|
800
|
+
name,
|
|
801
|
+
value: displayValue,
|
|
802
|
+
type: resolvedType,
|
|
803
|
+
disabled,
|
|
804
|
+
placeholder,
|
|
805
|
+
minLength,
|
|
806
|
+
maxLength,
|
|
807
|
+
onChange: handleChange,
|
|
808
|
+
onFocus: handleFocus,
|
|
809
|
+
onBlur: handleBlur,
|
|
810
|
+
min,
|
|
811
|
+
max,
|
|
812
|
+
step
|
|
813
|
+
});
|
|
814
|
+
if (!isPassword) return input;
|
|
815
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
816
|
+
className: "relative w-full",
|
|
817
|
+
children: [input, /* @__PURE__ */ jsx("button", {
|
|
818
|
+
type: "button",
|
|
819
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 theme-muted-text",
|
|
820
|
+
onClick: () => setShowPassword((prev) => !prev),
|
|
821
|
+
disabled,
|
|
822
|
+
tabIndex: -1,
|
|
823
|
+
"aria-label": showPassword ? "Hide value" : "Show value",
|
|
824
|
+
children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(EyeIcon, { className: "h-4 w-4" })
|
|
825
|
+
})]
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
//#endregion
|
|
829
|
+
//#region src/form/SpecialTypes/FormikNumericInput.tsx
|
|
830
|
+
var DEBOUNCE_MS$1 = 500;
|
|
831
|
+
function FormikNumericInput({ name, placeholder, onChange, disabled, min, max, invalid = false }) {
|
|
832
|
+
const [field] = useField(name);
|
|
833
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
834
|
+
const [localValue, setLocalValue] = useState(field.value);
|
|
835
|
+
const isFocused = useRef(false);
|
|
836
|
+
const debounceRef = useRef(null);
|
|
837
|
+
useEffect(() => {
|
|
838
|
+
if (!isFocused.current) setLocalValue(field.value);
|
|
839
|
+
}, [field.value]);
|
|
840
|
+
const flush = (val) => {
|
|
841
|
+
onChange?.(val);
|
|
842
|
+
setFieldTouched(name, true);
|
|
843
|
+
setFieldValue(name, val);
|
|
844
|
+
};
|
|
845
|
+
const handleBlur = () => {
|
|
846
|
+
isFocused.current = false;
|
|
847
|
+
clearTimeout(debounceRef.current ?? void 0);
|
|
848
|
+
let val = localValue;
|
|
849
|
+
if (val !== void 0) {
|
|
850
|
+
if (min !== void 0 && val < min) val = min;
|
|
851
|
+
if (max !== void 0 && val > max) val = max;
|
|
852
|
+
}
|
|
853
|
+
setLocalValue(val);
|
|
854
|
+
flush(val);
|
|
855
|
+
};
|
|
856
|
+
return /* @__PURE__ */ jsx(NumericFormat, {
|
|
857
|
+
"data-1p-ignore": true,
|
|
858
|
+
className: cx("w-full", disabled && "disabled", invalid && "invalid"),
|
|
859
|
+
name,
|
|
860
|
+
value: localValue,
|
|
861
|
+
thousandSeparator: ",",
|
|
862
|
+
decimalScale: 20,
|
|
863
|
+
allowNegative: true,
|
|
864
|
+
disabled,
|
|
865
|
+
placeholder,
|
|
866
|
+
onValueChange: (v) => {
|
|
867
|
+
setLocalValue(v.floatValue);
|
|
868
|
+
clearTimeout(debounceRef.current ?? void 0);
|
|
869
|
+
debounceRef.current = setTimeout(() => flush(v.floatValue), DEBOUNCE_MS$1);
|
|
870
|
+
},
|
|
871
|
+
onFocus: () => {
|
|
872
|
+
isFocused.current = true;
|
|
873
|
+
},
|
|
874
|
+
onBlur: handleBlur
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
//#endregion
|
|
878
|
+
//#region src/form/utils/getPlaceholder.ts
|
|
879
|
+
/** `placeholder=""` (empty string) intentionally overrides the default; undefined does not. */
|
|
880
|
+
function getPlaceholder(placeholder, defaultValue) {
|
|
881
|
+
if (placeholder !== void 0) return placeholder;
|
|
882
|
+
if (defaultValue && (typeof defaultValue === "string" || typeof defaultValue === "number")) return defaultValue.toString();
|
|
883
|
+
return "";
|
|
884
|
+
}
|
|
885
|
+
//#endregion
|
|
886
|
+
//#region src/form/Fields/StringField.tsx
|
|
887
|
+
var StringField_default = memo(function StringField(props) {
|
|
888
|
+
const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, currentValue } = props;
|
|
889
|
+
const value = field.prefillDefault ? currentValue : void 0;
|
|
890
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
891
|
+
optional: field.optional,
|
|
892
|
+
label,
|
|
893
|
+
tooltipComponent,
|
|
894
|
+
description: field.description,
|
|
895
|
+
labelPosition,
|
|
896
|
+
spaceCompact,
|
|
897
|
+
children: /* @__PURE__ */ jsx(FormikCustomInput, {
|
|
898
|
+
invalid: missing,
|
|
899
|
+
name: field.name,
|
|
900
|
+
type: field.type,
|
|
901
|
+
lowercase: field.lowercase,
|
|
902
|
+
placeholder: field.type === "password" && field.placeholder === void 0 ? "•".repeat((field.default ?? "").toString().length > 0 ? 16 : 0) : getPlaceholder(field.placeholder, field.default),
|
|
903
|
+
disabled,
|
|
904
|
+
step: field.step,
|
|
905
|
+
min: field.min,
|
|
906
|
+
max: field.max,
|
|
907
|
+
minLength: field.minLength,
|
|
908
|
+
maxLength: field.maxLength,
|
|
909
|
+
sanitize: field.sanitize,
|
|
910
|
+
defaultValue: field.default,
|
|
911
|
+
value,
|
|
912
|
+
...onChange && { onChange }
|
|
913
|
+
}, field.name)
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
//#endregion
|
|
917
|
+
//#region src/form/Fields/NumberField.tsx
|
|
918
|
+
function NumberField(props) {
|
|
919
|
+
const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact } = props;
|
|
920
|
+
const [fieldState] = useField(field.name);
|
|
921
|
+
const { setFieldValue } = useFormikContext();
|
|
922
|
+
useEffect(() => {
|
|
923
|
+
if (field.prefillDefault && field.default !== void 0 && (fieldState.value === void 0 || fieldState.value === null || fieldState.value === "")) setFieldValue(field.name, field.default);
|
|
924
|
+
}, [
|
|
925
|
+
field.prefillDefault,
|
|
926
|
+
field.default,
|
|
927
|
+
field.name,
|
|
928
|
+
fieldState.value,
|
|
929
|
+
setFieldValue
|
|
930
|
+
]);
|
|
931
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
932
|
+
optional: field.optional,
|
|
933
|
+
label,
|
|
934
|
+
tooltipComponent,
|
|
935
|
+
labelPosition,
|
|
936
|
+
spaceCompact,
|
|
937
|
+
children: field.thousandsSeparator ? /* @__PURE__ */ jsx(FormikNumericInput, {
|
|
938
|
+
invalid: missing,
|
|
939
|
+
name: field.name,
|
|
940
|
+
placeholder: getPlaceholder(field.placeholder, field.default),
|
|
941
|
+
disabled,
|
|
942
|
+
min: field.min,
|
|
943
|
+
max: field.max,
|
|
944
|
+
...onChange && { onChange }
|
|
945
|
+
}, field.name) : /* @__PURE__ */ jsx(FormikCustomInput, {
|
|
946
|
+
invalid: missing,
|
|
947
|
+
name: field.name,
|
|
948
|
+
type: "number",
|
|
949
|
+
placeholder: getPlaceholder(field.placeholder, field.default),
|
|
950
|
+
disabled,
|
|
951
|
+
step: field.step,
|
|
952
|
+
min: field.min,
|
|
953
|
+
max: field.max,
|
|
954
|
+
defaultValue: field.default,
|
|
955
|
+
...onChange && { onChange }
|
|
956
|
+
}, field.name)
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region src/form/Fields/BooleanField.tsx
|
|
961
|
+
function BooleanField(props) {
|
|
962
|
+
const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty } = props;
|
|
963
|
+
const [fieldState] = useField(field.name);
|
|
964
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
965
|
+
const handleChange = (val) => {
|
|
966
|
+
if (disabled) return;
|
|
967
|
+
if (onChange) onChange(val);
|
|
968
|
+
setFieldTouched(field.name, true);
|
|
969
|
+
setFieldValue(field.name, val);
|
|
970
|
+
setFormDirty(true);
|
|
971
|
+
};
|
|
972
|
+
const value = fieldState.value;
|
|
973
|
+
const options = field.options ? {
|
|
974
|
+
onOption: String(field.options.onOption ?? true),
|
|
975
|
+
offOption: String(field.options.offOption ?? false)
|
|
976
|
+
} : void 0;
|
|
977
|
+
const displayLabel = (option) => option.charAt(0).toUpperCase() + option.slice(1).toLowerCase();
|
|
978
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
979
|
+
optional: true,
|
|
980
|
+
label,
|
|
981
|
+
tooltipComponent: /* @__PURE__ */ jsx("div", { children: tooltipComponent }),
|
|
982
|
+
description: field.description,
|
|
983
|
+
labelPosition,
|
|
984
|
+
spaceCompact,
|
|
985
|
+
children: options ? /* @__PURE__ */ jsx(SwitchToggle, {
|
|
986
|
+
onChange: (on) => handleChange(on ? options.onOption : options.offOption),
|
|
987
|
+
value: value === true || value === options.onOption,
|
|
988
|
+
yesLabel: displayLabel(options.onOption),
|
|
989
|
+
noLabel: displayLabel(options.offOption),
|
|
990
|
+
disabled,
|
|
991
|
+
invalid: missing
|
|
992
|
+
}) : /* @__PURE__ */ jsx(SwitchToggle, {
|
|
993
|
+
datatip: field.disabled_message,
|
|
994
|
+
onChange: handleChange,
|
|
995
|
+
value: value === true || value === "true",
|
|
996
|
+
disabled,
|
|
997
|
+
invalid: missing
|
|
998
|
+
})
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
//#endregion
|
|
1002
|
+
//#region src/form/Fields/DropdownField.tsx
|
|
1003
|
+
function DropdownField(props) {
|
|
1004
|
+
const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFieldValue, setFieldTouched, currentValue } = props;
|
|
1005
|
+
let parentValue = field.parentValue;
|
|
1006
|
+
let options = Array.isArray(field.options) && field.options || [];
|
|
1007
|
+
if (field.depends_on) {
|
|
1008
|
+
parentValue = field.depends_on;
|
|
1009
|
+
if (!Array.isArray(field.options)) options = field.options[parentValue];
|
|
1010
|
+
}
|
|
1011
|
+
const secondaryField = Array.isArray(field.secondaryField) ? field.secondaryField[0] : field.secondaryField;
|
|
1012
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
1013
|
+
optional: field.optional,
|
|
1014
|
+
label,
|
|
1015
|
+
tooltipComponent,
|
|
1016
|
+
description: field.description,
|
|
1017
|
+
labelPosition,
|
|
1018
|
+
spaceCompact,
|
|
1019
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1020
|
+
className: "grow min-w-0",
|
|
1021
|
+
children: /* @__PURE__ */ jsx(FormikCustomDropdown_default, {
|
|
1022
|
+
invalid: missing,
|
|
1023
|
+
className: "w-full",
|
|
1024
|
+
name: field.name,
|
|
1025
|
+
ariaLabel: label,
|
|
1026
|
+
options,
|
|
1027
|
+
setFormDirty: props.setFormDirty,
|
|
1028
|
+
parentValue,
|
|
1029
|
+
onChange,
|
|
1030
|
+
secondaryField,
|
|
1031
|
+
disabled,
|
|
1032
|
+
resetOnChange: field.resetOnChange ? field.name : "",
|
|
1033
|
+
loading: false,
|
|
1034
|
+
autoselect: field.autoselect,
|
|
1035
|
+
allowCustomValue: field.allowCustomValue,
|
|
1036
|
+
customValueLabel: field.customValueLabel,
|
|
1037
|
+
placeholder: getPlaceholder(field.placeholder, typeof field.default === "string" ? field.default : void 0),
|
|
1038
|
+
currentValue,
|
|
1039
|
+
setFieldValue,
|
|
1040
|
+
setFieldTouched
|
|
1041
|
+
}, field.name)
|
|
1042
|
+
})
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
//#endregion
|
|
1046
|
+
//#region src/form/Fields/TextAreaField.tsx
|
|
1047
|
+
function TextAreaField(props) {
|
|
1048
|
+
const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact } = props;
|
|
1049
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
1050
|
+
optional: field.optional,
|
|
1051
|
+
label,
|
|
1052
|
+
tooltipComponent,
|
|
1053
|
+
description: field.description,
|
|
1054
|
+
labelPosition,
|
|
1055
|
+
spaceCompact,
|
|
1056
|
+
children: /* @__PURE__ */ jsx(Field, {
|
|
1057
|
+
className: cx("theme-input h-[82px] w-full rounded leading-4 overflow-y-scroll", disabled && "disabled", missing && "border-red-600 focus:border-red-600 focus:ring-red-500"),
|
|
1058
|
+
as: "textarea",
|
|
1059
|
+
name: field.name,
|
|
1060
|
+
type: "search",
|
|
1061
|
+
placeholder: getPlaceholder(field.placeholder, field.default),
|
|
1062
|
+
disabled,
|
|
1063
|
+
...onChange && { onChange }
|
|
1064
|
+
}, field.name)
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
//#endregion
|
|
1068
|
+
//#region src/form/Fields/RangeField.tsx
|
|
1069
|
+
function RangeField(props) {
|
|
1070
|
+
const { field, label, labelPosition, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty } = props;
|
|
1071
|
+
const [fieldState] = useField(field.name);
|
|
1072
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
1073
|
+
const handleChange = (e) => {
|
|
1074
|
+
let val = e.target.valueAsNumber;
|
|
1075
|
+
if (!isNaN(val)) {
|
|
1076
|
+
val = Math.max(field.min, Math.min(field.max, val));
|
|
1077
|
+
setFieldValue(field.name, val);
|
|
1078
|
+
if (onChange) onChange(val);
|
|
1079
|
+
setFieldTouched(field.name, true);
|
|
1080
|
+
setFormDirty(true);
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
const seedValue = field.default ?? field.min;
|
|
1084
|
+
if ((fieldState.value === void 0 || fieldState.value === null) && seedValue !== void 0) setFieldValue(field.name, seedValue);
|
|
1085
|
+
const currentValue = fieldState.value !== void 0 ? fieldState.value : seedValue;
|
|
1086
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
1087
|
+
optional: field.optional,
|
|
1088
|
+
label,
|
|
1089
|
+
tooltipComponent,
|
|
1090
|
+
labelPosition,
|
|
1091
|
+
spaceCompact,
|
|
1092
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1093
|
+
className: "flex items-center",
|
|
1094
|
+
children: [/* @__PURE__ */ jsx(Field, {
|
|
1095
|
+
className: cx("theme-input h-7 w-1/3 mr-5 text-center rounded border text-[14px]", disabled && "disabled"),
|
|
1096
|
+
type: "number",
|
|
1097
|
+
value: currentValue,
|
|
1098
|
+
min: field.min,
|
|
1099
|
+
max: field.max,
|
|
1100
|
+
step: field.step || 1,
|
|
1101
|
+
disabled,
|
|
1102
|
+
onChange: handleChange
|
|
1103
|
+
}), /* @__PURE__ */ jsx(Field, {
|
|
1104
|
+
className: cx("rounded w-full", disabled && "disabled"),
|
|
1105
|
+
name: field.name,
|
|
1106
|
+
value: currentValue,
|
|
1107
|
+
type: "range",
|
|
1108
|
+
min: field.min,
|
|
1109
|
+
max: field.max,
|
|
1110
|
+
step: field.step || 1,
|
|
1111
|
+
disabled,
|
|
1112
|
+
onChange: handleChange
|
|
1113
|
+
})]
|
|
1114
|
+
})
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
//#endregion
|
|
1118
|
+
//#region src/form/utils/slurmDuration.ts
|
|
1119
|
+
var WITH_DAYS = /^(\d+)-(\d{1,2})(?::(\d{1,2})(?::(\d{1,2}))?)?$/;
|
|
1120
|
+
var WITHOUT_DAYS = /^(\d+):(\d{1,2}):(\d{1,2})$/;
|
|
1121
|
+
function parseSlurmDuration(text) {
|
|
1122
|
+
const trimmed = text.trim();
|
|
1123
|
+
let days = 0;
|
|
1124
|
+
let hours;
|
|
1125
|
+
let minutes;
|
|
1126
|
+
let seconds;
|
|
1127
|
+
const withDays = trimmed.match(WITH_DAYS);
|
|
1128
|
+
if (withDays) {
|
|
1129
|
+
days = Number(withDays[1]);
|
|
1130
|
+
hours = Number(withDays[2]);
|
|
1131
|
+
minutes = Number(withDays[3] ?? 0);
|
|
1132
|
+
seconds = Number(withDays[4] ?? 0);
|
|
1133
|
+
if (hours > 23) return null;
|
|
1134
|
+
} else {
|
|
1135
|
+
const withoutDays = trimmed.match(WITHOUT_DAYS);
|
|
1136
|
+
if (!withoutDays) return null;
|
|
1137
|
+
hours = Number(withoutDays[1]);
|
|
1138
|
+
minutes = Number(withoutDays[2]);
|
|
1139
|
+
seconds = Number(withoutDays[3]);
|
|
1140
|
+
}
|
|
1141
|
+
if (minutes > 59 || seconds > 59) return null;
|
|
1142
|
+
return days * 86400 + hours * 3600 + minutes * 60 + seconds;
|
|
1143
|
+
}
|
|
1144
|
+
function formatSecondsAsSlurmDuration(totalSeconds) {
|
|
1145
|
+
if (!Number.isFinite(totalSeconds) || totalSeconds < 0) return "";
|
|
1146
|
+
const parts = Duration.fromObject({ seconds: Math.floor(totalSeconds) }).shiftTo("days", "hours", "minutes", "seconds");
|
|
1147
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1148
|
+
const time = `${pad(parts.hours)}:${pad(parts.minutes)}:${pad(parts.seconds)}`;
|
|
1149
|
+
return parts.days > 0 ? `${parts.days}-${time}` : time;
|
|
1150
|
+
}
|
|
1151
|
+
var FLEX_MAX_DURATION_RANGE = {
|
|
1152
|
+
min: 600,
|
|
1153
|
+
max: 604800
|
|
1154
|
+
};
|
|
1155
|
+
var FLEX_WAIT_TIME_RANGE = {
|
|
1156
|
+
min: 30,
|
|
1157
|
+
max: 604800
|
|
1158
|
+
};
|
|
1159
|
+
var STANDARD_MAX_DURATION_RANGE = {
|
|
1160
|
+
min: 30,
|
|
1161
|
+
max: 10368e3
|
|
1162
|
+
};
|
|
1163
|
+
var DURATION_PATH = /^partitions\[\d+\]\.(maxDuration|flexStartWaitTime|suspendTime)$/;
|
|
1164
|
+
function collectPartitionDurationIssues(partitions, invalidDurationPaths) {
|
|
1165
|
+
const issues = new Set(invalidDurationPaths.filter((path) => DURATION_PATH.test(path)));
|
|
1166
|
+
(partitions ?? []).forEach((partition, i) => {
|
|
1167
|
+
const p = partition;
|
|
1168
|
+
const check = (fieldName, range, required) => {
|
|
1169
|
+
const raw = p?.[fieldName];
|
|
1170
|
+
if (raw === void 0 || raw === null || raw === "") {
|
|
1171
|
+
if (required) issues.add(`partitions[${i}].${fieldName}`);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const value = Number(raw);
|
|
1175
|
+
if (!Number.isFinite(value) || value < range.min || value > range.max) issues.add(`partitions[${i}].${fieldName}`);
|
|
1176
|
+
};
|
|
1177
|
+
if (p?.provisioningMode === "flex") {
|
|
1178
|
+
check("maxDuration", FLEX_MAX_DURATION_RANGE, true);
|
|
1179
|
+
check("flexStartWaitTime", FLEX_WAIT_TIME_RANGE, true);
|
|
1180
|
+
} else check("maxDuration", STANDARD_MAX_DURATION_RANGE, false);
|
|
1181
|
+
});
|
|
1182
|
+
return [...issues];
|
|
1183
|
+
}
|
|
1184
|
+
//#endregion
|
|
1185
|
+
//#region src/form/Fields/durationValidity.ts
|
|
1186
|
+
var invalidPaths = /* @__PURE__ */ new Set();
|
|
1187
|
+
function setDurationValidity(path, valid) {
|
|
1188
|
+
if (valid) invalidPaths.delete(path);
|
|
1189
|
+
else invalidPaths.add(path);
|
|
1190
|
+
}
|
|
1191
|
+
function getInvalidDurationPaths() {
|
|
1192
|
+
return [...invalidPaths];
|
|
1193
|
+
}
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region src/form/Fields/DurationField.tsx
|
|
1196
|
+
var DEBOUNCE_MS = 500;
|
|
1197
|
+
var isNumericString = (value) => typeof value === "string" && /^\d+$/.test(value.trim());
|
|
1198
|
+
var toDisplay = (value) => {
|
|
1199
|
+
if (typeof value === "number") return formatSecondsAsSlurmDuration(value);
|
|
1200
|
+
if (isNumericString(value)) return formatSecondsAsSlurmDuration(Number(value));
|
|
1201
|
+
return "";
|
|
1202
|
+
};
|
|
1203
|
+
var DurationField_default = memo(function DurationField({ field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty, currentValue, setFieldValue, setFieldTouched }) {
|
|
1204
|
+
const { form: t } = useStrings();
|
|
1205
|
+
const name = field.name;
|
|
1206
|
+
const min = field.min !== void 0 ? Number(field.min) : Number.NEGATIVE_INFINITY;
|
|
1207
|
+
const max = field.max !== void 0 ? Number(field.max) : Number.POSITIVE_INFINITY;
|
|
1208
|
+
const disableValue = field.disableValue;
|
|
1209
|
+
const hasDisableCheckbox = disableValue !== void 0 && field.disableLabel !== void 0;
|
|
1210
|
+
const isSentinel = hasDisableCheckbox && currentValue !== "" && Number(currentValue) === disableValue;
|
|
1211
|
+
const [text, setText] = useState(() => toDisplay(currentValue));
|
|
1212
|
+
const focusedRef = useRef(false);
|
|
1213
|
+
const debounceRef = useRef(null);
|
|
1214
|
+
useEffect(() => {
|
|
1215
|
+
if (isSentinel) {
|
|
1216
|
+
if (typeof currentValue !== "number") setFieldValue?.(name, disableValue);
|
|
1217
|
+
} else if (isNumericString(currentValue)) setFieldValue?.(name, Number(currentValue));
|
|
1218
|
+
else if ((currentValue === void 0 || currentValue === null || currentValue === "") && typeof field.default === "number") setFieldValue?.(name, field.default);
|
|
1219
|
+
}, []);
|
|
1220
|
+
useEffect(() => {
|
|
1221
|
+
if (!focusedRef.current) setText(toDisplay(currentValue));
|
|
1222
|
+
}, [currentValue]);
|
|
1223
|
+
const trimmed = text.trim();
|
|
1224
|
+
const parsed = trimmed === "" ? null : parseSlurmDuration(trimmed);
|
|
1225
|
+
let error = null;
|
|
1226
|
+
if (!isSentinel && trimmed !== "") {
|
|
1227
|
+
if (parsed === null) error = t.invalidDuration;
|
|
1228
|
+
else if (parsed < min || parsed > max) error = Number.isFinite(max) ? t.durationOutOfRange(formatSecondsAsSlurmDuration(Math.max(min, 0)), formatSecondsAsSlurmDuration(max)) : t.durationBelowMin(formatSecondsAsSlurmDuration(Math.max(min, 0)));
|
|
1229
|
+
}
|
|
1230
|
+
useEffect(() => {
|
|
1231
|
+
setDurationValidity(name, error === null);
|
|
1232
|
+
}, [name, error]);
|
|
1233
|
+
useEffect(() => () => {
|
|
1234
|
+
setDurationValidity(name, true);
|
|
1235
|
+
clearTimeout(debounceRef.current ?? void 0);
|
|
1236
|
+
}, [name]);
|
|
1237
|
+
const flushToFormik = (val) => {
|
|
1238
|
+
setFieldValue?.(name, val);
|
|
1239
|
+
onChange?.(val);
|
|
1240
|
+
setFieldTouched?.(name, true);
|
|
1241
|
+
};
|
|
1242
|
+
const handleDisableToggle = (checked) => {
|
|
1243
|
+
clearTimeout(debounceRef.current ?? void 0);
|
|
1244
|
+
setFormDirty(true);
|
|
1245
|
+
if (checked) {
|
|
1246
|
+
setText("");
|
|
1247
|
+
flushToFormik(disableValue);
|
|
1248
|
+
} else flushToFormik(typeof field.default === "number" ? field.default : "");
|
|
1249
|
+
};
|
|
1250
|
+
const handleChange = (e) => {
|
|
1251
|
+
const raw = e.target.value;
|
|
1252
|
+
setText(raw);
|
|
1253
|
+
setFormDirty(true);
|
|
1254
|
+
clearTimeout(debounceRef.current ?? void 0);
|
|
1255
|
+
const rawTrimmed = raw.trim();
|
|
1256
|
+
const value = rawTrimmed === "" ? "" : parseSlurmDuration(rawTrimmed);
|
|
1257
|
+
if (value === "" || typeof value === "number" && value >= min && value <= max) debounceRef.current = setTimeout(() => flushToFormik(value), DEBOUNCE_MS);
|
|
1258
|
+
};
|
|
1259
|
+
const handleFocus = () => {
|
|
1260
|
+
focusedRef.current = true;
|
|
1261
|
+
};
|
|
1262
|
+
const handleBlur = () => {
|
|
1263
|
+
focusedRef.current = false;
|
|
1264
|
+
clearTimeout(debounceRef.current ?? void 0);
|
|
1265
|
+
if (trimmed === "") {
|
|
1266
|
+
setText("");
|
|
1267
|
+
flushToFormik("");
|
|
1268
|
+
} else if (parsed !== null && parsed >= min && parsed <= max) {
|
|
1269
|
+
setText(formatSecondsAsSlurmDuration(parsed));
|
|
1270
|
+
flushToFormik(parsed);
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
return /* @__PURE__ */ jsxs(FieldWrapper, {
|
|
1274
|
+
optional: field.optional ?? false,
|
|
1275
|
+
label,
|
|
1276
|
+
tooltipComponent,
|
|
1277
|
+
labelPosition,
|
|
1278
|
+
spaceCompact: spaceCompact ?? false,
|
|
1279
|
+
...field.description !== void 0 && { description: field.description },
|
|
1280
|
+
children: [
|
|
1281
|
+
!isSentinel && /* @__PURE__ */ jsx("input", {
|
|
1282
|
+
"data-1p-ignore": true,
|
|
1283
|
+
className: cx("w-full", disabled && "disabled", (missing || error !== null) && "invalid"),
|
|
1284
|
+
name,
|
|
1285
|
+
value: text,
|
|
1286
|
+
type: "text",
|
|
1287
|
+
disabled,
|
|
1288
|
+
placeholder: field.placeholder ?? "DD-HH:MM:SS",
|
|
1289
|
+
onChange: handleChange,
|
|
1290
|
+
onFocus: handleFocus,
|
|
1291
|
+
onBlur: handleBlur
|
|
1292
|
+
}),
|
|
1293
|
+
error && /* @__PURE__ */ jsx("div", {
|
|
1294
|
+
className: "text-xs text-red-500 mt-1",
|
|
1295
|
+
children: error
|
|
1296
|
+
}),
|
|
1297
|
+
hasDisableCheckbox && /* @__PURE__ */ jsxs("label", {
|
|
1298
|
+
className: cx("flex items-center gap-2 mt-1", disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"),
|
|
1299
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
1300
|
+
type: "checkbox",
|
|
1301
|
+
checked: isSentinel,
|
|
1302
|
+
disabled,
|
|
1303
|
+
onChange: (e) => handleDisableToggle(e.target.checked),
|
|
1304
|
+
className: cx("h-4 w-4 accent-(--theme-link)", disabled ? "cursor-not-allowed" : "cursor-pointer")
|
|
1305
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1306
|
+
className: "text-sm theme-text",
|
|
1307
|
+
children: field.disableLabel
|
|
1308
|
+
})]
|
|
1309
|
+
})
|
|
1310
|
+
]
|
|
1311
|
+
});
|
|
1312
|
+
});
|
|
1313
|
+
//#endregion
|
|
1314
|
+
//#region src/form/Fields/RadioField.tsx
|
|
1315
|
+
function RadioField(props) {
|
|
1316
|
+
const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty } = props;
|
|
1317
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
1318
|
+
const handleChange = (val) => {
|
|
1319
|
+
if (disabled) return;
|
|
1320
|
+
if (onChange) onChange(val);
|
|
1321
|
+
setFieldTouched(field.name, true);
|
|
1322
|
+
setFieldValue(field.name, val);
|
|
1323
|
+
setFormDirty(true);
|
|
1324
|
+
};
|
|
1325
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
1326
|
+
optional: field.optional,
|
|
1327
|
+
label,
|
|
1328
|
+
tooltipComponent,
|
|
1329
|
+
labelPosition,
|
|
1330
|
+
spaceCompact,
|
|
1331
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1332
|
+
className: cx("flex", field.optionLabelPosition === "top" ? "flex-col" : "flex-row"),
|
|
1333
|
+
children: field.options?.map((option) => /* @__PURE__ */ jsxs("label", {
|
|
1334
|
+
className: "capitalize",
|
|
1335
|
+
children: [/* @__PURE__ */ jsx(Field, {
|
|
1336
|
+
className: cx("theme-input mr-1", disabled && "disabled", missing && "invalid"),
|
|
1337
|
+
name: field.name,
|
|
1338
|
+
type: "radio",
|
|
1339
|
+
value: option,
|
|
1340
|
+
disabled,
|
|
1341
|
+
onChange: (e) => {
|
|
1342
|
+
handleChange(e.target.value);
|
|
1343
|
+
}
|
|
1344
|
+
}), option]
|
|
1345
|
+
}, field.name + option))
|
|
1346
|
+
})
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
//#endregion
|
|
1350
|
+
//#region src/form/Fields/LabelField.tsx
|
|
1351
|
+
function LabelField(props) {
|
|
1352
|
+
const { field, label } = props;
|
|
1353
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1354
|
+
className: "flex items-center w-full",
|
|
1355
|
+
children: /* @__PURE__ */ jsx("h3", {
|
|
1356
|
+
style: field.size ? {
|
|
1357
|
+
fontSize: field.size.toString() + "px",
|
|
1358
|
+
whiteSpace: "pre-wrap"
|
|
1359
|
+
} : { whiteSpace: "pre-wrap" },
|
|
1360
|
+
className: cx("relative", "text-md", field.bold !== false ? "font-bold" : ""),
|
|
1361
|
+
children: field.text && field.text.toString().replace(/(\r)/g, "").replace(/(\\n)/g, "\n") || label
|
|
1362
|
+
})
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/form/Fields/ColorField.tsx
|
|
1367
|
+
function ColorField(props) {
|
|
1368
|
+
const { field, label, labelPosition, onChange, tooltipComponent, spaceCompact } = props;
|
|
1369
|
+
const [fieldState] = useField(field.name);
|
|
1370
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
1371
|
+
const handleChange = (e) => {
|
|
1372
|
+
const val = e.target.value;
|
|
1373
|
+
if (onChange) onChange(val);
|
|
1374
|
+
setFieldTouched(field.name, true);
|
|
1375
|
+
setFieldValue(field.name, val);
|
|
1376
|
+
};
|
|
1377
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
1378
|
+
optional: field.optional,
|
|
1379
|
+
label,
|
|
1380
|
+
tooltipComponent,
|
|
1381
|
+
labelPosition,
|
|
1382
|
+
spaceCompact,
|
|
1383
|
+
children: /* @__PURE__ */ jsx(Field, {
|
|
1384
|
+
className: "theme-input h-8 w-18 rounded border border-neutral-400 text-[14px] placeholder:text-xs",
|
|
1385
|
+
name: field.name,
|
|
1386
|
+
value: fieldState.value || "#fbbf24",
|
|
1387
|
+
type: "color",
|
|
1388
|
+
onChange: handleChange
|
|
1389
|
+
})
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
//#endregion
|
|
1393
|
+
//#region src/form/Fields/MultiDropdownField.tsx
|
|
1394
|
+
function MultiDropdownField({ field, label, tooltipComponent, labelPosition, spaceCompact, disabled, missing, setFormDirty, onChange }) {
|
|
1395
|
+
let parentValue = field.parentValue;
|
|
1396
|
+
let options = field.options;
|
|
1397
|
+
if (field.depends_on) {
|
|
1398
|
+
parentValue = field.depends_on;
|
|
1399
|
+
if (!Array.isArray(field.options)) options = field.options[parentValue] || [];
|
|
1400
|
+
}
|
|
1401
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
1402
|
+
optional: field.optional,
|
|
1403
|
+
label,
|
|
1404
|
+
tooltipComponent,
|
|
1405
|
+
labelPosition,
|
|
1406
|
+
spaceCompact,
|
|
1407
|
+
children: disabled ? /* @__PURE__ */ jsx(Field, {
|
|
1408
|
+
className: "theme-input h-7 w-full rounded border text-[14px] placeholder:text-xs",
|
|
1409
|
+
type: "search",
|
|
1410
|
+
name: field.name,
|
|
1411
|
+
disabled: true,
|
|
1412
|
+
onChange
|
|
1413
|
+
}, field.name) : /* @__PURE__ */ jsx(MultiSelectionDropdown, {
|
|
1414
|
+
className: "themee-input h-7 w-full rounded border text-[14px] flex items-center px-3 py-2",
|
|
1415
|
+
name: field.name,
|
|
1416
|
+
label: field.label || label,
|
|
1417
|
+
options: Array.isArray(options) ? options : [],
|
|
1418
|
+
setFormDirty,
|
|
1419
|
+
parentValue,
|
|
1420
|
+
onChange,
|
|
1421
|
+
secondaryField: typeof field.secondaryField === "string" ? field.secondaryField : void 0,
|
|
1422
|
+
invalid: missing
|
|
1423
|
+
}, field.name)
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
//#endregion
|
|
1427
|
+
//#region src/form/Fields/CheckboxGroupField.tsx
|
|
1428
|
+
function impliedFrom(values, implies = {}) {
|
|
1429
|
+
const result = /* @__PURE__ */ new Set();
|
|
1430
|
+
for (const value of values) for (const target of implies[value] ?? []) result.add(target);
|
|
1431
|
+
return result;
|
|
1432
|
+
}
|
|
1433
|
+
function union(manual, forced) {
|
|
1434
|
+
return [...manual, ...[...forced].filter((v) => !manual.includes(v))];
|
|
1435
|
+
}
|
|
1436
|
+
function CheckboxGroupField({ field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty }) {
|
|
1437
|
+
const [fieldState] = useField(field.name);
|
|
1438
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
1439
|
+
const implies = field.implies;
|
|
1440
|
+
const [manual, setManual] = useState(() => {
|
|
1441
|
+
const saved = fieldState.value || [];
|
|
1442
|
+
const impliedBySaved = impliedFrom(saved, implies);
|
|
1443
|
+
return saved.filter((v) => !impliedBySaved.has(v));
|
|
1444
|
+
});
|
|
1445
|
+
const forced = impliedFrom(manual, implies);
|
|
1446
|
+
const handleToggle = (value) => {
|
|
1447
|
+
if (disabled) return;
|
|
1448
|
+
const nextManual = manual.includes(value) ? manual.filter((v) => v !== value) : [...manual, value];
|
|
1449
|
+
const next = union(nextManual, impliedFrom(nextManual, implies));
|
|
1450
|
+
setManual(nextManual);
|
|
1451
|
+
setFieldTouched(field.name, true);
|
|
1452
|
+
setFieldValue(field.name, next);
|
|
1453
|
+
if (onChange) onChange(next);
|
|
1454
|
+
setFormDirty(true);
|
|
1455
|
+
};
|
|
1456
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
1457
|
+
optional: field.optional,
|
|
1458
|
+
label,
|
|
1459
|
+
tooltipComponent,
|
|
1460
|
+
labelPosition,
|
|
1461
|
+
spaceCompact,
|
|
1462
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1463
|
+
className: "flex max-w-lg flex-col gap-1",
|
|
1464
|
+
children: field.options?.map((option) => {
|
|
1465
|
+
const locked = forced.has(option.value);
|
|
1466
|
+
const optionDisabled = disabled || locked;
|
|
1467
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
1468
|
+
className: cx("flex items-start gap-2 rounded px-2 py-1 transition-colors", optionDisabled ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:bg-(--theme-hover) has-[:focus-visible]:bg-(--theme-hover)"),
|
|
1469
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
1470
|
+
type: "checkbox",
|
|
1471
|
+
checked: manual.includes(option.value) || locked,
|
|
1472
|
+
disabled: optionDisabled,
|
|
1473
|
+
onChange: () => handleToggle(option.value),
|
|
1474
|
+
className: cx("mt-0.5 h-4 w-4 accent-(--theme-link)", optionDisabled ? "cursor-not-allowed" : "cursor-pointer", missing && "invalid")
|
|
1475
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1476
|
+
className: "flex flex-col",
|
|
1477
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1478
|
+
className: "font-medium theme-text",
|
|
1479
|
+
children: option.label
|
|
1480
|
+
}), option.description && /* @__PURE__ */ jsx("span", {
|
|
1481
|
+
className: "text-xs theme-muted-text leading-relaxed",
|
|
1482
|
+
children: option.description
|
|
1483
|
+
})]
|
|
1484
|
+
})]
|
|
1485
|
+
}, field.name + option.value);
|
|
1486
|
+
})
|
|
1487
|
+
})
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
//#endregion
|
|
1491
|
+
//#region src/form/Fields/ObjectField.tsx
|
|
1492
|
+
function ObjectField({ field, label, tooltipComponent, missingFields, setFormDirty, workflowForm, labelPosition, spaceCompact }) {
|
|
1493
|
+
const [open, setOpen] = useState(field.collapsed !== void 0 ? !field.collapsed : true);
|
|
1494
|
+
const { values, setFieldValue, setFieldTouched } = useFormikContext();
|
|
1495
|
+
const meta = resolveMetaOverrides(field.options, labelPosition, spaceCompact ?? false);
|
|
1496
|
+
const HeaderElement = field.noCollapse ? React.Fragment : ControlledCollapsiblePanel;
|
|
1497
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1498
|
+
className: "flex flex-col w-full",
|
|
1499
|
+
children: [field.noCollapse && /* @__PURE__ */ jsx("div", {
|
|
1500
|
+
className: "flex items-center w-full mb-[15px]",
|
|
1501
|
+
children: /* @__PURE__ */ jsxs("h3", {
|
|
1502
|
+
className: "flex items-center gap-1 text-md font-bold",
|
|
1503
|
+
children: [
|
|
1504
|
+
label,
|
|
1505
|
+
" ",
|
|
1506
|
+
tooltipComponent
|
|
1507
|
+
]
|
|
1508
|
+
})
|
|
1509
|
+
}), /* @__PURE__ */ jsx(HeaderElement, {
|
|
1510
|
+
open,
|
|
1511
|
+
setOpen,
|
|
1512
|
+
title: /* @__PURE__ */ jsx(GroupHeader, {
|
|
1513
|
+
open,
|
|
1514
|
+
setOpen,
|
|
1515
|
+
title: field.label || label
|
|
1516
|
+
}),
|
|
1517
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1518
|
+
className: "w-full",
|
|
1519
|
+
children: /* @__PURE__ */ jsx(FieldsFromOptions, {
|
|
1520
|
+
options: field.options,
|
|
1521
|
+
values,
|
|
1522
|
+
setFormDirty,
|
|
1523
|
+
setFieldValue,
|
|
1524
|
+
setFieldTouched,
|
|
1525
|
+
missingFields,
|
|
1526
|
+
workflowForm,
|
|
1527
|
+
labelPosition: meta.labelPosition,
|
|
1528
|
+
spaceCompact: meta.spaceCompact,
|
|
1529
|
+
parentInfo: {
|
|
1530
|
+
parentName: field.name,
|
|
1531
|
+
fieldNamePrefix: `${field.name}.`
|
|
1532
|
+
}
|
|
1533
|
+
})
|
|
1534
|
+
})
|
|
1535
|
+
})]
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
//#endregion
|
|
1539
|
+
//#region src/form/Fields/StepField.tsx
|
|
1540
|
+
function StepField(_props) {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
//#endregion
|
|
1544
|
+
//#region src/form/Fields/BucketReadOnlyField.tsx
|
|
1545
|
+
function BucketReadOnlyField({ field, disabled, missing, labelPosition, setFormDirty }) {
|
|
1546
|
+
const { form: t } = useStrings();
|
|
1547
|
+
const [fieldState] = useField(field.name);
|
|
1548
|
+
const { setFieldTouched, setFieldValue, values } = useFormikContext();
|
|
1549
|
+
const accessFieldName = field.name?.replace(/readOnly$/, "bucketAccessWritable");
|
|
1550
|
+
const forced = fieldState.value === true && !!accessFieldName && getIn(values, accessFieldName) === false;
|
|
1551
|
+
return /* @__PURE__ */ jsx(FieldWrapper, {
|
|
1552
|
+
optional: true,
|
|
1553
|
+
label: t.readOnly,
|
|
1554
|
+
description: t.readOnlyDescription,
|
|
1555
|
+
labelPosition,
|
|
1556
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1557
|
+
className: "flex flex-col gap-2",
|
|
1558
|
+
children: [/* @__PURE__ */ jsx(SwitchToggle, {
|
|
1559
|
+
onChange: (value) => {
|
|
1560
|
+
if (disabled) return;
|
|
1561
|
+
setFieldTouched(field.name, true);
|
|
1562
|
+
setFieldValue(field.name, value);
|
|
1563
|
+
setFormDirty(true);
|
|
1564
|
+
},
|
|
1565
|
+
value: fieldState.value === true,
|
|
1566
|
+
disabled,
|
|
1567
|
+
invalid: missing,
|
|
1568
|
+
yesLabel: t.readOnly,
|
|
1569
|
+
noLabel: t.readWrite
|
|
1570
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
1571
|
+
className: cx("text-sm theme-muted-text max-w-[65ch]", !forced && "hidden"),
|
|
1572
|
+
children: t.readOnlyForced
|
|
1573
|
+
})]
|
|
1574
|
+
})
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
//#endregion
|
|
1578
|
+
//#region src/form/fieldRegistry.tsx
|
|
1579
|
+
var CORE_FIELD_COMPONENTS = {
|
|
1580
|
+
string: StringField_default,
|
|
1581
|
+
password: StringField_default,
|
|
1582
|
+
number: NumberField,
|
|
1583
|
+
boolean: BooleanField,
|
|
1584
|
+
dropdown: DropdownField,
|
|
1585
|
+
textarea: TextAreaField,
|
|
1586
|
+
range: RangeField,
|
|
1587
|
+
duration: DurationField_default,
|
|
1588
|
+
radio: RadioField,
|
|
1589
|
+
label: LabelField,
|
|
1590
|
+
color: ColorField,
|
|
1591
|
+
"multi-dropdown": MultiDropdownField,
|
|
1592
|
+
"checkbox-group": CheckboxGroupField,
|
|
1593
|
+
object: ObjectField,
|
|
1594
|
+
step: StepField,
|
|
1595
|
+
"bucket-read-only": BucketReadOnlyField
|
|
1596
|
+
};
|
|
1597
|
+
var FieldRegistryContext = createContext({});
|
|
1598
|
+
/** Returns null for field types no registry knows. Per-form `fields` win over
|
|
1599
|
+
* the provider's `slots.formFields`, which win over the core registry. */
|
|
1600
|
+
function Registry(props) {
|
|
1601
|
+
const injected = useContext(FieldRegistryContext);
|
|
1602
|
+
const { formFields } = useSlots();
|
|
1603
|
+
const Component = injected[props.field.type] ?? formFields?.[props.field.type] ?? CORE_FIELD_COMPONENTS[props.field.type];
|
|
1604
|
+
if (!Component) return null;
|
|
1605
|
+
return /* @__PURE__ */ jsx(Suspense, {
|
|
1606
|
+
fallback: null,
|
|
1607
|
+
children: /* @__PURE__ */ jsx(Component, { ...props })
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
//#endregion
|
|
1611
|
+
//#region src/form/Form.tsx
|
|
1612
|
+
/** Applies `$meta` overrides for `labelPosition` / `spaceCompact`. */
|
|
1613
|
+
function resolveMetaOverrides(options, labelPosition, spaceCompact) {
|
|
1614
|
+
return {
|
|
1615
|
+
labelPosition: options?.$meta?.labelPosition ?? labelPosition,
|
|
1616
|
+
spaceCompact: options?.$meta?.spaceCompact ?? spaceCompact
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
/** Cheap signature of every list's length; gates the expensive collect walk. */
|
|
1620
|
+
function listLengthsSignature(options, values = {}, prefix = "") {
|
|
1621
|
+
if (!options || typeof options !== "object") return "";
|
|
1622
|
+
const parts = [];
|
|
1623
|
+
for (const [fieldName, field] of Object.entries(options)) {
|
|
1624
|
+
if (fieldName === "$meta" || !field || typeof field !== "object") continue;
|
|
1625
|
+
const fullName = prefix ? `${prefix}${fieldName}` : fieldName;
|
|
1626
|
+
if (field.type === "list") {
|
|
1627
|
+
const items = values?.[fieldName];
|
|
1628
|
+
const len = Array.isArray(items) ? items.length : 0;
|
|
1629
|
+
parts.push(`${fullName}:${len}`);
|
|
1630
|
+
const template = field.template || field.options;
|
|
1631
|
+
if (template && Array.isArray(items)) for (let i = 0; i < items.length; i++) parts.push(listLengthsSignature(template, items[i] || {}, `${fullName}[${i}].`));
|
|
1632
|
+
} else if (field.type === "group" || field.type === "object") {
|
|
1633
|
+
const groupItems = field.items || field.options;
|
|
1634
|
+
if (groupItems) {
|
|
1635
|
+
const nestedValues = values?.[fieldName] || {};
|
|
1636
|
+
parts.push(listLengthsSignature(groupItems, nestedValues, `${fullName}.`));
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return parts.join("|");
|
|
1641
|
+
}
|
|
1642
|
+
/** Collects every field with a `default` for the form-level sync to track. */
|
|
1643
|
+
function collectFieldsWithDefaults(options, values = {}, prefix = "") {
|
|
1644
|
+
const result = [];
|
|
1645
|
+
if (!options || typeof options !== "object") return result;
|
|
1646
|
+
for (const [fieldName, field] of Object.entries(options)) {
|
|
1647
|
+
if (!field || typeof field !== "object") continue;
|
|
1648
|
+
if (fieldName === "$meta") continue;
|
|
1649
|
+
const fullName = prefix ? `${prefix}${fieldName}` : fieldName;
|
|
1650
|
+
if (field.type === "group" || field.type === "object") {
|
|
1651
|
+
const groupItems = field.items || field.options;
|
|
1652
|
+
if (groupItems) {
|
|
1653
|
+
const nestedValues = values?.[fieldName] || {};
|
|
1654
|
+
result.push(...collectFieldsWithDefaults(groupItems, nestedValues, `${fullName}.`));
|
|
1655
|
+
}
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
if (field.type === "list") {
|
|
1659
|
+
const listTemplate = field.template || field.options;
|
|
1660
|
+
const listValues = values?.[fieldName];
|
|
1661
|
+
if (listTemplate && Array.isArray(listValues)) for (let i = 0; i < listValues.length; i++) {
|
|
1662
|
+
const itemValues = listValues[i] || {};
|
|
1663
|
+
result.push(...collectFieldsWithDefaults(listTemplate, itemValues, `${fullName}[${i}].`));
|
|
1664
|
+
}
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
if (field.default !== void 0) result.push({
|
|
1668
|
+
name: fullName,
|
|
1669
|
+
defaultValue: field.default
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
return result;
|
|
1673
|
+
}
|
|
1674
|
+
function defaultReferencesField(defaultValue, fieldPath) {
|
|
1675
|
+
if (typeof defaultValue !== "string" || !defaultValue.includes("${{")) return false;
|
|
1676
|
+
const escaped = fieldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1677
|
+
return new RegExp(`\\binputs\\.${escaped}(?![\\w.[-])`).test(defaultValue);
|
|
1678
|
+
}
|
|
1679
|
+
function findSelfReferencingFieldNames(schema, values = {}) {
|
|
1680
|
+
return collectFieldsWithDefaults(schema, values).filter(({ name, defaultValue }) => defaultReferencesField(defaultValue, name)).map(({ name }) => name);
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Re-syncs each field's value when its resolved default changes between
|
|
1684
|
+
* renders, unless `meta.touched` (this session) or a synth-touched marker
|
|
1685
|
+
* (cross-session, set on first-seen when saved ≠ default) is set. Must be
|
|
1686
|
+
* remounted via `key` when the run/config context changes.
|
|
1687
|
+
*/
|
|
1688
|
+
function DynamicDefaultsSync({ options }) {
|
|
1689
|
+
const { setFieldValue, getFieldMeta, values } = useFormikContext();
|
|
1690
|
+
const fieldsWithDefaults = useMemo(() => collectFieldsWithDefaults(options, values), [options, useMemo(() => listLengthsSignature(options, values), [options, values])]);
|
|
1691
|
+
const prevDefaultsRef = useRef(/* @__PURE__ */ new Map());
|
|
1692
|
+
const synthTouchedRef = useRef(/* @__PURE__ */ new Set());
|
|
1693
|
+
const fieldsWithDefaultsRef = useRef(null);
|
|
1694
|
+
if (!deepEqual(fieldsWithDefaultsRef.current, fieldsWithDefaults)) {
|
|
1695
|
+
fieldsWithDefaultsRef.current = fieldsWithDefaults;
|
|
1696
|
+
const currentNames = new Set(fieldsWithDefaults.map((f) => f.name));
|
|
1697
|
+
for (const name of prevDefaultsRef.current.keys()) if (!currentNames.has(name)) {
|
|
1698
|
+
prevDefaultsRef.current.delete(name);
|
|
1699
|
+
synthTouchedRef.current.delete(name);
|
|
1700
|
+
}
|
|
1701
|
+
for (const { name, defaultValue } of fieldsWithDefaults) {
|
|
1702
|
+
if (synthTouchedRef.current.has(name)) continue;
|
|
1703
|
+
if (getFieldMeta(name).touched) continue;
|
|
1704
|
+
const wasSeen = prevDefaultsRef.current.has(name);
|
|
1705
|
+
const serializedDefault = JSON.stringify(defaultValue ?? null);
|
|
1706
|
+
const prevDefault = prevDefaultsRef.current.get(name);
|
|
1707
|
+
prevDefaultsRef.current.set(name, serializedDefault);
|
|
1708
|
+
if (!wasSeen) {
|
|
1709
|
+
const currVal = JSON.stringify(getValueUsingPath(values, name));
|
|
1710
|
+
if (currVal !== void 0 && currVal !== serializedDefault) synthTouchedRef.current.add(name);
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
if (prevDefault === serializedDefault) continue;
|
|
1714
|
+
if (defaultValue === null || defaultValue === void 0) continue;
|
|
1715
|
+
if (JSON.stringify(getValueUsingPath(values, name)) !== serializedDefault) setFieldValue(name, defaultValue);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return null;
|
|
1719
|
+
}
|
|
1720
|
+
function GroupHeader({ children, open, setOpen, title }) {
|
|
1721
|
+
return /* @__PURE__ */ jsxs(SectionHeader, {
|
|
1722
|
+
className: "flex items-center justify-between pb-1 mb-2 border-b",
|
|
1723
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1724
|
+
className: cx("w-full flex items-center transform ease-in transition cursor-pointer"),
|
|
1725
|
+
onClick: () => setOpen((open) => !open),
|
|
1726
|
+
children: [/* @__PURE__ */ jsx(AngleRightIcon, { className: cx(open && "transform rotate-90 ", "px-1 text-[1.25rem] transition ease-in ") }), /* @__PURE__ */ jsx("span", { children: title })]
|
|
1727
|
+
}), children]
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
function deleteFieldByPath(obj, path) {
|
|
1731
|
+
if (!path) return;
|
|
1732
|
+
const parts = path.replaceAll("[", ".").replaceAll("]", "").split(".");
|
|
1733
|
+
const fieldToRemove = parts.pop();
|
|
1734
|
+
if (!fieldToRemove) {
|
|
1735
|
+
console.error("Invalid path");
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
const parent = parts.reduce((acc, curr) => {
|
|
1739
|
+
if (acc && typeof acc === "object") return acc[curr];
|
|
1740
|
+
return {};
|
|
1741
|
+
}, obj);
|
|
1742
|
+
if (parent && typeof parent === "object" && Object.prototype.hasOwnProperty.call(parent, fieldToRemove)) delete parent[fieldToRemove];
|
|
1743
|
+
}
|
|
1744
|
+
function getClosestParentIndex(parentPath) {
|
|
1745
|
+
for (let i = parentPath.length - 1; i >= 0; i--) if (parentPath[i] === "]") {
|
|
1746
|
+
let j = i - 1;
|
|
1747
|
+
while (j >= 0 && parentPath[j] !== "[") j--;
|
|
1748
|
+
return parseInt(parentPath.slice(j + 1, i));
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function getInstanceTypesForForm(instances) {
|
|
1752
|
+
return Object.keys(instances).reduce((acc, key) => {
|
|
1753
|
+
const validInstances = [];
|
|
1754
|
+
Object.keys(instances[key]).forEach((item) => {
|
|
1755
|
+
const instance = instances[key][item];
|
|
1756
|
+
const secondaryValue = [
|
|
1757
|
+
instances[key][item].Arch,
|
|
1758
|
+
!!instance.AcceleratedNetworking,
|
|
1759
|
+
!!instance.AcceleratedNetworking,
|
|
1760
|
+
!!instance.LocalDiskSizeGb,
|
|
1761
|
+
!!instance.isFlex
|
|
1762
|
+
];
|
|
1763
|
+
const flexLabel = instance.isFlex ? ", Flex" : "";
|
|
1764
|
+
if (instance.LocalDiskSizeGb) validInstances.push({
|
|
1765
|
+
value: item,
|
|
1766
|
+
label: `${item} (${instance.vCPUs} vCPUs, ${instance["Mem (GB)"]} GB Memory, ${instance.Arch} , ${instance.LocalDiskSizeGb} GB Local Disk${flexLabel})`,
|
|
1767
|
+
secondaryValue
|
|
1768
|
+
});
|
|
1769
|
+
else validInstances.push({
|
|
1770
|
+
value: item,
|
|
1771
|
+
label: `${item} (${instance.vCPUs} vCPUs, ${instance["Mem (GB)"]} GB Memory, ${instance.Arch}${flexLabel})`,
|
|
1772
|
+
secondaryValue
|
|
1773
|
+
});
|
|
1774
|
+
});
|
|
1775
|
+
return {
|
|
1776
|
+
...acc,
|
|
1777
|
+
[key]: validInstances
|
|
1778
|
+
};
|
|
1779
|
+
}, {});
|
|
1780
|
+
}
|
|
1781
|
+
function StatusBlock({ status }) {
|
|
1782
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("div", {
|
|
1783
|
+
"data-tooltip-id": TOOLTIP_ID,
|
|
1784
|
+
"data-tooltip-content": status ? "Storage has been attached." : "Attaching the storage to cluster...",
|
|
1785
|
+
children: status ? /* @__PURE__ */ jsx(SuccessCheckmark, {}) : /* @__PURE__ */ jsx(Loader, {
|
|
1786
|
+
size: 13,
|
|
1787
|
+
full: false
|
|
1788
|
+
})
|
|
1789
|
+
}) });
|
|
1790
|
+
}
|
|
1791
|
+
function MultiSelectionDropdown({ name, label = "", invalid = false, options: initialOptions = [], setFormDirty, onChange, parentValue }) {
|
|
1792
|
+
const [field] = useField(name);
|
|
1793
|
+
const { setFieldValue } = useFormikContext();
|
|
1794
|
+
const options = useMemo(() => {
|
|
1795
|
+
return initialOptions.map((option) => {
|
|
1796
|
+
const op = typeof option === "string" ? {
|
|
1797
|
+
label: option,
|
|
1798
|
+
value: option,
|
|
1799
|
+
selected: false
|
|
1800
|
+
} : { ...option };
|
|
1801
|
+
if (field.value?.includes(op.value)) op.selected = true;
|
|
1802
|
+
return op;
|
|
1803
|
+
});
|
|
1804
|
+
}, [initialOptions, field.value]);
|
|
1805
|
+
const handleOptionSelected = (selectedValue) => {
|
|
1806
|
+
let selected = [];
|
|
1807
|
+
if (field.value) selected = [...field.value];
|
|
1808
|
+
selected.push(selectedValue);
|
|
1809
|
+
setFieldValue(name, selected);
|
|
1810
|
+
if (onChange) onChange(options);
|
|
1811
|
+
setFormDirty(true);
|
|
1812
|
+
};
|
|
1813
|
+
const handleRemoveOption = (key) => {
|
|
1814
|
+
setFieldValue(name, [...field.value.filter((item) => item !== key)]);
|
|
1815
|
+
if (onChange) onChange(options);
|
|
1816
|
+
setFormDirty(true);
|
|
1817
|
+
};
|
|
1818
|
+
const prevParent = usePrevious(parentValue);
|
|
1819
|
+
useEffect(() => {
|
|
1820
|
+
if (prevParent && parentValue && prevParent !== parentValue) setFieldValue(name, []);
|
|
1821
|
+
}, [parentValue]);
|
|
1822
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CustomListbox, {
|
|
1823
|
+
options,
|
|
1824
|
+
type: label.toLowerCase(),
|
|
1825
|
+
handleOptionSelected,
|
|
1826
|
+
className: "mb-3",
|
|
1827
|
+
invalid,
|
|
1828
|
+
closeOnSelect: false
|
|
1829
|
+
}), /* @__PURE__ */ jsx(Table, { children: field.value && field.value.length > 0 ? options.filter((option) => option.selected).map((option, index) => {
|
|
1830
|
+
return /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx(Table.Item, {
|
|
1831
|
+
className: "w-full",
|
|
1832
|
+
children: option?.label
|
|
1833
|
+
}), /* @__PURE__ */ jsx(Table.Item, { children: /* @__PURE__ */ jsx(TrashIcon, {
|
|
1834
|
+
className: "link mr-4",
|
|
1835
|
+
onClick: () => handleRemoveOption(option.value)
|
|
1836
|
+
}) })] }, index);
|
|
1837
|
+
}) : /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs(Table.Item, { children: [
|
|
1838
|
+
"No ",
|
|
1839
|
+
label !== "" ? label.toLowerCase() : "options",
|
|
1840
|
+
" selected"
|
|
1841
|
+
] }) }) })] });
|
|
1842
|
+
}
|
|
1843
|
+
var InputField = React.memo(function InputField({ label, field, setFormDirty, onChange, disabled = false, labelPosition = "left", missingFields = [], computeOn, spaceCompact = false, workflowForm = false, setFieldValue, setFieldTouched, values, currentValue }) {
|
|
1844
|
+
const prevField = useRef(field);
|
|
1845
|
+
const fieldObj = useMemo(() => {
|
|
1846
|
+
if (deepEqual(prevField.current, field)) return prevField.current;
|
|
1847
|
+
prevField.current = field;
|
|
1848
|
+
return { ...field };
|
|
1849
|
+
}, [field]);
|
|
1850
|
+
const tooltipComponent = useMemo(() => {
|
|
1851
|
+
if (!fieldObj.tooltip) return null;
|
|
1852
|
+
return /* @__PURE__ */ jsx(TooltipInfo, {
|
|
1853
|
+
className: "pr-2",
|
|
1854
|
+
place: "top",
|
|
1855
|
+
text: typeof fieldObj.tooltip === "string" ? fieldObj.tooltip.replace(/\r/g, "") : fieldObj.tooltip.join("\n")
|
|
1856
|
+
});
|
|
1857
|
+
}, [fieldObj.tooltip]);
|
|
1858
|
+
return /* @__PURE__ */ jsx(Registry, {
|
|
1859
|
+
field: fieldObj,
|
|
1860
|
+
label,
|
|
1861
|
+
labelPosition,
|
|
1862
|
+
missing: missingFields.includes(fieldObj.name),
|
|
1863
|
+
missingFields,
|
|
1864
|
+
values,
|
|
1865
|
+
setFormDirty,
|
|
1866
|
+
onChange,
|
|
1867
|
+
disabled,
|
|
1868
|
+
computeOn,
|
|
1869
|
+
spaceCompact,
|
|
1870
|
+
tooltipComponent,
|
|
1871
|
+
workflowForm,
|
|
1872
|
+
setFieldTouched,
|
|
1873
|
+
setFieldValue,
|
|
1874
|
+
currentValue
|
|
1875
|
+
});
|
|
1876
|
+
}, (prev, next) => {
|
|
1877
|
+
if (!deepEqual(prev.field, next.field)) return false;
|
|
1878
|
+
for (const key of Object.keys(prev)) {
|
|
1879
|
+
if (key === "field" || key === "values") continue;
|
|
1880
|
+
if (prev[key] !== next[key]) return false;
|
|
1881
|
+
}
|
|
1882
|
+
return true;
|
|
1883
|
+
});
|
|
1884
|
+
function FormikStateBridge({ onChange }) {
|
|
1885
|
+
const { values } = useFormikContext();
|
|
1886
|
+
const prevEmitted = useRef(null);
|
|
1887
|
+
useEffect(() => {
|
|
1888
|
+
const { editorKeyPostfix: _, ...next } = values;
|
|
1889
|
+
if (prevEmitted.current !== null && prevEmitted.current !== void 0 && deepEqual(prevEmitted.current, next)) return;
|
|
1890
|
+
prevEmitted.current = next;
|
|
1891
|
+
onChange(next);
|
|
1892
|
+
}, [values, onChange]);
|
|
1893
|
+
return null;
|
|
1894
|
+
}
|
|
1895
|
+
function DirtyStateBridge({ setFormDirty }) {
|
|
1896
|
+
const { dirty } = useFormikContext();
|
|
1897
|
+
useEffect(() => {
|
|
1898
|
+
setFormDirty(dirty);
|
|
1899
|
+
}, [dirty, setFormDirty]);
|
|
1900
|
+
return null;
|
|
1901
|
+
}
|
|
1902
|
+
/** Three-tier cache: no expressions → raw pass-through; input-dep expressions → re-parse on dep change; org-var/path expressions → parse once. */
|
|
1903
|
+
function useFieldParse(optionsField, values, arrayIndex) {
|
|
1904
|
+
const c = useRef({
|
|
1905
|
+
lastOptField: null,
|
|
1906
|
+
convertedField: null,
|
|
1907
|
+
inputDeps: /* @__PURE__ */ new Set(),
|
|
1908
|
+
hasExpressions: false,
|
|
1909
|
+
lastRelevant: {},
|
|
1910
|
+
lastIndex: void 0,
|
|
1911
|
+
result: null
|
|
1912
|
+
}).current;
|
|
1913
|
+
if (optionsField !== c.lastOptField) {
|
|
1914
|
+
c.lastOptField = optionsField;
|
|
1915
|
+
c.convertedField = convertToDynamicForm(optionsField);
|
|
1916
|
+
const { inputDeps, hasExpressions } = extractInputDeps(c.convertedField);
|
|
1917
|
+
c.inputDeps = inputDeps;
|
|
1918
|
+
c.hasExpressions = hasExpressions;
|
|
1919
|
+
c.result = null;
|
|
1920
|
+
}
|
|
1921
|
+
if (!c.hasExpressions) return optionsField;
|
|
1922
|
+
if (c.inputDeps.size === 0) {
|
|
1923
|
+
if (c.result !== null) return c.result;
|
|
1924
|
+
}
|
|
1925
|
+
if (c.result !== null) {
|
|
1926
|
+
let changed = arrayIndex !== c.lastIndex;
|
|
1927
|
+
if (!changed) {
|
|
1928
|
+
for (const key of c.inputDeps) if (!deepEqual(values[key], c.lastRelevant[key])) {
|
|
1929
|
+
changed = true;
|
|
1930
|
+
break;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
if (!changed) return c.result;
|
|
1934
|
+
}
|
|
1935
|
+
c.result = parseStringsOfObj({
|
|
1936
|
+
inputs: values,
|
|
1937
|
+
obj: c.convertedField,
|
|
1938
|
+
orgVars: {},
|
|
1939
|
+
index: arrayIndex
|
|
1940
|
+
});
|
|
1941
|
+
c.lastIndex = arrayIndex;
|
|
1942
|
+
c.lastRelevant = {};
|
|
1943
|
+
for (const key of c.inputDeps) c.lastRelevant[key] = values[key];
|
|
1944
|
+
return c.result;
|
|
1945
|
+
}
|
|
1946
|
+
var FormField = React.memo(function FormField({ id, optionsField, fieldName, values, setFormDirty, setFieldValue, setFieldTouched, parentInfo, labelPosition = "left", missingFields = [], spaceCompact = false, workflowForm = false }) {
|
|
1947
|
+
const parsedField = useFieldParse(optionsField, values, parentInfo?.arrayIndex);
|
|
1948
|
+
const field = parentInfo ? parsedField : optionsField;
|
|
1949
|
+
if (field === null || field === void 0) return field;
|
|
1950
|
+
const fieldNamePrefix = parentInfo?.fieldNamePrefix || "";
|
|
1951
|
+
const fieldObj = {
|
|
1952
|
+
...field,
|
|
1953
|
+
label: field.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1),
|
|
1954
|
+
name: fieldNamePrefix + fieldName,
|
|
1955
|
+
optional: resolvedFlag(field.optional) ?? field.default !== void 0
|
|
1956
|
+
};
|
|
1957
|
+
if (!fieldObj.name || fieldName === "v3") return null;
|
|
1958
|
+
if (field.hidden === true || !field.type) {
|
|
1959
|
+
if (resolvedFlag(field.ignore) && !workflowForm) deleteFieldByPath(values, fieldObj.name);
|
|
1960
|
+
return null;
|
|
1961
|
+
}
|
|
1962
|
+
if (!field.type && field.options) fieldObj.type = "dropdown";
|
|
1963
|
+
let onChange;
|
|
1964
|
+
if (field.one_must_be_true && parentInfo?.onChange) {
|
|
1965
|
+
const parentOnChange = parentInfo.onChange;
|
|
1966
|
+
const parentArrayIndex = parentInfo.arrayIndex ?? 0;
|
|
1967
|
+
onChange = (val) => parentOnChange(fieldName, parentArrayIndex, fieldObj.options?.offOption || false, fieldObj.options?.onOption || true, val);
|
|
1968
|
+
}
|
|
1969
|
+
if (field.show_if !== void 0 || field.show_if_not !== void 0) {
|
|
1970
|
+
let index;
|
|
1971
|
+
if (parentInfo) if (!parentInfo?.arrayIndex) index = getClosestParentIndex(parentInfo.fieldNamePrefix);
|
|
1972
|
+
else index = parentInfo.arrayIndex;
|
|
1973
|
+
let shouldShowField = true;
|
|
1974
|
+
if (field.show_if !== void 0) shouldShowField = !!getParentValue(index, field.show_if, values, "", field);
|
|
1975
|
+
if (shouldShowField && field.show_if_not !== void 0) shouldShowField = !getParentValue(index, field.show_if_not, values, "", field);
|
|
1976
|
+
if (!shouldShowField) {
|
|
1977
|
+
if (!workflowForm) deleteFieldByPath(values, fieldObj.name);
|
|
1978
|
+
return null;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
if (field.depends_on) {
|
|
1982
|
+
if (parentInfo?.arrayIndex !== void 0) fieldObj.depends_on = field.depends_on.replace("[index]", `[${parentInfo.arrayIndex}]`);
|
|
1983
|
+
}
|
|
1984
|
+
if (field.enable_if && field.disabled === false) {
|
|
1985
|
+
if (typeof field.enable_if === "string") {
|
|
1986
|
+
let path = field.enable_if;
|
|
1987
|
+
if (parentInfo?.arrayIndex !== void 0) path = field.enable_if.replace("[index]", `[${parentInfo.arrayIndex}]`);
|
|
1988
|
+
if (getValueUsingPath(values, path) === false) fieldObj.disabled = true;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
if (field.type === "group") {
|
|
1992
|
+
const hideHeader = field.noCollapse;
|
|
1993
|
+
const HeaderElement = hideHeader ? React.Fragment : UncontrolledCollapsiblePanel;
|
|
1994
|
+
const meta = resolveMetaOverrides(field.options, labelPosition, spaceCompact);
|
|
1995
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1996
|
+
className: "flex flex-col w-full",
|
|
1997
|
+
children: /* @__PURE__ */ jsx(HeaderElement, {
|
|
1998
|
+
...!hideHeader && {
|
|
1999
|
+
title: (open, setOpen) => /* @__PURE__ */ jsx(GroupHeader, {
|
|
2000
|
+
open,
|
|
2001
|
+
setOpen,
|
|
2002
|
+
title: field.label
|
|
2003
|
+
}),
|
|
2004
|
+
initialState: field.collapsed !== void 0 ? !field.collapsed : true
|
|
2005
|
+
},
|
|
2006
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2007
|
+
className: "w-full",
|
|
2008
|
+
children: /* @__PURE__ */ jsx(FieldsFromOptions, {
|
|
2009
|
+
options: field.options,
|
|
2010
|
+
setFormDirty,
|
|
2011
|
+
setFieldValue,
|
|
2012
|
+
setFieldTouched,
|
|
2013
|
+
values,
|
|
2014
|
+
parentInfo,
|
|
2015
|
+
workflowForm,
|
|
2016
|
+
labelPosition: meta.labelPosition,
|
|
2017
|
+
spaceCompact: meta.spaceCompact,
|
|
2018
|
+
missingFields
|
|
2019
|
+
})
|
|
2020
|
+
})
|
|
2021
|
+
})
|
|
2022
|
+
}, fieldObj.name);
|
|
2023
|
+
}
|
|
2024
|
+
if (typeof field.type === "object" && field.depends_on) {
|
|
2025
|
+
const parentValue = getValueUsingPath(values, field.depends_on);
|
|
2026
|
+
if (!field.type[parentValue]) return null;
|
|
2027
|
+
fieldObj.type = field.type[parentValue];
|
|
2028
|
+
}
|
|
2029
|
+
if (field.type === "range" && field.depends_on) {
|
|
2030
|
+
const parentValue = getValueUsingPath(values, field.depends_on);
|
|
2031
|
+
if (typeof field?.min === "object" && typeof field?.max === "object") {
|
|
2032
|
+
fieldObj.min = field.min[parentValue];
|
|
2033
|
+
fieldObj.max = field.max[parentValue];
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
if (field.resetOnChange) {
|
|
2037
|
+
const parentValue = getValueUsingPath(values, field.resetOnChange);
|
|
2038
|
+
const prevParentValue = field.prevParentValue;
|
|
2039
|
+
if (prevParentValue !== void 0 && prevParentValue !== parentValue) deleteFieldByPath(values, fieldObj.name);
|
|
2040
|
+
field.prevParentValue = parentValue;
|
|
2041
|
+
}
|
|
2042
|
+
if (field.secondaryField) if (Array.isArray(field.secondaryField)) fieldObj.secondaryField = field.secondaryField.map((secondaryField) => fieldNamePrefix + secondaryField);
|
|
2043
|
+
else fieldObj.secondaryField = fieldNamePrefix + field.secondaryField;
|
|
2044
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2045
|
+
className: cx("flex w-full", labelPosition === "left" ? "mb-[15px]" : "mb-[5px]"),
|
|
2046
|
+
children: /* @__PURE__ */ jsx(InputField, {
|
|
2047
|
+
field: fieldObj,
|
|
2048
|
+
label: fieldObj.label,
|
|
2049
|
+
setFormDirty,
|
|
2050
|
+
setFieldValue,
|
|
2051
|
+
setFieldTouched,
|
|
2052
|
+
values,
|
|
2053
|
+
currentValue: getValueUsingPath(values, fieldObj.name),
|
|
2054
|
+
onChange,
|
|
2055
|
+
disabled: fieldObj.disabled,
|
|
2056
|
+
computeOn: parentInfo?.computeOn || fieldObj.computeOn,
|
|
2057
|
+
labelPosition,
|
|
2058
|
+
missingFields,
|
|
2059
|
+
spaceCompact,
|
|
2060
|
+
workflowForm
|
|
2061
|
+
})
|
|
2062
|
+
}, `field-${id}`);
|
|
2063
|
+
}, (prev, next) => {
|
|
2064
|
+
if (!deepEqual(prev.optionsField, next.optionsField)) return false;
|
|
2065
|
+
if (prev.values !== next.values) return false;
|
|
2066
|
+
if (!deepEqual(prev.parentInfo, next.parentInfo)) return false;
|
|
2067
|
+
if (!deepEqual(prev.missingFields, next.missingFields)) return false;
|
|
2068
|
+
if (prev.labelPosition !== next.labelPosition) return false;
|
|
2069
|
+
if (prev.spaceCompact !== next.spaceCompact) return false;
|
|
2070
|
+
if (prev.workflowForm !== next.workflowForm) return false;
|
|
2071
|
+
return true;
|
|
2072
|
+
});
|
|
2073
|
+
function FieldsFromOptions({ options = {}, values, setFormDirty, setFieldValue, setFieldTouched, parentInfo, labelPosition = "left", missingFields = [], spaceCompact = false, workflowForm = false }) {
|
|
2074
|
+
return Object.keys(options).map((fieldName, idx) => /* @__PURE__ */ jsx(FormField, {
|
|
2075
|
+
id: idx,
|
|
2076
|
+
optionsField: options[fieldName],
|
|
2077
|
+
fieldName,
|
|
2078
|
+
values,
|
|
2079
|
+
setFormDirty,
|
|
2080
|
+
setFieldValue,
|
|
2081
|
+
setFieldTouched,
|
|
2082
|
+
parentInfo,
|
|
2083
|
+
labelPosition,
|
|
2084
|
+
missingFields,
|
|
2085
|
+
spaceCompact,
|
|
2086
|
+
workflowForm
|
|
2087
|
+
}, idx));
|
|
2088
|
+
}
|
|
2089
|
+
var noop = () => {};
|
|
2090
|
+
function DynamicFormContent({ options, setValues, setFormDirty, labelPosition, missingFields, spaceCompact, organizationVariables, workflowForm, onSubmit, submitLabel, values, contextKey }) {
|
|
2091
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
2092
|
+
const opts = useParsedOpts(options, values, organizationVariables);
|
|
2093
|
+
const handleChange = useCallback((state) => {
|
|
2094
|
+
if (setValues) setValues(state);
|
|
2095
|
+
}, [setValues]);
|
|
2096
|
+
const wizardConfig = parseWizardConfig(opts);
|
|
2097
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2098
|
+
/* @__PURE__ */ jsx(DirtyStateBridge, { setFormDirty }),
|
|
2099
|
+
/* @__PURE__ */ jsx(FormikStateBridge, { onChange: handleChange }),
|
|
2100
|
+
workflowForm && /* @__PURE__ */ jsx(DynamicDefaultsSync, { options: opts }, contextKey ?? ""),
|
|
2101
|
+
wizardConfig !== null && wizardConfig ? /* @__PURE__ */ jsx(WizardContainer, {
|
|
2102
|
+
wizardConfig,
|
|
2103
|
+
values,
|
|
2104
|
+
onChange: handleChange,
|
|
2105
|
+
onSubmit,
|
|
2106
|
+
className: "w-full",
|
|
2107
|
+
...resolveMetaOverrides(options, labelPosition, spaceCompact),
|
|
2108
|
+
missingFields,
|
|
2109
|
+
workflowForm,
|
|
2110
|
+
setFieldValue,
|
|
2111
|
+
setFieldTouched
|
|
2112
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FieldsFromOptions, {
|
|
2113
|
+
...resolveMetaOverrides(options, labelPosition, spaceCompact),
|
|
2114
|
+
options: opts,
|
|
2115
|
+
values,
|
|
2116
|
+
setFormDirty,
|
|
2117
|
+
setFieldValue,
|
|
2118
|
+
setFieldTouched,
|
|
2119
|
+
missingFields,
|
|
2120
|
+
workflowForm
|
|
2121
|
+
}), onSubmit !== void 0 && !workflowForm && /* @__PURE__ */ jsx("div", {
|
|
2122
|
+
className: "mt-4",
|
|
2123
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
2124
|
+
type: "submit",
|
|
2125
|
+
className: "btn btn-info",
|
|
2126
|
+
value: submitLabel
|
|
2127
|
+
})
|
|
2128
|
+
})] })
|
|
2129
|
+
] });
|
|
2130
|
+
}
|
|
2131
|
+
var NO_FIELDS = {};
|
|
2132
|
+
function DynamicForm({ formJSONs: options = {}, reinitialize = false, initialValues, setValues, setFormDirty = noop, formikRef, labelPosition = "left", missingFields = [], spaceCompact = false, organizationVariables, needsOrganizationVariables = false, onSubmit = void 0, submitLabel = "Submit", className = "w-full", workflowForm = false, contextKey, skipValueParse = false, fields = NO_FIELDS }) {
|
|
2133
|
+
const [parseReady, setParseReady] = useState(parseStringsOfObjReady);
|
|
2134
|
+
useEffect(() => {
|
|
2135
|
+
initializeParseStringsOfObj();
|
|
2136
|
+
return callWhenParserInitialized(setParseReady);
|
|
2137
|
+
}, []);
|
|
2138
|
+
if (!parseReady) return /* @__PURE__ */ jsx(Loader, {});
|
|
2139
|
+
const parsedInitVals = skipValueParse ? initialValues : initialValues && parseStringsOfObj({
|
|
2140
|
+
inputs: initialValues,
|
|
2141
|
+
obj: initialValues,
|
|
2142
|
+
orgVars: organizationVariables
|
|
2143
|
+
});
|
|
2144
|
+
return /* @__PURE__ */ jsx(FieldRegistryContext.Provider, {
|
|
2145
|
+
value: fields,
|
|
2146
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2147
|
+
className,
|
|
2148
|
+
children: (!needsOrganizationVariables || organizationVariables) && initialValues && /* @__PURE__ */ jsx(Formik, {
|
|
2149
|
+
enableReinitialize: reinitialize,
|
|
2150
|
+
initialValues: parsedInitVals,
|
|
2151
|
+
onSubmit: onSubmit ?? (() => {}),
|
|
2152
|
+
onReset: () => {
|
|
2153
|
+
setFormDirty(false);
|
|
2154
|
+
},
|
|
2155
|
+
innerRef: formikRef,
|
|
2156
|
+
children: ({ values }) => /* @__PURE__ */ jsx(Form, {
|
|
2157
|
+
className: "w-full text-sm",
|
|
2158
|
+
children: /* @__PURE__ */ jsx(DynamicFormContent, {
|
|
2159
|
+
values,
|
|
2160
|
+
options,
|
|
2161
|
+
setValues,
|
|
2162
|
+
setFormDirty,
|
|
2163
|
+
labelPosition,
|
|
2164
|
+
missingFields,
|
|
2165
|
+
spaceCompact,
|
|
2166
|
+
organizationVariables,
|
|
2167
|
+
workflowForm,
|
|
2168
|
+
onSubmit,
|
|
2169
|
+
submitLabel,
|
|
2170
|
+
contextKey
|
|
2171
|
+
})
|
|
2172
|
+
})
|
|
2173
|
+
})
|
|
2174
|
+
})
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
//#endregion
|
|
2178
|
+
//#region src/form/SpecialTypes/DropdownWrapper.tsx
|
|
2179
|
+
function DropdownWrapper({ options, onChange, fieldObj, setFormDirty, invalid = false, loading = false, disabled = void 0, allowCustomValue = void 0, currentValue, setFieldValue, setFieldTouched }) {
|
|
2180
|
+
return /* @__PURE__ */ jsx(FormikCustomDropdown_default, {
|
|
2181
|
+
invalid,
|
|
2182
|
+
className: "w-full",
|
|
2183
|
+
onChange,
|
|
2184
|
+
loading,
|
|
2185
|
+
setFormDirty,
|
|
2186
|
+
options,
|
|
2187
|
+
disabled: disabled ?? fieldObj.disabled ?? false,
|
|
2188
|
+
placeholder: getPlaceholder(fieldObj.placeholder, typeof fieldObj.default === "string" ? fieldObj.default : void 0),
|
|
2189
|
+
name: fieldObj.name,
|
|
2190
|
+
ariaLabel: fieldObj.label,
|
|
2191
|
+
parentValue: fieldObj.parentValue,
|
|
2192
|
+
secondaryField: fieldObj.secondaryField,
|
|
2193
|
+
resetOnChange: fieldObj.resetOnChange,
|
|
2194
|
+
value: fieldObj.value,
|
|
2195
|
+
autoselect: fieldObj.autoselect,
|
|
2196
|
+
allowCustomValue: allowCustomValue ?? fieldObj.allowCustomValue,
|
|
2197
|
+
customValueLabel: fieldObj.customValueLabel,
|
|
2198
|
+
currentValue,
|
|
2199
|
+
setFieldValue,
|
|
2200
|
+
setFieldTouched
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
//#endregion
|
|
2204
|
+
//#region src/form/SpecialTypes/FormikMultiSelectDropdown.tsx
|
|
2205
|
+
function FormikMultiSelectDropdown({ name, options, setFormDirty, onChange, disabled = false, loading = false, invalid = false, placeholder, compareBy }) {
|
|
2206
|
+
const [field] = useField(name);
|
|
2207
|
+
const { setFieldValue, setFieldTouched } = useFormikContext();
|
|
2208
|
+
const handleChange = (next) => {
|
|
2209
|
+
setFieldValue(name, next);
|
|
2210
|
+
setFieldTouched(name, true);
|
|
2211
|
+
setFormDirty(true);
|
|
2212
|
+
onChange?.(next);
|
|
2213
|
+
};
|
|
2214
|
+
return /* @__PURE__ */ jsx(MultiSelectDropdown, {
|
|
2215
|
+
textBoxClassName: invalid ? "invalid" : void 0,
|
|
2216
|
+
options,
|
|
2217
|
+
value: Array.isArray(field.value) ? field.value : [],
|
|
2218
|
+
onChange: handleChange,
|
|
2219
|
+
disabled,
|
|
2220
|
+
loading,
|
|
2221
|
+
placeholder,
|
|
2222
|
+
compareBy
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
//#endregion
|
|
2226
|
+
//#region src/form/utils/pwResolver.ts
|
|
2227
|
+
function resolvePwString(raw, options, fallbackUsername, opts) {
|
|
2228
|
+
let inputForm = null;
|
|
2229
|
+
let owner;
|
|
2230
|
+
let name;
|
|
2231
|
+
if (typeof raw === "string" && raw.startsWith("pw://")) {
|
|
2232
|
+
const parts = raw.slice(5).split("/");
|
|
2233
|
+
if (parts.length === 1 && parts[0]) {
|
|
2234
|
+
inputForm = "shorthand";
|
|
2235
|
+
name = parts[0];
|
|
2236
|
+
owner = fallbackUsername;
|
|
2237
|
+
} else if (parts.length === 2 && parts[0] && parts[1]) {
|
|
2238
|
+
inputForm = "full";
|
|
2239
|
+
owner = parts[0];
|
|
2240
|
+
name = parts[1];
|
|
2241
|
+
}
|
|
2242
|
+
} else if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
2243
|
+
const obj = raw;
|
|
2244
|
+
if (typeof obj.uri === "string" && obj.uri.startsWith("pw://")) {
|
|
2245
|
+
const parts = obj.uri.slice(5).split("/");
|
|
2246
|
+
if (parts.length === 1 && parts[0]) {
|
|
2247
|
+
inputForm = "shorthand";
|
|
2248
|
+
name = parts[0];
|
|
2249
|
+
owner = fallbackUsername;
|
|
2250
|
+
} else if (parts.length === 2 && parts[0] && parts[1]) {
|
|
2251
|
+
inputForm = "full";
|
|
2252
|
+
owner = parts[0];
|
|
2253
|
+
name = parts[1];
|
|
2254
|
+
}
|
|
2255
|
+
} else if (typeof obj.name === "string") {
|
|
2256
|
+
name = obj.name;
|
|
2257
|
+
const ownerVal = typeof obj.user === "string" ? obj.user : typeof obj.namespace === "string" ? obj.namespace : void 0;
|
|
2258
|
+
if (obj.type === "managed-cluster" || !ownerVal) {
|
|
2259
|
+
inputForm = "shorthand";
|
|
2260
|
+
owner = fallbackUsername;
|
|
2261
|
+
} else {
|
|
2262
|
+
inputForm = "full";
|
|
2263
|
+
owner = ownerVal;
|
|
2264
|
+
}
|
|
2265
|
+
} else if (typeof obj.id === "string" && obj.id) {
|
|
2266
|
+
inputForm = "shorthand";
|
|
2267
|
+
name = obj.id;
|
|
2268
|
+
owner = fallbackUsername;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
if (!inputForm || !name || !owner) return raw;
|
|
2272
|
+
const special = opts.specialCase?.(name, owner);
|
|
2273
|
+
if (special !== void 0) return special;
|
|
2274
|
+
const find = (pred) => {
|
|
2275
|
+
for (const category of options) for (const opt of category.options) if (opt.value && pred(opt.value)) return opt.value;
|
|
2276
|
+
};
|
|
2277
|
+
if (inputForm === "full") return find((v) => v.name === name && v[opts.ownerKey] === owner) ?? raw;
|
|
2278
|
+
const managed = find((v) => v.name === name && v.type === "managed-cluster");
|
|
2279
|
+
if (managed) return managed;
|
|
2280
|
+
const ownedByCaller = find((v) => v.name === name && v[opts.ownerKey] === fallbackUsername);
|
|
2281
|
+
if (ownedByCaller) return ownedByCaller;
|
|
2282
|
+
const sharedWithCaller = find((v) => v.name === name && typeof v[opts.ownerKey] === "string" && v[opts.ownerKey] !== fallbackUsername);
|
|
2283
|
+
if (sharedWithCaller) return sharedWithCaller;
|
|
2284
|
+
return raw;
|
|
2285
|
+
}
|
|
2286
|
+
function resolvePwValue(currentValue, options, fallbackUsername, opts) {
|
|
2287
|
+
if (Array.isArray(currentValue)) {
|
|
2288
|
+
let changed = false;
|
|
2289
|
+
const next = currentValue.map((v) => {
|
|
2290
|
+
const resolved = resolvePwString(v, options, fallbackUsername, opts);
|
|
2291
|
+
if (resolved !== v) changed = true;
|
|
2292
|
+
return resolved;
|
|
2293
|
+
});
|
|
2294
|
+
return changed ? next : currentValue;
|
|
2295
|
+
}
|
|
2296
|
+
return resolvePwString(currentValue, options, fallbackUsername, opts);
|
|
2297
|
+
}
|
|
2298
|
+
//#endregion
|
|
2299
|
+
//#region src/form/Fields/ResourceOptionIcon.tsx
|
|
2300
|
+
function ResourceOptionIcon({ imageUrl, fallbackIcon, alt, variant = "success", dotSize = 6 }) {
|
|
2301
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
2302
|
+
className: "flex items-center gap-1.5 shrink-0",
|
|
2303
|
+
children: [/* @__PURE__ */ jsx(StatusDot, {
|
|
2304
|
+
variant,
|
|
2305
|
+
size: dotSize
|
|
2306
|
+
}), /* @__PURE__ */ jsx("img", {
|
|
2307
|
+
src: imageUrl || fallbackIcon,
|
|
2308
|
+
alt,
|
|
2309
|
+
className: "h-4 w-4 object-contain",
|
|
2310
|
+
onError: (e) => {
|
|
2311
|
+
e.currentTarget.src = fallbackIcon;
|
|
2312
|
+
}
|
|
2313
|
+
})]
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
//#endregion
|
|
2317
|
+
export { DropdownWrapper, DynamicDefaultsSync, DynamicForm, FLEX_MAX_DURATION_RANGE, FLEX_WAIT_TIME_RANGE, FieldRegistryContext, FieldWrapper, FieldsFromOptions, FormikCustomDropdown_default as FormikCustomDropdown, FormikCustomInput, FormikMultiSelectDropdown, FormikNumericInput, GroupHeader, MultiSelectionDropdown, NumberField, Registry, ResourceOptionIcon, STANDARD_MAX_DURATION_RANGE, StatusBlock, applySecondaryField, collectFieldsWithDefaults, collectPartitionDurationIssues, enforceOneMustBeTrue, findSelfReferencingFieldNames, flattenGroups, formatSecondsAsSlurmDuration, getInstanceTypesForForm, getInvalidDurationPaths, impureSetValueFromPath, initializeValues, parseSlurmDuration, resolveMetaOverrides, resolvePwString, resolvePwValue, resolvedFlag, setDurationValidity, useParsedOpts };
|