@tduniec/plugin-template-designer-foundation 0.1.1
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/CHANGELOG.md +13 -0
- package/README.md +13 -0
- package/dist/api/useScaffolderActions.esm.js +59 -0
- package/dist/api/useScaffolderActions.esm.js.map +1 -0
- package/dist/components/FieldEditorDialog.esm.js +55 -0
- package/dist/components/FieldEditorDialog.esm.js.map +1 -0
- package/dist/components/Nodes/ActionNode.esm.js +613 -0
- package/dist/components/Nodes/ActionNode.esm.js.map +1 -0
- package/dist/components/Nodes/OutputNode.esm.js +373 -0
- package/dist/components/Nodes/OutputNode.esm.js.map +1 -0
- package/dist/components/Nodes/ParameterInputNode.esm.js +320 -0
- package/dist/components/Nodes/ParameterInputNode.esm.js.map +1 -0
- package/dist/components/Nodes/ParameterTitlesNode.esm.js +251 -0
- package/dist/components/Nodes/ParameterTitlesNode.esm.js.map +1 -0
- package/dist/components/Nodes/ParametersNode.esm.js +147 -0
- package/dist/components/Nodes/ParametersNode.esm.js.map +1 -0
- package/dist/components/Nodes/action/schema.esm.js +68 -0
- package/dist/components/Nodes/action/schema.esm.js.map +1 -0
- package/dist/components/Nodes/action/useActionInputs.esm.js +71 -0
- package/dist/components/Nodes/action/useActionInputs.esm.js.map +1 -0
- package/dist/components/Nodes/common/AutoWidthPopper.esm.js +11 -0
- package/dist/components/Nodes/common/AutoWidthPopper.esm.js.map +1 -0
- package/dist/components/Nodes/common/nodeInteraction.esm.js +20 -0
- package/dist/components/Nodes/common/nodeInteraction.esm.js.map +1 -0
- package/dist/components/Nodes/output/useOutputController.esm.js +125 -0
- package/dist/components/Nodes/output/useOutputController.esm.js.map +1 -0
- package/dist/components/TemplateDesigner/TemplateLanding.esm.js +157 -0
- package/dist/components/TemplateDesigner/TemplateLanding.esm.js.map +1 -0
- package/dist/components/TemplateDesigner/TemplateWorkspace.esm.js +416 -0
- package/dist/components/TemplateDesigner/TemplateWorkspace.esm.js.map +1 -0
- package/dist/components/TemplateDesigner/codemirrorTheme.esm.js +30 -0
- package/dist/components/TemplateDesigner/codemirrorTheme.esm.js.map +1 -0
- package/dist/components/TemplateDesigner/useFieldEditor.esm.js +95 -0
- package/dist/components/TemplateDesigner/useFieldEditor.esm.js.map +1 -0
- package/dist/components/TemplateDesignerIcon.esm.js +33 -0
- package/dist/components/TemplateDesignerIcon.esm.js.map +1 -0
- package/dist/components/designerFlowConfig.esm.js +13 -0
- package/dist/components/designerFlowConfig.esm.js.map +1 -0
- package/dist/designerFlow/DesignerFlow.esm.js +828 -0
- package/dist/designerFlow/DesignerFlow.esm.js.map +1 -0
- package/dist/designerFlow/handlers.esm.js +317 -0
- package/dist/designerFlow/handlers.esm.js.map +1 -0
- package/dist/designerFlow/model.esm.js +166 -0
- package/dist/designerFlow/model.esm.js.map +1 -0
- package/dist/designerFlow/nodeLayout.esm.js +108 -0
- package/dist/designerFlow/nodeLayout.esm.js.map +1 -0
- package/dist/designerFlow/parameterTransforms.esm.js +124 -0
- package/dist/designerFlow/parameterTransforms.esm.js.map +1 -0
- package/dist/designerFlow/utils/stableComparators.esm.js +69 -0
- package/dist/designerFlow/utils/stableComparators.esm.js.map +1 -0
- package/dist/foundation/actionNodeCustomization.esm.js +20 -0
- package/dist/foundation/actionNodeCustomization.esm.js.map +1 -0
- package/dist/foundation/actionNodeRegistry.esm.js +30 -0
- package/dist/foundation/actionNodeRegistry.esm.js.map +1 -0
- package/dist/foundation/featureFlags.esm.js +6 -0
- package/dist/foundation/featureFlags.esm.js.map +1 -0
- package/dist/foundation/templateSources.esm.js +16 -0
- package/dist/foundation/templateSources.esm.js.map +1 -0
- package/dist/index.d.ts +382 -0
- package/dist/index.esm.js +25 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/state/templateUtils.esm.js +46 -0
- package/dist/state/templateUtils.esm.js.map +1 -0
- package/dist/state/useParameterSections.esm.js +162 -0
- package/dist/state/useParameterSections.esm.js.map +1 -0
- package/dist/state/useTemplateState.esm.js +627 -0
- package/dist/state/useTemplateState.esm.js.map +1 -0
- package/dist/types/flowNodes.esm.js +8 -0
- package/dist/types/flowNodes.esm.js.map +1 -0
- package/dist/utils/createSequentialEdges.esm.js +15 -0
- package/dist/utils/createSequentialEdges.esm.js.map +1 -0
- package/dist/utils/mocks/mocks.esm.js +120 -0
- package/dist/utils/mocks/mocks.esm.js.map +1 -0
- package/dist/utils/sampleTemplate.esm.js +40 -0
- package/dist/utils/sampleTemplate.esm.js.map +1 -0
- package/dist/utils/yamlJsonConversion.esm.js +47 -0
- package/dist/utils/yamlJsonConversion.esm.js.map +1 -0
- package/package.json +103 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
2
|
+
import { convertJsonToYaml, convertYamlToJson } from '../utils/yamlJsonConversion.esm.js';
|
|
3
|
+
import { SAMPLE_TEMPLATE_BLUEPRINT } from '../utils/sampleTemplate.esm.js';
|
|
4
|
+
import { cloneDeep, FILE_PICKER_TYPES, asRecord, isTaskStep, cloneSteps, downloadString, DEFAULT_FILE_NAME } from './templateUtils.esm.js';
|
|
5
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
6
|
+
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
|
7
|
+
|
|
8
|
+
const parseTemplateYaml = (value) => {
|
|
9
|
+
const parsed = JSON.parse(convertYamlToJson(value));
|
|
10
|
+
if (!parsed || typeof parsed !== "object") {
|
|
11
|
+
throw new Error("Template YAML must describe an object");
|
|
12
|
+
}
|
|
13
|
+
return parsed;
|
|
14
|
+
};
|
|
15
|
+
const useTemplateState = () => {
|
|
16
|
+
const [templateObject, setTemplateObject] = useState(null);
|
|
17
|
+
const [templateYaml, setTemplateYaml] = useState("");
|
|
18
|
+
const [yamlError, setYamlError] = useState();
|
|
19
|
+
const [loadError, setLoadError] = useState();
|
|
20
|
+
const [isDirty, setIsDirty] = useState(false);
|
|
21
|
+
const [templateSource, setTemplateSource] = useState();
|
|
22
|
+
const [isReloading, setIsReloading] = useState(false);
|
|
23
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
24
|
+
const [isSyncing, setIsSyncing] = useState(false);
|
|
25
|
+
const templateObjectRef = useRef(null);
|
|
26
|
+
const yamlParseTimeoutRef = useRef(
|
|
27
|
+
null
|
|
28
|
+
);
|
|
29
|
+
const yamlSerializeTimeoutRef = useRef(
|
|
30
|
+
null
|
|
31
|
+
);
|
|
32
|
+
const yamlParseIdleCancelRef = useRef(null);
|
|
33
|
+
const yamlSerializeIdleCancelRef = useRef(null);
|
|
34
|
+
const yamlParseCompletionRef = useRef(null);
|
|
35
|
+
const yamlSerializeCompletionRef = useRef(null);
|
|
36
|
+
const syncingCounterRef = useRef(0);
|
|
37
|
+
const catalogApi = useApi(catalogApiRef);
|
|
38
|
+
const [selectedTemplate, setSelectedTemplate] = useState(void 0);
|
|
39
|
+
const [availableTemplates, setAvailableTemplates] = useState([]);
|
|
40
|
+
const fileInputRef = useRef(null);
|
|
41
|
+
const ensureHandlePermission = useCallback(
|
|
42
|
+
async (handle, mode) => {
|
|
43
|
+
if (!handle || !handle.queryPermission || !handle.requestPermission) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const options = { mode };
|
|
47
|
+
const current = await handle.queryPermission(options);
|
|
48
|
+
if (current === "granted") {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
const request = await handle.requestPermission(options);
|
|
52
|
+
return request === "granted";
|
|
53
|
+
},
|
|
54
|
+
[]
|
|
55
|
+
);
|
|
56
|
+
const applyTemplate = useCallback(
|
|
57
|
+
(template, source) => {
|
|
58
|
+
const nextTemplate = cloneDeep(template);
|
|
59
|
+
const nextYaml = convertJsonToYaml(nextTemplate);
|
|
60
|
+
templateObjectRef.current = nextTemplate;
|
|
61
|
+
setTemplateObject(nextTemplate);
|
|
62
|
+
setTemplateYaml(nextYaml);
|
|
63
|
+
setYamlError(void 0);
|
|
64
|
+
setLoadError(void 0);
|
|
65
|
+
setTemplateSource(source);
|
|
66
|
+
setIsDirty(false);
|
|
67
|
+
},
|
|
68
|
+
[]
|
|
69
|
+
);
|
|
70
|
+
const confirmDiscardChanges = useCallback(() => {
|
|
71
|
+
if (!templateObject || !isDirty) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (typeof window === "undefined") {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return window.confirm(
|
|
78
|
+
"This will discard the changes you have made. Continue?"
|
|
79
|
+
);
|
|
80
|
+
}, [isDirty, templateObject]);
|
|
81
|
+
const startSyncTask = useCallback(() => {
|
|
82
|
+
syncingCounterRef.current += 1;
|
|
83
|
+
if (syncingCounterRef.current === 1) {
|
|
84
|
+
setIsSyncing(true);
|
|
85
|
+
}
|
|
86
|
+
return () => {
|
|
87
|
+
syncingCounterRef.current = Math.max(syncingCounterRef.current - 1, 0);
|
|
88
|
+
if (syncingCounterRef.current === 0) {
|
|
89
|
+
setIsSyncing(false);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
93
|
+
const scheduleYamlParse = useCallback(
|
|
94
|
+
(nextYaml) => {
|
|
95
|
+
if (yamlParseTimeoutRef.current) {
|
|
96
|
+
clearTimeout(yamlParseTimeoutRef.current);
|
|
97
|
+
}
|
|
98
|
+
if (yamlParseIdleCancelRef.current) {
|
|
99
|
+
yamlParseIdleCancelRef.current();
|
|
100
|
+
yamlParseIdleCancelRef.current = null;
|
|
101
|
+
}
|
|
102
|
+
if (yamlParseCompletionRef.current) {
|
|
103
|
+
yamlParseCompletionRef.current();
|
|
104
|
+
yamlParseCompletionRef.current = null;
|
|
105
|
+
}
|
|
106
|
+
yamlParseCompletionRef.current = startSyncTask();
|
|
107
|
+
yamlParseTimeoutRef.current = setTimeout(() => {
|
|
108
|
+
yamlParseTimeoutRef.current = null;
|
|
109
|
+
const runParse = () => {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = parseTemplateYaml(nextYaml);
|
|
112
|
+
templateObjectRef.current = parsed;
|
|
113
|
+
setTemplateObject(parsed);
|
|
114
|
+
setYamlError(void 0);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : "Unknown error parsing YAML";
|
|
117
|
+
setYamlError(message);
|
|
118
|
+
}
|
|
119
|
+
if (yamlParseCompletionRef.current) {
|
|
120
|
+
yamlParseCompletionRef.current();
|
|
121
|
+
yamlParseCompletionRef.current = null;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const idle = typeof window !== "undefined" && typeof window.requestIdleCallback === "function";
|
|
125
|
+
if (idle) {
|
|
126
|
+
const handle = window.requestIdleCallback(runParse, {
|
|
127
|
+
timeout: 800
|
|
128
|
+
});
|
|
129
|
+
yamlParseIdleCancelRef.current = () => {
|
|
130
|
+
window.cancelIdleCallback?.(handle);
|
|
131
|
+
if (yamlParseCompletionRef.current) {
|
|
132
|
+
yamlParseCompletionRef.current();
|
|
133
|
+
yamlParseCompletionRef.current = null;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
runParse();
|
|
139
|
+
}, 450);
|
|
140
|
+
},
|
|
141
|
+
[startSyncTask]
|
|
142
|
+
);
|
|
143
|
+
const scheduleYamlSerialization = useCallback(
|
|
144
|
+
(nextTemplate) => {
|
|
145
|
+
if (yamlSerializeTimeoutRef.current) {
|
|
146
|
+
clearTimeout(yamlSerializeTimeoutRef.current);
|
|
147
|
+
}
|
|
148
|
+
if (yamlSerializeIdleCancelRef.current) {
|
|
149
|
+
yamlSerializeIdleCancelRef.current();
|
|
150
|
+
yamlSerializeIdleCancelRef.current = null;
|
|
151
|
+
}
|
|
152
|
+
if (yamlSerializeCompletionRef.current) {
|
|
153
|
+
yamlSerializeCompletionRef.current();
|
|
154
|
+
yamlSerializeCompletionRef.current = null;
|
|
155
|
+
}
|
|
156
|
+
yamlSerializeCompletionRef.current = startSyncTask();
|
|
157
|
+
yamlSerializeTimeoutRef.current = setTimeout(() => {
|
|
158
|
+
yamlSerializeTimeoutRef.current = null;
|
|
159
|
+
if (!nextTemplate) {
|
|
160
|
+
setTemplateYaml("");
|
|
161
|
+
if (yamlSerializeCompletionRef.current) {
|
|
162
|
+
yamlSerializeCompletionRef.current();
|
|
163
|
+
yamlSerializeCompletionRef.current = null;
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const runSerialize = () => {
|
|
168
|
+
try {
|
|
169
|
+
setTemplateYaml(convertJsonToYaml(nextTemplate));
|
|
170
|
+
} catch (error) {
|
|
171
|
+
const message = error instanceof Error ? error.message : "Unknown error updating YAML";
|
|
172
|
+
setLoadError(message);
|
|
173
|
+
}
|
|
174
|
+
if (yamlSerializeCompletionRef.current) {
|
|
175
|
+
yamlSerializeCompletionRef.current();
|
|
176
|
+
yamlSerializeCompletionRef.current = null;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const idle = typeof window !== "undefined" && typeof window.requestIdleCallback === "function";
|
|
180
|
+
if (idle) {
|
|
181
|
+
const handle = window.requestIdleCallback(runSerialize, {
|
|
182
|
+
timeout: 800
|
|
183
|
+
});
|
|
184
|
+
yamlSerializeIdleCancelRef.current = () => {
|
|
185
|
+
window.cancelIdleCallback?.(handle);
|
|
186
|
+
if (yamlSerializeCompletionRef.current) {
|
|
187
|
+
yamlSerializeCompletionRef.current();
|
|
188
|
+
yamlSerializeCompletionRef.current = null;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
runSerialize();
|
|
194
|
+
}, 250);
|
|
195
|
+
},
|
|
196
|
+
[startSyncTask]
|
|
197
|
+
);
|
|
198
|
+
const flushPendingYamlSerialization = useCallback(() => {
|
|
199
|
+
if (yamlSerializeTimeoutRef.current) {
|
|
200
|
+
clearTimeout(yamlSerializeTimeoutRef.current);
|
|
201
|
+
yamlSerializeTimeoutRef.current = null;
|
|
202
|
+
}
|
|
203
|
+
if (yamlSerializeIdleCancelRef.current) {
|
|
204
|
+
yamlSerializeIdleCancelRef.current();
|
|
205
|
+
yamlSerializeIdleCancelRef.current = null;
|
|
206
|
+
}
|
|
207
|
+
if (yamlSerializeCompletionRef.current) {
|
|
208
|
+
yamlSerializeCompletionRef.current();
|
|
209
|
+
yamlSerializeCompletionRef.current = null;
|
|
210
|
+
}
|
|
211
|
+
const latest = templateObjectRef.current;
|
|
212
|
+
if (!latest) {
|
|
213
|
+
setTemplateYaml("");
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const serialized = convertJsonToYaml(latest);
|
|
218
|
+
setTemplateYaml(serialized);
|
|
219
|
+
return serialized;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const message = error instanceof Error ? error.message : "Unknown error updating YAML";
|
|
222
|
+
setLoadError(message);
|
|
223
|
+
return templateYaml;
|
|
224
|
+
}
|
|
225
|
+
}, [templateYaml]);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
return () => {
|
|
228
|
+
if (yamlParseTimeoutRef.current) {
|
|
229
|
+
clearTimeout(yamlParseTimeoutRef.current);
|
|
230
|
+
}
|
|
231
|
+
if (yamlParseIdleCancelRef.current) {
|
|
232
|
+
yamlParseIdleCancelRef.current();
|
|
233
|
+
}
|
|
234
|
+
if (yamlParseCompletionRef.current) {
|
|
235
|
+
yamlParseCompletionRef.current();
|
|
236
|
+
yamlParseCompletionRef.current = null;
|
|
237
|
+
}
|
|
238
|
+
if (yamlSerializeTimeoutRef.current) {
|
|
239
|
+
clearTimeout(yamlSerializeTimeoutRef.current);
|
|
240
|
+
}
|
|
241
|
+
if (yamlSerializeIdleCancelRef.current) {
|
|
242
|
+
yamlSerializeIdleCancelRef.current();
|
|
243
|
+
}
|
|
244
|
+
if (yamlSerializeCompletionRef.current) {
|
|
245
|
+
yamlSerializeCompletionRef.current();
|
|
246
|
+
yamlSerializeCompletionRef.current = null;
|
|
247
|
+
}
|
|
248
|
+
syncingCounterRef.current = 0;
|
|
249
|
+
setIsSyncing(false);
|
|
250
|
+
};
|
|
251
|
+
}, []);
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
templateObjectRef.current = templateObject;
|
|
254
|
+
}, [templateObject]);
|
|
255
|
+
const handleStartSampleTemplate = useCallback(() => {
|
|
256
|
+
if (!confirmDiscardChanges()) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
applyTemplate(SAMPLE_TEMPLATE_BLUEPRINT, {
|
|
260
|
+
type: "sample",
|
|
261
|
+
label: "Sample template"
|
|
262
|
+
});
|
|
263
|
+
}, [applyTemplate, confirmDiscardChanges]);
|
|
264
|
+
const handleTemplateFileSelected = useCallback(
|
|
265
|
+
async (event) => {
|
|
266
|
+
const file = event.target.files?.[0];
|
|
267
|
+
event.target.value = "";
|
|
268
|
+
if (!file) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (!confirmDiscardChanges()) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const fileContents = await file.text();
|
|
276
|
+
const parsed = parseTemplateYaml(fileContents);
|
|
277
|
+
applyTemplate(parsed, {
|
|
278
|
+
type: "file",
|
|
279
|
+
label: file.name
|
|
280
|
+
});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const message = error instanceof Error ? error.message : "Unknown error loading template";
|
|
283
|
+
setLoadError(`Could not load template: ${message}`);
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
[applyTemplate, confirmDiscardChanges]
|
|
287
|
+
);
|
|
288
|
+
const handleOpenTemplatePicker = useCallback(async () => {
|
|
289
|
+
if (!confirmDiscardChanges()) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (typeof window === "undefined") {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const fsWindow = window;
|
|
296
|
+
if (fsWindow.showOpenFilePicker) {
|
|
297
|
+
try {
|
|
298
|
+
const handles = await fsWindow.showOpenFilePicker({
|
|
299
|
+
multiple: false,
|
|
300
|
+
types: FILE_PICKER_TYPES
|
|
301
|
+
});
|
|
302
|
+
const [handle] = handles ?? [];
|
|
303
|
+
if (!handle) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const granted = await ensureHandlePermission(handle, "read");
|
|
307
|
+
if (!granted) {
|
|
308
|
+
setLoadError("Permission to read the selected file was denied.");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const file = await handle.getFile();
|
|
312
|
+
const text = await file.text();
|
|
313
|
+
const parsed = parseTemplateYaml(text);
|
|
314
|
+
applyTemplate(parsed, {
|
|
315
|
+
type: "file",
|
|
316
|
+
label: handle.name ?? file.name,
|
|
317
|
+
handle
|
|
318
|
+
});
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const message = error instanceof Error ? error.message : "Unknown error loading template";
|
|
324
|
+
setLoadError(`Could not load template: ${message}`);
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
fileInputRef.current?.click();
|
|
329
|
+
}, [applyTemplate, confirmDiscardChanges, ensureHandlePermission]);
|
|
330
|
+
const templateSteps = useMemo(() => {
|
|
331
|
+
if (!templateObject) {
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
const template = asRecord(templateObject);
|
|
335
|
+
if (!template) {
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
const spec = asRecord(template.spec);
|
|
339
|
+
if (!spec) {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
const maybeSteps = spec.steps;
|
|
343
|
+
if (!Array.isArray(maybeSteps)) {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
const validSteps = maybeSteps.filter(isTaskStep);
|
|
347
|
+
return cloneSteps(validSteps);
|
|
348
|
+
}, [templateObject]);
|
|
349
|
+
const templateParameters = useMemo(() => {
|
|
350
|
+
if (!templateObject) {
|
|
351
|
+
return void 0;
|
|
352
|
+
}
|
|
353
|
+
const template = asRecord(templateObject);
|
|
354
|
+
if (!template) {
|
|
355
|
+
return void 0;
|
|
356
|
+
}
|
|
357
|
+
const spec = asRecord(template.spec);
|
|
358
|
+
if (!spec) {
|
|
359
|
+
return void 0;
|
|
360
|
+
}
|
|
361
|
+
if (!Object.prototype.hasOwnProperty.call(spec, "parameters")) {
|
|
362
|
+
return void 0;
|
|
363
|
+
}
|
|
364
|
+
const rawParameters = spec.parameters;
|
|
365
|
+
if (rawParameters === void 0) {
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
return cloneDeep(rawParameters);
|
|
369
|
+
}, [templateObject]);
|
|
370
|
+
const templateOutput = useMemo(() => {
|
|
371
|
+
if (!templateObject) {
|
|
372
|
+
return void 0;
|
|
373
|
+
}
|
|
374
|
+
const template = asRecord(templateObject);
|
|
375
|
+
if (!template) {
|
|
376
|
+
return void 0;
|
|
377
|
+
}
|
|
378
|
+
const spec = asRecord(template.spec);
|
|
379
|
+
if (!spec) {
|
|
380
|
+
return void 0;
|
|
381
|
+
}
|
|
382
|
+
const rawOutput = spec.output;
|
|
383
|
+
if (!rawOutput || typeof rawOutput !== "object") {
|
|
384
|
+
return void 0;
|
|
385
|
+
}
|
|
386
|
+
return cloneDeep(rawOutput);
|
|
387
|
+
}, [templateObject]);
|
|
388
|
+
const handleYamlChange = useCallback(
|
|
389
|
+
(value) => {
|
|
390
|
+
setTemplateYaml(value);
|
|
391
|
+
setIsDirty(true);
|
|
392
|
+
scheduleYamlParse(value);
|
|
393
|
+
},
|
|
394
|
+
[scheduleYamlParse]
|
|
395
|
+
);
|
|
396
|
+
const handleStepsChange = useCallback(
|
|
397
|
+
(steps) => {
|
|
398
|
+
setIsDirty(true);
|
|
399
|
+
setTemplateObject((prevTemplate) => {
|
|
400
|
+
const base = prevTemplate && typeof prevTemplate === "object" ? cloneDeep(prevTemplate) : {};
|
|
401
|
+
const spec = asRecord(base.spec) ?? {};
|
|
402
|
+
const nextSteps = cloneSteps(steps);
|
|
403
|
+
const nextTemplate = {
|
|
404
|
+
...base,
|
|
405
|
+
spec: {
|
|
406
|
+
...spec,
|
|
407
|
+
steps: nextSteps
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
templateObjectRef.current = nextTemplate;
|
|
411
|
+
scheduleYamlSerialization(nextTemplate);
|
|
412
|
+
return nextTemplate;
|
|
413
|
+
});
|
|
414
|
+
},
|
|
415
|
+
[scheduleYamlSerialization]
|
|
416
|
+
);
|
|
417
|
+
const handleParametersChange = useCallback(
|
|
418
|
+
(parameters) => {
|
|
419
|
+
setIsDirty(true);
|
|
420
|
+
setTemplateObject((prevTemplate) => {
|
|
421
|
+
const base = prevTemplate && typeof prevTemplate === "object" ? cloneDeep(prevTemplate) : {};
|
|
422
|
+
const spec = asRecord(base.spec) ?? {};
|
|
423
|
+
const nextTemplate = {
|
|
424
|
+
...base,
|
|
425
|
+
spec: {
|
|
426
|
+
...spec,
|
|
427
|
+
parameters: cloneDeep(parameters)
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
templateObjectRef.current = nextTemplate;
|
|
431
|
+
scheduleYamlSerialization(nextTemplate);
|
|
432
|
+
return nextTemplate;
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
[scheduleYamlSerialization]
|
|
436
|
+
);
|
|
437
|
+
const handleOutputChange = useCallback(
|
|
438
|
+
(output) => {
|
|
439
|
+
setIsDirty(true);
|
|
440
|
+
setTemplateObject((prevTemplate) => {
|
|
441
|
+
const base = prevTemplate && typeof prevTemplate === "object" ? cloneDeep(prevTemplate) : {};
|
|
442
|
+
const spec = asRecord(base.spec) ?? {};
|
|
443
|
+
const nextTemplate = {
|
|
444
|
+
...base,
|
|
445
|
+
spec: {
|
|
446
|
+
...spec,
|
|
447
|
+
output: cloneDeep(output)
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
templateObjectRef.current = nextTemplate;
|
|
451
|
+
scheduleYamlSerialization(nextTemplate);
|
|
452
|
+
return nextTemplate;
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
[scheduleYamlSerialization]
|
|
456
|
+
);
|
|
457
|
+
const handleReloadFromFile = useCallback(async () => {
|
|
458
|
+
if (!templateSource) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (templateSource.type === "catalog") {
|
|
462
|
+
if (selectedTemplate) {
|
|
463
|
+
applyTemplate(selectedTemplate, {
|
|
464
|
+
type: "catalog",
|
|
465
|
+
label: selectedTemplate.metadata.title ?? selectedTemplate.metadata.name
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (templateSource.type !== "file") {
|
|
471
|
+
handleStartSampleTemplate();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (!templateSource.handle) {
|
|
475
|
+
handleOpenTemplatePicker();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (!confirmDiscardChanges()) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
setIsReloading(true);
|
|
483
|
+
const granted = await ensureHandlePermission(
|
|
484
|
+
templateSource.handle,
|
|
485
|
+
"read"
|
|
486
|
+
);
|
|
487
|
+
if (!granted) {
|
|
488
|
+
setLoadError("Permission to read the selected file was denied.");
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const file = await templateSource.handle.getFile();
|
|
492
|
+
const text = await file.text();
|
|
493
|
+
const parsed = parseTemplateYaml(text);
|
|
494
|
+
applyTemplate(parsed, {
|
|
495
|
+
type: "file",
|
|
496
|
+
label: templateSource.handle.name ?? file.name ?? templateSource.label,
|
|
497
|
+
handle: templateSource.handle
|
|
498
|
+
});
|
|
499
|
+
} catch (error) {
|
|
500
|
+
const message = error instanceof Error ? error.message : "Unknown error reloading template";
|
|
501
|
+
setLoadError(`Could not reload template: ${message}`);
|
|
502
|
+
} finally {
|
|
503
|
+
setIsReloading(false);
|
|
504
|
+
}
|
|
505
|
+
}, [
|
|
506
|
+
applyTemplate,
|
|
507
|
+
confirmDiscardChanges,
|
|
508
|
+
ensureHandlePermission,
|
|
509
|
+
handleOpenTemplatePicker,
|
|
510
|
+
handleStartSampleTemplate,
|
|
511
|
+
selectedTemplate,
|
|
512
|
+
templateSource
|
|
513
|
+
]);
|
|
514
|
+
const handleSaveTemplate = useCallback(async () => {
|
|
515
|
+
if (!templateObject) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const yamlToPersist = flushPendingYamlSerialization();
|
|
519
|
+
if (typeof window === "undefined") {
|
|
520
|
+
downloadString(yamlToPersist, DEFAULT_FILE_NAME);
|
|
521
|
+
setIsDirty(false);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
try {
|
|
525
|
+
setIsSaving(true);
|
|
526
|
+
setLoadError(void 0);
|
|
527
|
+
const fsWindow = window;
|
|
528
|
+
if (templateSource?.type === "file" && templateSource.handle) {
|
|
529
|
+
const granted = await ensureHandlePermission(
|
|
530
|
+
templateSource.handle,
|
|
531
|
+
"readwrite"
|
|
532
|
+
);
|
|
533
|
+
if (!granted) {
|
|
534
|
+
setLoadError("Permission to save to the selected file was denied.");
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const writable = await templateSource.handle.createWritable?.();
|
|
538
|
+
if (!writable) {
|
|
539
|
+
throw new Error("Unable to open file for writing.");
|
|
540
|
+
}
|
|
541
|
+
await writable.write(yamlToPersist);
|
|
542
|
+
await writable.close();
|
|
543
|
+
setIsDirty(false);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (fsWindow.showSaveFilePicker) {
|
|
547
|
+
const handle = await fsWindow.showSaveFilePicker({
|
|
548
|
+
suggestedName: templateSource?.label ?? DEFAULT_FILE_NAME,
|
|
549
|
+
types: FILE_PICKER_TYPES
|
|
550
|
+
});
|
|
551
|
+
const writable = await handle.createWritable?.();
|
|
552
|
+
if (!writable) {
|
|
553
|
+
throw new Error("Unable to open file for writing.");
|
|
554
|
+
}
|
|
555
|
+
await writable.write(yamlToPersist);
|
|
556
|
+
await writable.close();
|
|
557
|
+
setTemplateSource({
|
|
558
|
+
type: "file",
|
|
559
|
+
label: handle.name ?? templateSource?.label ?? DEFAULT_FILE_NAME,
|
|
560
|
+
handle
|
|
561
|
+
});
|
|
562
|
+
setIsDirty(false);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
downloadString(yamlToPersist, templateSource?.label ?? DEFAULT_FILE_NAME);
|
|
566
|
+
setIsDirty(false);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
const message = error instanceof Error ? error.message : "Unknown error saving template";
|
|
569
|
+
setLoadError(`Failed to save template: ${message}`);
|
|
570
|
+
} finally {
|
|
571
|
+
setIsSaving(false);
|
|
572
|
+
}
|
|
573
|
+
}, [
|
|
574
|
+
ensureHandlePermission,
|
|
575
|
+
flushPendingYamlSerialization,
|
|
576
|
+
templateObject,
|
|
577
|
+
templateSource
|
|
578
|
+
]);
|
|
579
|
+
useEffect(() => {
|
|
580
|
+
catalogApi.getEntities({
|
|
581
|
+
filter: {
|
|
582
|
+
kind: "Template"
|
|
583
|
+
}
|
|
584
|
+
}).then((data) => {
|
|
585
|
+
setAvailableTemplates(data.items);
|
|
586
|
+
}).catch(() => setAvailableTemplates([]));
|
|
587
|
+
}, [catalogApi]);
|
|
588
|
+
const selectCatalogTemplate = useCallback(
|
|
589
|
+
(selected) => {
|
|
590
|
+
setSelectedTemplate(selected);
|
|
591
|
+
applyTemplate(selected, {
|
|
592
|
+
type: "catalog",
|
|
593
|
+
label: selected.metadata.title ?? selected.metadata.name
|
|
594
|
+
});
|
|
595
|
+
},
|
|
596
|
+
[applyTemplate]
|
|
597
|
+
);
|
|
598
|
+
return {
|
|
599
|
+
templateObject,
|
|
600
|
+
templateYaml,
|
|
601
|
+
yamlError,
|
|
602
|
+
loadError,
|
|
603
|
+
isDirty,
|
|
604
|
+
templateSource,
|
|
605
|
+
isReloading,
|
|
606
|
+
isSaving,
|
|
607
|
+
isSyncing,
|
|
608
|
+
templateSteps,
|
|
609
|
+
templateParameters,
|
|
610
|
+
templateOutput,
|
|
611
|
+
fileInputRef,
|
|
612
|
+
handleStartSampleTemplate,
|
|
613
|
+
handleTemplateFileSelected,
|
|
614
|
+
handleOpenTemplatePicker,
|
|
615
|
+
handleYamlChange,
|
|
616
|
+
handleStepsChange,
|
|
617
|
+
handleParametersChange,
|
|
618
|
+
handleOutputChange,
|
|
619
|
+
handleReloadFromFile,
|
|
620
|
+
handleSaveTemplate,
|
|
621
|
+
availableTemplates,
|
|
622
|
+
selectCatalogTemplate
|
|
623
|
+
};
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
export { useTemplateState };
|
|
627
|
+
//# sourceMappingURL=useTemplateState.esm.js.map
|