@morscherlab/mint-sdk 1.0.0-rc.1 → 1.0.0-rc.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.
Files changed (300) hide show
  1. package/README.md +9 -1
  2. package/dist/__tests__/components/LcmsSequenceTable.test.d.ts +1 -0
  3. package/dist/__tests__/components/ProgressBar.test.d.ts +1 -0
  4. package/dist/__tests__/components/RackEditor.test.d.ts +1 -0
  5. package/dist/__tests__/components/SequenceProgressBar.test.d.ts +1 -0
  6. package/dist/__tests__/composables/useExperimentSamples.test.d.ts +1 -0
  7. package/dist/__tests__/utils/instrument.test.d.ts +1 -0
  8. package/dist/__tests__/utils/lcms.test.d.ts +1 -0
  9. package/dist/__tests__/utils/permissions.test.d.ts +1 -0
  10. package/dist/__tests__/utils/rack.test.d.ts +1 -0
  11. package/dist/{auth-CBG3bWEc.js → auth-B7g4J4ZF.js} +99 -5
  12. package/dist/auth-B7g4J4ZF.js.map +1 -0
  13. package/dist/components/AppSidebar.vue.d.ts +3 -3
  14. package/dist/components/AutoGroupModal.vue.d.ts +1 -1
  15. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  16. package/dist/components/BaseToggle.vue.d.ts +2 -2
  17. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
  18. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
  19. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +1 -1
  20. package/dist/components/ControlWorkspaceView.vue.d.ts +3 -3
  21. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +2 -2
  22. package/dist/components/FormulaInput.vue.d.ts +1 -1
  23. package/dist/components/InstrumentAlertLog.vue.d.ts +22 -0
  24. package/dist/components/InstrumentStateBadge.vue.d.ts +11 -0
  25. package/dist/components/InstrumentStatusCard.vue.d.ts +13 -0
  26. package/dist/components/LcmsSequenceTable.vue.d.ts +26 -0
  27. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -3
  28. package/dist/components/ProgressBar.vue.d.ts +1 -0
  29. package/dist/components/RackEditor.vue.d.ts +41 -3
  30. package/dist/components/ReagentList.vue.d.ts +1 -1
  31. package/dist/components/SampleSelector.vue.d.ts +5 -2
  32. package/dist/components/SegmentedControl.vue.d.ts +2 -0
  33. package/dist/components/SequenceInput.vue.d.ts +1 -1
  34. package/dist/components/SequenceProgressBar.vue.d.ts +15 -0
  35. package/dist/components/SettingsModal.vue.d.ts +3 -1
  36. package/dist/components/TagsInput.vue.d.ts +1 -1
  37. package/dist/components/WellPlate.vue.d.ts +42 -3
  38. package/dist/components/index.d.ts +5 -0
  39. package/dist/components/index.js +3 -3
  40. package/dist/{components-5KSfsVqf.js → components-DafPc4rM.js} +2091 -1051
  41. package/dist/components-DafPc4rM.js.map +1 -0
  42. package/dist/composables/controlComponentBindings.d.ts +7 -0
  43. package/dist/composables/controlSchemaAdapters.d.ts +20 -0
  44. package/dist/composables/controlSchemaFormFields.d.ts +3 -0
  45. package/dist/composables/controlSchemaLayout.d.ts +7 -0
  46. package/dist/composables/controlSchemaNormalize.d.ts +15 -0
  47. package/dist/composables/controlSchemaUtils.d.ts +9 -0
  48. package/dist/composables/controlWorkspaceOptions.d.ts +2 -0
  49. package/dist/composables/experimentDesignData.d.ts +17 -0
  50. package/dist/composables/index.d.ts +2 -0
  51. package/dist/composables/index.js +4 -4
  52. package/dist/composables/useControlSchema.d.ts +15 -30
  53. package/dist/composables/useControlWorkspace.d.ts +5 -0
  54. package/dist/composables/useExperimentData.d.ts +11 -3
  55. package/dist/composables/useExperimentSamples.d.ts +42 -0
  56. package/dist/composables/usePlatformContext.d.ts +54 -0
  57. package/dist/{composables-D4Myb30a.js → composables-BMkPQhVK.js} +5 -3
  58. package/dist/composables-BMkPQhVK.js.map +1 -0
  59. package/dist/index.d.ts +4 -0
  60. package/dist/index.js +168 -6
  61. package/dist/index.js.map +1 -0
  62. package/dist/install.js +2 -2
  63. package/dist/instrument.d.ts +7 -0
  64. package/dist/lcms.d.ts +27 -0
  65. package/dist/permissions.d.ts +46 -0
  66. package/dist/stores/auth.d.ts +74 -2
  67. package/dist/stores/index.js +1 -1
  68. package/dist/styles.css +3316 -1216
  69. package/dist/templates/adapters.d.ts +14 -47
  70. package/dist/templates/assayLookups.d.ts +3 -0
  71. package/dist/templates/assayMatrixAdapters.d.ts +6 -0
  72. package/dist/templates/assayMatrixBuilder.d.ts +2 -0
  73. package/dist/templates/assayNormalizers.d.ts +4 -0
  74. package/dist/templates/builderDefaults.d.ts +1 -0
  75. package/dist/templates/builderIdUtils.d.ts +4 -0
  76. package/dist/templates/builderPresetControls.d.ts +10 -0
  77. package/dist/templates/builderReadUtils.d.ts +8 -0
  78. package/dist/templates/builders.d.ts +26 -63
  79. package/dist/templates/calibrationCurveAdapters.d.ts +4 -0
  80. package/dist/templates/calibrationCurveBuilder.d.ts +2 -0
  81. package/dist/templates/calibrationNormalizers.d.ts +5 -0
  82. package/dist/templates/componentBindingCatalog.d.ts +25 -0
  83. package/dist/templates/componentBindingHelpers.d.ts +17 -0
  84. package/dist/templates/componentDoseResponseProps.d.ts +3 -0
  85. package/dist/templates/componentGenericProps.d.ts +8 -0
  86. package/dist/templates/componentPlateHelpers.d.ts +5 -0
  87. package/dist/templates/componentPlateMapProps.d.ts +3 -0
  88. package/dist/templates/componentPropsFactory.d.ts +3 -0
  89. package/dist/templates/componentQpcrPlateProps.d.ts +3 -0
  90. package/dist/templates/componentTargetResolvers.d.ts +5 -0
  91. package/dist/templates/componentTemplateProps.d.ts +3 -0
  92. package/dist/templates/controlSchemaClone.d.ts +4 -0
  93. package/dist/templates/controlSchemaConstants.d.ts +10 -0
  94. package/dist/templates/controlSchemaMerge.d.ts +3 -0
  95. package/dist/templates/controlSchemaTargets.d.ts +17 -0
  96. package/dist/templates/controlSchemaTypes.d.ts +4 -0
  97. package/dist/templates/controlSchemas.d.ts +4 -4
  98. package/dist/templates/defaultBioTemplateBuilder.d.ts +2 -0
  99. package/dist/templates/doseResponseAdapters.d.ts +4 -0
  100. package/dist/templates/doseResponseBuilder.d.ts +2 -0
  101. package/dist/templates/elisaAssayCollectionBuilder.d.ts +2 -0
  102. package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +2 -0
  103. package/dist/templates/flowCytometryPanelBuilder.d.ts +2 -0
  104. package/dist/templates/flowNormalizers.d.ts +8 -0
  105. package/dist/templates/flowPanelAdapters.d.ts +4 -0
  106. package/dist/templates/index.d.ts +2 -2
  107. package/dist/templates/index.js +2 -2
  108. package/dist/templates/instrumentRunAdapterHelpers.d.ts +8 -0
  109. package/dist/templates/instrumentRunAdapters.d.ts +8 -0
  110. package/dist/templates/instrumentRunBuilder.d.ts +2 -0
  111. package/dist/templates/lcmsBatchCollectionBuilder.d.ts +2 -0
  112. package/dist/templates/plateGeometry.d.ts +4 -0
  113. package/dist/templates/plateMapAdapters.d.ts +3 -0
  114. package/dist/templates/plateMapBuilder.d.ts +2 -0
  115. package/dist/templates/presetControlSchemas.d.ts +534 -0
  116. package/dist/templates/presets.d.ts +12 -0
  117. package/dist/templates/protocolAdapters.d.ts +5 -0
  118. package/dist/templates/protocolNormalizers.d.ts +6 -0
  119. package/dist/templates/protocolStepsBuilder.d.ts +2 -0
  120. package/dist/templates/qpcrAdapters.d.ts +5 -0
  121. package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +2 -0
  122. package/dist/templates/qpcrPlateBuilder.d.ts +2 -0
  123. package/dist/templates/reagentAdapters.d.ts +5 -0
  124. package/dist/templates/reagentListBuilder.d.ts +2 -0
  125. package/dist/templates/runNormalizers.d.ts +10 -0
  126. package/dist/templates/sampleNormalizers.d.ts +4 -0
  127. package/dist/templates/samplePrepAdapters.d.ts +4 -0
  128. package/dist/templates/samplePrepBuilder.d.ts +2 -0
  129. package/dist/templates/sampleSheetAdapters.d.ts +5 -0
  130. package/dist/templates/sampleSheetBuilder.d.ts +2 -0
  131. package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +2 -0
  132. package/dist/templates/targetedMetabolomicsHelpers.d.ts +5 -0
  133. package/dist/templates/templateControlSchemas.d.ts +400 -0
  134. package/dist/templates/templateEnvelopes.d.ts +9 -0
  135. package/dist/templates/templatePackCollectionBuilder.d.ts +2 -0
  136. package/dist/templates/templatePresetCollectionBuilder.d.ts +18 -0
  137. package/dist/templates/templateValidators.d.ts +13 -0
  138. package/dist/templates/timeCourseAdapters.d.ts +5 -0
  139. package/dist/templates/timeCourseBuilder.d.ts +2 -0
  140. package/dist/templates/types.d.ts +16 -1
  141. package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
  142. package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
  143. package/dist/{templates-BSlxwV2c.js → templates-bUAWMn5L.js} +3610 -3154
  144. package/dist/templates-bUAWMn5L.js.map +1 -0
  145. package/dist/types/auth.d.ts +2 -0
  146. package/dist/types/components.d.ts +32 -3
  147. package/dist/types/form-builder.d.ts +2 -1
  148. package/dist/types/index.d.ts +4 -1
  149. package/dist/types/instrument.d.ts +56 -0
  150. package/dist/types/platform.d.ts +3 -0
  151. package/dist/{useExperimentData-BbbdI5xT.js → useProtocolTemplates-QZtHFFH2.js} +534 -359
  152. package/dist/useProtocolTemplates-QZtHFFH2.js.map +1 -0
  153. package/dist/utils/rack.d.ts +47 -0
  154. package/package.json +1 -1
  155. package/src/__tests__/components/AppTopBar.test.ts +15 -0
  156. package/src/__tests__/components/BaseTabs.test.ts +15 -0
  157. package/src/__tests__/components/LcmsSequenceTable.test.ts +57 -0
  158. package/src/__tests__/components/ProgressBar.test.ts +18 -0
  159. package/src/__tests__/components/RackEditor.test.ts +125 -0
  160. package/src/__tests__/components/SampleSelector.test.ts +25 -0
  161. package/src/__tests__/components/SegmentedControl.test.ts +45 -0
  162. package/src/__tests__/components/SequenceProgressBar.test.ts +39 -0
  163. package/src/__tests__/components/SettingsModal.test.ts +83 -2
  164. package/src/__tests__/composables/useControlSchema.test.ts +4 -0
  165. package/src/__tests__/composables/useExperimentData.test.ts +23 -0
  166. package/src/__tests__/composables/useExperimentSamples.test.ts +91 -0
  167. package/src/__tests__/templates/templates.test.ts +86 -0
  168. package/src/__tests__/utils/instrument.test.ts +47 -0
  169. package/src/__tests__/utils/lcms.test.ts +73 -0
  170. package/src/__tests__/utils/permissions.test.ts +50 -0
  171. package/src/__tests__/utils/rack.test.ts +120 -0
  172. package/src/components/AppTopBar.vue +1 -0
  173. package/src/components/BaseTabs.vue +22 -1
  174. package/src/components/InstrumentAlertLog.vue +191 -0
  175. package/src/components/InstrumentStateBadge.vue +50 -0
  176. package/src/components/InstrumentStatusCard.vue +188 -0
  177. package/src/components/LcmsSequenceTable.vue +191 -0
  178. package/src/components/ProgressBar.vue +3 -0
  179. package/src/components/RackEditor.vue +73 -2
  180. package/src/components/SampleSelector.vue +28 -9
  181. package/src/components/SegmentedControl.story.vue +17 -0
  182. package/src/components/SegmentedControl.vue +14 -3
  183. package/src/components/SequenceProgressBar.vue +71 -0
  184. package/src/components/SettingsModal.vue +42 -2
  185. package/src/components/WellPlate.vue +142 -21
  186. package/src/components/index.ts +5 -0
  187. package/src/components/internal/WellEditPopupInternal.vue +1 -0
  188. package/src/composables/controlComponentBindings.ts +80 -0
  189. package/src/composables/controlSchemaAdapters.ts +196 -0
  190. package/src/composables/controlSchemaFormFields.ts +61 -0
  191. package/src/composables/controlSchemaLayout.ts +59 -0
  192. package/src/composables/controlSchemaNormalize.ts +101 -0
  193. package/src/composables/controlSchemaUtils.ts +36 -0
  194. package/src/composables/controlWorkspaceOptions.ts +21 -0
  195. package/src/composables/experimentDesignData.ts +182 -0
  196. package/src/composables/index.ts +14 -0
  197. package/src/composables/useAuth.ts +4 -0
  198. package/src/composables/useAutoGroup.ts +5 -1
  199. package/src/composables/useControlSchema.ts +62 -618
  200. package/src/composables/useControlWorkspace.ts +207 -0
  201. package/src/composables/useExperimentData.ts +57 -16
  202. package/src/composables/useExperimentSamples.ts +142 -0
  203. package/src/index.ts +27 -0
  204. package/src/instrument.ts +90 -0
  205. package/src/lcms.ts +108 -0
  206. package/src/permissions.ts +143 -0
  207. package/src/stores/auth.ts +31 -3
  208. package/src/styles/components/instrument-monitor.css +478 -0
  209. package/src/styles/components/lcms-sequence-table.css +189 -0
  210. package/src/styles/components/sequence-progress-bar.css +63 -0
  211. package/src/styles/components/tabs.css +9 -0
  212. package/src/styles/components/well-edit-popup.css +7 -1
  213. package/src/styles/components/well-plate.css +5 -0
  214. package/src/styles/index.css +3 -0
  215. package/src/templates/adapters.ts +89 -930
  216. package/src/templates/assayLookups.ts +33 -0
  217. package/src/templates/assayMatrixAdapters.ts +78 -0
  218. package/src/templates/assayMatrixBuilder.ts +59 -0
  219. package/src/templates/assayNormalizers.ts +34 -0
  220. package/src/templates/builderDefaults.ts +11 -0
  221. package/src/templates/builderIdUtils.ts +20 -0
  222. package/src/templates/builderPresetControls.ts +165 -0
  223. package/src/templates/builderReadUtils.ts +57 -0
  224. package/src/templates/builders.ts +122 -2149
  225. package/src/templates/calibrationCurveAdapters.ts +59 -0
  226. package/src/templates/calibrationCurveBuilder.ts +99 -0
  227. package/src/templates/calibrationNormalizers.ts +60 -0
  228. package/src/templates/componentBindingCatalog.ts +90 -0
  229. package/src/templates/componentBindingHelpers.ts +93 -0
  230. package/src/templates/componentBindings.ts +12 -461
  231. package/src/templates/componentDoseResponseProps.ts +42 -0
  232. package/src/templates/componentGenericProps.ts +77 -0
  233. package/src/templates/componentPlateHelpers.ts +29 -0
  234. package/src/templates/componentPlateMapProps.ts +32 -0
  235. package/src/templates/componentPropsFactory.ts +21 -0
  236. package/src/templates/componentQpcrPlateProps.ts +28 -0
  237. package/src/templates/componentTargetResolvers.ts +69 -0
  238. package/src/templates/componentTemplateProps.ts +78 -0
  239. package/src/templates/controlSchemaClone.ts +32 -0
  240. package/src/templates/controlSchemaConstants.ts +11 -0
  241. package/src/templates/controlSchemaMerge.ts +40 -0
  242. package/src/templates/controlSchemaTargets.ts +87 -0
  243. package/src/templates/controlSchemaTypes.ts +20 -0
  244. package/src/templates/controlSchemas.ts +22 -636
  245. package/src/templates/defaultBioTemplateBuilder.ts +124 -0
  246. package/src/templates/doseResponseAdapters.ts +45 -0
  247. package/src/templates/doseResponseBuilder.ts +44 -0
  248. package/src/templates/elisaAssayCollectionBuilder.ts +62 -0
  249. package/src/templates/flowCytometryAssayCollectionBuilder.ts +41 -0
  250. package/src/templates/flowCytometryPanelBuilder.ts +53 -0
  251. package/src/templates/flowNormalizers.ts +58 -0
  252. package/src/templates/flowPanelAdapters.ts +58 -0
  253. package/src/templates/index.ts +2 -0
  254. package/src/templates/instrumentRunAdapterHelpers.ts +94 -0
  255. package/src/templates/instrumentRunAdapters.ts +163 -0
  256. package/src/templates/instrumentRunBuilder.ts +97 -0
  257. package/src/templates/lcmsBatchCollectionBuilder.ts +38 -0
  258. package/src/templates/plateGeometry.ts +62 -0
  259. package/src/templates/plateMapAdapters.ts +36 -0
  260. package/src/templates/plateMapBuilder.ts +43 -0
  261. package/src/templates/presetControlSchemas.ts +258 -0
  262. package/src/templates/presets.ts +23 -0
  263. package/src/templates/protocolAdapters.ts +69 -0
  264. package/src/templates/protocolNormalizers.ts +37 -0
  265. package/src/templates/protocolStepsBuilder.ts +36 -0
  266. package/src/templates/qpcrAdapters.ts +104 -0
  267. package/src/templates/qpcrExpressionCollectionBuilder.ts +33 -0
  268. package/src/templates/qpcrPlateBuilder.ts +96 -0
  269. package/src/templates/reagentAdapters.ts +77 -0
  270. package/src/templates/reagentListBuilder.ts +30 -0
  271. package/src/templates/runNormalizers.ts +63 -0
  272. package/src/templates/sampleNormalizers.ts +58 -0
  273. package/src/templates/samplePrepAdapters.ts +63 -0
  274. package/src/templates/samplePrepBuilder.ts +51 -0
  275. package/src/templates/sampleSheetAdapters.ts +75 -0
  276. package/src/templates/sampleSheetBuilder.ts +23 -0
  277. package/src/templates/targetedMetabolomicsCollectionBuilder.ts +79 -0
  278. package/src/templates/targetedMetabolomicsHelpers.ts +102 -0
  279. package/src/templates/templateControlSchemas.ts +320 -0
  280. package/src/templates/templateEnvelopes.ts +137 -0
  281. package/src/templates/templatePackCollectionBuilder.ts +23 -0
  282. package/src/templates/templatePresetCollectionBuilder.ts +139 -0
  283. package/src/templates/templateValidators.ts +414 -0
  284. package/src/templates/timeCourseAdapters.ts +73 -0
  285. package/src/templates/timeCourseBuilder.ts +64 -0
  286. package/src/templates/types.ts +17 -0
  287. package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
  288. package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -0
  289. package/src/types/auth.ts +3 -0
  290. package/src/types/components.ts +45 -3
  291. package/src/types/form-builder.ts +2 -1
  292. package/src/types/index.ts +35 -0
  293. package/src/types/instrument.ts +61 -0
  294. package/src/types/platform.ts +4 -0
  295. package/src/utils/rack.ts +209 -0
  296. package/dist/auth-CBG3bWEc.js.map +0 -1
  297. package/dist/components-5KSfsVqf.js.map +0 -1
  298. package/dist/composables-D4Myb30a.js.map +0 -1
  299. package/dist/templates-BSlxwV2c.js.map +0 -1
  300. package/dist/useExperimentData-BbbdI5xT.js.map +0 -1
