@morscherlab/mint-sdk 1.0.0-beta.3 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
- package/dist/__tests__/composables/useApi.test.d.ts +1 -0
- package/dist/components/AppContainer.vue.d.ts +1 -1
- package/dist/components/AppLayout.vue.d.ts +20 -1
- package/dist/components/AppSidebar.vue.d.ts +56 -4
- package/dist/components/AppTopBar.vue.d.ts +7 -25
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +3 -1
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -0
- package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +5 -0
- package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
- package/dist/components/ControlWorkspaceView.vue.d.ts +24 -7
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
- package/dist/components/FormBuilder.vue.d.ts +9 -9
- package/dist/components/PlateMapEditor.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/WellPlate.vue.d.ts +2 -2
- package/dist/components/index.d.ts +3 -12
- package/dist/components/index.js +3 -3
- package/dist/components/{AppPageSelector.vue.d.ts → internal/AppPageSelectorInternal.vue.d.ts} +1 -1
- package/dist/components/{AppPillNav.vue.d.ts → internal/AppPillNavInternal.vue.d.ts} +3 -1
- package/dist/components/{CalendarGridPanel.vue.d.ts → internal/CalendarGridPanelInternal.vue.d.ts} +1 -1
- package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
- package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
- package/dist/{components-D_Sr0adg.js → components-BkGF4B4y.js} +4484 -3967
- package/dist/components-BkGF4B4y.js.map +1 -0
- package/dist/composables/experiment-utils.d.ts +8 -0
- package/dist/composables/index.d.ts +5 -7
- package/dist/composables/index.js +4 -4
- package/dist/composables/useAppExperiment.d.ts +31 -2
- package/dist/composables/useBioTemplateComponents.d.ts +5 -3
- package/dist/composables/useBioTemplatePackWorkspace.d.ts +3 -2
- package/dist/composables/useBioTemplatePresetWorkspace.d.ts +6 -5
- package/dist/composables/useBioTemplateWorkspace.d.ts +5 -4
- package/dist/composables/useControlSchema.d.ts +43 -21
- package/dist/composables/usePluginClient.d.ts +5 -2
- package/dist/{composables-C3dpXQN5.js → composables-CHsME9H1.js} +40 -28
- package/dist/composables-CHsME9H1.js.map +1 -0
- package/dist/index.d.ts +5 -12
- package/dist/index.js +5 -5
- package/dist/install.js +2 -2
- package/dist/styles.css +3625 -3651
- package/dist/templates/componentBindings.d.ts +13 -0
- package/dist/templates/index.d.ts +3 -3
- package/dist/templates/index.js +2 -2
- package/dist/{templates-50NPjaxL.js → templates-B5jmTWuk.js} +111 -56
- package/dist/templates-B5jmTWuk.js.map +1 -0
- package/dist/types/components.d.ts +6 -25
- package/dist/types/index.d.ts +1 -1
- package/dist/{useScheduleDrag-D4oWdh41.js → useScheduleDrag-BgzpQT53.js} +160 -117
- package/dist/useScheduleDrag-BgzpQT53.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/ActionItem.test.ts +6 -6
- package/src/__tests__/components/AppLayout.test.ts +44 -0
- package/src/__tests__/components/AppPageSelector.test.ts +8 -8
- package/src/__tests__/components/AppPillNav.test.ts +53 -6
- package/src/__tests__/components/AppSidebar.test.ts +126 -0
- package/src/__tests__/components/AppToastContainer.test.ts +0 -11
- package/src/__tests__/components/AppTopBar.test.ts +182 -119
- package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
- package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +15 -1
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +26 -1
- package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
- package/src/__tests__/components/ComponentBindingRenderer.test.ts +161 -0
- package/src/__tests__/components/ControlWorkspaceView.test.ts +134 -63
- package/src/__tests__/components/DateTimePicker.test.ts +2 -2
- package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
- package/src/__tests__/composables/experiment-utils.test.ts +30 -0
- package/src/__tests__/composables/useApi.test.ts +30 -0
- package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
- package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +6 -3
- package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +6 -6
- package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
- package/src/__tests__/composables/useControlSchema.test.ts +150 -36
- package/src/__tests__/composables/usePluginClient.test.ts +99 -2
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +120 -25
- package/src/__tests__/templates/templates.test.ts +12 -0
- package/src/components/AppAvatarMenu.vue +3 -3
- package/src/components/AppLayout.story.vue +39 -0
- package/src/components/AppLayout.vue +83 -2
- package/src/components/AppPluginSwitcher.vue +5 -5
- package/src/components/AppSidebar.story.vue +113 -5
- package/src/components/AppSidebar.vue +144 -24
- package/src/components/AppTopBar.story.vue +2 -5
- package/src/components/AppTopBar.vue +35 -425
- package/src/components/BioTemplateExperimentWorkspaceView.story.vue +2 -2
- package/src/components/BioTemplateExperimentWorkspaceView.vue +6 -0
- package/src/components/BioTemplatePackWorkspaceView.story.vue +4 -4
- package/src/components/BioTemplatePackWorkspaceView.vue +1 -0
- package/src/components/BioTemplatePresetWorkspaceView.story.vue +14 -2
- package/src/components/BioTemplatePresetWorkspaceView.vue +11 -2
- package/src/components/BioTemplateRenderer.vue +15 -227
- package/src/components/ComponentBindingRenderer.story.vue +57 -0
- package/src/components/ComponentBindingRenderer.vue +308 -0
- package/src/components/ControlWorkspaceView.story.vue +20 -9
- package/src/components/ControlWorkspaceView.vue +43 -12
- package/src/components/DatePicker.vue +2 -2
- package/src/components/DateTimePicker.vue +2 -2
- package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
- package/src/components/DoseDesignWorkspaceView.vue +255 -0
- package/src/components/ExperimentPopover.vue +2 -6
- package/src/components/ExperimentSelectorModal.vue +6 -5
- package/src/components/FormBuilder.story.vue +190 -0
- package/src/components/PluginWorkspaceView.story.vue +334 -0
- package/src/components/PluginWorkspaceView.vue +708 -0
- package/src/components/SettingsModal.story.vue +87 -0
- package/src/components/WellPlate.vue +2 -2
- package/src/components/index.ts +3 -12
- package/src/components/{AppPageSelector.vue → internal/AppPageSelectorInternal.vue} +9 -9
- package/src/components/internal/AppPillNavInternal.vue +194 -0
- package/src/components/{CalendarGridPanel.vue → internal/CalendarGridPanelInternal.vue} +1 -1
- package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +3 -3
- package/src/composables/experiment-utils.ts +26 -0
- package/src/composables/index.ts +21 -7
- package/src/composables/useApi.ts +9 -2
- package/src/composables/useAppExperiment.ts +85 -13
- package/src/composables/useBioTemplateComponents.ts +12 -0
- package/src/composables/useBioTemplatePackWorkspace.ts +6 -2
- package/src/composables/useBioTemplatePresetWorkspace.ts +10 -21
- package/src/composables/useBioTemplateWorkspace.ts +6 -4
- package/src/composables/useControlSchema.ts +157 -69
- package/src/composables/usePluginClient.ts +50 -9
- package/src/index.ts +6 -563
- package/src/styles/components/app-layout.css +82 -0
- package/src/styles/components/app-pill-nav.css +70 -0
- package/src/styles/components/app-sidebar.css +119 -0
- package/src/styles/components/app-top-bar.css +0 -235
- package/src/styles/index.css +0 -1
- package/src/templates/componentBindings.ts +38 -0
- package/src/templates/index.ts +4 -0
- package/src/types/components.ts +6 -31
- package/src/types/index.ts +2 -6
- package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
- package/dist/components/FormFieldRenderer.vue.d.ts +0 -28
- package/dist/components/FormSection.vue.d.ts +0 -30
- package/dist/components/GroupingModal.vue.d.ts +0 -12
- package/dist/components/SettingsButton.vue.d.ts +0 -30
- package/dist/components/ToastNotification.vue.d.ts +0 -2
- package/dist/components-D_Sr0adg.js.map +0 -1
- package/dist/composables/usePluginApi.d.ts +0 -22
- package/dist/composables-C3dpXQN5.js.map +0 -1
- package/dist/templates-50NPjaxL.js.map +0 -1
- package/dist/useScheduleDrag-D4oWdh41.js.map +0 -1
- package/src/__tests__/components/FormCompatibility.test.ts +0 -94
- package/src/__tests__/components/GroupingModal.test.ts +0 -73
- package/src/__tests__/components/SettingsButton.test.ts +0 -44
- package/src/__tests__/composables/usePluginApi.test.ts +0 -81
- package/src/components/AppPillNav.vue +0 -71
- package/src/components/FormFieldRenderer.vue +0 -35
- package/src/components/FormSection.vue +0 -37
- package/src/components/GroupingModal.story.vue +0 -52
- package/src/components/GroupingModal.vue +0 -61
- package/src/components/SettingsButton.story.vue +0 -58
- package/src/components/SettingsButton.vue +0 -64
- package/src/components/ToastNotification.vue +0 -9
- package/src/composables/usePluginApi.ts +0 -32
- package/src/styles/components/settings-button.css +0 -31
- /package/dist/__tests__/components/{FormCompatibility.test.d.ts → ComponentBindingRenderer.test.d.ts} +0 -0
- /package/dist/__tests__/components/{GroupingModal.test.d.ts → DoseDesignWorkspaceView.test.d.ts} +0 -0
- /package/dist/__tests__/components/{SettingsButton.test.d.ts → PluginWorkspaceView.test.d.ts} +0 -0
- /package/dist/components/{ActionItem.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +0 -0
- /package/src/components/{ActionItem.vue → internal/ActionItemInternal.vue} +0 -0
|
@@ -5,7 +5,6 @@ import type {
|
|
|
5
5
|
SelectOption,
|
|
6
6
|
SettingsModalSchema,
|
|
7
7
|
TopBarSettingsConfig,
|
|
8
|
-
TopBarTab,
|
|
9
8
|
} from '../types'
|
|
10
9
|
import type {
|
|
11
10
|
FieldCondition,
|
|
@@ -45,7 +44,7 @@ export interface ControlViewConfig {
|
|
|
45
44
|
to?: string
|
|
46
45
|
href?: string
|
|
47
46
|
disabled?: boolean
|
|
48
|
-
children?:
|
|
47
|
+
children?: PillNavItem['children']
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
export interface ControlSidebarConfig extends Omit<ControlSectionConfig, 'id' | 'title' | 'description' | 'columns'> {
|
|
@@ -120,6 +119,10 @@ export interface ControlModel extends Omit<ControlWorkspaceOptions, 'sections' |
|
|
|
120
119
|
controls?: ControlSchema
|
|
121
120
|
sections?: Record<string, ControlModelSectionConfig>
|
|
122
121
|
views?: Record<string, ControlModelViewConfig>
|
|
122
|
+
/** Optional SDK component bindings returned with generated component props for custom workspace slots. */
|
|
123
|
+
componentBindings?: ControlComponentBindingsConfig
|
|
124
|
+
/** Alias for componentBindings in raw control models. */
|
|
125
|
+
components?: ControlComponentBindingsConfig
|
|
123
126
|
/** Optional ControlWorkspaceView componentProps mapping returned with the generated controls/options. */
|
|
124
127
|
componentProps?: ControlComponentPropsMap
|
|
125
128
|
/** Optional named componentProps mappings returned for multiple SDK components. */
|
|
@@ -129,6 +132,7 @@ export interface ControlModel extends Omit<ControlWorkspaceOptions, 'sections' |
|
|
|
129
132
|
export interface ControlModelBinding {
|
|
130
133
|
controls: ControlSchema
|
|
131
134
|
controlOptions: ControlWorkspaceOptions
|
|
135
|
+
componentBindings?: ControlComponentBindingsConfig
|
|
132
136
|
componentProps?: ControlComponentPropsMap
|
|
133
137
|
componentPropsById?: ControlComponentPropsByIdMap
|
|
134
138
|
}
|
|
@@ -171,37 +175,28 @@ export interface ControlWorkspaceTopBarSettingsBinding extends ControlTopBarSett
|
|
|
171
175
|
onSettingsValuesChange: (values: Record<string, unknown>) => void
|
|
172
176
|
}
|
|
173
177
|
|
|
174
|
-
export interface ControlWorkspaceTopBarBinding {
|
|
175
|
-
tabs: TopBarTab[]
|
|
176
|
-
currentTabId: string
|
|
177
|
-
onTabSelect: (tab: TopBarTab) => void
|
|
178
|
-
}
|
|
179
|
-
|
|
180
178
|
export interface ControlWorkspacePillNavBinding {
|
|
181
179
|
items: PillNavItem[]
|
|
182
180
|
currentItemId: string
|
|
183
181
|
onSelect: (item: PillNavItem) => void
|
|
184
182
|
}
|
|
185
183
|
|
|
186
|
-
export interface
|
|
184
|
+
export interface ControlWorkspaceTopBarBinding extends ControlWorkspaceTopBarSettingsBinding {
|
|
187
185
|
pillNav: PillNavItem[]
|
|
188
186
|
currentPillId: string
|
|
189
187
|
onPillSelect: (item: PillNavItem) => void
|
|
190
188
|
}
|
|
191
189
|
|
|
192
|
-
export
|
|
193
|
-
tabs: TopBarTab[]
|
|
194
|
-
currentTabId: string
|
|
195
|
-
onTabSelect: (tab: TopBarTab) => void
|
|
196
|
-
}
|
|
190
|
+
export type ControlWorkspaceAppTopBarPillBinding = ControlWorkspaceTopBarBinding
|
|
197
191
|
|
|
198
192
|
export interface ControlWorkspaceComponentBindings {
|
|
199
193
|
form: ControlWorkspaceFormBinding
|
|
200
194
|
sidebar: ControlWorkspaceSidebarBinding
|
|
201
|
-
topBar: ComputedRef<
|
|
202
|
-
topBarTabs: ComputedRef<ControlWorkspaceAppTopBarTabsBinding>
|
|
195
|
+
topBar: ComputedRef<ControlWorkspaceTopBarBinding>
|
|
203
196
|
topBarSettings: ControlWorkspaceTopBarSettingsBinding
|
|
204
197
|
pillNav: ControlWorkspacePillNavBinding
|
|
198
|
+
componentBindings: ComputedRef<ControlComponentBinding[]>
|
|
199
|
+
componentBindingsById: ComputedRef<ControlComponentBindingsById>
|
|
205
200
|
componentProps: ComputedRef<Record<string, unknown>>
|
|
206
201
|
componentPropsById: ComputedRef<Record<string, Record<string, unknown>>>
|
|
207
202
|
}
|
|
@@ -217,6 +212,29 @@ export type ControlComponentPropsMap<TValues extends Record<string, unknown> = R
|
|
|
217
212
|
export type ControlComponentPropsByIdMap<TValues extends Record<string, unknown> = Record<string, unknown>> =
|
|
218
213
|
Record<string, ControlComponentPropsMap<TValues>>
|
|
219
214
|
|
|
215
|
+
export interface ControlComponentBindingConfig<TValues extends Record<string, unknown> = Record<string, unknown>> {
|
|
216
|
+
id?: string
|
|
217
|
+
component: string
|
|
218
|
+
props?: ControlComponentPropsMap<TValues>
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface ControlComponentBindingRecordConfig<TValues extends Record<string, unknown> = Record<string, unknown>> {
|
|
222
|
+
component: string
|
|
223
|
+
props?: ControlComponentPropsMap<TValues>
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export type ControlComponentBindingsConfig<TValues extends Record<string, unknown> = Record<string, unknown>> =
|
|
227
|
+
| readonly ControlComponentBindingConfig<TValues>[]
|
|
228
|
+
| Record<string, ControlComponentBindingRecordConfig<TValues>>
|
|
229
|
+
|
|
230
|
+
export interface ControlComponentBinding {
|
|
231
|
+
id: string
|
|
232
|
+
component: string
|
|
233
|
+
props: Record<string, unknown>
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export type ControlComponentBindingsById = Record<string, ControlComponentBinding>
|
|
237
|
+
|
|
220
238
|
export interface WellPlateControlPropsOptions<TValues extends Record<string, unknown> = Record<string, unknown>> {
|
|
221
239
|
selectedWells?: ControlComponentPropSource<TValues>
|
|
222
240
|
format?: ControlComponentPropSource<TValues>
|
|
@@ -276,7 +294,6 @@ export interface UseControlSchemaReturn<TControls extends ControlSchema> {
|
|
|
276
294
|
sidebarPanels: Record<string, SidebarToolSection[]>
|
|
277
295
|
viewIds: string[]
|
|
278
296
|
viewItems: PillNavItem[]
|
|
279
|
-
topBarTabs: TopBarTab[]
|
|
280
297
|
defaultView: string
|
|
281
298
|
sectionSchemas: Record<string, ControlFormSchema>
|
|
282
299
|
sidebar: ControlSidebarBinding
|
|
@@ -291,10 +308,12 @@ export interface UseControlWorkspaceReturn<TControls extends ControlSchema>
|
|
|
291
308
|
activeView: Ref<string>
|
|
292
309
|
form: ControlWorkspaceFormBinding
|
|
293
310
|
sidebar: ControlWorkspaceSidebarBinding
|
|
294
|
-
topBar: ControlWorkspaceTopBarBinding
|
|
311
|
+
topBar: ComputedRef<ControlWorkspaceTopBarBinding>
|
|
295
312
|
pillNav: ControlWorkspacePillNavBinding
|
|
296
313
|
topBarSettings: ControlWorkspaceTopBarSettingsBinding
|
|
297
314
|
bindings: ControlWorkspaceComponentBindings
|
|
315
|
+
componentBindings: ComputedRef<ControlComponentBinding[]>
|
|
316
|
+
componentBindingsById: ComputedRef<ControlComponentBindingsById>
|
|
298
317
|
componentProps: ComputedRef<Record<string, unknown>>
|
|
299
318
|
componentPropsById: ComputedRef<Record<string, Record<string, unknown>>>
|
|
300
319
|
setActiveView: (viewId: string) => void
|
|
@@ -306,6 +325,12 @@ export interface UseControlWorkspaceReturn<TControls extends ControlSchema>
|
|
|
306
325
|
getComponentPropsById: (
|
|
307
326
|
mappings?: ControlComponentPropsByIdMap<ControlValues<TControls> & Record<string, unknown>>
|
|
308
327
|
) => Record<string, Record<string, unknown>>
|
|
328
|
+
getComponentBindings: (
|
|
329
|
+
bindings?: ControlComponentBindingsConfig<ControlValues<TControls> & Record<string, unknown>>
|
|
330
|
+
) => ControlComponentBinding[]
|
|
331
|
+
getComponentBindingsById: (
|
|
332
|
+
bindings?: ControlComponentBindingsConfig<ControlValues<TControls> & Record<string, unknown>>
|
|
333
|
+
) => ControlComponentBindingsById
|
|
309
334
|
}
|
|
310
335
|
|
|
311
336
|
export type ControlValues<TControls extends ControlSchema> = {
|
|
@@ -339,16 +364,26 @@ export function defineControls<TControls extends ControlSchema>(controls: TContr
|
|
|
339
364
|
return controls
|
|
340
365
|
}
|
|
341
366
|
|
|
342
|
-
/**
|
|
367
|
+
/** Preserve literal SDK component binding ids while marking an object as generated workspace component bindings. */
|
|
368
|
+
export function defineControlComponentBindings<TBindings extends ControlComponentBindingsConfig>(
|
|
369
|
+
bindings: TBindings,
|
|
370
|
+
): TBindings {
|
|
371
|
+
return bindings
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Create a complete workspace component binding from a simple controls data model for ControlWorkspaceView, generated forms, and sidebars. */
|
|
343
375
|
export function defineControlModel(model: ControlModel): ControlModelBinding {
|
|
344
376
|
const {
|
|
345
377
|
controls: rootControls,
|
|
346
378
|
sections: rootSections,
|
|
347
379
|
views: modelViews,
|
|
380
|
+
components,
|
|
381
|
+
componentBindings,
|
|
348
382
|
componentProps,
|
|
349
383
|
componentPropsById,
|
|
350
384
|
...baseOptions
|
|
351
385
|
} = model
|
|
386
|
+
const resolvedComponentBindings = componentBindings ?? components
|
|
352
387
|
const controls: ControlSchema = {}
|
|
353
388
|
const views: Record<string, ControlViewConfig> = {}
|
|
354
389
|
const sections: Record<string, ControlSectionConfig> = {}
|
|
@@ -418,6 +453,7 @@ export function defineControlModel(model: ControlModel): ControlModelBinding {
|
|
|
418
453
|
return {
|
|
419
454
|
controls,
|
|
420
455
|
controlOptions,
|
|
456
|
+
...(resolvedComponentBindings === undefined ? {} : { componentBindings: resolvedComponentBindings }),
|
|
421
457
|
...(componentProps === undefined ? {} : { componentProps }),
|
|
422
458
|
...(componentPropsById === undefined ? {} : { componentPropsById }),
|
|
423
459
|
}
|
|
@@ -456,6 +492,30 @@ export function controlValuesToComponentProps<TValues extends Record<string, unk
|
|
|
456
492
|
return props
|
|
457
493
|
}
|
|
458
494
|
|
|
495
|
+
/** Map control workspace values into named SDK component bindings for direct slot rendering. */
|
|
496
|
+
export function controlValuesToComponentBindings<TValues extends Record<string, unknown>>(
|
|
497
|
+
values: TValues,
|
|
498
|
+
bindings?: ControlComponentBindingsConfig<TValues>,
|
|
499
|
+
): ControlComponentBinding[] {
|
|
500
|
+
if (bindings === undefined) return []
|
|
501
|
+
|
|
502
|
+
return normalizeControlComponentBindingConfigs(bindings).map(binding => ({
|
|
503
|
+
id: binding.id,
|
|
504
|
+
component: binding.component,
|
|
505
|
+
props: controlValuesToComponentProps(values, binding.props),
|
|
506
|
+
}))
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** Map control workspace values into SDK component bindings keyed by binding id. */
|
|
510
|
+
export function controlValuesToComponentBindingsById<TValues extends Record<string, unknown>>(
|
|
511
|
+
values: TValues,
|
|
512
|
+
bindings?: ControlComponentBindingsConfig<TValues>,
|
|
513
|
+
): ControlComponentBindingsById {
|
|
514
|
+
return Object.fromEntries(
|
|
515
|
+
controlValuesToComponentBindings(values, bindings).map(binding => [binding.id, binding]),
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
|
|
459
519
|
/** Return a default WellPlate prop mapping for generated control workspaces. */
|
|
460
520
|
export function defineWellPlateControlProps<TValues extends Record<string, unknown> = Record<string, unknown>>(
|
|
461
521
|
options: WellPlateControlPropsOptions<TValues> = {},
|
|
@@ -506,6 +566,24 @@ export function defineWellPlateDoseControlProps<TValues extends Record<string, u
|
|
|
506
566
|
}
|
|
507
567
|
}
|
|
508
568
|
|
|
569
|
+
/** Return named WellPlate + DoseCalculator component bindings for one dose-design control model. */
|
|
570
|
+
export function defineWellPlateDoseComponentBindings<TValues extends Record<string, unknown> = Record<string, unknown>>(
|
|
571
|
+
options: WellPlateDoseControlPropsOptions<TValues> = {},
|
|
572
|
+
): ControlComponentBindingsConfig<TValues> {
|
|
573
|
+
return [
|
|
574
|
+
{
|
|
575
|
+
id: options.plateId ?? 'plate',
|
|
576
|
+
component: 'WellPlate',
|
|
577
|
+
props: defineWellPlateControlProps(options.plate),
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: options.doseId ?? 'dose',
|
|
581
|
+
component: 'DoseCalculator',
|
|
582
|
+
props: defineDoseCalculatorControlProps(options.dose),
|
|
583
|
+
},
|
|
584
|
+
]
|
|
585
|
+
}
|
|
586
|
+
|
|
509
587
|
/** Return a complete ControlWorkspaceView model for WellPlate + DoseCalculator dose design. */
|
|
510
588
|
export function defineDoseDesignControlModel(
|
|
511
589
|
options: DoseDesignControlModelOptions = {},
|
|
@@ -543,16 +621,19 @@ export function defineDoseDesignControlModel(
|
|
|
543
621
|
}
|
|
544
622
|
}
|
|
545
623
|
|
|
624
|
+
const componentProps: WellPlateDoseControlPropsOptions = {
|
|
625
|
+
...options.componentProps,
|
|
626
|
+
dose: {
|
|
627
|
+
...(options.componentProps?.dose ?? {}),
|
|
628
|
+
...(options.includeMolecularWeight && options.componentProps?.dose?.molecularWeight === undefined
|
|
629
|
+
? { molecularWeight: 'molecularWeight' }
|
|
630
|
+
: {}),
|
|
631
|
+
},
|
|
632
|
+
}
|
|
633
|
+
|
|
546
634
|
return defineControlModel({
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
dose: {
|
|
550
|
-
...(options.componentProps?.dose ?? {}),
|
|
551
|
-
...(options.includeMolecularWeight && options.componentProps?.dose?.molecularWeight === undefined
|
|
552
|
-
? { molecularWeight: 'molecularWeight' }
|
|
553
|
-
: {}),
|
|
554
|
-
},
|
|
555
|
-
}),
|
|
635
|
+
componentBindings: defineWellPlateDoseComponentBindings(componentProps),
|
|
636
|
+
componentPropsById: defineWellPlateDoseControlProps(componentProps),
|
|
556
637
|
views: {
|
|
557
638
|
[viewId]: {
|
|
558
639
|
label: options.viewLabel ?? 'Design',
|
|
@@ -705,7 +786,7 @@ export function controlsToViewIds(
|
|
|
705
786
|
return controlsToViewItems(controls, options).map(item => item.id)
|
|
706
787
|
}
|
|
707
788
|
|
|
708
|
-
/** Return
|
|
789
|
+
/** Return AppTopBar pillNav-compatible view items for switching generated control sidebars. */
|
|
709
790
|
export function controlsToViewItems(
|
|
710
791
|
controls: ControlSchema,
|
|
711
792
|
options: ControlSchemaOptions = {},
|
|
@@ -715,16 +796,6 @@ export function controlsToViewItems(
|
|
|
715
796
|
.map(([id]) => controlViewItem(id, options))
|
|
716
797
|
}
|
|
717
798
|
|
|
718
|
-
/** Return AppTopBar-compatible tabs for the same views that drive generated AppSidebar panels. */
|
|
719
|
-
export function controlsToTopBarTabs(
|
|
720
|
-
controls: ControlSchema,
|
|
721
|
-
options: ControlSchemaOptions = {},
|
|
722
|
-
): TopBarTab[] {
|
|
723
|
-
return Object.entries(controlsToSidebarPanels(controls, options))
|
|
724
|
-
.filter(([, sections]) => sections.length > 0)
|
|
725
|
-
.map(([id]) => controlTopBarTab(id, options))
|
|
726
|
-
}
|
|
727
|
-
|
|
728
799
|
/** Return the first generated sidebar view ID, or an empty string when controls render no sidebar panels. */
|
|
729
800
|
export function getDefaultControlView(
|
|
730
801
|
controls: ControlSchema,
|
|
@@ -779,7 +850,6 @@ export function useControlSchema<TControls extends ControlSchema>(
|
|
|
779
850
|
const sidebarPanels = controlsToSidebarPanels(controls, options)
|
|
780
851
|
const viewItems = controlsToViewItems(controls, options)
|
|
781
852
|
const viewIds = viewItems.map(item => item.id)
|
|
782
|
-
const topBarTabs = controlsToTopBarTabs(controls, options)
|
|
783
853
|
const defaultView = viewIds[0] ?? ''
|
|
784
854
|
const sectionSchemas = controlsToSectionFormSchemas(controls, options)
|
|
785
855
|
|
|
@@ -802,7 +872,6 @@ export function useControlSchema<TControls extends ControlSchema>(
|
|
|
802
872
|
sidebarPanels,
|
|
803
873
|
viewIds,
|
|
804
874
|
viewItems,
|
|
805
|
-
topBarTabs,
|
|
806
875
|
defaultView,
|
|
807
876
|
sectionSchemas,
|
|
808
877
|
sidebar: {
|
|
@@ -843,7 +912,6 @@ export function useControlWorkspace<TControls extends ControlSchema>(
|
|
|
843
912
|
if (!schema.viewIds.includes(viewId)) return
|
|
844
913
|
if (activeView.value !== viewId) activeView.value = viewId
|
|
845
914
|
if (sidebar.activeView !== viewId) sidebar.activeView = viewId
|
|
846
|
-
if (topBar.currentTabId !== viewId) topBar.currentTabId = viewId
|
|
847
915
|
if (pillNav.currentItemId !== viewId) pillNav.currentItemId = viewId
|
|
848
916
|
}
|
|
849
917
|
|
|
@@ -876,10 +944,24 @@ export function useControlWorkspace<TControls extends ControlSchema>(
|
|
|
876
944
|
]),
|
|
877
945
|
)
|
|
878
946
|
}
|
|
947
|
+
|
|
948
|
+
function getComponentBindings(
|
|
949
|
+
bindings?: ControlComponentBindingsConfig<ControlValues<TControls> & Record<string, unknown>>,
|
|
950
|
+
): ControlComponentBinding[] {
|
|
951
|
+
return controlValuesToComponentBindings(values, bindings)
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function getComponentBindingsById(
|
|
955
|
+
bindings?: ControlComponentBindingsConfig<ControlValues<TControls> & Record<string, unknown>>,
|
|
956
|
+
): ControlComponentBindingsById {
|
|
957
|
+
return controlValuesToComponentBindingsById(values, bindings)
|
|
958
|
+
}
|
|
879
959
|
const componentProps = computed(() => (
|
|
880
960
|
model?.componentProps === undefined ? {} : getComponentProps(model.componentProps)
|
|
881
961
|
))
|
|
882
962
|
const componentPropsById = computed(() => getComponentPropsById(model?.componentPropsById))
|
|
963
|
+
const componentBindings = computed(() => getComponentBindings(model?.componentBindings))
|
|
964
|
+
const componentBindingsById = computed(() => getComponentBindingsById(model?.componentBindings))
|
|
883
965
|
const form = {
|
|
884
966
|
...schema.form,
|
|
885
967
|
modelValue: values,
|
|
@@ -899,11 +981,6 @@ export function useControlWorkspace<TControls extends ControlSchema>(
|
|
|
899
981
|
values,
|
|
900
982
|
'onUpdate:values': setValues,
|
|
901
983
|
}) as ControlWorkspaceSidebarBinding
|
|
902
|
-
const topBar = reactive({
|
|
903
|
-
tabs: schema.topBarTabs,
|
|
904
|
-
currentTabId: activeView.value,
|
|
905
|
-
onTabSelect: (tab: TopBarTab) => setActiveView(tab.id),
|
|
906
|
-
}) as ControlWorkspaceTopBarBinding
|
|
907
984
|
const pillNav = reactive({
|
|
908
985
|
items: schema.viewItems,
|
|
909
986
|
currentItemId: activeView.value,
|
|
@@ -914,32 +991,26 @@ export function useControlWorkspace<TControls extends ControlSchema>(
|
|
|
914
991
|
settingsConfig: topBarSettingsConfig,
|
|
915
992
|
onSettingsValuesChange: setValues,
|
|
916
993
|
} as ControlWorkspaceTopBarSettingsBinding
|
|
917
|
-
const topBarProps = computed<
|
|
994
|
+
const topBarProps = computed<ControlWorkspaceTopBarBinding>(() => ({
|
|
918
995
|
pillNav: pillNav.items,
|
|
919
996
|
currentPillId: pillNav.currentItemId,
|
|
920
997
|
onPillSelect: pillNav.onSelect,
|
|
921
998
|
...topBarSettingsBinding,
|
|
922
999
|
}))
|
|
923
|
-
const topBarTabsProps = computed<ControlWorkspaceAppTopBarTabsBinding>(() => ({
|
|
924
|
-
tabs: topBar.tabs,
|
|
925
|
-
currentTabId: topBar.currentTabId,
|
|
926
|
-
onTabSelect: topBar.onTabSelect,
|
|
927
|
-
...topBarSettingsBinding,
|
|
928
|
-
}))
|
|
929
1000
|
const bindings: ControlWorkspaceComponentBindings = {
|
|
930
1001
|
form,
|
|
931
1002
|
sidebar,
|
|
932
1003
|
topBar: topBarProps,
|
|
933
|
-
topBarTabs: topBarTabsProps,
|
|
934
1004
|
topBarSettings: topBarSettingsBinding,
|
|
935
1005
|
pillNav,
|
|
1006
|
+
componentBindings,
|
|
1007
|
+
componentBindingsById,
|
|
936
1008
|
componentProps,
|
|
937
1009
|
componentPropsById,
|
|
938
1010
|
}
|
|
939
1011
|
|
|
940
1012
|
watch(activeView, syncActiveView, { flush: 'sync' })
|
|
941
1013
|
watch(() => sidebar.activeView, syncActiveView, { flush: 'sync' })
|
|
942
|
-
watch(() => topBar.currentTabId, syncActiveView, { flush: 'sync' })
|
|
943
1014
|
watch(() => pillNav.currentItemId, syncActiveView, { flush: 'sync' })
|
|
944
1015
|
|
|
945
1016
|
return {
|
|
@@ -950,10 +1021,12 @@ export function useControlWorkspace<TControls extends ControlSchema>(
|
|
|
950
1021
|
topBarSettingsConfig,
|
|
951
1022
|
form,
|
|
952
1023
|
sidebar,
|
|
953
|
-
topBar,
|
|
1024
|
+
topBar: topBarProps,
|
|
954
1025
|
pillNav,
|
|
955
1026
|
topBarSettings: topBarSettingsBinding,
|
|
956
1027
|
bindings,
|
|
1028
|
+
componentBindings,
|
|
1029
|
+
componentBindingsById,
|
|
957
1030
|
componentProps,
|
|
958
1031
|
componentPropsById,
|
|
959
1032
|
setActiveView,
|
|
@@ -961,6 +1034,8 @@ export function useControlWorkspace<TControls extends ControlSchema>(
|
|
|
961
1034
|
resetValues,
|
|
962
1035
|
getComponentProps,
|
|
963
1036
|
getComponentPropsById,
|
|
1037
|
+
getComponentBindings,
|
|
1038
|
+
getComponentBindingsById,
|
|
964
1039
|
}
|
|
965
1040
|
}
|
|
966
1041
|
|
|
@@ -1158,6 +1233,31 @@ function compactComponentPropsMap<TValues extends Record<string, unknown>>(
|
|
|
1158
1233
|
)
|
|
1159
1234
|
}
|
|
1160
1235
|
|
|
1236
|
+
function normalizeControlComponentBindingConfigs<TValues extends Record<string, unknown>>(
|
|
1237
|
+
bindings: ControlComponentBindingsConfig<TValues>,
|
|
1238
|
+
): Array<Required<Pick<ControlComponentBindingConfig<TValues>, 'id' | 'component'>> & Pick<ControlComponentBindingConfig<TValues>, 'props'>> {
|
|
1239
|
+
if (Array.isArray(bindings)) {
|
|
1240
|
+
const usedIds = new Map<string, number>()
|
|
1241
|
+
return bindings.map(binding => ({
|
|
1242
|
+
id: uniqueComponentBindingId(binding.id ?? binding.component, usedIds),
|
|
1243
|
+
component: binding.component,
|
|
1244
|
+
props: binding.props,
|
|
1245
|
+
}))
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
return Object.entries(bindings).map(([id, binding]) => ({
|
|
1249
|
+
id,
|
|
1250
|
+
component: binding.component,
|
|
1251
|
+
props: binding.props,
|
|
1252
|
+
}))
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function uniqueComponentBindingId(id: string, usedIds: Map<string, number>): string {
|
|
1256
|
+
const count = usedIds.get(id) ?? 0
|
|
1257
|
+
usedIds.set(id, count + 1)
|
|
1258
|
+
return count === 0 ? id : `${id}-${count + 1}`
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1161
1261
|
function sourceKey<TValues extends Record<string, unknown>>(key: string): keyof TValues & string {
|
|
1162
1262
|
return key as keyof TValues & string
|
|
1163
1263
|
}
|
|
@@ -1234,18 +1334,6 @@ function controlViewItem(viewId: string, options: ControlSchemaOptions): PillNav
|
|
|
1234
1334
|
...(config?.to !== undefined ? { to: config.to } : {}),
|
|
1235
1335
|
...(config?.href !== undefined ? { href: config.href } : {}),
|
|
1236
1336
|
...(config?.disabled !== undefined ? { disabled: config.disabled } : {}),
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
function controlTopBarTab(viewId: string, options: ControlSchemaOptions): TopBarTab {
|
|
1241
|
-
const config = options.views?.[viewId]
|
|
1242
|
-
return {
|
|
1243
|
-
id: viewId,
|
|
1244
|
-
label: config?.label ?? humanize(viewId),
|
|
1245
|
-
...(config?.to !== undefined ? { to: config.to } : {}),
|
|
1246
|
-
...(config?.href !== undefined ? { href: config.href } : {}),
|
|
1247
|
-
...(config?.icon !== undefined ? { icon: config.icon } : {}),
|
|
1248
|
-
...(config?.disabled !== undefined ? { disabled: config.disabled } : {}),
|
|
1249
1337
|
...(config?.children !== undefined ? { children: config.children } : {}),
|
|
1250
1338
|
}
|
|
1251
1339
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getInjectedPlatformContext,
|
|
8
8
|
resolveCurrentExperimentId,
|
|
9
9
|
} from './platformContextHelpers'
|
|
10
|
+
import type { ExperimentSummary, PageSelectorItem } from '../types'
|
|
10
11
|
|
|
11
12
|
export type PluginHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'
|
|
12
13
|
|
|
@@ -90,7 +91,7 @@ export interface UseCurrentExperimentOptions {
|
|
|
90
91
|
immediate?: boolean
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
export interface UseCurrentExperimentReturn<TExperiment =
|
|
94
|
+
export interface UseCurrentExperimentReturn<TExperiment = ExperimentSummary> {
|
|
94
95
|
/** Current experiment id resolved from platform injection or URL conventions. */
|
|
95
96
|
experimentId: ComputedRef<number | undefined>
|
|
96
97
|
/** Whether a current experiment id is available. */
|
|
@@ -114,27 +115,67 @@ export interface UseCurrentExperimentReturn<TExperiment = unknown> {
|
|
|
114
115
|
export type PluginEndpointCaller = (payload?: unknown) => Promise<unknown>
|
|
115
116
|
export type GeneratedPluginClient = Record<string, (...args: any[]) => Promise<any>>
|
|
116
117
|
|
|
118
|
+
function normalizeApiBaseUrl(baseUrl: string | undefined): string | undefined {
|
|
119
|
+
if (!baseUrl) return undefined
|
|
120
|
+
return baseUrl.length > 1 ? baseUrl.replace(/\/+$/, '') : baseUrl
|
|
121
|
+
}
|
|
122
|
+
|
|
117
123
|
function normalizeApiPrefix(prefix: string | undefined): string | undefined {
|
|
118
124
|
if (!prefix) return undefined
|
|
119
|
-
if (prefix.startsWith('/api/')) return prefix
|
|
125
|
+
if (prefix.startsWith('/api/')) return normalizeApiBaseUrl(prefix)
|
|
120
126
|
if (prefix === '/api') return prefix
|
|
121
|
-
if (prefix.startsWith('/')) return `/api${prefix}`
|
|
122
|
-
return `/api/${prefix}`
|
|
127
|
+
if (prefix.startsWith('/')) return normalizeApiBaseUrl(`/api${prefix}`)
|
|
128
|
+
return normalizeApiBaseUrl(`/api/${prefix}`)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function normalizePluginNavPath(path: string): string {
|
|
132
|
+
const raw = path.trim() || '/'
|
|
133
|
+
const prefixed = raw.startsWith('/') ? raw : `/${raw}`
|
|
134
|
+
return prefixed.replace(/\/+$/, '') || '/'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function pluginPageIdFromPath(path: string, fallbackIndex: number): string {
|
|
138
|
+
const normalizedPath = normalizePluginNavPath(path)
|
|
139
|
+
if (normalizedPath === '/') return 'dashboard'
|
|
140
|
+
return normalizedPath.replace(/^\/+/, '').replace(/\/+/g, '-') || `page-${fallbackIndex + 1}`
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Convert PluginMetadata.nav_items into AppTopBar pageSelector items for topbar page switches, homepage PluginCards, and fallback views. */
|
|
144
|
+
export function getPluginPageSelectorItems(contract: PluginContract): PageSelectorItem[] {
|
|
145
|
+
const pluginIcon = contract.plugin.icon ?? ''
|
|
146
|
+
const navItems = contract.plugin.navItems ?? []
|
|
147
|
+
const source: PluginNavItemContract[] = navItems.length > 0
|
|
148
|
+
? navItems
|
|
149
|
+
: [{
|
|
150
|
+
path: '/',
|
|
151
|
+
label: contract.plugin.name ?? 'Dashboard',
|
|
152
|
+
id: 'dashboard',
|
|
153
|
+
icon: pluginIcon || undefined,
|
|
154
|
+
description: contract.plugin.description,
|
|
155
|
+
}]
|
|
156
|
+
|
|
157
|
+
return source.map((item, index) => ({
|
|
158
|
+
id: item.id || pluginPageIdFromPath(item.path, index),
|
|
159
|
+
label: item.label,
|
|
160
|
+
to: normalizePluginNavPath(item.path),
|
|
161
|
+
icon: item.icon || pluginIcon || undefined,
|
|
162
|
+
hint: item.description || contract.plugin.name,
|
|
163
|
+
}))
|
|
123
164
|
}
|
|
124
165
|
|
|
125
166
|
/** Resolve the runtime plugin API base URL from env, explicit options, platform injection, or contract metadata. */
|
|
126
167
|
export function resolvePluginBaseUrl(contract: PluginContract, explicitBaseUrl?: string): string {
|
|
127
168
|
const envPrefix = (import.meta.env?.VITE_API_PREFIX as string | undefined) || undefined
|
|
128
|
-
if (envPrefix) return envPrefix
|
|
129
|
-
if (explicitBaseUrl) return explicitBaseUrl
|
|
169
|
+
if (envPrefix) return normalizeApiBaseUrl(envPrefix) ?? envPrefix
|
|
170
|
+
if (explicitBaseUrl) return normalizeApiBaseUrl(explicitBaseUrl) ?? explicitBaseUrl
|
|
130
171
|
|
|
131
172
|
const injected = getInjectedPlatformContext()
|
|
132
173
|
const platformPrefix =
|
|
133
|
-
injected?.plugin?.api_prefix ||
|
|
174
|
+
normalizeApiBaseUrl(injected?.plugin?.api_prefix) ||
|
|
134
175
|
normalizeApiPrefix(injected?.plugin?.route_prefix)
|
|
135
176
|
if (platformPrefix) return platformPrefix
|
|
136
177
|
|
|
137
|
-
return contract.plugin.apiPrefix || '/api'
|
|
178
|
+
return normalizeApiBaseUrl(contract.plugin.apiPrefix) || '/api'
|
|
138
179
|
}
|
|
139
180
|
|
|
140
181
|
function encodePath(path: string, payload: Record<string, unknown>, pathParams: string[]): string {
|
|
@@ -352,7 +393,7 @@ export function usePluginSettings<TSettings = Record<string, unknown>>() {
|
|
|
352
393
|
}
|
|
353
394
|
|
|
354
395
|
/** Read and optionally load the current platform experiment for integrated plugin views. */
|
|
355
|
-
export function useCurrentExperiment<TExperiment =
|
|
396
|
+
export function useCurrentExperiment<TExperiment = ExperimentSummary>(
|
|
356
397
|
options: UseCurrentExperimentOptions = {},
|
|
357
398
|
): UseCurrentExperimentReturn<TExperiment> {
|
|
358
399
|
const api = useApi({ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl })
|