@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.
Files changed (181) hide show
  1. package/README.md +9 -2
  2. package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
  3. package/dist/__tests__/composables/useApi.test.d.ts +1 -0
  4. package/dist/components/AppContainer.vue.d.ts +1 -1
  5. package/dist/components/AppLayout.vue.d.ts +20 -1
  6. package/dist/components/AppSidebar.vue.d.ts +57 -5
  7. package/dist/components/AppTopBar.vue.d.ts +7 -25
  8. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +3 -1
  9. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -0
  10. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +5 -0
  11. package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
  12. package/dist/components/ControlWorkspaceView.vue.d.ts +24 -7
  13. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
  14. package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
  15. package/dist/components/FormBuilder.vue.d.ts +9 -9
  16. package/dist/components/PlateMapEditor.vue.d.ts +1 -1
  17. package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
  18. package/dist/components/SettingsModal.vue.d.ts +1 -1
  19. package/dist/components/WellPlate.vue.d.ts +2 -2
  20. package/dist/components/index.d.ts +3 -12
  21. package/dist/components/index.js +3 -3
  22. package/dist/components/{AppPageSelector.vue.d.ts → internal/AppTopBarPageSelectorInternal.vue.d.ts} +1 -1
  23. package/dist/components/{AppPillNav.vue.d.ts → internal/AppTopBarPillNavInternal.vue.d.ts} +3 -1
  24. package/dist/components/{CalendarGridPanel.vue.d.ts → internal/CalendarGridPanelInternal.vue.d.ts} +1 -1
  25. package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
  26. package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
  27. package/dist/{components-D_Sr0adg.js → components-DihbSJjU.js} +5932 -5408
  28. package/dist/components-DihbSJjU.js.map +1 -0
  29. package/dist/composables/experiment-utils.d.ts +8 -0
  30. package/dist/composables/index.d.ts +5 -7
  31. package/dist/composables/index.js +4 -4
  32. package/dist/composables/useAppExperiment.d.ts +31 -2
  33. package/dist/composables/useBioTemplateComponents.d.ts +5 -3
  34. package/dist/composables/useBioTemplatePackWorkspace.d.ts +3 -2
  35. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +6 -5
  36. package/dist/composables/useBioTemplateWorkspace.d.ts +5 -4
  37. package/dist/composables/useControlSchema.d.ts +43 -21
  38. package/dist/composables/usePluginClient.d.ts +5 -2
  39. package/dist/{composables-C3dpXQN5.js → composables-BcgZ6diz.js} +40 -28
  40. package/dist/composables-BcgZ6diz.js.map +1 -0
  41. package/dist/index.d.ts +5 -12
  42. package/dist/index.js +5 -5
  43. package/dist/install.js +2 -2
  44. package/dist/styles.css +5637 -5663
  45. package/dist/templates/adapters.d.ts +7 -1
  46. package/dist/templates/catalog.d.ts +5 -5
  47. package/dist/templates/componentBindings.d.ts +13 -0
  48. package/dist/templates/index.d.ts +5 -5
  49. package/dist/templates/index.js +2 -2
  50. package/dist/templates/presets.d.ts +4 -4
  51. package/dist/templates/types.d.ts +4 -1
  52. package/dist/{templates-50NPjaxL.js → templates-Cyt0Suwf.js} +322 -73
  53. package/dist/templates-Cyt0Suwf.js.map +1 -0
  54. package/dist/types/components.d.ts +6 -25
  55. package/dist/types/index.d.ts +1 -1
  56. package/dist/{useScheduleDrag-D4oWdh41.js → useExperimentData-CM6Y0u5L.js} +400 -357
  57. package/dist/useExperimentData-CM6Y0u5L.js.map +1 -0
  58. package/package.json +1 -1
  59. package/src/__tests__/components/ActionItem.test.ts +6 -6
  60. package/src/__tests__/components/AppLayout.test.ts +44 -0
  61. package/src/__tests__/components/AppSidebar.test.ts +130 -2
  62. package/src/__tests__/components/AppToastContainer.test.ts +0 -11
  63. package/src/__tests__/components/AppTopBar.test.ts +189 -120
  64. package/src/__tests__/components/{AppPageSelector.test.ts → AppTopBarPageSelector.test.ts} +8 -8
  65. package/src/__tests__/components/{AppPillNav.test.ts → AppTopBarPillNav.test.ts} +53 -6
  66. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
  67. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +32 -1
  68. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +48 -1
  69. package/src/__tests__/components/BioTemplateRenderer.test.ts +25 -0
  70. package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
  71. package/src/__tests__/components/ComponentBindingRenderer.test.ts +278 -0
  72. package/src/__tests__/components/ControlWorkspaceView.test.ts +134 -63
  73. package/src/__tests__/components/DateTimePicker.test.ts +2 -2
  74. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
  75. package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
  76. package/src/__tests__/composables/experiment-utils.test.ts +30 -0
  77. package/src/__tests__/composables/useApi.test.ts +30 -0
  78. package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
  79. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +7 -4
  80. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +7 -7
  81. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
  82. package/src/__tests__/composables/useControlSchema.test.ts +151 -37
  83. package/src/__tests__/composables/usePluginClient.test.ts +99 -2
  84. package/src/__tests__/docs/frontendDocsCatalog.test.ts +120 -25
  85. package/src/__tests__/templates/templates.test.ts +56 -0
  86. package/src/components/AppAvatarMenu.vue +3 -3
  87. package/src/components/AppLayout.story.vue +39 -0
  88. package/src/components/AppLayout.vue +83 -2
  89. package/src/components/AppPluginSwitcher.vue +5 -5
  90. package/src/components/AppSidebar.story.vue +113 -5
  91. package/src/components/AppSidebar.vue +147 -27
  92. package/src/components/AppTopBar.story.vue +2 -5
  93. package/src/components/AppTopBar.vue +35 -425
  94. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +2 -2
  95. package/src/components/BioTemplateExperimentWorkspaceView.vue +6 -0
  96. package/src/components/BioTemplatePackWorkspaceView.story.vue +4 -4
  97. package/src/components/BioTemplatePackWorkspaceView.vue +1 -0
  98. package/src/components/BioTemplatePresetWorkspaceView.story.vue +14 -2
  99. package/src/components/BioTemplatePresetWorkspaceView.vue +12 -3
  100. package/src/components/BioTemplateRenderer.story.vue +2 -2
  101. package/src/components/BioTemplateRenderer.vue +15 -227
  102. package/src/components/ComponentBindingRenderer.story.vue +87 -0
  103. package/src/components/ComponentBindingRenderer.vue +317 -0
  104. package/src/components/ControlWorkspaceView.story.vue +20 -9
  105. package/src/components/ControlWorkspaceView.vue +43 -12
  106. package/src/components/DatePicker.vue +2 -2
  107. package/src/components/DateTimePicker.vue +2 -2
  108. package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
  109. package/src/components/DoseDesignWorkspaceView.vue +255 -0
  110. package/src/components/ExperimentPopover.story.vue +2 -2
  111. package/src/components/ExperimentPopover.vue +2 -6
  112. package/src/components/ExperimentSelectorModal.vue +6 -5
  113. package/src/components/FormBuilder.story.vue +190 -0
  114. package/src/components/PluginWorkspaceView.story.vue +334 -0
  115. package/src/components/PluginWorkspaceView.vue +708 -0
  116. package/src/components/SettingsModal.story.vue +87 -0
  117. package/src/components/WellPlate.vue +2 -2
  118. package/src/components/index.ts +3 -12
  119. package/src/components/{AppPageSelector.vue → internal/AppTopBarPageSelectorInternal.vue} +9 -9
  120. package/src/components/internal/AppTopBarPillNavInternal.vue +194 -0
  121. package/src/components/{CalendarGridPanel.vue → internal/CalendarGridPanelInternal.vue} +1 -1
  122. package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +3 -3
  123. package/src/composables/experiment-utils.ts +26 -0
  124. package/src/composables/index.ts +21 -7
  125. package/src/composables/useApi.ts +9 -2
  126. package/src/composables/useAppExperiment.ts +85 -13
  127. package/src/composables/useBioTemplateComponents.ts +12 -0
  128. package/src/composables/useBioTemplatePackWorkspace.ts +6 -2
  129. package/src/composables/useBioTemplatePresetWorkspace.ts +10 -21
  130. package/src/composables/useBioTemplateWorkspace.ts +6 -4
  131. package/src/composables/useControlSchema.ts +157 -69
  132. package/src/composables/usePluginClient.ts +50 -9
  133. package/src/index.ts +6 -563
  134. package/src/styles/components/app-layout.css +82 -0
  135. package/src/styles/components/app-page-selector.css +1 -1
  136. package/src/styles/components/app-pill-nav.css +71 -1
  137. package/src/styles/components/app-sidebar.css +119 -0
  138. package/src/styles/components/app-top-bar.css +0 -235
  139. package/src/styles/components/experiment-popover.css +2 -2
  140. package/src/styles/index.css +0 -1
  141. package/src/templates/adapters.ts +193 -0
  142. package/src/templates/catalog.ts +5 -5
  143. package/src/templates/componentBindings.ts +90 -3
  144. package/src/templates/index.ts +10 -0
  145. package/src/templates/packs.ts +10 -1
  146. package/src/templates/presets.ts +14 -4
  147. package/src/templates/types.ts +4 -0
  148. package/src/types/components.ts +6 -31
  149. package/src/types/index.ts +2 -6
  150. package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
  151. package/dist/components/FormFieldRenderer.vue.d.ts +0 -28
  152. package/dist/components/FormSection.vue.d.ts +0 -30
  153. package/dist/components/GroupingModal.vue.d.ts +0 -12
  154. package/dist/components/SettingsButton.vue.d.ts +0 -30
  155. package/dist/components/ToastNotification.vue.d.ts +0 -2
  156. package/dist/components-D_Sr0adg.js.map +0 -1
  157. package/dist/composables/usePluginApi.d.ts +0 -22
  158. package/dist/composables-C3dpXQN5.js.map +0 -1
  159. package/dist/templates-50NPjaxL.js.map +0 -1
  160. package/dist/useScheduleDrag-D4oWdh41.js.map +0 -1
  161. package/src/__tests__/components/FormCompatibility.test.ts +0 -94
  162. package/src/__tests__/components/GroupingModal.test.ts +0 -73
  163. package/src/__tests__/components/SettingsButton.test.ts +0 -44
  164. package/src/__tests__/composables/usePluginApi.test.ts +0 -81
  165. package/src/components/AppPillNav.vue +0 -71
  166. package/src/components/FormFieldRenderer.vue +0 -35
  167. package/src/components/FormSection.vue +0 -37
  168. package/src/components/GroupingModal.story.vue +0 -52
  169. package/src/components/GroupingModal.vue +0 -61
  170. package/src/components/SettingsButton.story.vue +0 -58
  171. package/src/components/SettingsButton.vue +0 -64
  172. package/src/components/ToastNotification.vue +0 -9
  173. package/src/composables/usePluginApi.ts +0 -32
  174. package/src/styles/components/settings-button.css +0 -31
  175. /package/dist/__tests__/components/{AppPageSelector.test.d.ts → AppTopBarPageSelector.test.d.ts} +0 -0
  176. /package/dist/__tests__/components/{AppPillNav.test.d.ts → AppTopBarPillNav.test.d.ts} +0 -0
  177. /package/dist/__tests__/components/{FormCompatibility.test.d.ts → ComponentBindingRenderer.test.d.ts} +0 -0
  178. /package/dist/__tests__/components/{GroupingModal.test.d.ts → DoseDesignWorkspaceView.test.d.ts} +0 -0
  179. /package/dist/__tests__/components/{SettingsButton.test.d.ts → PluginWorkspaceView.test.d.ts} +0 -0
  180. /package/dist/components/{ActionItem.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +0 -0
  181. /package/src/components/{ActionItem.vue → internal/ActionItemInternal.vue} +0 -0
@@ -28,16 +28,16 @@ function initState() {
28
28
  <div class="pack-component-layout">
29
29
  <div class="pack-component-panel pack-component-panel--plate">
30
30
  <WellPlate
31
- v-if="bindings.componentPropsById['plate-map:WellPlate']"
32
- v-bind="bindings.componentPropsById['plate-map:WellPlate']"
31
+ v-if="bindings.componentBindingsById['plate-map:WellPlate']"
32
+ v-bind="bindings.componentBindingsById['plate-map:WellPlate'].props"
33
33
  size="sm"
34
34
  readonly
35
35
  />
36
36
  </div>
37
37
  <div class="pack-component-panel pack-component-panel--dose">
38
38
  <DoseCalculator
39
- v-if="bindings.componentPropsById['dose-response:DoseCalculator']"
40
- v-bind="bindings.componentPropsById['dose-response:DoseCalculator']"
39
+ v-if="bindings.componentBindingsById['dose-response:DoseCalculator']"
40
+ v-bind="bindings.componentBindingsById['dose-response:DoseCalculator'].props"
41
41
  />
42
42
  </div>
43
43
  </div>
@@ -14,6 +14,7 @@ interface BioTemplatePackWorkspaceSlotProps {
14
14
  bindings: UseBioTemplatePackWorkspaceReturn['bindings']['value']
15
15
  pack: UseBioTemplatePackWorkspaceReturn['pack']
16
16
  target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope
17
+ componentBindingsById: UseBioTemplatePackWorkspaceReturn['componentBindingsById']['value']
17
18
  componentProps: UseBioTemplatePackWorkspaceReturn['componentProps']['value']
18
19
  componentPropsById: UseBioTemplatePackWorkspaceReturn['componentPropsById']['value']
19
20
  componentPropsByComponent: UseBioTemplatePackWorkspaceReturn['componentPropsByComponent']['value']
@@ -6,6 +6,8 @@ import WellPlate from './WellPlate.vue'
6
6
  import { useBioTemplatePresetWorkspace } from '../composables/useBioTemplatePresetWorkspace'
7
7
  import type { BioTemplateControlValues } from '../templates'
8
8
 
9
+ type SidebarVariant = 'analysis' | 'default'
10
+
9
11
  const wellplateWorkspace = useBioTemplatePresetWorkspace('wellplate-screen', {
10
12
  initialValues: {
11
13
  sampleNames: ['Vehicle', 'Treatment'],
@@ -28,6 +30,7 @@ const componentPresetValues = ref<BioTemplateControlValues>({
28
30
  function initState() {
29
31
  return {
30
32
  preset: 'wellplate-screen',
33
+ sidebarVariant: 'analysis' as SidebarVariant,
31
34
  showTemplateSummary: true,
32
35
  showComponentSummary: false,
33
36
  dense: true,
@@ -50,6 +53,7 @@ function workspaceForPreset(preset: string) {
50
53
  <div style="padding: 2rem; min-height: 720px; background: var(--bg-primary, #f8fafc);">
51
54
  <BioTemplatePresetWorkspaceView
52
55
  :workspace="workspaceForPreset(state.preset)"
56
+ :sidebar-variant="state.sidebarVariant"
53
57
  :show-template-summary="state.showTemplateSummary"
54
58
  :show-component-summary="state.showComponentSummary"
55
59
  :dense="state.dense"
@@ -69,6 +73,14 @@ function workspaceForPreset(preset: string) {
69
73
  { label: 'Western blot assay', value: 'western-blot-assay' },
70
74
  ]"
71
75
  />
76
+ <HstSelect
77
+ v-model="state.sidebarVariant"
78
+ title="Sidebar variant"
79
+ :options="[
80
+ { label: 'Analysis', value: 'analysis' },
81
+ { label: 'Default', value: 'default' },
82
+ ]"
83
+ />
72
84
  <HstCheckbox v-model="state.showTemplateSummary" title="Template summary" />
73
85
  <HstCheckbox v-model="state.showComponentSummary" title="Component summary" />
74
86
  <HstCheckbox v-model="state.dense" title="Dense" />
@@ -97,14 +109,14 @@ function workspaceForPreset(preset: string) {
97
109
  <div class="preset-component-layout">
98
110
  <div class="preset-component-panel preset-component-panel--plate">
99
111
  <WellPlate
100
- v-bind="bindings.componentPropsById.value['plate-map:WellPlate']"
112
+ v-bind="bindings.componentBindingsById.value['plate-map:WellPlate'].props"
101
113
  size="sm"
102
114
  readonly
103
115
  />
104
116
  </div>
105
117
  <div class="preset-component-panel preset-component-panel--dose">
106
118
  <DoseCalculator
107
- v-bind="bindings.componentPropsById.value['dose-response:DoseCalculator']"
119
+ v-bind="bindings.componentBindingsById.value['dose-response:DoseCalculator'].props"
108
120
  />
109
121
  </div>
110
122
  </div>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- /** Complete editable WellPlate/DoseCalculator workspace for dose design that rebuilds generated AppSidebar preset wiring when preset props load late. */
2
+ /** Complete editable biology template preset workspace for WellPlate/DoseCalculator, LC-MS, qPCR, generated controls, preview components, and current-experiment save wiring. */
3
3
  import { computed, effectScope, onScopeDispose, shallowRef, toRaw, unref, watch, type EffectScope } from 'vue'
4
4
  import type { BioTemplateControlValues, TemplatePresetId } from '../templates'
5
5
  import type {
@@ -8,16 +8,19 @@ import type {
8
8
  } from '../composables/useBioTemplatePresetWorkspace'
9
9
  import { useBioTemplatePresetWorkspace } from '../composables/useBioTemplatePresetWorkspace'
10
10
  import AlertBox from './AlertBox.vue'
11
- import AppPillNav from './AppPillNav.vue'
12
11
  import AppSidebar from './AppSidebar.vue'
13
12
  import BaseButton from './BaseButton.vue'
14
13
  import BioTemplateRenderer from './BioTemplateRenderer.vue'
14
+ import AppTopBarPillNavInternal from './internal/AppTopBarPillNavInternal.vue'
15
+
16
+ type BioTemplatePresetSidebarVariant = 'default' | 'analysis'
15
17
 
16
18
  interface BioTemplatePresetWorkspaceSlotProps {
17
19
  workspace: UseBioTemplatePresetWorkspaceReturn
18
20
  bindings: UseBioTemplatePresetWorkspaceReturn['bindings']
19
21
  collection: UseBioTemplatePresetWorkspaceReturn['collection']['value']
20
22
  renderer: UseBioTemplatePresetWorkspaceReturn['renderer']['value']
23
+ componentBindingsById: UseBioTemplatePresetWorkspaceReturn['componentBindingsById']['value']
21
24
  componentProps: UseBioTemplatePresetWorkspaceReturn['componentProps']['value']
22
25
  componentPropsById: UseBioTemplatePresetWorkspaceReturn['componentPropsById']['value']
23
26
  componentPropsByComponent: UseBioTemplatePresetWorkspaceReturn['componentPropsByComponent']['value']
@@ -41,6 +44,8 @@ interface Props {
41
44
  label?: string
42
45
  /** Sidebar CSS width. */
43
46
  sidebarWidth?: string
47
+ /** AppSidebar visual preset. `analysis` matches the LEAF-style MINT analysis sidebar design language. */
48
+ sidebarVariant?: BioTemplatePresetSidebarVariant
44
49
  /** Compact sidebar and preview layout. */
45
50
  dense?: boolean
46
51
  /** Render preview components in read-only mode. */
@@ -74,6 +79,7 @@ const props = withDefaults(defineProps<Props>(), {
74
79
  modelValue: undefined,
75
80
  values: undefined,
76
81
  sidebarWidth: '320px',
82
+ sidebarVariant: 'analysis',
77
83
  dense: true,
78
84
  readonly: true,
79
85
  showStatus: true,
@@ -150,6 +156,7 @@ const isSaving = computed(() => unref(resolvedWorkspace.value.saving))
150
156
  const error = computed(() => unref(resolvedWorkspace.value.error))
151
157
  const lastSavedAt = computed(() => unref(resolvedWorkspace.value.lastSavedAt))
152
158
  const renderer = computed(() => unref(resolvedWorkspace.value.renderer))
159
+ const componentBindingsById = computed(() => unref(resolvedWorkspace.value.componentBindingsById))
153
160
  const componentProps = computed(() => unref(resolvedWorkspace.value.componentProps))
154
161
  const componentPropsById = computed(() => unref(resolvedWorkspace.value.componentPropsById))
155
162
  const componentPropsByComponent = computed(() => unref(resolvedWorkspace.value.componentPropsByComponent))
@@ -240,6 +247,7 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
240
247
  <div class="mint-bio-template-preset-workspace__main">
241
248
  <AppSidebar
242
249
  v-bind="resolvedWorkspace.sidebar"
250
+ :variant="sidebarVariant"
243
251
  :floating="false"
244
252
  :width="sidebarWidth"
245
253
  :dense="dense"
@@ -248,7 +256,7 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
248
256
  <p class="mint-bio-template-preset-workspace__sidebar-title">
249
257
  Controls
250
258
  </p>
251
- <AppPillNav
259
+ <AppTopBarPillNavInternal
252
260
  v-if="resolvedWorkspace.pillNav.items.length > 1"
253
261
  class="mint-bio-template-preset-workspace__view-nav"
254
262
  :items="resolvedWorkspace.pillNav.items"
@@ -266,6 +274,7 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
266
274
  :bindings="bindings"
267
275
  :collection="collection"
268
276
  :renderer="renderer"
277
+ :component-bindings-by-id="componentBindingsById"
269
278
  :component-props="componentProps"
270
279
  :component-props-by-id="componentPropsById"
271
280
  :component-props-by-component="componentPropsByComponent"
@@ -44,11 +44,11 @@ const lcmsBatch = createLcmsBatchCollection({
44
44
  </div>
45
45
  </Variant>
46
46
 
47
- <Variant title="LCMS Batch Tables">
47
+ <Variant title="LC-MS Batch Preview">
48
48
  <div style="padding: 2rem; background: var(--bg-primary, #f8fafc);">
49
49
  <BioTemplateRenderer
50
50
  :target="lcmsBatch"
51
- :include="['DataFrame']"
51
+ :include="['DataFrame', 'ExperimentTimeline', 'ScheduleCalendar']"
52
52
  dense
53
53
  />
54
54
  </div>
@@ -1,21 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  /** Render biology template envelopes with matching SDK components such as WellPlate, DoseCalculator, and DataFrame. */
3
- import { computed, type Component } from 'vue'
3
+ import { computed } from 'vue'
4
4
  import {
5
- toBioTemplateComponentProps,
6
- type BioTemplateComponentPropsBinding,
5
+ toBioTemplateComponentBindings,
7
6
  } from '../templates/componentBindings'
8
7
  import type {
9
8
  BioTemplateEnvelope,
10
9
  TemplateCollectionEnvelope,
11
10
  } from '../templates/types'
12
- import DataFrame from './DataFrame.vue'
13
- import DoseCalculator from './DoseCalculator.vue'
14
- import ExperimentTimeline from './ExperimentTimeline.vue'
15
- import PlateMapEditor from './PlateMapEditor.vue'
16
- import ReagentList from './ReagentList.vue'
17
- import SampleSelector from './SampleSelector.vue'
18
- import WellPlate from './WellPlate.vue'
11
+ import ComponentBindingRenderer from './ComponentBindingRenderer.vue'
19
12
 
20
13
  interface Props {
21
14
  /** Template envelope or template collection to render. */
@@ -36,11 +29,6 @@ interface Props {
36
29
  emptyText?: string
37
30
  }
38
31
 
39
- type RenderableBinding = BioTemplateComponentPropsBinding & {
40
- componentImpl: Component
41
- normalizedProps: Record<string, unknown>
42
- }
43
-
44
32
  const props = withDefaults(defineProps<Props>(), {
45
33
  include: () => [],
46
34
  exclude: () => [],
@@ -51,219 +39,19 @@ const props = withDefaults(defineProps<Props>(), {
51
39
  emptyText: 'No renderable biology template components.',
52
40
  })
53
41
 
54
- const componentRegistry: Record<string, Component> = {
55
- DataFrame,
56
- DoseCalculator,
57
- ExperimentTimeline,
58
- PlateMapEditor,
59
- ReagentList,
60
- SampleSelector,
61
- WellPlate,
62
- }
63
-
64
- const renderableBindings = computed<RenderableBinding[]>(() =>
65
- toBioTemplateComponentProps(props.target)
66
- .filter(binding => isIncluded(binding.component))
67
- .map((binding) => {
68
- const componentImpl = componentRegistry[binding.component]
69
- if (!componentImpl) return null
70
- return {
71
- ...binding,
72
- componentImpl,
73
- normalizedProps: normalizeProps(binding),
74
- }
75
- })
76
- .filter((binding): binding is RenderableBinding => binding !== null)
77
- )
78
-
79
- const rendererClasses = computed(() => [
80
- 'mint-bio-template-renderer',
81
- props.dense ? 'mint-bio-template-renderer--dense' : '',
82
- ])
83
-
84
- function isIncluded(componentName: string): boolean {
85
- if (props.include.length > 0 && !props.include.includes(componentName)) return false
86
- return !props.exclude.includes(componentName)
87
- }
88
-
89
- function normalizeProps(binding: BioTemplateComponentPropsBinding): Record<string, unknown> {
90
- const base = { ...binding.propsObject }
91
-
92
- switch (binding.component) {
93
- case 'WellPlate':
94
- return {
95
- ...base,
96
- readonly: props.readonly || Boolean(base.readonly),
97
- size: props.dense ? 'md' : (base.size ?? 'fill'),
98
- }
99
- case 'PlateMapEditor':
100
- return {
101
- ...base,
102
- size: props.dense ? 'md' : (base.size ?? 'fill'),
103
- showToolbar: props.readonly ? false : (base.showToolbar ?? true),
104
- showSidebar: props.readonly ? false : (base.showSidebar ?? true),
105
- allowAddPlates: props.readonly ? false : (base.allowAddPlates ?? true),
106
- allowAddSamples: props.readonly ? false : (base.allowAddSamples ?? true),
107
- }
108
- case 'DataFrame':
109
- return {
110
- ...base,
111
- size: props.dense ? 'sm' : (base.size ?? 'md'),
112
- maxHeight: base.maxHeight ?? (props.dense ? '280px' : undefined),
113
- }
114
- case 'ExperimentTimeline':
115
- return {
116
- ...base,
117
- editable: props.readonly ? false : base.editable,
118
- size: props.dense ? 'sm' : (base.size ?? 'md'),
119
- }
120
- case 'ReagentList':
121
- return {
122
- ...base,
123
- readonly: props.readonly || Boolean(base.readonly),
124
- }
125
- case 'SampleSelector':
126
- return {
127
- ...base,
128
- enableGrouping: props.readonly ? false : (base.enableGrouping ?? true),
129
- enableSmartGroup: props.readonly ? false : (base.enableSmartGroup ?? true),
130
- }
131
- default:
132
- return base
133
- }
134
- }
42
+ const componentBindings = computed(() => toBioTemplateComponentBindings(props.target))
135
43
  </script>
136
44
 
137
45
  <template>
138
- <div :class="rendererClasses">
139
- <section
140
- v-for="binding in renderableBindings"
141
- :key="binding.id"
142
- class="mint-bio-template-renderer__item"
143
- :data-template-id="binding.template_id"
144
- :data-template-component="binding.component"
145
- >
146
- <header
147
- v-if="showHeaders"
148
- class="mint-bio-template-renderer__header"
149
- >
150
- <div class="mint-bio-template-renderer__title-group">
151
- <p class="mint-bio-template-renderer__component">
152
- {{ binding.component }}
153
- </p>
154
- <p class="mint-bio-template-renderer__template">
155
- {{ binding.template_id }}
156
- </p>
157
- </div>
158
- <p
159
- v-if="showDescriptions"
160
- class="mint-bio-template-renderer__description"
161
- >
162
- {{ binding.description }}
163
- </p>
164
- </header>
165
-
166
- <div class="mint-bio-template-renderer__body">
167
- <component
168
- :is="binding.componentImpl"
169
- v-bind="binding.normalizedProps"
170
- />
171
- </div>
172
- </section>
173
-
174
- <p
175
- v-if="renderableBindings.length === 0"
176
- class="mint-bio-template-renderer__empty"
177
- >
178
- {{ emptyText }}
179
- </p>
180
- </div>
46
+ <ComponentBindingRenderer
47
+ class="mint-bio-template-renderer"
48
+ :bindings="componentBindings"
49
+ :include="include"
50
+ :exclude="exclude"
51
+ :dense="dense"
52
+ :readonly="readonly"
53
+ :show-headers="showHeaders"
54
+ :show-descriptions="showDescriptions"
55
+ :empty-text="emptyText"
56
+ />
181
57
  </template>
182
-
183
- <style scoped>
184
- .mint-bio-template-renderer {
185
- display: grid;
186
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));
187
- gap: 1rem;
188
- min-width: 0;
189
- }
190
-
191
- .mint-bio-template-renderer--dense {
192
- gap: 0.75rem;
193
- }
194
-
195
- .mint-bio-template-renderer__item {
196
- min-width: 0;
197
- overflow: hidden;
198
- border: 1px solid var(--border-color, #e5e7eb);
199
- border-radius: 0.5rem;
200
- background: var(--bg-card, #ffffff);
201
- box-shadow: var(--shadow-sm, 0 1px 2px rgb(15 23 42 / 0.06));
202
- }
203
-
204
- .mint-bio-template-renderer__header {
205
- display: flex;
206
- align-items: flex-start;
207
- justify-content: space-between;
208
- gap: 1rem;
209
- padding: 0.75rem 1rem;
210
- border-bottom: 1px solid var(--border-color, #e5e7eb);
211
- background: var(--bg-secondary, #f8fafc);
212
- }
213
-
214
- .mint-bio-template-renderer__title-group {
215
- min-width: 0;
216
- }
217
-
218
- .mint-bio-template-renderer__component {
219
- margin: 0;
220
- color: var(--text-primary, #0f172a);
221
- font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
222
- font-size: 0.8125rem;
223
- font-weight: 600;
224
- }
225
-
226
- .mint-bio-template-renderer__template,
227
- .mint-bio-template-renderer__description,
228
- .mint-bio-template-renderer__empty {
229
- margin: 0;
230
- color: var(--text-secondary, #475569);
231
- font-size: 0.75rem;
232
- line-height: 1.4;
233
- }
234
-
235
- .mint-bio-template-renderer__description {
236
- max-width: 24rem;
237
- text-align: right;
238
- }
239
-
240
- .mint-bio-template-renderer__body {
241
- min-width: 0;
242
- padding: 1rem;
243
- }
244
-
245
- .mint-bio-template-renderer--dense .mint-bio-template-renderer__header,
246
- .mint-bio-template-renderer--dense .mint-bio-template-renderer__body {
247
- padding: 0.75rem;
248
- }
249
-
250
- .mint-bio-template-renderer__empty {
251
- padding: 1rem;
252
- border: 1px dashed var(--border-color, #e5e7eb);
253
- border-radius: 0.5rem;
254
- background: var(--bg-secondary, #f8fafc);
255
- text-align: center;
256
- }
257
-
258
- @media (max-width: 720px) {
259
- .mint-bio-template-renderer__header {
260
- display: block;
261
- }
262
-
263
- .mint-bio-template-renderer__description {
264
- max-width: none;
265
- margin-top: 0.25rem;
266
- text-align: left;
267
- }
268
- }
269
- </style>
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import ComponentBindingRenderer from './ComponentBindingRenderer.vue'
4
+ import {
5
+ defineDoseDesignControlModel,
6
+ useControlWorkspace,
7
+ } from '../composables/useControlSchema'
8
+ import {
9
+ createLcmsBatchCollection,
10
+ toBioTemplateComponentBindings,
11
+ } from '../templates'
12
+
13
+ const model = defineDoseDesignControlModel({
14
+ selectedWells: ['A1', 'A2', 'B1'],
15
+ plateFormat: 96,
16
+ includeMolecularWeight: true,
17
+ })
18
+ const workspace = useControlWorkspace(model)
19
+ const lcmsCollection = createLcmsBatchCollection({
20
+ samples: [
21
+ { sampleId: 'blank-1', name: 'Blank', group: 'Blank' },
22
+ { sampleId: 'qc-pool', name: 'Pooled QC', group: 'QC' },
23
+ { sampleId: 's001', name: 'Patient 001', group: 'Case' },
24
+ { sampleId: 's002', name: 'Patient 002', group: 'Control' },
25
+ ],
26
+ features: ['Glucose', 'Lactate', 'Citrate'],
27
+ instrument: 'Orbitrap LC-MS',
28
+ method: 'HILIC negative',
29
+ includeQc: true,
30
+ })
31
+
32
+ const state = ref({
33
+ dense: false,
34
+ readonly: true,
35
+ showHeaders: true,
36
+ showDescriptions: true,
37
+ layout: 'grid' as 'grid' | 'stack',
38
+ })
39
+
40
+ const bindings = computed(() => workspace.componentBindings.value)
41
+ const lcmsBindings = computed(() => toBioTemplateComponentBindings(lcmsCollection))
42
+ </script>
43
+
44
+ <template>
45
+ <Story title="Layout/ComponentBindingRenderer">
46
+ <Variant title="Dose Design Bindings">
47
+ <div style="padding: 2rem; min-height: 640px; background: var(--bg-secondary, #f8fafc);">
48
+ <ComponentBindingRenderer
49
+ :bindings="bindings"
50
+ :dense="state.dense"
51
+ :readonly="state.readonly"
52
+ :show-headers="state.showHeaders"
53
+ :show-descriptions="state.showDescriptions"
54
+ :layout="state.layout"
55
+ />
56
+ </div>
57
+ </Variant>
58
+
59
+ <Variant title="LC-MS Batch Bindings">
60
+ <div style="padding: 2rem; min-height: 640px; background: var(--bg-secondary, #f8fafc);">
61
+ <ComponentBindingRenderer
62
+ :bindings="lcmsBindings"
63
+ :dense="state.dense"
64
+ :readonly="state.readonly"
65
+ :show-headers="state.showHeaders"
66
+ :show-descriptions="state.showDescriptions"
67
+ :layout="state.layout"
68
+ />
69
+ </div>
70
+ </Variant>
71
+
72
+ <template #controls>
73
+ <HstCheckbox v-model="state.dense" title="Dense" />
74
+ <HstCheckbox v-model="state.readonly" title="Readonly" />
75
+ <HstCheckbox v-model="state.showHeaders" title="Headers" />
76
+ <HstCheckbox v-model="state.showDescriptions" title="Descriptions" />
77
+ <HstSelect
78
+ v-model="state.layout"
79
+ title="Layout"
80
+ :options="[
81
+ { label: 'Grid', value: 'grid' },
82
+ { label: 'Stack', value: 'stack' },
83
+ ]"
84
+ />
85
+ </template>
86
+ </Story>
87
+ </template>