@@ -0,0 +1,207 @@
1
+ import {
2
+ computed,
3
+ reactive,
4
+ ref,
5
+ watch,
6
+ } from 'vue'
7
+ import type {
8
+ PillNavItem,
9
+ TopBarSettingsConfig,
10
+ } from '../types'
11
+ import {
12
+ replaceRecord,
13
+ } from './controlSchemaUtils'
14
+ import {
15
+ mergeControlWorkspaceOptions,
16
+ } from './controlWorkspaceOptions'
17
+ import {
18
+ controlValuesToComponentBindings,
19
+ controlValuesToComponentBindingsById,
20
+ controlValuesToComponentProps,
21
+ } from './controlComponentBindings'
22
+ import {
23
+ useControlSchema,
24
+ } from './useControlSchema'
25
+ import type {
26
+ ControlComponentBinding,
27
+ ControlComponentBindingsById,
28
+ ControlComponentBindingsConfig,
29
+ ControlComponentPropsByIdMap,
30
+ ControlComponentPropsMap,
31
+ ControlModelBinding,
32
+ ControlSchema,
33
+ ControlValues,
34
+ ControlWorkspaceComponentBindings,
35
+ ControlWorkspaceFormBinding,
36
+ ControlWorkspaceOptions,
37
+ ControlWorkspacePillNavBinding,
38
+ ControlWorkspaceSidebarBinding,
39
+ ControlWorkspaceTopBarBinding,
40
+ ControlWorkspaceTopBarSettingsBinding,
41
+ UseControlWorkspaceReturn,
42
+ } from './useControlSchema'
43
+
44
+ /** Prepare shared reactive values plus AppTopBar/AppSidebar/FormBuilder bindings from one simple controls data model. */
45
+ export function useControlWorkspace<TControls extends ControlSchema>(
46
+ controlsOrModel: TControls | (ControlModelBinding & { controls: TControls }),
47
+ options: ControlWorkspaceOptions = {},
48
+ ): UseControlWorkspaceReturn<TControls> {
49
+ const model = isControlModelBindingInput<TControls>(controlsOrModel) ? controlsOrModel : undefined
50
+ const controls: TControls = model ? model.controls : controlsOrModel as TControls
51
+ const workspaceOptions = model
52
+ ? mergeControlWorkspaceOptions(model.controlOptions, options)
53
+ : options
54
+ const { initialValues, topBarSettings, ...schemaOptions } = workspaceOptions
55
+ const schema = useControlSchema(controls, schemaOptions)
56
+ const activeView = ref(schema.defaultView)
57
+ const values = reactive({
58
+ ...schema.initialValues,
59
+ ...(initialValues ?? {}),
60
+ }) as ControlValues<TControls> & Record<string, unknown>
61
+
62
+ function setActiveView(viewId: string) {
63
+ syncActiveView(viewId)
64
+ }
65
+
66
+ function syncActiveView(viewId: string) {
67
+ if (!schema.viewIds.includes(viewId)) return
68
+ if (activeView.value !== viewId) activeView.value = viewId
69
+ if (sidebar.activeView !== viewId) sidebar.activeView = viewId
70
+ if (pillNav.currentItemId !== viewId) pillNav.currentItemId = viewId
71
+ }
72
+
73
+ function setValues(nextValues: Record<string, unknown>) {
74
+ Object.assign(values, nextValues)
75
+ }
76
+
77
+ function resetValues(nextValues: Record<string, unknown> = {}) {
78
+ replaceRecord(values, {
79
+ ...schema.initialValues,
80
+ ...nextValues,
81
+ })
82
+ }
83
+
84
+ function getComponentProps(
85
+ mapping?: ControlComponentPropsMap<ControlValues<TControls> & Record<string, unknown>>,
86
+ ): Record<string, unknown> {
87
+ return controlValuesToComponentProps(values, mapping)
88
+ }
89
+
90
+ function getComponentPropsById(
91
+ mappings?: ControlComponentPropsByIdMap<ControlValues<TControls> & Record<string, unknown>>,
92
+ ): Record<string, Record<string, unknown>> {
93
+ if (mappings === undefined) return {}
94
+
95
+ return Object.fromEntries(
96
+ Object.entries(mappings).map(([id, mapping]) => [
97
+ id,
98
+ controlValuesToComponentProps(values, mapping),
99
+ ]),
100
+ )
101
+ }
102
+
103
+ function getComponentBindings(
104
+ bindings?: ControlComponentBindingsConfig<ControlValues<TControls> & Record<string, unknown>>,
105
+ ): ControlComponentBinding[] {
106
+ return controlValuesToComponentBindings(values, bindings)
107
+ }
108
+
109
+ function getComponentBindingsById(
110
+ bindings?: ControlComponentBindingsConfig<ControlValues<TControls> & Record<string, unknown>>,
111
+ ): ControlComponentBindingsById {
112
+ return controlValuesToComponentBindingsById(values, bindings)
113
+ }
114
+
115
+ const componentProps = computed(() => (
116
+ model?.componentProps === undefined ? {} : getComponentProps(model.componentProps)
117
+ ))
118
+ const componentPropsById = computed(() => getComponentPropsById(model?.componentPropsById))
119
+ const componentBindings = computed(() => getComponentBindings(model?.componentBindings))
120
+ const componentBindingsById = computed(() => getComponentBindingsById(model?.componentBindings))
121
+ const form = {
122
+ ...schema.form,
123
+ modelValue: values,
124
+ 'onUpdate:modelValue': setValues,
125
+ } as ControlWorkspaceFormBinding
126
+
127
+ const topBarSettingsConfig: TopBarSettingsConfig = {
128
+ ...schema.topBarSettingsConfig,
129
+ ...(topBarSettings ?? {}),
130
+ values,
131
+ }
132
+ const sidebar = reactive({
133
+ ...schema.sidebar,
134
+ activeView: activeView.value,
135
+ modelValue: values,
136
+ 'onUpdate:modelValue': setValues,
137
+ values,
138
+ 'onUpdate:values': setValues,
139
+ }) as ControlWorkspaceSidebarBinding
140
+ const pillNav = reactive({
141
+ items: schema.viewItems,
142
+ currentItemId: activeView.value,
143
+ onSelect: (item: PillNavItem) => setActiveView(item.id),
144
+ }) as ControlWorkspacePillNavBinding
145
+ const topBarSettingsBinding = {
146
+ showSettings: true,
147
+ settingsConfig: topBarSettingsConfig,
148
+ onSettingsValuesChange: setValues,
149
+ } as ControlWorkspaceTopBarSettingsBinding
150
+ const topBarProps = computed<ControlWorkspaceTopBarBinding>(() => ({
151
+ pillNav: pillNav.items,
152
+ currentPillId: pillNav.currentItemId,
153
+ onPillSelect: pillNav.onSelect,
154
+ ...topBarSettingsBinding,
155
+ }))
156
+ const bindings: ControlWorkspaceComponentBindings = {
157
+ form,
158
+ sidebar,
159
+ topBar: topBarProps,
160
+ topBarSettings: topBarSettingsBinding,
161
+ pillNav,
162
+ componentBindings,
163
+ componentBindingsById,
164
+ componentProps,
165
+ componentPropsById,
166
+ }
167
+
168
+ watch(activeView, syncActiveView, { flush: 'sync' })
169
+ watch(() => sidebar.activeView, syncActiveView, { flush: 'sync' })
170
+ watch(() => pillNav.currentItemId, syncActiveView, { flush: 'sync' })
171
+
172
+ return {
173
+ ...schema,
174
+ schema,
175
+ values,
176
+ activeView,
177
+ topBarSettingsConfig,
178
+ form,
179
+ sidebar,
180
+ topBar: topBarProps,
181
+ pillNav,
182
+ topBarSettings: topBarSettingsBinding,
183
+ bindings,
184
+ componentBindings,
185
+ componentBindingsById,
186
+ componentProps,
187
+ componentPropsById,
188
+ setActiveView,
189
+ setValues,
190
+ resetValues,
191
+ getComponentProps,
192
+ getComponentPropsById,
193
+ getComponentBindings,
194
+ getComponentBindingsById,
195
+ }
196
+ }
197
+
198
+ function isControlModelBindingInput<TControls extends ControlSchema>(
199
+ value: TControls | (ControlModelBinding & { controls: TControls }),
200
+ ): value is ControlModelBinding & { controls: TControls } {
201
+ return (
202
+ typeof value === 'object'
203
+ && value !== null
204
+ && 'controls' in value
205
+ && 'controlOptions' in value
206
+ )
207
+ }
@@ -1,7 +1,12 @@
1
- import { ref, computed, type Ref, type ComputedRef } from 'vue'
2
- import { useApi } from './useApi'
1
+ import { shallowRef, computed, type Ref, type ComputedRef } from 'vue'
2
+ import { useApi, type UseApiReturn } from './useApi'
3
3
  import { useRequestSyncState } from './useRequestSyncState'
