@finos/legend-extension-dsl-data-space-studio 0.1.341 → 0.1.342

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.
Files changed (73) hide show
  1. package/lib/components/DSL_DataProduct_ElementDriver.d.ts.map +1 -1
  2. package/lib/components/DSL_DataProduct_ElementDriver.js +1 -10
  3. package/lib/components/DSL_DataProduct_ElementDriver.js.map +1 -1
  4. package/lib/components/DataSpaceEditor.js +9 -4
  5. package/lib/components/DataSpaceEditor.js.map +1 -1
  6. package/lib/components/DataSpaceExecutableQueryAction.d.ts +20 -0
  7. package/lib/components/DataSpaceExecutableQueryAction.d.ts.map +1 -0
  8. package/lib/components/DataSpaceExecutableQueryAction.js +85 -0
  9. package/lib/components/DataSpaceExecutableQueryAction.js.map +1 -0
  10. package/lib/components/DataSpaceExecutionContextEditor.d.ts +1 -12
  11. package/lib/components/DataSpaceExecutionContextEditor.d.ts.map +1 -1
  12. package/lib/components/DataSpaceExecutionContextEditor.js +206 -134
  13. package/lib/components/DataSpaceExecutionContextEditor.js.map +1 -1
  14. package/lib/components/DataSpaceGeneralEditor/DataSpaceExecutablesSection.d.ts.map +1 -1
  15. package/lib/components/DataSpaceGeneralEditor/DataSpaceExecutablesSection.js +130 -26
  16. package/lib/components/DataSpaceGeneralEditor/DataSpaceExecutablesSection.js.map +1 -1
  17. package/lib/components/DataSpaceGeneralEditor/DataSpaceGeneralEditor.d.ts.map +1 -1
  18. package/lib/components/DataSpaceGeneralEditor/DataSpaceGeneralEditor.js +16 -75
  19. package/lib/components/DataSpaceGeneralEditor/DataSpaceGeneralEditor.js.map +1 -1
  20. package/lib/components/DataSpaceGeneralEditor/{DataSpaceDefaultExecutionContextSection.d.ts → DataSpaceHomeTab.d.ts} +3 -3
  21. package/lib/components/DataSpaceGeneralEditor/DataSpaceHomeTab.d.ts.map +1 -0
  22. package/lib/components/DataSpaceGeneralEditor/DataSpaceHomeTab.js +100 -0
  23. package/lib/components/DataSpaceGeneralEditor/DataSpaceHomeTab.js.map +1 -0
  24. package/lib/components/DataSpaceGeneralEditor/DataSpaceSidebar.d.ts +23 -0
  25. package/lib/components/DataSpaceGeneralEditor/DataSpaceSidebar.d.ts.map +1 -0
  26. package/lib/components/DataSpaceGeneralEditor/DataSpaceSidebar.js +31 -0
  27. package/lib/components/DataSpaceGeneralEditor/DataSpaceSidebar.js.map +1 -0
  28. package/lib/components/DataSpaceGeneralEditor/DataSpaceValidation.d.ts +28 -0
  29. package/lib/components/DataSpaceGeneralEditor/DataSpaceValidation.d.ts.map +1 -0
  30. package/lib/components/DataSpaceGeneralEditor/DataSpaceValidation.js +45 -0
  31. package/lib/components/DataSpaceGeneralEditor/DataSpaceValidation.js.map +1 -0
  32. package/lib/components/DataSpaceQueryAction.d.ts.map +1 -1
  33. package/lib/components/DataSpaceQueryAction.js +11 -8
  34. package/lib/components/DataSpaceQueryAction.js.map +1 -1
  35. package/lib/index.css +2 -2
  36. package/lib/index.css.map +1 -1
  37. package/lib/package.json +1 -1
  38. package/lib/stores/DataSpaceEditorState.d.ts +9 -0
  39. package/lib/stores/DataSpaceEditorState.d.ts.map +1 -1
  40. package/lib/stores/DataSpaceEditorState.js +15 -0
  41. package/lib/stores/DataSpaceEditorState.js.map +1 -1
  42. package/lib/stores/DataSpaceExecutableTemplateState.d.ts +48 -0
  43. package/lib/stores/DataSpaceExecutableTemplateState.d.ts.map +1 -0
  44. package/lib/stores/DataSpaceExecutableTemplateState.js +105 -0
  45. package/lib/stores/DataSpaceExecutableTemplateState.js.map +1 -0
  46. package/lib/stores/DataSpaceExecutionContextState.d.ts +4 -9
  47. package/lib/stores/DataSpaceExecutionContextState.d.ts.map +1 -1
  48. package/lib/stores/DataSpaceExecutionContextState.js +13 -39
  49. package/lib/stores/DataSpaceExecutionContextState.js.map +1 -1
  50. package/lib/stores/studio/DSL_DataSpace_GraphModifierHelper.d.ts +12 -6
  51. package/lib/stores/studio/DSL_DataSpace_GraphModifierHelper.d.ts.map +1 -1
  52. package/lib/stores/studio/DSL_DataSpace_GraphModifierHelper.js +45 -3
  53. package/lib/stores/studio/DSL_DataSpace_GraphModifierHelper.js.map +1 -1
  54. package/package.json +6 -6
  55. package/src/components/DSL_DataProduct_ElementDriver.tsx +0 -14
  56. package/src/components/DataSpaceEditor.tsx +10 -10
  57. package/src/components/DataSpaceExecutableQueryAction.tsx +159 -0
  58. package/src/components/DataSpaceExecutionContextEditor.tsx +699 -558
  59. package/src/components/DataSpaceGeneralEditor/DataSpaceExecutablesSection.tsx +517 -60
  60. package/src/components/DataSpaceGeneralEditor/DataSpaceGeneralEditor.tsx +22 -198
  61. package/src/components/DataSpaceGeneralEditor/DataSpaceHomeTab.tsx +235 -0
  62. package/src/components/DataSpaceGeneralEditor/DataSpaceSidebar.tsx +60 -0
  63. package/src/components/DataSpaceGeneralEditor/DataSpaceValidation.tsx +83 -0
  64. package/src/components/DataSpaceQueryAction.tsx +19 -12
  65. package/src/stores/DataSpaceEditorState.ts +18 -0
  66. package/src/stores/DataSpaceExecutableTemplateState.ts +152 -0
  67. package/src/stores/DataSpaceExecutionContextState.ts +17 -70
  68. package/src/stores/studio/DSL_DataSpace_GraphModifierHelper.ts +79 -6
  69. package/tsconfig.json +6 -2
  70. package/lib/components/DataSpaceGeneralEditor/DataSpaceDefaultExecutionContextSection.d.ts.map +0 -1
  71. package/lib/components/DataSpaceGeneralEditor/DataSpaceDefaultExecutionContextSection.js +0 -138
  72. package/lib/components/DataSpaceGeneralEditor/DataSpaceDefaultExecutionContextSection.js.map +0 -1
  73. package/src/components/DataSpaceGeneralEditor/DataSpaceDefaultExecutionContextSection.tsx +0 -393
