@morscherlab/mint-sdk 1.0.0-beta.3 → 1.0.0-beta.5
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 +57 -5
- 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/AppTopBarPageSelectorInternal.vue.d.ts} +1 -1
- package/dist/components/{AppPillNav.vue.d.ts → internal/AppTopBarPillNavInternal.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-DihbSJjU.js} +5932 -5408
- package/dist/components-DihbSJjU.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-BcgZ6diz.js} +40 -28
- package/dist/composables-BcgZ6diz.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 +5637 -5663
- package/dist/templates/adapters.d.ts +7 -1
- package/dist/templates/catalog.d.ts +5 -5
- package/dist/templates/componentBindings.d.ts +13 -0
- package/dist/templates/index.d.ts +5 -5
- package/dist/templates/index.js +2 -2
- package/dist/templates/presets.d.ts +4 -4
- package/dist/templates/types.d.ts +4 -1
- package/dist/{templates-50NPjaxL.js → templates-Cyt0Suwf.js} +322 -73
- package/dist/templates-Cyt0Suwf.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 → useExperimentData-CM6Y0u5L.js} +400 -357
- package/dist/useExperimentData-CM6Y0u5L.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/AppSidebar.test.ts +130 -2
- package/src/__tests__/components/AppToastContainer.test.ts +0 -11
- package/src/__tests__/components/AppTopBar.test.ts +189 -120
- package/src/__tests__/components/{AppPageSelector.test.ts → AppTopBarPageSelector.test.ts} +8 -8
- package/src/__tests__/components/{AppPillNav.test.ts → AppTopBarPillNav.test.ts} +53 -6
- package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
- package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +32 -1
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +48 -1
- package/src/__tests__/components/BioTemplateRenderer.test.ts +25 -0
- package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
- package/src/__tests__/components/ComponentBindingRenderer.test.ts +278 -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 +7 -4
- package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +7 -7
- package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
- package/src/__tests__/composables/useControlSchema.test.ts +151 -37
- 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 +56 -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 +147 -27
- 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 +12 -3
- package/src/components/BioTemplateRenderer.story.vue +2 -2
- package/src/components/BioTemplateRenderer.vue +15 -227
- package/src/components/ComponentBindingRenderer.story.vue +87 -0
- package/src/components/ComponentBindingRenderer.vue +317 -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.story.vue +2 -2
- 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/AppTopBarPageSelectorInternal.vue} +9 -9
- package/src/components/internal/AppTopBarPillNavInternal.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-page-selector.css +1 -1
- package/src/styles/components/app-pill-nav.css +71 -1
- package/src/styles/components/app-sidebar.css +119 -0
- package/src/styles/components/app-top-bar.css +0 -235
- package/src/styles/components/experiment-popover.css +2 -2
- package/src/styles/index.css +0 -1
- package/src/templates/adapters.ts +193 -0
- package/src/templates/catalog.ts +5 -5
- package/src/templates/componentBindings.ts +90 -3
- package/src/templates/index.ts +10 -0
- package/src/templates/packs.ts +10 -1
- package/src/templates/presets.ts +14 -4
- package/src/templates/types.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/{AppPageSelector.test.d.ts → AppTopBarPageSelector.test.d.ts} +0 -0
- /package/dist/__tests__/components/{AppPillNav.test.d.ts → AppTopBarPillNav.test.d.ts} +0 -0
- /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
|
@@ -7,6 +7,7 @@ import BaseSelect from './BaseSelect.vue'
|
|
|
7
7
|
import BaseCheckbox from './BaseCheckbox.vue'
|
|
8
8
|
import BaseToggle from './BaseToggle.vue'
|
|
9
9
|
import NumberInput from './NumberInput.vue'
|
|
10
|
+
import { defineControlModel } from '../composables/useControlSchema'
|
|
10
11
|
import type {
|
|
11
12
|
SettingsTab,
|
|
12
13
|
SettingsModalLayout,
|
|
@@ -19,6 +20,7 @@ const customOpen = ref(false)
|
|
|
19
20
|
const appearanceOnlyOpen = ref(false)
|
|
20
21
|
const verticalOpen = ref(false)
|
|
21
22
|
const schemaOpen = ref(false)
|
|
23
|
+
const modelOpen = ref(false)
|
|
22
24
|
|
|
23
25
|
const customTabs: SettingsTab[] = [
|
|
24
26
|
{ id: 'general', label: 'General' },
|
|
@@ -208,6 +210,66 @@ const settingsSchema: SettingsModalSchema = {
|
|
|
208
210
|
],
|
|
209
211
|
}
|
|
210
212
|
|
|
213
|
+
const settingsModel = defineControlModel({
|
|
214
|
+
views: {
|
|
215
|
+
settings: {
|
|
216
|
+
label: 'Settings',
|
|
217
|
+
sections: {
|
|
218
|
+
general: {
|
|
219
|
+
label: 'General',
|
|
220
|
+
description: 'Plugin name, defaults, locale',
|
|
221
|
+
icon: iconGeneral,
|
|
222
|
+
controls: {
|
|
223
|
+
pluginName: {
|
|
224
|
+
label: 'Plugin Display Name',
|
|
225
|
+
default: 'IC50 Calculator',
|
|
226
|
+
required: true,
|
|
227
|
+
},
|
|
228
|
+
locale: {
|
|
229
|
+
label: 'Locale',
|
|
230
|
+
default: 'en-US',
|
|
231
|
+
options: localeOptions,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
model: {
|
|
236
|
+
label: 'Model Parameters',
|
|
237
|
+
description: 'Curve choice and bounds',
|
|
238
|
+
icon: iconModel,
|
|
239
|
+
columns: 2,
|
|
240
|
+
controls: {
|
|
241
|
+
curveModel: {
|
|
242
|
+
label: 'Default Curve Model',
|
|
243
|
+
default: '4pl',
|
|
244
|
+
options: curveModelOptions,
|
|
245
|
+
},
|
|
246
|
+
bottom: { type: 'number', label: 'Bottom bound', default: 0 },
|
|
247
|
+
top: { type: 'number', label: 'Top bound', default: 100 },
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
fitting: {
|
|
251
|
+
label: 'Curve Fitting',
|
|
252
|
+
description: 'Optimizer and transforms',
|
|
253
|
+
icon: iconCurve,
|
|
254
|
+
controls: {
|
|
255
|
+
optimizer: {
|
|
256
|
+
label: 'Optimizer',
|
|
257
|
+
default: 'lm',
|
|
258
|
+
options: optimizerOptions,
|
|
259
|
+
},
|
|
260
|
+
logTransform: {
|
|
261
|
+
label: 'Apply log-dose transform',
|
|
262
|
+
default: true,
|
|
263
|
+
type: 'toggle',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
})
|
|
271
|
+
const modelSettings = ref<Record<string, unknown>>({})
|
|
272
|
+
|
|
211
273
|
const sizes: Array<'md' | 'lg' | 'xl'> = ['md', 'lg', 'xl']
|
|
212
274
|
const layouts: SettingsModalLayout[] = ['horizontal', 'vertical']
|
|
213
275
|
</script>
|
|
@@ -406,6 +468,31 @@ const layouts: SettingsModalLayout[] = ['horizontal', 'vertical']
|
|
|
406
468
|
</div>
|
|
407
469
|
</Variant>
|
|
408
470
|
|
|
471
|
+
<Variant title="Model Driven Settings">
|
|
472
|
+
<div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem; align-items: center;">
|
|
473
|
+
<button
|
|
474
|
+
type="button"
|
|
475
|
+
style="padding: 0.5rem 1rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; background: var(--bg-card, #fff); color: var(--text-primary, #1e293b); cursor: pointer; font-size: 0.875rem;"
|
|
476
|
+
@click="modelOpen = true"
|
|
477
|
+
>
|
|
478
|
+
Open Settings (from model)
|
|
479
|
+
</button>
|
|
480
|
+
<details style="font-size: 0.75rem; color: var(--text-secondary); max-width: 36rem; width: 100%;">
|
|
481
|
+
<summary style="cursor: pointer; user-select: none;">Live values</summary>
|
|
482
|
+
<pre style="margin: 0.5rem 0 0; padding: 0.75rem; background: var(--bg-tertiary); border-radius: 0.375rem; font-size: 0.6875rem; overflow-x: auto;">{{ JSON.stringify(modelSettings, null, 2) }}</pre>
|
|
483
|
+
</details>
|
|
484
|
+
<SettingsModal
|
|
485
|
+
v-model="modelOpen"
|
|
486
|
+
v-model:values="modelSettings"
|
|
487
|
+
title="IC50 Plugin Settings"
|
|
488
|
+
:model="settingsModel"
|
|
489
|
+
:show-appearance="true"
|
|
490
|
+
size="xl"
|
|
491
|
+
layout="vertical"
|
|
492
|
+
/>
|
|
493
|
+
</div>
|
|
494
|
+
</Variant>
|
|
495
|
+
|
|
409
496
|
<Variant title="Schema-Driven (Auto-Render)">
|
|
410
497
|
<div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem; align-items: center;">
|
|
411
498
|
<button
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { ref, computed } from 'vue'
|
|
4
4
|
import type { WellPlateFormat, WellPlateSelectionMode, WellPlateSize, Well, HeatmapConfig, WellShape, WellEditField, WellEditData, WellLegendItem, ColumnCondition, RowCondition } from '../types'
|
|
5
5
|
import { useEventListener } from '../composables/useEventListener'
|
|
6
|
-
import
|
|
6
|
+
import WellEditPopupInternal from './internal/WellEditPopupInternal.vue'
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
modelValue?: string[]
|
|
@@ -805,7 +805,7 @@ const tableStyle = computed(() => ({
|
|
|
805
805
|
</div>
|
|
806
806
|
|
|
807
807
|
<!-- Edit popup -->
|
|
808
|
-
<
|
|
808
|
+
<WellEditPopupInternal
|
|
809
809
|
v-if="editable && editingWellId"
|
|
810
810
|
:well-id="editingWellId"
|
|
811
811
|
:well-data="wells[editingWellId]"
|
package/src/components/index.ts
CHANGED
|
@@ -27,26 +27,23 @@ export { default as FileUploader } from './FileUploader.vue'
|
|
|
27
27
|
|
|
28
28
|
// Feedback components
|
|
29
29
|
export { default as AlertBox } from './AlertBox.vue'
|
|
30
|
-
/** @deprecated Use AppToastContainer instead. */
|
|
31
|
-
export { default as ToastNotification } from './ToastNotification.vue'
|
|
32
30
|
export { default as AppToastContainer } from './AppToastContainer.vue'
|
|
33
31
|
|
|
34
32
|
// Action components
|
|
35
33
|
export { default as IconButton } from './IconButton.vue'
|
|
36
34
|
export { default as ThemeToggle } from './ThemeToggle.vue'
|
|
37
|
-
/** @deprecated Use AppTopBar instead. */
|
|
38
|
-
export { default as SettingsButton } from './SettingsButton.vue'
|
|
39
35
|
|
|
40
36
|
// Layout components
|
|
41
37
|
export { default as CollapsibleCard } from './CollapsibleCard.vue'
|
|
42
38
|
export { default as AppTopBar } from './AppTopBar.vue'
|
|
43
|
-
export { default as AppPageSelector } from './AppPageSelector.vue'
|
|
44
|
-
export { default as AppPillNav } from './AppPillNav.vue'
|
|
45
39
|
export { default as AppAvatarMenu } from './AppAvatarMenu.vue'
|
|
46
40
|
export { default as AppPluginSwitcher } from './AppPluginSwitcher.vue'
|
|
47
41
|
export { default as AppSidebar } from './AppSidebar.vue'
|
|
48
42
|
export { default as AppLayout } from './AppLayout.vue'
|
|
43
|
+
export { default as PluginWorkspaceView } from './PluginWorkspaceView.vue'
|
|
44
|
+
export { default as ComponentBindingRenderer } from './ComponentBindingRenderer.vue'
|
|
49
45
|
export { default as ControlWorkspaceView } from './ControlWorkspaceView.vue'
|
|
46
|
+
export { default as DoseDesignWorkspaceView } from './DoseDesignWorkspaceView.vue'
|
|
50
47
|
export { default as AppContainer } from './AppContainer.vue'
|
|
51
48
|
export { default as PluginIcon } from './PluginIcon.vue'
|
|
52
49
|
|
|
@@ -77,8 +74,6 @@ export { default as ExperimentTimeline } from './ExperimentTimeline.vue'
|
|
|
77
74
|
|
|
78
75
|
// Sample management components
|
|
79
76
|
export { default as SampleSelector } from './SampleSelector.vue'
|
|
80
|
-
/** @deprecated Use AutoGroupModal instead. */
|
|
81
|
-
export { default as GroupingModal } from './GroupingModal.vue'
|
|
82
77
|
export { default as AutoGroupModal } from './AutoGroupModal.vue'
|
|
83
78
|
export { default as GroupAssigner } from './GroupAssigner.vue'
|
|
84
79
|
|
|
@@ -107,11 +102,7 @@ export { default as BatchProgressList } from './BatchProgressList.vue'
|
|
|
107
102
|
|
|
108
103
|
// Form builder components
|
|
109
104
|
export { default as FormBuilder } from './FormBuilder.vue'
|
|
110
|
-
/** @deprecated Use FormBuilder instead. */
|
|
111
|
-
export { default as FormSection } from './FormSection.vue'
|
|
112
105
|
export { default as FormActions } from './FormActions.vue'
|
|
113
|
-
/** @deprecated Use FormBuilder instead. */
|
|
114
|
-
export { default as FormFieldRenderer } from './FormFieldRenderer.vue'
|
|
115
106
|
|
|
116
107
|
// Experiment data display components
|
|
117
108
|
export { default as ExperimentDataViewer } from './ExperimentDataViewer.vue'
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Dropdown trigger that switches between named pages, closing on outside click or Escape. */
|
|
3
3
|
import { computed } from 'vue'
|
|
4
|
-
import { useDropdownState } from '
|
|
5
|
-
import type { PageSelectorItem, PageSelectorItemInput } from '
|
|
6
|
-
import { normalizeItemInput } from '
|
|
7
|
-
import { isPluginIconFormat } from '
|
|
8
|
-
import
|
|
9
|
-
import PluginIcon from '
|
|
4
|
+
import { useDropdownState } from '../../composables/useDropdownState'
|
|
5
|
+
import type { PageSelectorItem, PageSelectorItemInput } from '../../types/components'
|
|
6
|
+
import { normalizeItemInput } from '../../utils/items'
|
|
7
|
+
import { isPluginIconFormat } from '../../utils/pluginIcon'
|
|
8
|
+
import ActionItemInternal from './ActionItemInternal.vue'
|
|
9
|
+
import PluginIcon from '../PluginIcon.vue'
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
12
|
pages: PageSelectorItemInput[]
|
|
@@ -87,7 +87,7 @@ function handleSelect(page: PageSelectorItem) {
|
|
|
87
87
|
|
|
88
88
|
<div v-if="isOpen" class="mint-page-selector__menu" role="menu">
|
|
89
89
|
<div class="mint-page-selector__menu-title">Go to</div>
|
|
90
|
-
<
|
|
90
|
+
<ActionItemInternal
|
|
91
91
|
v-for="page in normalizedPages"
|
|
92
92
|
:key="page.id"
|
|
93
93
|
:href="page.href"
|
|
@@ -118,11 +118,11 @@ function handleSelect(page: PageSelectorItem) {
|
|
|
118
118
|
</span>
|
|
119
119
|
<span class="mint-page-selector__item-label">{{ page.label }}</span>
|
|
120
120
|
<span v-if="page.hint" class="mint-page-selector__item-hint">{{ page.hint }}</span>
|
|
121
|
-
</
|
|
121
|
+
</ActionItemInternal>
|
|
122
122
|
</div>
|
|
123
123
|
</div>
|
|
124
124
|
</template>
|
|
125
125
|
|
|
126
126
|
<style>
|
|
127
|
-
@import '
|
|
127
|
+
@import '../../styles/components/app-page-selector.css';
|
|
128
128
|
</style>
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Horizontal pill-style navigation bar that emits select events and supports href, router-link, button, or dropdown items. */
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
import type { PillNavItem, PillNavItemInput, PillNavOption, PillNavOptionInput } from '../../types/components'
|
|
5
|
+
import { normalizeItemInput } from '../../utils/items'
|
|
6
|
+
import { useEventListener } from '../../composables/useEventListener'
|
|
7
|
+
import ActionItemInternal from './ActionItemInternal.vue'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
items: PillNavItemInput[]
|
|
11
|
+
currentItemId?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = defineProps<Props>()
|
|
15
|
+
|
|
16
|
+
const emit = defineEmits<{
|
|
17
|
+
select: [item: PillNavItem]
|
|
18
|
+
'option-select': [option: PillNavOption, item: PillNavItem]
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const openItemId = ref<string | null>(null)
|
|
22
|
+
const itemRefs = ref<Map<string, HTMLElement>>(new Map())
|
|
23
|
+
|
|
24
|
+
const normalizedItems = computed<PillNavItem[]>(() => props.items.map(normalizePillNavItem))
|
|
25
|
+
|
|
26
|
+
function handleClick(item: PillNavItem) {
|
|
27
|
+
if (item.disabled) return
|
|
28
|
+
if (item.children?.length) {
|
|
29
|
+
toggleDropdown(item.id)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
if (item.href || item.to) return
|
|
33
|
+
emit('select', item)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function handleOptionClick(option: PillNavOption, item: PillNavItem) {
|
|
37
|
+
if (option.disabled) return
|
|
38
|
+
if (!option.href && !option.to) emit('option-select', option, item)
|
|
39
|
+
openItemId.value = null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizePillNavItem(item: PillNavItemInput): PillNavItem {
|
|
43
|
+
const normalized = normalizeItemInput<Omit<PillNavItem, 'children'> & { children?: PillNavOptionInput[] }>(item)
|
|
44
|
+
return {
|
|
45
|
+
...normalized,
|
|
46
|
+
children: normalized.children?.map(normalizeItemInput),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toggleDropdown(itemId: string) {
|
|
51
|
+
openItemId.value = openItemId.value === itemId ? null : itemId
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setItemRef(el: HTMLElement | null, itemId: string) {
|
|
55
|
+
if (el) {
|
|
56
|
+
itemRefs.value.set(itemId, el)
|
|
57
|
+
} else {
|
|
58
|
+
itemRefs.value.delete(itemId)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function handleClickOutside(event: MouseEvent) {
|
|
63
|
+
const target = event.target as Node
|
|
64
|
+
const clickedInside = Array.from(itemRefs.value.values()).some(el => el.contains(target))
|
|
65
|
+
if (!clickedInside) openItemId.value = null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const hasActiveChild = (item: PillNavItem) =>
|
|
69
|
+
item.children?.some(child => child.id === props.currentItemId) ?? false
|
|
70
|
+
|
|
71
|
+
function isSvgIcon(icon: PillNavItem['icon']): icon is string | string[] {
|
|
72
|
+
if (!icon) return false
|
|
73
|
+
return Array.isArray(icon) || icon.startsWith('M') || icon.startsWith('m')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
useEventListener(() => document, 'click', handleClickOutside)
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<nav class="mint-pill-nav" aria-label="Primary">
|
|
81
|
+
<div
|
|
82
|
+
v-for="item in normalizedItems"
|
|
83
|
+
:key="item.id"
|
|
84
|
+
:ref="(el) => setItemRef(el as HTMLElement | null, item.id)"
|
|
85
|
+
class="mint-pill-nav__item-wrap"
|
|
86
|
+
>
|
|
87
|
+
<button
|
|
88
|
+
v-if="item.children?.length"
|
|
89
|
+
type="button"
|
|
90
|
+
:class="[
|
|
91
|
+
'mint-pill-nav__item',
|
|
92
|
+
{ 'mint-pill-nav__item--active': item.id === currentItemId || hasActiveChild(item) },
|
|
93
|
+
{ 'mint-pill-nav__item--disabled': item.disabled },
|
|
94
|
+
]"
|
|
95
|
+
:disabled="item.disabled"
|
|
96
|
+
:aria-expanded="openItemId === item.id"
|
|
97
|
+
aria-haspopup="menu"
|
|
98
|
+
@click.stop="handleClick(item)"
|
|
99
|
+
>
|
|
100
|
+
<svg
|
|
101
|
+
v-if="isSvgIcon(item.icon)"
|
|
102
|
+
class="mint-pill-nav__icon"
|
|
103
|
+
viewBox="0 0 24 24"
|
|
104
|
+
fill="none"
|
|
105
|
+
stroke="currentColor"
|
|
106
|
+
stroke-width="2"
|
|
107
|
+
stroke-linecap="round"
|
|
108
|
+
stroke-linejoin="round"
|
|
109
|
+
aria-hidden="true"
|
|
110
|
+
>
|
|
111
|
+
<template v-if="Array.isArray(item.icon)">
|
|
112
|
+
<path v-for="(d, index) in item.icon" :key="index" :d="d" />
|
|
113
|
+
</template>
|
|
114
|
+
<path v-else :d="item.icon" />
|
|
115
|
+
</svg>
|
|
116
|
+
{{ item.label }}
|
|
117
|
+
<svg
|
|
118
|
+
class="mint-pill-nav__chevron"
|
|
119
|
+
:class="{ 'mint-pill-nav__chevron--open': openItemId === item.id }"
|
|
120
|
+
viewBox="0 0 24 24"
|
|
121
|
+
fill="none"
|
|
122
|
+
stroke="currentColor"
|
|
123
|
+
stroke-width="2"
|
|
124
|
+
stroke-linecap="round"
|
|
125
|
+
stroke-linejoin="round"
|
|
126
|
+
aria-hidden="true"
|
|
127
|
+
>
|
|
128
|
+
<path d="m6 9 6 6 6-6" />
|
|
129
|
+
</svg>
|
|
130
|
+
</button>
|
|
131
|
+
|
|
132
|
+
<ActionItemInternal
|
|
133
|
+
v-else
|
|
134
|
+
:href="item.href"
|
|
135
|
+
:to="item.to"
|
|
136
|
+
:disabled="item.disabled"
|
|
137
|
+
:class="[
|
|
138
|
+
'mint-pill-nav__item',
|
|
139
|
+
{ 'mint-pill-nav__item--active': item.id === currentItemId },
|
|
140
|
+
{ 'mint-pill-nav__item--disabled': item.disabled },
|
|
141
|
+
]"
|
|
142
|
+
@click="handleClick(item)"
|
|
143
|
+
>
|
|
144
|
+
<svg
|
|
145
|
+
v-if="isSvgIcon(item.icon)"
|
|
146
|
+
class="mint-pill-nav__icon"
|
|
147
|
+
viewBox="0 0 24 24"
|
|
148
|
+
fill="none"
|
|
149
|
+
stroke="currentColor"
|
|
150
|
+
stroke-width="2"
|
|
151
|
+
stroke-linecap="round"
|
|
152
|
+
stroke-linejoin="round"
|
|
153
|
+
aria-hidden="true"
|
|
154
|
+
>
|
|
155
|
+
<template v-if="Array.isArray(item.icon)">
|
|
156
|
+
<path v-for="(d, index) in item.icon" :key="index" :d="d" />
|
|
157
|
+
</template>
|
|
158
|
+
<path v-else :d="item.icon" />
|
|
159
|
+
</svg>
|
|
160
|
+
{{ item.label }}
|
|
161
|
+
</ActionItemInternal>
|
|
162
|
+
|
|
163
|
+
<div
|
|
164
|
+
v-if="item.children?.length && openItemId === item.id"
|
|
165
|
+
class="mint-pill-nav__dropdown"
|
|
166
|
+
role="menu"
|
|
167
|
+
>
|
|
168
|
+
<ActionItemInternal
|
|
169
|
+
v-for="option in item.children"
|
|
170
|
+
:key="option.id"
|
|
171
|
+
:href="option.href"
|
|
172
|
+
:to="option.to"
|
|
173
|
+
:disabled="option.disabled"
|
|
174
|
+
:class="[
|
|
175
|
+
'mint-pill-nav__dropdown-item',
|
|
176
|
+
{ 'mint-pill-nav__dropdown-item--active': option.id === currentItemId },
|
|
177
|
+
{ 'mint-pill-nav__dropdown-item--disabled': option.disabled },
|
|
178
|
+
]"
|
|
179
|
+
role="menuitem"
|
|
180
|
+
@click="handleOptionClick(option, item)"
|
|
181
|
+
>
|
|
182
|
+
<span class="mint-pill-nav__dropdown-label">{{ option.label }}</span>
|
|
183
|
+
<span v-if="option.description" class="mint-pill-nav__dropdown-description">
|
|
184
|
+
{{ option.description }}
|
|
185
|
+
</span>
|
|
186
|
+
</ActionItemInternal>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</nav>
|
|
190
|
+
</template>
|
|
191
|
+
|
|
192
|
+
<style>
|
|
193
|
+
@import '../../styles/components/app-pill-nav.css';
|
|
194
|
+
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Shared calendar grid panel used by date-oriented picker components. */
|
|
3
|
-
import type { CalendarGridDay } from '
|
|
3
|
+
import type { CalendarGridDay } from '../../composables/useCalendarGrid'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
weekDays: readonly string[]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, watch, computed } from 'vue'
|
|
3
|
-
import type { Well, WellEditData, WellEditField } from '
|
|
4
|
-
import { useEventListener } from '
|
|
3
|
+
import type { Well, WellEditData, WellEditField } from '../../types'
|
|
4
|
+
import { useEventListener } from '../../composables/useEventListener'
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
wellId: string
|
|
@@ -225,5 +225,5 @@ const sampleTypeButtons = [
|
|
|
225
225
|
</template>
|
|
226
226
|
|
|
227
227
|
<style>
|
|
228
|
-
@import '
|
|
228
|
+
@import '../../styles/components/well-edit-popup.css';
|
|
229
229
|
</style>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { DatePreset, ExperimentStatus, SelectOption, PillVariant } from '../types'
|
|
2
2
|
|
|
3
|
+
interface ExperimentCodeSource {
|
|
4
|
+
id?: number | null
|
|
5
|
+
experiment_code?: string | null
|
|
6
|
+
}
|
|
7
|
+
|
|
3
8
|
export function formatExperimentDate(dateStr: string): string {
|
|
4
9
|
try {
|
|
5
10
|
return new Date(dateStr).toLocaleDateString(undefined, {
|
|
@@ -41,6 +46,27 @@ export const EXPERIMENT_STATUS_LABELS: Record<ExperimentStatus, string> = {
|
|
|
41
46
|
cancelled: 'Cancelled',
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
export function formatExperimentStatus(status?: ExperimentStatus | string | null): string {
|
|
50
|
+
if (!status) return ''
|
|
51
|
+
if (status in EXPERIMENT_STATUS_LABELS) {
|
|
52
|
+
return EXPERIMENT_STATUS_LABELS[status as ExperimentStatus]
|
|
53
|
+
}
|
|
54
|
+
const label = String(status).replace(/[-_]+/g, ' ').trim()
|
|
55
|
+
return label ? label.replace(/^\w/, c => c.toUpperCase()) : ''
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getExperimentStatusVariant(status?: ExperimentStatus | string | null): PillVariant {
|
|
59
|
+
if (status && status in EXPERIMENT_STATUS_VARIANT_MAP) {
|
|
60
|
+
return EXPERIMENT_STATUS_VARIANT_MAP[status as ExperimentStatus]
|
|
61
|
+
}
|
|
62
|
+
return 'default'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resolveExperimentCode(experiment: ExperimentCodeSource): string | undefined {
|
|
66
|
+
if (experiment.experiment_code) return experiment.experiment_code
|
|
67
|
+
return experiment.id != null ? `EXP-${experiment.id}` : undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
44
70
|
export const DATE_PRESET_OPTIONS: SelectOption<string>[] = [
|
|
45
71
|
{ value: '', label: 'Any time' },
|
|
46
72
|
{ value: 'last_7_days', label: 'Last 7 days' },
|
package/src/composables/index.ts
CHANGED
|
@@ -129,6 +129,9 @@ export {
|
|
|
129
129
|
} from './useExperimentSelector'
|
|
130
130
|
export {
|
|
131
131
|
formatExperimentDate,
|
|
132
|
+
formatExperimentStatus,
|
|
133
|
+
getExperimentStatusVariant,
|
|
134
|
+
resolveExperimentCode,
|
|
132
135
|
datePresetToISO,
|
|
133
136
|
EXPERIMENT_STATUS_OPTIONS,
|
|
134
137
|
EXPERIMENT_STATUS_VARIANT_MAP,
|
|
@@ -149,9 +152,13 @@ export {
|
|
|
149
152
|
export {
|
|
150
153
|
useAppExperiment,
|
|
151
154
|
APP_EXPERIMENT_KEY,
|
|
155
|
+
type AppExperimentRecord,
|
|
156
|
+
type AppExperimentSource,
|
|
152
157
|
type UseAppExperimentOptions,
|
|
153
158
|
type UseAppExperimentReturn,
|
|
154
159
|
type AppExperimentState,
|
|
160
|
+
type AppExperimentPopoverBinding,
|
|
161
|
+
type AppExperimentSelectorModalBinding,
|
|
155
162
|
} from './useAppExperiment'
|
|
156
163
|
export {
|
|
157
164
|
useExperimentSave,
|
|
@@ -227,11 +234,6 @@ export {
|
|
|
227
234
|
type UseTextSearchOptions,
|
|
228
235
|
type UseTextSearchReturn,
|
|
229
236
|
} from './useTextSearch'
|
|
230
|
-
/** @deprecated Use generated plugin clients from `mint sdk generate` instead. */
|
|
231
|
-
export {
|
|
232
|
-
usePluginApi,
|
|
233
|
-
type UsePluginApiOptions,
|
|
234
|
-
} from './usePluginApi'
|
|
235
237
|
export {
|
|
236
238
|
controlsToFormSchema,
|
|
237
239
|
controlsToSectionFormSchema,
|
|
@@ -239,21 +241,29 @@ export {
|
|
|
239
241
|
controlsToSidebarPanels,
|
|
240
242
|
controlsToSettingsSchema,
|
|
241
243
|
controlsToTopBarSettingsConfig,
|
|
242
|
-
controlsToTopBarTabs,
|
|
243
244
|
controlsToViewIds,
|
|
244
245
|
controlsToViewItems,
|
|
246
|
+
controlValuesToComponentBindings,
|
|
247
|
+
controlValuesToComponentBindingsById,
|
|
245
248
|
controlValuesToComponentProps,
|
|
249
|
+
defineControlComponentBindings,
|
|
246
250
|
defineControlModel,
|
|
247
251
|
defineDoseDesignControlModel,
|
|
248
252
|
defineDoseCalculatorControlProps,
|
|
249
253
|
defineControls,
|
|
250
254
|
defineWellPlateControlProps,
|
|
255
|
+
defineWellPlateDoseComponentBindings,
|
|
251
256
|
defineWellPlateDoseControlProps,
|
|
252
257
|
getDefaultControlView,
|
|
253
258
|
getControlDefaults,
|
|
254
259
|
useControlSchema,
|
|
255
260
|
useControlWorkspace,
|
|
256
261
|
type ControlDefinition,
|
|
262
|
+
type ControlComponentBinding,
|
|
263
|
+
type ControlComponentBindingConfig,
|
|
264
|
+
type ControlComponentBindingRecordConfig,
|
|
265
|
+
type ControlComponentBindingsById,
|
|
266
|
+
type ControlComponentBindingsConfig,
|
|
257
267
|
type ControlComponentPropSource,
|
|
258
268
|
type ControlComponentPropsByIdMap,
|
|
259
269
|
type ControlComponentPropsMap,
|
|
@@ -282,7 +292,6 @@ export {
|
|
|
282
292
|
type ControlValues,
|
|
283
293
|
type UseControlSchemaReturn,
|
|
284
294
|
type ControlWorkspaceAppTopBarPillBinding,
|
|
285
|
-
type ControlWorkspaceAppTopBarTabsBinding,
|
|
286
295
|
type ControlWorkspaceComponentBindings,
|
|
287
296
|
type ControlWorkspaceFormBinding,
|
|
288
297
|
type ControlWorkspacePillNavBinding,
|
|
@@ -303,15 +312,19 @@ export {
|
|
|
303
312
|
export {
|
|
304
313
|
getBioTemplateComponentProps,
|
|
305
314
|
getBioTemplateComponentBindings,
|
|
315
|
+
toBioTemplateComponentBindings,
|
|
316
|
+
toBioTemplateComponentBindingsById,
|
|
306
317
|
toBioTemplateComponentProps,
|
|
307
318
|
toBioTemplateComponentPropsByComponent,
|
|
308
319
|
toBioTemplateComponentPropsById,
|
|
309
320
|
useBioTemplateComponents,
|
|
310
321
|
type BioTemplateComponentBinding,
|
|
322
|
+
type BioTemplateComponentBindingsById,
|
|
311
323
|
type BioTemplateComponentPropsByComponent,
|
|
312
324
|
type BioTemplateComponentPropsBinding,
|
|
313
325
|
type BioTemplateComponentPropsById,
|
|
314
326
|
type BioTemplateComponentPropsLookupOptions,
|
|
327
|
+
type BioTemplateResolvedComponentBinding,
|
|
315
328
|
type BioTemplateComponentTarget,
|
|
316
329
|
type UseBioTemplateComponentsReturn,
|
|
317
330
|
} from './useBioTemplateComponents'
|
|
@@ -336,6 +349,7 @@ export {
|
|
|
336
349
|
export {
|
|
337
350
|
buildPluginEndpointUrl,
|
|
338
351
|
createPluginClient,
|
|
352
|
+
getPluginPageSelectorItems,
|
|
339
353
|
resolvePluginBaseUrl,
|
|
340
354
|
usePluginClient,
|
|
341
355
|
usePluginSettings,
|
|
@@ -5,6 +5,13 @@ import { useAuthStore } from '../stores/auth'
|
|
|
5
5
|
let apiClientInstance: AxiosInstance | null = null
|
|
6
6
|
let interceptorAttached = false
|
|
7
7
|
|
|
8
|
+
function joinUrlPath(baseUrl: string, path: string): string {
|
|
9
|
+
if (!path) return baseUrl
|
|
10
|
+
const normalizedBase = baseUrl.replace(/\/+$/, '')
|
|
11
|
+
const normalizedPath = path.replace(/^\/+/, '/')
|
|
12
|
+
return `${normalizedBase}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
function getApiClient(): AxiosInstance {
|
|
9
16
|
if (!apiClientInstance) {
|
|
10
17
|
apiClientInstance = axios.create({
|
|
@@ -144,12 +151,12 @@ export function useApi(options: ApiClientOptions = {}): UseApiReturn {
|
|
|
144
151
|
// Build full URL for external use (e.g., <a href="...">)
|
|
145
152
|
function buildUrl(path: string): string {
|
|
146
153
|
const baseUrl = options.baseUrl ?? settingsStore.getApiBaseUrl()
|
|
147
|
-
return
|
|
154
|
+
return joinUrlPath(baseUrl, path)
|
|
148
155
|
}
|
|
149
156
|
|
|
150
157
|
// WebSocket URL builder
|
|
151
158
|
function buildWsUrl(path: string): string {
|
|
152
|
-
return
|
|
159
|
+
return joinUrlPath(settingsStore.getWsBaseUrl(), path)
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
return {
|