4
- import type { TreeNode, SummaryData } from '../types'
4
+ import {
5
+ extractSampleNamesFromDesignData,
6
+ extractSampleOptionsFromDesignData,
7
+ unwrapExperimentDesignData,
8
+ } from './experimentDesignData'
9
+ import type { TreeNode, SummaryData, SelectOption } from '../types'
5
10
 
6
11
  export interface UseExperimentDataOptions {
7
12
  apiBaseUrl?: string
@@ -9,8 +14,14 @@ export interface UseExperimentDataOptions {
9
14
  }
10
15
 
11
16
  export interface UseExperimentDataReturn {
12
- /** Raw experiment data payload from `/experiments/{id}/data`, or null. */
17
+ /** Raw experiment data response from `/experiments/{id}/data`, or null. */
13
18
  data: Ref<Record<string, unknown> | null>
19
+ /** Plugin-defined design_data payload unwrapped from platform response shapes, or null. */
20
+ designData: ComputedRef<Record<string, unknown> | null>
21
+ /** Sample values extracted from design_data for SampleSelector-style components. */
22
+ sampleNames: ComputedRef<string[]>
23
+ /** Sample options extracted from design_data for select/dropdown components. */
24
+ sampleOptions: ComputedRef<SelectOption<string>[]>
14
25
  /** Hierarchy tree rows normalised from `tree_data` or `treeData`. */
15
26
  treeData: ComputedRef<TreeNode[]>
16
27
  /** Table rows normalised from `table_data` or `tableData`. */
@@ -24,54 +35,74 @@ export interface UseExperimentDataReturn {
24
35
  /** Timestamp of the last successful experiment data fetch, or null. */
25
36
  lastLoadedAt: Ref<Date | null>
26
37
  /** Fetch experiment data for an experiment id. */
27
- fetch: (experimentId: number) => Promise<void>
38
+ fetch: (experimentId: number) => Promise<Record<string, unknown> | null>
28
39
  /** Refetch the most recently fetched experiment id. */
29
40
  refresh: () => Promise<void>
41
+ /** Clear local experiment data and current fetch target. */
42
+ clear: () => void
30
43
  }
31
44
 
32
45
  /** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */
33
46
  export function useExperimentData(
34
47
  options: UseExperimentDataOptions = {},
35
48
  ): UseExperimentDataReturn {
36
- const api = useApi({ baseUrl: options.apiBaseUrl })
37
-
38
- const data = ref<Record<string, unknown> | null>(null)
49
+ const data = shallowRef<Record<string, unknown> | null>(null)
39
50
  const request = useRequestSyncState('Failed to fetch experiment data')
40
51
  const isLoading = request.loading
41
52
  const error = request.error
42
53
  const lastLoadedAt = request.lastLoadedAt
43
54
  let lastExperimentId: number | null = null
55
+ let api: UseApiReturn | null = null
56
+
57
+ function getApi(): UseApiReturn {
58
+ api ??= useApi({ baseUrl: options.apiBaseUrl })
59
+ return api
60
+ }
61
+
62
+ const designData = computed<Record<string, unknown> | null>(() =>
63
+ unwrapExperimentDesignData(data.value),
64
+ )
65
+
66
+ const sampleNames = computed<string[]>(() =>
67
+ extractSampleNamesFromDesignData(designData.value),
68
+ )
69
+
70
+ const sampleOptions = computed<SelectOption<string>[]>(() =>
71
+ extractSampleOptionsFromDesignData(designData.value),
72
+ )
44
73
 
45
74
  const treeData = computed<TreeNode[]>(() => {
46
- if (!data.value) return []
47
- const tree = data.value.tree_data ?? data.value.treeData
75
+ if (!designData.value) return []
76
+ const tree = designData.value.tree_data ?? designData.value.treeData
48
77
  return Array.isArray(tree) ? tree as TreeNode[] : []
49
78
  })
50
79
 
51
80
  const tableData = computed<Record<string, unknown>[]>(() => {
52
- if (!data.value) return []
53
- const table = data.value.table_data ?? data.value.tableData
81
+ if (!designData.value) return []
82
+ const table = designData.value.table_data ?? designData.value.tableData
54
83
  return Array.isArray(table) ? table as Record<string, unknown>[] : []
55
84
  })
56
85
 
57
86
  const summaryData = computed<SummaryData | null>(() => {
58
- if (!data.value) return null
59
- const summary = data.value.summary_data ?? data.value.summaryData
87
+ if (!designData.value) return null
88
+ const summary = designData.value.summary_data ?? designData.value.summaryData
60
89
  if (summary && typeof summary === 'object' && 'metadata' in (summary as Record<string, unknown>)) {
61
90
  return summary as SummaryData
62
91
  }
63
92
  return null
64
93
  })
65
94
 
66
- async function fetchData(experimentId: number): Promise<void> {
95
+ async function fetchData(experimentId: number): Promise<Record<string, unknown> | null> {
67
96
  lastExperimentId = experimentId
68
97
  try {
69
98
  data.value = await request.run(
70
- () => api.get<Record<string, unknown>>(`/experiments/${experimentId}/data`),
99
+ () => getApi().get<Record<string, unknown>>(`/experiments/${experimentId}/data`),
71
100
  { success: 'load', errorMessage: 'Failed to fetch experiment data' },
72
101
  )
102
+ return data.value
73
103
  } catch {
74
104
  data.value = null
105
+ return null
75
106
  }
76
107
  }
77
108
 
@@ -81,8 +112,17 @@ export function useExperimentData(
81
112
  }
82
113
  }
83
114
 
115
+ function clear(): void {
116
+ data.value = null
117
+ lastExperimentId = null
118
+ request.clearError()
119
+ }
120
+
84
121
  return {
85
122
  data,
123
+ designData,
124
+ sampleNames,
125
+ sampleOptions,
86
126
  treeData,
87
127
  tableData,
88
128
  summaryData,
@@ -91,5 +131,6 @@ export function useExperimentData(
91
131
  lastLoadedAt,
92
132
  fetch: fetchData,
93
133
  refresh,
134
+ clear,
94
135
  }
95
136
  }
@@ -0,0 +1,142 @@
1
+ import {
2
+ computed,
3
+ inject,
4
+ toValue,
5
+ watch,
6
+ type ComputedRef,
7
+ type MaybeRefOrGetter,
8
+ type Ref,
9
+ } from 'vue'
10
+ import type { SelectOption } from '../types'
11
+ import { APP_EXPERIMENT_KEY } from './useAppExperiment'
12
+ import {
13
+ extractSampleNamesFromDesignData,
14
+ extractSampleOptionsFromDesignData,
15
+ unwrapExperimentDesignData,
16
+ type ExtractExperimentSamplesOptions,
17
+ } from './experimentDesignData'
18
+ import { parseExperimentId } from './platformContextHelpers'
19
+ import { useExperimentData } from './useExperimentData'
20
+
21
+ export type ExperimentIdSource = MaybeRefOrGetter<number | string | null | undefined>
22
+ export type ExperimentDesignDataSource = MaybeRefOrGetter<Record<string, unknown> | null | undefined>
23
+
24
+ export interface UseExperimentSamplesOptions extends ExtractExperimentSamplesOptions {
25
+ /** Experiment id to load from `/experiments/{id}/data`; defaults to provided AppExperiment context. */
26
+ experimentId?: ExperimentIdSource
27
+ /** Pre-fetched design_data or full platform design-data response. */
28
+ designData?: ExperimentDesignDataSource
29
+ /** API base URL override for platform calls. */
30
+ apiBaseUrl?: string
31
+ /** Auto-load when an experiment id becomes available. Defaults to true. */
32
+ immediate?: boolean
33
+ /** Enable or disable automatic loading while keeping derived samples reactive. Defaults to true. */
34
+ enabled?: MaybeRefOrGetter<boolean>
35
+ /** Use `useAppExperiment` provided state when `experimentId` is not set. Defaults to true. */
36
+ syncAppExperiment?: boolean
37
+ }
38
+
39
+ export interface UseExperimentSamplesReturn {
40
+ /** Experiment id currently used for automatic sample loading. */
41
+ experimentId: ComputedRef<number | undefined>
42
+ /** Raw `/experiments/{id}/data` response when loaded through the platform API. */
43
+ data: Ref<Record<string, unknown> | null>
44
+ /** Plugin-defined design_data payload from props or platform response. */
45
+ designData: ComputedRef<Record<string, unknown> | null>
46
+ /** SampleSelector-compatible string values extracted from design_data. */
47
+ samples: ComputedRef<string[]>
48
+ /** Select/dropdown-friendly sample options extracted from design_data. */
49
+ sampleOptions: ComputedRef<SelectOption<string>[]>
50
+ isLoading: Ref<boolean>
51
+ error: Ref<string | null>
52
+ lastLoadedAt: Ref<Date | null>
53
+ /** Fetch samples for a specific experiment id, defaulting to the resolved id. */
54
+ fetch: (experimentId?: number | string | null) => Promise<Record<string, unknown> | null>
55
+ /** Refetch the current experiment id. */
56
+ refresh: () => Promise<void>
57
+ /** Clear fetched platform data. */
58
+ clear: () => void
59
+ }
60
+
61
+ /** Loads experiment design_data and derives samples for SampleSelector-style UIs. */
62
+ export function useExperimentSamples(
63
+ options: UseExperimentSamplesOptions = {},
64
+ ): UseExperimentSamplesReturn {
65
+ const appExperiment = options.syncAppExperiment === false
66
+ ? undefined
67
+ : inject(APP_EXPERIMENT_KEY, undefined)
68
+ const experimentData = useExperimentData({ apiBaseUrl: options.apiBaseUrl })
69
+
70
+ const experimentId = computed<number | undefined>(() => {
71
+ const explicitId = parseExperimentId(toValue(options.experimentId))
72
+ return explicitId ?? parseExperimentId(appExperiment?.experimentId.value)
73
+ })
74
+
75
+ const explicitDesignData = computed<Record<string, unknown> | null>(() =>
76
+ unwrapExperimentDesignData(toValue(options.designData) ?? null),
77
+ )
78
+
79
+ const designData = computed<Record<string, unknown> | null>(() =>
80
+ explicitDesignData.value ?? experimentData.designData.value,
81
+ )
82
+
83
+ const extractOptions = computed<ExtractExperimentSamplesOptions>(() => ({
84
+ includeControls: options.includeControls,
85
+ }))
86
+
87
+ const samples = computed<string[]>(() =>
88
+ extractSampleNamesFromDesignData(designData.value, extractOptions.value),
89
+ )
90
+
91
+ const sampleOptions = computed<SelectOption<string>[]>(() =>
92
+ extractSampleOptionsFromDesignData(designData.value, extractOptions.value),
93
+ )
94
+
95
+ const enabled = computed(() => toValue(options.enabled) ?? true)
96
+
97
+ async function fetchSamples(
98
+ id: number | string | null | undefined = experimentId.value,
99
+ ): Promise<Record<string, unknown> | null> {
100
+ const parsedId = parseExperimentId(id)
101
+ if (parsedId === undefined) {
102
+ experimentData.clear()
103
+ return null
104
+ }
105
+ return experimentData.fetch(parsedId)
106
+ }
107
+
108
+ watch(
109
+ () => ({
110
+ designData: explicitDesignData.value,
111
+ enabled: enabled.value,
112
+ experimentId: experimentId.value,
113
+ }),
114
+ ({ designData: currentDesignData, enabled: isEnabled, experimentId: currentExperimentId }) => {
115
+ if (options.immediate === false || !isEnabled) return
116
+ if (currentDesignData) {
117
+ experimentData.clear()
118
+ return
119
+ }
120
+ if (currentExperimentId === undefined) {
121
+ experimentData.clear()
122
+ return
123
+ }
124
+ void experimentData.fetch(currentExperimentId)
125
+ },
126
+ { immediate: options.immediate !== false },
127
+ )
128
+
129
+ return {
130
+ experimentId,
131
+ data: experimentData.data,
132
+ designData,
133
+ samples,
134
+ sampleOptions,
135
+ isLoading: experimentData.isLoading,
136
+ error: experimentData.error,
137
+ lastLoadedAt: experimentData.lastLoadedAt,
138
+ fetch: fetchSamples,
139
+ refresh: experimentData.refresh,
140
+ clear: experimentData.clear,
141
+ }
142
+ }
package/src/index.ts CHANGED
@@ -19,6 +19,15 @@ export {
19
19
  // Stores
20
20
  export * from './stores'
21
21
 
22
+ // Permissions
23
+ export * from './permissions'
24
+
25
+ // Instrument monitoring
26
+ export * from './instrument'
27
+
28
+ // LCMS sequence helpers
29
+ export * from './lcms'
30
+
22
31
  // Utils
23
32
  export {
24
33
  hexToHsl,
@@ -26,6 +35,24 @@ export {
26
35
  deriveShade,
27
36
  type Hsl,
28
37
  } from './utils/color'
38
+ export {
39
+ LCMS_DEFAULT_CONTROL_POSITIONS,
40
+ createLcmsControlWellEditData,
41
+ getLcmsDefaultControlWellId,
42
+ lcmsPlateCellsToRack,
43
+ lcmsPlateCellsToRacks,
44
+ lcmsPlateTypeToRackFormat,
45
+ lcmsWellId,
46
+ parseLcmsWellId,
47
+ rackFormatToLcmsPlateType,
48
+ rackToLcmsPlateCells,
49
+ racksToLcmsPlateCells,
50
+ type LcmsControlSampleType,
51
+ type LcmsPlateCell,
52
+ type LcmsPlateCellsToRackOptions,
53
+ type LcmsPlateCellsToRacksOptions,
54
+ type LcmsPlateType,
55
+ } from './utils/rack'
29
56
 
30
57
  // Biology data templates
31
58
  export * from './templates'
@@ -0,0 +1,90 @@
1
+ import type { SequenceProgress } from './types'
2
+
3
+ export function sequenceProgressPercent(progress: SequenceProgress | null | undefined): number {
4
+ if (!progress || progress.total_samples <= 0) return 0
5
+ return Math.round(clamp((progress.current_sample / progress.total_samples) * 100, 0, 100))
6
+ }
7
+
8
+ export function sequenceSamplesRemaining(progress: SequenceProgress | null | undefined): number {
9
+ if (!progress) return 0
10
+ return Math.max(0, progress.total_samples - progress.current_sample)
11
+ }
12
+
13
+ export function estimateSequenceRemainingSeconds(
14
+ progress: SequenceProgress | null | undefined,
15
+ ): number | null {
16
+ if (!progress) return null
17
+ const samplesLeft = sequenceSamplesRemaining(progress)
18
+ if (samplesLeft <= 0) return 0
19
+
20
+ const explicit = progress.estimated_remaining_seconds
21
+ if (typeof explicit === 'number' && explicit > 0) return explicit
22
+
23
+ const avg = progress.avg_sample_seconds ?? average(progress.sample_durations)
24
+ if (typeof avg === 'number' && avg > 0) return samplesLeft * avg
25
+
26
+ const elapsed = progress.elapsed_seconds ?? 0
27
+ if (elapsed > 0 && progress.current_sample > 0) {
28
+ return (elapsed / progress.current_sample) * samplesLeft
29
+ }
30
+
31
+ return null
32
+ }
33
+
34
+ export function estimateSequenceFinishDate(
35
+ progress: SequenceProgress | null | undefined,
36
+ now = new Date(),
37
+ ): Date | null {
38
+ if (!progress) return null
39
+
40
+ const explicit = coerceDate(progress.estimated_finish_time)
41
+ if (explicit) return explicit
42
+
43
+ const remaining = estimateSequenceRemainingSeconds(progress)
44
+ if (remaining == null || remaining <= 0) return null
45
+ return new Date(now.getTime() + remaining * 1000)
46
+ }
47
+
48
+ export function formatSequenceRemaining(progress: SequenceProgress | null | undefined): string | null {
49
+ const remaining = estimateSequenceRemainingSeconds(progress)
50
+ if (remaining == null || remaining <= 0) return null
51
+
52
+ const minutes = remaining / 60
53
+ if (minutes < 1) return `${Math.round(remaining)}s left`
54
+ if (minutes < 60) return `~${Math.round(minutes)}m left`
55
+
56
+ const hours = Math.floor(minutes / 60)
57
+ const mins = Math.round(minutes % 60)
58
+ return mins > 0 ? `~${hours}h ${mins}m left` : `~${hours}h left`
59
+ }
60
+
61
+ export function formatSequenceEta(
62
+ progress: SequenceProgress | null | undefined,
63
+ locale?: Intl.LocalesArgument,
64
+ options: Intl.DateTimeFormatOptions = {},
65
+ ): string | null {
66
+ const finish = estimateSequenceFinishDate(progress)
67
+ if (!finish) return null
68
+ return finish.toLocaleTimeString(locale, {
69
+ hour: '2-digit',
70
+ minute: '2-digit',
71
+ ...options,
72
+ })
73
+ }
74
+
75
+ function average(values: readonly number[] | undefined): number | null {
76
+ if (!values?.length) return null
77
+ const usable = values.filter(value => Number.isFinite(value) && value >= 0)
78
+ if (usable.length === 0) return null
79
+ return usable.reduce((sum, value) => sum + value, 0) / usable.length
80
+ }
81
+
82
+ function coerceDate(value: string | Date | null | undefined): Date | null {
83
+ if (!value) return null
84
+ const date = value instanceof Date ? value : new Date(value)
85
+ return Number.isNaN(date.getTime()) ? null : date
86
+ }
87
+
88
+ function clamp(value: number, min: number, max: number): number {
89
+ return Math.min(max, Math.max(min, value))
90
+ }