@@ -15,94 +15,551 @@
15
15
  */
16
16
 
17
17
  import {
18
+ DataSpaceExecutableTemplate,
18
19
  type DataSpaceExecutable,
19
20
  DataSpacePackageableElementExecutable,
20
21
  } from '@finos/legend-extension-dsl-data-space/graph';
21
22
  import { observer } from 'mobx-react-lite';
23
+ import { useEffect, useState } from 'react';
24
+ import { flowResult } from 'mobx';
22
25
  import {
23
26
  dataSpace_addExecutable,
24
27
  dataSpace_removeExecutable,
28
+ dataSpace_setExecutableDescription,
29
+ dataSpace_setExecutableExecutionContextKey,
30
+ dataSpace_setExecutableTitle,
25
31
  } from '../../stores/studio/DSL_DataSpace_GraphModifierHelper.js';
26
32
  import { useEditorStore } from '@finos/legend-application-studio';
27
33
  import { DataSpaceEditorState } from '../../stores/DataSpaceEditorState.js';
28
- import { CustomSelectorInput, ListEditor } from '@finos/legend-art';
34
+ import {
35
+ BlankPanelPlaceholder,
36
+ clsx,
37
+ CustomSelectorInput,
38
+ Dialog,
39
+ LongArrowRightIcon,
40
+ ModalTitle,
41
+ PanelContent,
42
+ PanelDivider,
43
+ PanelFormSection,
44
+ PanelFormTextField,
45
+ PanelFormValidatedTextField,
46
+ PanelHeader,
47
+ PencilEditIcon,
48
+ PlusIcon,
49
+ TimesIcon,
50
+ } from '@finos/legend-art';
29
51
  import {
30
52
  type PackageableElement,
31
53
  PackageableElementExplicitReference,
32
54
  } from '@finos/legend-graph';
55
+ import { InlineLambdaEditor } from '@finos/legend-query-builder';
56
+ import {
57
+ stub_DataSpaceExecutableTemplateQuery,
58
+ type DataSpaceExecutableTemplateLambdaState,
59
+ } from '../../stores/DataSpaceExecutableTemplateState.js';
60
+ import { editDataSpaceExecutableTemplateInQueryBuilder } from '../DataSpaceExecutableQueryAction.js';
61
+
62
+ type NewExecutableKind = 'element' | 'inlineQuery';
63
+
64
+ const NewExecutableModal = observer(
65
+ (props: {
66
+ dataSpaceState: DataSpaceEditorState;
67
+ isOpen: boolean;
68
+ isReadOnly: boolean;
69
+ onClose: () => void;
70
+ onCreated: (executable: DataSpaceExecutable) => void;
71
+ }) => {
72
+ const { dataSpaceState, isOpen, isReadOnly, onClose, onCreated } = props;
73
+ const dataSpace = dataSpaceState.dataSpace;
74
+ const existingIds = (dataSpace.executables ?? [])
75
+ .map((executable) => executable.id)
76
+ .filter((executableId): executableId is string => Boolean(executableId));
77
+ const [kind, setKind] = useState<NewExecutableKind>('element');
78
+ const [selectedElement, setSelectedElement] = useState<
79
+ PackageableElement | undefined
80
+ >(undefined);
81
+ const [title, setTitle] = useState('');
82
+ const [isTitleValid, setIsTitleValid] = useState(false);
83
+ const [id, setId] = useState('');
84
+ const [isIdValid, setIsIdValid] = useState(false);
85
+
86
+ const elementOptions = dataSpaceState.getDataSpaceExecutableOptions();
87
+
88
+ const closeModal = (): void => {
89
+ setKind('element');
90
+ setSelectedElement(undefined);
91
+ setTitle('');
92
+ setId('');
93
+ onClose();
94
+ };
95
+
96
+ const validateTitle = (val: string): string | undefined =>
97
+ val ? undefined : `Title can't be empty`;
98
+
99
+ const validateId = (val: string): string | undefined => {
100
+ if (!val) {
101
+ return `Id can't be empty`;
102
+ }
103
+ if (existingIds.includes(val)) {
104
+ return `Id '${val}' already exists`;
105
+ }
106
+ return undefined;
107
+ };
108
+
109
+ const canCreate =
110
+ kind === 'element'
111
+ ? Boolean(selectedElement) && isTitleValid
112
+ : isTitleValid && isIdValid;
113
+
114
+ const create = (): void => {
115
+ if (isReadOnly || !canCreate) {
116
+ return;
117
+ }
118
+ if (kind === 'element' && selectedElement) {
119
+ const executablePointer = new DataSpacePackageableElementExecutable();
120
+ executablePointer.executable =
121
+ PackageableElementExplicitReference.create(selectedElement);
122
+ executablePointer.title = title;
123
+ dataSpace_addExecutable(dataSpace, executablePointer);
124
+ onCreated(executablePointer);
125
+ } else if (kind === 'inlineQuery') {
126
+ const executableTemplate = new DataSpaceExecutableTemplate();
127
+ executableTemplate.id = id;
128
+ executableTemplate.title = title;
129
+ executableTemplate.query = stub_DataSpaceExecutableTemplateQuery();
130
+ dataSpace_addExecutable(dataSpace, executableTemplate);
131
+ onCreated(executableTemplate);
132
+ }
133
+ closeModal();
134
+ };
135
+
136
+ return (
137
+ <Dialog
138
+ open={isOpen}
139
+ onClose={closeModal}
140
+ classes={{ container: 'search-modal__container' }}
141
+ slotProps={{
142
+ paper: { classes: { root: 'search-modal__inner-container' } },
143
+ }}
144
+ >
145
+ <form
146
+ onSubmit={(event): void => {
147
+ event.preventDefault();
148
+ create();
149
+ }}
150
+ className="modal search-modal modal--dark"
151
+ >
152
+ <ModalTitle title="New Executable" />
153
+ <PanelFormSection>
154
+ <div className="dataSpace-editor__tab-toggle">
155
+ <button
156
+ type="button"
157
+ className={clsx('dataSpace-editor__tab-toggle__tab', {
158
+ 'dataSpace-editor__tab-toggle__tab--active':
159
+ kind === 'element',
160
+ })}
161
+ onClick={(): void => setKind('element')}
162
+ >
163
+ Existing Element
164
+ </button>
165
+ <button
166
+ type="button"
167
+ className={clsx('dataSpace-editor__tab-toggle__tab', {
168
+ 'dataSpace-editor__tab-toggle__tab--active':
169
+ kind === 'inlineQuery',
170
+ })}
171
+ onClick={(): void => setKind('inlineQuery')}
172
+ >
173
+ Inline Query
174
+ </button>
175
+ </div>
176
+ </PanelFormSection>
177
+ {kind === 'element' && (
178
+ <>
179
+ <PanelFormSection>
180
+ <div className="panel__content__form__section__header__label">
181
+ Element
182
+ </div>
183
+ <CustomSelectorInput
184
+ options={elementOptions}
185
+ onChange={(
186
+ option: { value: PackageableElement } | null,
187
+ ): void => {
188
+ const element = option?.value;
189
+ setSelectedElement(element);
190
+ if (element && !title) {
191
+ setTitle(element.name);
192
+ }
193
+ }}
194
+ value={
195
+ selectedElement
196
+ ? {
197
+ label: selectedElement.path,
198
+ value: selectedElement,
199
+ }
200
+ : null
201
+ }
202
+ placeholder="Select a function or service..."
203
+ darkMode={true}
204
+ />
205
+ </PanelFormSection>
206
+ <PanelFormValidatedTextField
207
+ name="Title"
208
+ value={title}
209
+ update={(value): void => setTitle(value ?? '')}
210
+ validate={validateTitle}
211
+ onValidate={(issue): void => setIsTitleValid(!issue)}
212
+ isReadOnly={isReadOnly}
213
+ placeholder="Enter a title"
214
+ fullWidth={true}
215
+ />
216
+ </>
217
+ )}
218
+ {kind === 'inlineQuery' && (
219
+ <>
220
+ <PanelFormValidatedTextField
221
+ name="Id"
222
+ value={id}
223
+ update={(value): void => setId(value ?? '')}
224
+ validate={validateId}
225
+ onValidate={(issue): void => setIsIdValid(!issue)}
226
+ isReadOnly={isReadOnly}
227
+ placeholder="Enter a unique id"
228
+ fullWidth={true}
229
+ />
230
+ <PanelFormValidatedTextField
231
+ name="Title"
232
+ value={title}
233
+ update={(value): void => setTitle(value ?? '')}
234
+ validate={validateTitle}
235
+ onValidate={(issue): void => setIsTitleValid(!issue)}
236
+ isReadOnly={isReadOnly}
237
+ placeholder="Enter a title"
238
+ fullWidth={true}
239
+ />
240
+ </>
241
+ )}
242
+ <PanelDivider />
243
+ <PanelFormSection>
244
+ <div className="search-modal__actions">
245
+ <button
246
+ type="button"
247
+ className="btn btn--dark"
248
+ onClick={closeModal}
249
+ >
250
+ Cancel
251
+ </button>
252
+ <button
253
+ type="submit"
254
+ className="btn btn--dark"
255
+ disabled={isReadOnly || !canCreate}
256
+ >
257
+ Create
258
+ </button>
259
+ </div>
260
+ </PanelFormSection>
261
+ </form>
262
+ </Dialog>
263
+ );
264
+ },
265
+ );
266
+
267
+ const ExecutableTemplateQueryEditor = observer(
268
+ (props: {
269
+ dataSpaceState: DataSpaceEditorState;
270
+ executableTemplate: DataSpaceExecutableTemplate;
271
+ isReadOnly: boolean;
272
+ }) => {
273
+ const { dataSpaceState, executableTemplate, isReadOnly } = props;
274
+ const editorStore = dataSpaceState.editorStore;
275
+ const applicationStore = editorStore.applicationStore;
276
+ const lambdaState: DataSpaceExecutableTemplateLambdaState =
277
+ dataSpaceState.executableTemplateStates.getLambdaState(
278
+ executableTemplate,
279
+ );
280
+
281
+ useEffect(() => {
282
+ flowResult(
283
+ lambdaState.convertLambdaObjectToGrammarString({ pretty: true }),
284
+ ).catch(applicationStore.alertUnhandledError);
285
+ }, [lambdaState, applicationStore]);
286
+
287
+ const editInQueryBuilder = applicationStore.guardUnhandledError(async () =>
288
+ editDataSpaceExecutableTemplateInQueryBuilder(
289
+ dataSpaceState.dataSpace,
290
+ executableTemplate,
291
+ lambdaState,
292
+ editorStore,
293
+ ),
294
+ );
295
+
296
+ return (
297
+ <div className="dataSpace-editor__configuration__section">
298
+ <div className="dataSpace-editor__configuration__section__header">
299
+ <div className="dataSpace-editor__configuration__section__title">
300
+ Query
301
+ </div>
302
+ </div>
303
+ <div className="dataSpace-editor__executable__query">
304
+ <button
305
+ className="dataSpace-editor__executable__query-builder-btn"
306
+ onClick={editInQueryBuilder}
307
+ disabled={isReadOnly}
308
+ title="Edit query with Query Builder"
309
+ >
310
+ <PencilEditIcon />
311
+ <span>Query Builder</span>
312
+ </button>
313
+ <InlineLambdaEditor
314
+ className="dataSpace-editor__executable__lambda-editor"
315
+ disabled={isReadOnly}
316
+ lambdaEditorState={lambdaState}
317
+ forceBackdrop={Boolean(lambdaState.parserError)}
318
+ />
319
+ </div>
320
+ </div>
321
+ );
322
+ },
323
+ );
324
+
325
+ const ExecutableConfigurationEditor = observer(
326
+ (props: {
327
+ dataSpaceState: DataSpaceEditorState;
328
+ executable: DataSpaceExecutable;
329
+ isReadOnly: boolean;
330
+ }) => {
331
+ const { dataSpaceState, executable, isReadOnly } = props;
332
+ const editorStore = dataSpaceState.editorStore;
333
+ const dataSpace = dataSpaceState.dataSpace;
334
+ const executionContextOptions = (dataSpace.executionContexts ?? []).map(
335
+ (context) => ({ label: context.name, value: context.name }),
336
+ );
337
+ const executionContextKeyOption = executable.executionContextKey
338
+ ? {
339
+ label: executable.executionContextKey,
340
+ value: executable.executionContextKey,
341
+ }
342
+ : null;
343
+
344
+ return (
345
+ <div className="dataSpace-editor__configuration">
346
+ <PanelFormTextField
347
+ name="Title"
348
+ value={executable.title}
349
+ update={(value): void =>
350
+ dataSpace_setExecutableTitle(executable, value ?? '')
351
+ }
352
+ isReadOnly={isReadOnly}
353
+ placeholder="Enter title"
354
+ prompt="Provide a title for this executable"
355
+ fullWidth={true}
356
+ />
357
+ <PanelFormSection>
358
+ <div className="panel__content__form__section__header__label">
359
+ Description
360
+ </div>
361
+ <div className="panel__content__form__section__header__prompt">
362
+ Provide a description for this executable.
363
+ </div>
364
+ <textarea
365
+ className="panel__content__form__section__textarea"
366
+ spellCheck={false}
367
+ disabled={isReadOnly}
368
+ value={executable.description ?? ''}
369
+ onChange={(event): void =>
370
+ dataSpace_setExecutableDescription(
371
+ executable,
372
+ event.target.value || undefined,
373
+ )
374
+ }
375
+ rows={3}
376
+ placeholder="Enter description"
377
+ />
378
+ </PanelFormSection>
379
+ {executable instanceof DataSpacePackageableElementExecutable && (
380
+ <div className="dataSpace-editor__configuration__section">
381
+ <div className="dataSpace-editor__configuration__section__header">
382
+ <div className="dataSpace-editor__configuration__section__title">
383
+ Element
384
+ </div>
385
+ </div>
386
+ <div className="dataSpace-editor__configuration__row">
387
+ <span className="dataSpace-editor__executable__pointer__path">
388
+ {executable.executable.value.path}
389
+ </span>
390
+ <button
391
+ className="btn--dark btn--sm dataSpace-editor__configuration__row__btn"
392
+ onClick={(): void =>
393
+ editorStore.graphEditorMode.openElement(
394
+ executable.executable.value,
395
+ )
396
+ }
397
+ title="See element"
398
+ tabIndex={-1}
399
+ >
400
+ <LongArrowRightIcon />
401
+ </button>
402
+ </div>
403
+ </div>
404
+ )}
405
+ {Boolean(executionContextOptions.length) && (
406
+ <div className="dataSpace-editor__configuration__section">
407
+ <div className="dataSpace-editor__configuration__section__header">
408
+ <div className="dataSpace-editor__configuration__section__title">
409
+ Execution Context
410
+ </div>
411
+ <div className="dataSpace-editor__configuration__section__hint">
412
+ Optional. Pins this executable to a specific execution. If not
413
+ provided, points to the default execution context if set
414
+ </div>
415
+ </div>
416
+ <CustomSelectorInput
417
+ className="dataSpace-editor__configuration__row__select"
418
+ disabled={isReadOnly}
419
+ options={executionContextOptions}
420
+ onChange={(option: { value: string } | null): void =>
421
+ dataSpace_setExecutableExecutionContextKey(
422
+ executable,
423
+ option?.value,
424
+ )
425
+ }
426
+ value={executionContextKeyOption}
427
+ placeholder={
428
+ dataSpace.defaultExecutionContext
429
+ ? 'Use default execution context'
430
+ : 'Choose execution context'
431
+ }
432
+ isClearable={true}
433
+ darkMode={true}
434
+ />
435
+ </div>
436
+ )}
437
+ {executable instanceof DataSpaceExecutableTemplate && (
438
+ <ExecutableTemplateQueryEditor
439
+ dataSpaceState={dataSpaceState}
440
+ executableTemplate={executable}
441
+ isReadOnly={isReadOnly}
442
+ />
443
+ )}
444
+ </div>
445
+ );
446
+ },
447
+ );
448
+
449
+ const ExecutableTab = observer(
450
+ (props: {
451
+ executable: DataSpaceExecutable;
452
+ isActive: boolean;
453
+ isReadOnly: boolean;
454
+ onSelect: () => void;
455
+ onDelete: () => void;
456
+ }) => {
457
+ const { executable, isActive, isReadOnly, onSelect, onDelete } = props;
458
+
459
+ const remove = (event: React.MouseEvent): void => {
460
+ event.stopPropagation();
461
+ onDelete();
462
+ };
463
+
464
+ return (
465
+ <div
466
+ onClick={onSelect}
467
+ className={clsx('service-editor__tab', 'dataSpace-editor__tab', {
468
+ 'service-editor__tab--active': isActive,
469
+ })}
470
+ role="tab"
471
+ >
472
+ <span className="dataSpace-editor__tab__label">
473
+ {executable.title || '(untitled)'}
474
+ </span>
475
+ {!isReadOnly && (
476
+ <button
477
+ className="dataSpace-editor__tab__close-btn"
478
+ onClick={remove}
479
+ tabIndex={-1}
480
+ title="Delete executable"
481
+ >
482
+ <TimesIcon />
483
+ </button>
484
+ )}
485
+ </div>
486
+ );
487
+ },
488
+ );
33
489
 
34
490
  export const DataspaceExecutablesSection = observer(() => {
35
491
  const editorStore = useEditorStore();
36
-
37
492
  const dataSpaceState =
38
493
  editorStore.tabManagerState.getCurrentEditorState(DataSpaceEditorState);
39
494
  const dataSpace = dataSpaceState.dataSpace;
495
+ const isReadOnly = dataSpaceState.isReadOnly;
496
+ const executables = dataSpace.executables ?? [];
497
+ const [selectedExecutable, setSelectedExecutable] = useState<
498
+ DataSpaceExecutable | undefined
499
+ >(executables[0]);
500
+ const [isNewModalOpen, setIsNewModalOpen] = useState(false);
40
501
 
41
- // Event handlers
42
- const handleAddExecutable = (option: {
43
- label: string;
44
- value: PackageableElement;
45
- }): void => {
46
- if (typeof option.value === 'object') {
47
- const element = option.value;
48
- const executablePointer = new DataSpacePackageableElementExecutable();
49
- executablePointer.executable =
50
- PackageableElementExplicitReference.create(element);
51
- executablePointer.title = element.name;
52
- dataSpace_addExecutable(dataSpace, executablePointer);
53
- }
54
- };
502
+ const currentSelection =
503
+ selectedExecutable && executables.includes(selectedExecutable)
504
+ ? selectedExecutable
505
+ : executables[0];
55
506
 
56
507
  const handleRemoveExecutable = (executable: DataSpaceExecutable): void => {
57
508
  dataSpace_removeExecutable(dataSpace, executable);
509
+ setSelectedExecutable((dataSpace.executables ?? [])[0]);
58
510
  };
59
511
 
60
- // ListEditor component renderers
61
- const ExecutableComponent = observer(
62
- (props: { item: DataSpaceExecutable }): React.ReactElement => {
63
- const { item } = props;
512
+ const addExecutable = (): void => setIsNewModalOpen(true);
64
513
 
65
- return (
66
- <div className="panel__content__form__section__list__item__content">
67
- <div className="panel__content__form__section__list__item__content__label">
68
- {item.title}
69
- </div>
514
+ return (
515
+ <div className="panel dataSpace-editor__tab-panel">
516
+ <PanelHeader>
517
+ <div className="uml-element-editor__tabs">
518
+ {executables.map((executable) => (
519
+ <ExecutableTab
520
+ key={executable.hashCode}
521
+ executable={executable}
522
+ isActive={executable === currentSelection}
523
+ isReadOnly={isReadOnly}
524
+ onSelect={(): void => setSelectedExecutable(executable)}
525
+ onDelete={(): void => handleRemoveExecutable(executable)}
526
+ />
527
+ ))}
528
+ <button
529
+ className="panel__header__action"
530
+ disabled={isReadOnly}
531
+ onClick={addExecutable}
532
+ title="Add an executable"
533
+ tabIndex={-1}
534
+ >
535
+ <PlusIcon />
536
+ </button>
70
537
  </div>
71
- );
72
- },
73
- );
74
-
75
- const NewExecutableComponent = observer(
76
- (props: { onFinishEditing: () => void }) => {
77
- const { onFinishEditing } = props;
78
-
79
- return (
80
- <div className="panel__content__form__section__list__new-item__input">
81
- <CustomSelectorInput
82
- options={dataSpaceState.getDataSpaceExecutableOptions()}
83
- onChange={(event: { label: string; value: PackageableElement }) => {
84
- onFinishEditing();
85
- handleAddExecutable(event);
86
- }}
87
- placeholder="Select an element to add..."
88
- darkMode={true}
538
+ </PanelHeader>
539
+ <PanelContent>
540
+ {currentSelection ? (
541
+ <ExecutableConfigurationEditor
542
+ dataSpaceState={dataSpaceState}
543
+ executable={currentSelection}
544
+ isReadOnly={isReadOnly}
89
545
  />
90
- </div>
91
- );
92
- },
93
- );
94
-
95
- return (
96
- <ListEditor
97
- title="Executables"
98
- prompt="Add functions and services to display under Quick Start for users to see how this Data Product can be used."
99
- items={dataSpace.executables}
100
- keySelector={(element: DataSpaceExecutable) => element.hashCode}
101
- ItemComponent={ExecutableComponent}
102
- NewItemComponent={NewExecutableComponent}
103
- handleRemoveItem={handleRemoveExecutable}
104
- isReadOnly={dataSpaceState.isReadOnly}
105
- emptyMessage="No executables specified"
106
- />
546
+ ) : (
547
+ <BlankPanelPlaceholder
548
+ text="Add an executable"
549
+ onClick={addExecutable}
550
+ clickActionType="add"
551
+ tooltipText="Click to add an executable"
552
+ disabled={isReadOnly}
553
+ />
554
+ )}
555
+ </PanelContent>
556
+ <NewExecutableModal
557
+ dataSpaceState={dataSpaceState}
558
+ isOpen={isNewModalOpen}
559
+ isReadOnly={isReadOnly}
560
+ onClose={(): void => setIsNewModalOpen(false)}
561
+ onCreated={(executable): void => setSelectedExecutable(executable)}
562
+ />
563
+ </div>
107
564
  );
108
565
  });