@morscherlab/mint-sdk 1.0.0-alpha.2

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 (491) hide show
  1. package/README.md +326 -0
  2. package/dist/__stories__/experiment-helpers.d.ts +25 -0
  3. package/dist/__tests__/components/AppLayout.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppSidebar.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppTopBar.test.d.ts +1 -0
  6. package/dist/__tests__/components/BaseInput.test.d.ts +1 -0
  7. package/dist/__tests__/components/BasePill.test.d.ts +1 -0
  8. package/dist/__tests__/components/Calendar.test.d.ts +1 -0
  9. package/dist/__tests__/components/CollapsibleCard.test.d.ts +1 -0
  10. package/dist/__tests__/components/DataFrame.test.d.ts +1 -0
  11. package/dist/__tests__/components/DropdownButton.test.d.ts +1 -0
  12. package/dist/__tests__/composables/formBuilderRegistry.test.d.ts +1 -0
  13. package/dist/__tests__/composables/useAppExperiment.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useAuth.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useAutoGroup.test.d.ts +1 -0
  16. package/dist/__tests__/composables/useExperimentData.test.d.ts +13 -0
  17. package/dist/__tests__/composables/useExperimentSave.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useForm.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFormBuilder.test.d.ts +1 -0
  20. package/dist/__tests__/composables/usePlatformContext.test.d.ts +1 -0
  21. package/dist/__tests__/composables/usePluginApi.test.d.ts +13 -0
  22. package/dist/__tests__/composables/usePluginConfig.test.d.ts +14 -0
  23. package/dist/__tests__/utils/color.test.d.ts +1 -0
  24. package/dist/auth-BYmxZdJl.js +297 -0
  25. package/dist/auth-BYmxZdJl.js.map +1 -0
  26. package/dist/components/AlertBox.vue.d.ts +34 -0
  27. package/dist/components/AppAvatarMenu.vue.d.ts +58 -0
  28. package/dist/components/AppContainer.vue.d.ts +28 -0
  29. package/dist/components/AppLayout.vue.d.ts +31 -0
  30. package/dist/components/AppPageSelector.vue.d.ts +43 -0
  31. package/dist/components/AppPillNav.vue.d.ts +11 -0
  32. package/dist/components/AppPluginSwitcher.vue.d.ts +38 -0
  33. package/dist/components/AppSidebar.vue.d.ts +47 -0
  34. package/dist/components/AppTopBar.vue.d.ts +111 -0
  35. package/dist/components/AuditTrail.vue.d.ts +38 -0
  36. package/dist/components/AutoGroupModal.vue.d.ts +124 -0
  37. package/dist/components/Avatar.vue.d.ts +14 -0
  38. package/dist/components/BaseButton.vue.d.ts +37 -0
  39. package/dist/components/BaseCheckbox.vue.d.ts +17 -0
  40. package/dist/components/BaseInput.vue.d.ts +34 -0
  41. package/dist/components/BaseModal.vue.d.ts +46 -0
  42. package/dist/components/BasePill.vue.d.ts +57 -0
  43. package/dist/components/BaseRadioGroup.vue.d.ts +21 -0
  44. package/dist/components/BaseSelect.vue.d.ts +20 -0
  45. package/dist/components/BaseSlider.vue.d.ts +22 -0
  46. package/dist/components/BaseTabs.vue.d.ts +14 -0
  47. package/dist/components/BaseTextarea.vue.d.ts +30 -0
  48. package/dist/components/BaseToggle.vue.d.ts +19 -0
  49. package/dist/components/BatchProgressList.vue.d.ts +43 -0
  50. package/dist/components/Breadcrumb.vue.d.ts +33 -0
  51. package/dist/components/Calendar.vue.d.ts +107 -0
  52. package/dist/components/ChartContainer.vue.d.ts +31 -0
  53. package/dist/components/ChemicalFormula.vue.d.ts +8 -0
  54. package/dist/components/CollapsibleCard.vue.d.ts +41 -0
  55. package/dist/components/ColorSlider.vue.d.ts +34 -0
  56. package/dist/components/ConcentrationInput.vue.d.ts +25 -0
  57. package/dist/components/ConfirmDialog.vue.d.ts +42 -0
  58. package/dist/components/DataFrame.vue.d.ts +107 -0
  59. package/dist/components/DatePicker.vue.d.ts +25 -0
  60. package/dist/components/DateTimePicker.vue.d.ts +30 -0
  61. package/dist/components/Divider.vue.d.ts +14 -0
  62. package/dist/components/DoseCalculator.vue.d.ts +19 -0
  63. package/dist/components/DropdownButton.vue.d.ts +47 -0
  64. package/dist/components/EmptyState.vue.d.ts +36 -0
  65. package/dist/components/ExperimentCodeBadge.vue.d.ts +14 -0
  66. package/dist/components/ExperimentDataViewer.vue.d.ts +29 -0
  67. package/dist/components/ExperimentPopover.vue.d.ts +32 -0
  68. package/dist/components/ExperimentSelectorModal.vue.d.ts +28 -0
  69. package/dist/components/ExperimentTimeline.vue.d.ts +44 -0
  70. package/dist/components/FileUploader.vue.d.ts +40 -0
  71. package/dist/components/FitPanel.vue.d.ts +46 -0
  72. package/dist/components/FormActions.vue.d.ts +33 -0
  73. package/dist/components/FormBuilder.vue.d.ts +287 -0
  74. package/dist/components/FormField.vue.d.ts +28 -0
  75. package/dist/components/FormFieldRenderer.vue.d.ts +31 -0
  76. package/dist/components/FormSection.vue.d.ts +43 -0
  77. package/dist/components/FormulaInput.vue.d.ts +25 -0
  78. package/dist/components/GroupAssigner.vue.d.ts +25 -0
  79. package/dist/components/GroupingModal.vue.d.ts +12 -0
  80. package/dist/components/IconButton.vue.d.ts +34 -0
  81. package/dist/components/LoadingSpinner.vue.d.ts +12 -0
  82. package/dist/components/MoleculeInput.vue.d.ts +27 -0
  83. package/dist/components/MultiSelect.vue.d.ts +19 -0
  84. package/dist/components/NumberInput.vue.d.ts +22 -0
  85. package/dist/components/PlateMapEditor.vue.d.ts +50 -0
  86. package/dist/components/ProgressBar.vue.d.ts +23 -0
  87. package/dist/components/ProtocolStepEditor.vue.d.ts +24 -0
  88. package/dist/components/RackEditor.vue.d.ts +40 -0
  89. package/dist/components/ReagentEditor.vue.d.ts +30 -0
  90. package/dist/components/ReagentList.vue.d.ts +32 -0
  91. package/dist/components/ResourceCard.vue.d.ts +50 -0
  92. package/dist/components/SampleHierarchyTree.vue.d.ts +26 -0
  93. package/dist/components/SampleLegend.vue.d.ts +32 -0
  94. package/dist/components/SampleSelector.vue.d.ts +29 -0
  95. package/dist/components/ScheduleCalendar.vue.d.ts +110 -0
  96. package/dist/components/ScientificNumber.vue.d.ts +14 -0
  97. package/dist/components/SegmentedControl.vue.d.ts +20 -0
  98. package/dist/components/SequenceInput.vue.d.ts +54 -0
  99. package/dist/components/SettingsButton.vue.d.ts +30 -0
  100. package/dist/components/SettingsModal.vue.d.ts +36 -0
  101. package/dist/components/Skeleton.vue.d.ts +11 -0
  102. package/dist/components/StatusIndicator.vue.d.ts +13 -0
  103. package/dist/components/StepWizard.vue.d.ts +65 -0
  104. package/dist/components/TagsInput.vue.d.ts +39 -0
  105. package/dist/components/ThemeToggle.vue.d.ts +7 -0
  106. package/dist/components/TimePicker.vue.d.ts +29 -0
  107. package/dist/components/TimeRangeInput.vue.d.ts +27 -0
  108. package/dist/components/ToastNotification.vue.d.ts +2 -0
  109. package/dist/components/Tooltip.vue.d.ts +35 -0
  110. package/dist/components/UnitInput.vue.d.ts +39 -0
  111. package/dist/components/WellEditPopup.vue.d.ts +25 -0
  112. package/dist/components/WellPlate.vue.d.ts +73 -0
  113. package/dist/components/index.d.ts +87 -0
  114. package/dist/components/index.js +3 -0
  115. package/dist/components-CKf-UpGi.js +15089 -0
  116. package/dist/components-CKf-UpGi.js.map +1 -0
  117. package/dist/composables/experiment-utils.d.ts +8 -0
  118. package/dist/composables/formBuilderRegistry.d.ts +13 -0
  119. package/dist/composables/index.d.ts +28 -0
  120. package/dist/composables/index.js +3 -0
  121. package/dist/composables/useApi.d.ts +20 -0
  122. package/dist/composables/useAppExperiment.d.ts +37 -0
  123. package/dist/composables/useAsync.d.ts +128 -0
  124. package/dist/composables/useAuth.d.ts +47 -0
  125. package/dist/composables/useAutoGroup.d.ts +106 -0
  126. package/dist/composables/useChemicalFormula.d.ts +21 -0
  127. package/dist/composables/useConcentrationUnits.d.ts +29 -0
  128. package/dist/composables/useDoseCalculator.d.ts +58 -0
  129. package/dist/composables/useExperimentData.d.ts +18 -0
  130. package/dist/composables/useExperimentSave.d.ts +36 -0
  131. package/dist/composables/useExperimentSelector.d.ts +30 -0
  132. package/dist/composables/useForm.d.ts +92 -0
  133. package/dist/composables/useFormBuilder.d.ts +24 -0
  134. package/dist/composables/usePasskey.d.ts +10 -0
  135. package/dist/composables/usePlatformContext.d.ts +131 -0
  136. package/dist/composables/usePluginApi.d.ts +29 -0
  137. package/dist/composables/usePluginConfig.d.ts +13 -0
  138. package/dist/composables/useProtocolTemplates.d.ts +44 -0
  139. package/dist/composables/useRackEditor.d.ts +31 -0
  140. package/dist/composables/useReagentSeries.d.ts +23 -0
  141. package/dist/composables/useScheduleDrag.d.ts +78 -0
  142. package/dist/composables/useSequenceUtils.d.ts +14 -0
  143. package/dist/composables/useTheme.d.ts +8 -0
  144. package/dist/composables/useTimeUtils.d.ts +29 -0
  145. package/dist/composables/useToast.d.ts +22 -0
  146. package/dist/composables/useWellPlateEditor.d.ts +33 -0
  147. package/dist/composables-D0QfFzq1.js +805 -0
  148. package/dist/composables-D0QfFzq1.js.map +1 -0
  149. package/dist/histoire.setup.d.ts +1 -0
  150. package/dist/index.d.ts +6 -0
  151. package/dist/index.js +7 -0
  152. package/dist/install.d.ts +16 -0
  153. package/dist/install.js +23 -0
  154. package/dist/install.js.map +1 -0
  155. package/dist/stores/auth.d.ts +146 -0
  156. package/dist/stores/index.d.ts +2 -0
  157. package/dist/stores/index.js +2 -0
  158. package/dist/stores/settings.d.ts +75 -0
  159. package/dist/styles.css +29728 -0
  160. package/dist/tailwind.preset.d.ts +58 -0
  161. package/dist/tailwind.preset.js +66 -0
  162. package/dist/tailwind.preset.js.map +1 -0
  163. package/dist/types/auth.d.ts +42 -0
  164. package/dist/types/auto-group.d.ts +34 -0
  165. package/dist/types/components.d.ts +528 -0
  166. package/dist/types/form-builder.d.ts +167 -0
  167. package/dist/types/index.d.ts +5 -0
  168. package/dist/types/index.js +0 -0
  169. package/dist/types/platform.d.ts +75 -0
  170. package/dist/useScheduleDrag-DAJueTbK.js +7181 -0
  171. package/dist/useScheduleDrag-DAJueTbK.js.map +1 -0
  172. package/dist/utils/color.d.ts +24 -0
  173. package/package.json +114 -0
  174. package/src/__stories__/experiment-helpers.ts +83 -0
  175. package/src/__tests__/components/AppLayout.test.ts +163 -0
  176. package/src/__tests__/components/AppSidebar.test.ts +292 -0
  177. package/src/__tests__/components/AppTopBar.test.ts +683 -0
  178. package/src/__tests__/components/BaseInput.test.ts +99 -0
  179. package/src/__tests__/components/BasePill.test.ts +291 -0
  180. package/src/__tests__/components/Calendar.test.ts +566 -0
  181. package/src/__tests__/components/CollapsibleCard.test.ts +524 -0
  182. package/src/__tests__/components/DataFrame.test.ts +767 -0
  183. package/src/__tests__/components/DropdownButton.test.ts +471 -0
  184. package/src/__tests__/composables/formBuilderRegistry.test.ts +187 -0
  185. package/src/__tests__/composables/useAppExperiment.test.ts +560 -0
  186. package/src/__tests__/composables/useAuth.test.ts +188 -0
  187. package/src/__tests__/composables/useAutoGroup.test.ts +860 -0
  188. package/src/__tests__/composables/useExperimentData.test.ts +127 -0
  189. package/src/__tests__/composables/useExperimentSave.test.ts +347 -0
  190. package/src/__tests__/composables/useForm.test.ts +205 -0
  191. package/src/__tests__/composables/useFormBuilder.test.ts +917 -0
  192. package/src/__tests__/composables/usePlatformContext.test.ts +116 -0
  193. package/src/__tests__/composables/usePluginApi.test.ts +81 -0
  194. package/src/__tests__/composables/usePluginConfig.test.ts +176 -0
  195. package/src/__tests__/utils/color.test.ts +96 -0
  196. package/src/components/AlertBox.story.vue +204 -0
  197. package/src/components/AlertBox.vue +88 -0
  198. package/src/components/AppAvatarMenu.story.vue +155 -0
  199. package/src/components/AppAvatarMenu.vue +184 -0
  200. package/src/components/AppContainer.story.vue +104 -0
  201. package/src/components/AppContainer.vue +34 -0
  202. package/src/components/AppLayout.story.vue +292 -0
  203. package/src/components/AppLayout.vue +75 -0
  204. package/src/components/AppPageSelector.vue +159 -0
  205. package/src/components/AppPillNav.vue +66 -0
  206. package/src/components/AppPluginSwitcher.vue +241 -0
  207. package/src/components/AppSidebar.story.vue +309 -0
  208. package/src/components/AppSidebar.vue +119 -0
  209. package/src/components/AppTopBar.story.vue +304 -0
  210. package/src/components/AppTopBar.vue +661 -0
  211. package/src/components/AuditTrail.story.vue +163 -0
  212. package/src/components/AuditTrail.vue +151 -0
  213. package/src/components/AutoGroupModal.story.vue +273 -0
  214. package/src/components/AutoGroupModal.vue +566 -0
  215. package/src/components/Avatar.story.vue +115 -0
  216. package/src/components/Avatar.vue +79 -0
  217. package/src/components/BaseButton.story.vue +96 -0
  218. package/src/components/BaseButton.vue +73 -0
  219. package/src/components/BaseCheckbox.story.vue +73 -0
  220. package/src/components/BaseCheckbox.vue +69 -0
  221. package/src/components/BaseInput.story.vue +98 -0
  222. package/src/components/BaseInput.vue +74 -0
  223. package/src/components/BaseModal.story.vue +237 -0
  224. package/src/components/BaseModal.vue +182 -0
  225. package/src/components/BasePill.story.vue +142 -0
  226. package/src/components/BasePill.vue +89 -0
  227. package/src/components/BaseRadioGroup.story.vue +145 -0
  228. package/src/components/BaseRadioGroup.vue +124 -0
  229. package/src/components/BaseSelect.story.vue +120 -0
  230. package/src/components/BaseSelect.vue +71 -0
  231. package/src/components/BaseSlider.story.vue +122 -0
  232. package/src/components/BaseSlider.vue +126 -0
  233. package/src/components/BaseTabs.story.vue +127 -0
  234. package/src/components/BaseTabs.vue +59 -0
  235. package/src/components/BaseTextarea.story.vue +91 -0
  236. package/src/components/BaseTextarea.vue +62 -0
  237. package/src/components/BaseToggle.story.vue +81 -0
  238. package/src/components/BaseToggle.vue +76 -0
  239. package/src/components/BatchProgressList.story.vue +92 -0
  240. package/src/components/BatchProgressList.vue +184 -0
  241. package/src/components/Breadcrumb.story.vue +106 -0
  242. package/src/components/Breadcrumb.vue +75 -0
  243. package/src/components/Calendar.story.vue +106 -0
  244. package/src/components/Calendar.vue +363 -0
  245. package/src/components/ChartContainer.story.vue +113 -0
  246. package/src/components/ChartContainer.vue +64 -0
  247. package/src/components/ChemicalFormula.story.vue +102 -0
  248. package/src/components/ChemicalFormula.vue +39 -0
  249. package/src/components/CollapsibleCard.story.vue +135 -0
  250. package/src/components/CollapsibleCard.vue +167 -0
  251. package/src/components/ColorSlider.story.vue +120 -0
  252. package/src/components/ColorSlider.vue +164 -0
  253. package/src/components/ConcentrationInput.story.vue +77 -0
  254. package/src/components/ConcentrationInput.vue +185 -0
  255. package/src/components/ConfirmDialog.story.vue +248 -0
  256. package/src/components/ConfirmDialog.vue +93 -0
  257. package/src/components/DataFrame.story.vue +148 -0
  258. package/src/components/DataFrame.vue +419 -0
  259. package/src/components/DatePicker.story.vue +119 -0
  260. package/src/components/DatePicker.vue +330 -0
  261. package/src/components/DateTimePicker.story.vue +112 -0
  262. package/src/components/DateTimePicker.vue +392 -0
  263. package/src/components/Divider.story.vue +80 -0
  264. package/src/components/Divider.vue +49 -0
  265. package/src/components/DoseCalculator.story.vue +68 -0
  266. package/src/components/DoseCalculator.vue +476 -0
  267. package/src/components/DropdownButton.story.vue +102 -0
  268. package/src/components/DropdownButton.vue +181 -0
  269. package/src/components/EmptyState.story.vue +135 -0
  270. package/src/components/EmptyState.vue +69 -0
  271. package/src/components/ExperimentCodeBadge.story.vue +77 -0
  272. package/src/components/ExperimentCodeBadge.vue +64 -0
  273. package/src/components/ExperimentDataViewer.story.vue +174 -0
  274. package/src/components/ExperimentDataViewer.vue +288 -0
  275. package/src/components/ExperimentPopover.story.vue +384 -0
  276. package/src/components/ExperimentPopover.vue +241 -0
  277. package/src/components/ExperimentSelectorModal.story.vue +391 -0
  278. package/src/components/ExperimentSelectorModal.vue +387 -0
  279. package/src/components/ExperimentTimeline.story.vue +161 -0
  280. package/src/components/ExperimentTimeline.vue +382 -0
  281. package/src/components/FileUploader.story.vue +107 -0
  282. package/src/components/FileUploader.vue +386 -0
  283. package/src/components/FitPanel.story.vue +125 -0
  284. package/src/components/FitPanel.vue +120 -0
  285. package/src/components/FormActions.vue +92 -0
  286. package/src/components/FormBuilder.vue +214 -0
  287. package/src/components/FormField.story.vue +132 -0
  288. package/src/components/FormField.vue +59 -0
  289. package/src/components/FormFieldRenderer.vue +58 -0
  290. package/src/components/FormSection.vue +90 -0
  291. package/src/components/FormulaInput.story.vue +96 -0
  292. package/src/components/FormulaInput.vue +125 -0
  293. package/src/components/GroupAssigner.story.vue +83 -0
  294. package/src/components/GroupAssigner.vue +284 -0
  295. package/src/components/GroupingModal.story.vue +52 -0
  296. package/src/components/GroupingModal.vue +422 -0
  297. package/src/components/IconButton.story.vue +135 -0
  298. package/src/components/IconButton.vue +73 -0
  299. package/src/components/LoadingSpinner.story.vue +70 -0
  300. package/src/components/LoadingSpinner.vue +50 -0
  301. package/src/components/MoleculeInput.story.vue +66 -0
  302. package/src/components/MoleculeInput.vue +426 -0
  303. package/src/components/MultiSelect.story.vue +132 -0
  304. package/src/components/MultiSelect.vue +118 -0
  305. package/src/components/NumberInput.story.vue +122 -0
  306. package/src/components/NumberInput.vue +160 -0
  307. package/src/components/PlateMapEditor.story.vue +92 -0
  308. package/src/components/PlateMapEditor.vue +513 -0
  309. package/src/components/ProgressBar.story.vue +148 -0
  310. package/src/components/ProgressBar.vue +114 -0
  311. package/src/components/ProtocolStepEditor.story.vue +69 -0
  312. package/src/components/ProtocolStepEditor.vue +522 -0
  313. package/src/components/RackEditor.story.vue +100 -0
  314. package/src/components/RackEditor.vue +371 -0
  315. package/src/components/ReagentEditor.story.vue +153 -0
  316. package/src/components/ReagentEditor.vue +418 -0
  317. package/src/components/ReagentList.story.vue +137 -0
  318. package/src/components/ReagentList.vue +463 -0
  319. package/src/components/ResourceCard.story.vue +150 -0
  320. package/src/components/ResourceCard.vue +161 -0
  321. package/src/components/SampleHierarchyTree.story.vue +161 -0
  322. package/src/components/SampleHierarchyTree.vue +256 -0
  323. package/src/components/SampleLegend.story.vue +91 -0
  324. package/src/components/SampleLegend.vue +119 -0
  325. package/src/components/SampleSelector.story.vue +111 -0
  326. package/src/components/SampleSelector.vue +1033 -0
  327. package/src/components/ScheduleCalendar.story.vue +195 -0
  328. package/src/components/ScheduleCalendar.vue +569 -0
  329. package/src/components/ScientificNumber.story.vue +127 -0
  330. package/src/components/ScientificNumber.vue +197 -0
  331. package/src/components/SegmentedControl.story.vue +132 -0
  332. package/src/components/SegmentedControl.vue +79 -0
  333. package/src/components/SequenceInput.story.vue +119 -0
  334. package/src/components/SequenceInput.vue +209 -0
  335. package/src/components/SettingsButton.story.vue +58 -0
  336. package/src/components/SettingsButton.vue +76 -0
  337. package/src/components/SettingsModal.story.vue +145 -0
  338. package/src/components/SettingsModal.vue +146 -0
  339. package/src/components/Skeleton.story.vue +141 -0
  340. package/src/components/Skeleton.vue +74 -0
  341. package/src/components/StatusIndicator.story.vue +99 -0
  342. package/src/components/StatusIndicator.vue +40 -0
  343. package/src/components/StepWizard.story.vue +155 -0
  344. package/src/components/StepWizard.vue +223 -0
  345. package/src/components/TagsInput.story.vue +155 -0
  346. package/src/components/TagsInput.vue +265 -0
  347. package/src/components/ThemeToggle.story.vue +36 -0
  348. package/src/components/ThemeToggle.vue +54 -0
  349. package/src/components/TimePicker.story.vue +96 -0
  350. package/src/components/TimePicker.vue +273 -0
  351. package/src/components/TimeRangeInput.story.vue +104 -0
  352. package/src/components/TimeRangeInput.vue +122 -0
  353. package/src/components/ToastNotification.story.vue +157 -0
  354. package/src/components/ToastNotification.vue +62 -0
  355. package/src/components/Tooltip.story.vue +138 -0
  356. package/src/components/Tooltip.vue +119 -0
  357. package/src/components/UnitInput.story.vue +194 -0
  358. package/src/components/UnitInput.vue +213 -0
  359. package/src/components/WellEditPopup.vue +234 -0
  360. package/src/components/WellPlate.story.vue +282 -0
  361. package/src/components/WellPlate.vue +830 -0
  362. package/src/components/index.ts +118 -0
  363. package/src/composables/experiment-utils.ts +57 -0
  364. package/src/composables/formBuilderRegistry.ts +79 -0
  365. package/src/composables/index.ts +140 -0
  366. package/src/composables/useApi.ts +167 -0
  367. package/src/composables/useAppExperiment.ts +159 -0
  368. package/src/composables/useAsync.ts +323 -0
  369. package/src/composables/useAuth.ts +445 -0
  370. package/src/composables/useAutoGroup.ts +641 -0
  371. package/src/composables/useChemicalFormula.ts +275 -0
  372. package/src/composables/useConcentrationUnits.ts +246 -0
  373. package/src/composables/useDoseCalculator.ts +370 -0
  374. package/src/composables/useExperimentData.ts +86 -0
  375. package/src/composables/useExperimentSave.ts +192 -0
  376. package/src/composables/useExperimentSelector.ts +292 -0
  377. package/src/composables/useForm.ts +416 -0
  378. package/src/composables/useFormBuilder.ts +383 -0
  379. package/src/composables/usePasskey.ts +216 -0
  380. package/src/composables/usePlatformContext.ts +299 -0
  381. package/src/composables/usePluginApi.ts +39 -0
  382. package/src/composables/usePluginConfig.ts +93 -0
  383. package/src/composables/useProtocolTemplates.ts +518 -0
  384. package/src/composables/useRackEditor.ts +222 -0
  385. package/src/composables/useReagentSeries.ts +91 -0
  386. package/src/composables/useScheduleDrag.ts +245 -0
  387. package/src/composables/useSequenceUtils.ts +105 -0
  388. package/src/composables/useTheme.ts +58 -0
  389. package/src/composables/useTimeUtils.ts +131 -0
  390. package/src/composables/useToast.ts +40 -0
  391. package/src/composables/useWellPlateEditor.ts +421 -0
  392. package/src/histoire.setup.ts +17 -0
  393. package/src/index.ts +367 -0
  394. package/src/install.ts +32 -0
  395. package/src/stores/auth.ts +152 -0
  396. package/src/stores/index.ts +2 -0
  397. package/src/stores/settings.ts +218 -0
  398. package/src/styles/components/alert-box.css +150 -0
  399. package/src/styles/components/app-avatar-menu.css +155 -0
  400. package/src/styles/components/app-container.css +33 -0
  401. package/src/styles/components/app-layout.css +98 -0
  402. package/src/styles/components/app-page-selector.css +191 -0
  403. package/src/styles/components/app-pill-nav.css +57 -0
  404. package/src/styles/components/app-plugin-switcher.css +209 -0
  405. package/src/styles/components/app-sidebar.css +145 -0
  406. package/src/styles/components/app-top-bar.css +492 -0
  407. package/src/styles/components/audit-trail.css +143 -0
  408. package/src/styles/components/auto-group-modal.css +644 -0
  409. package/src/styles/components/avatar.css +73 -0
  410. package/src/styles/components/batch-progress-list.css +196 -0
  411. package/src/styles/components/breadcrumb.css +64 -0
  412. package/src/styles/components/button.css +188 -0
  413. package/src/styles/components/calendar.css +192 -0
  414. package/src/styles/components/chart-container.css +69 -0
  415. package/src/styles/components/checkbox.css +123 -0
  416. package/src/styles/components/chemical-formula.css +46 -0
  417. package/src/styles/components/collapsible-card.css +253 -0
  418. package/src/styles/components/color-slider.css +110 -0
  419. package/src/styles/components/concentration-input.css +156 -0
  420. package/src/styles/components/confirm-dialog.css +183 -0
  421. package/src/styles/components/dataframe.css +382 -0
  422. package/src/styles/components/date-picker.css +243 -0
  423. package/src/styles/components/datetime-picker.css +229 -0
  424. package/src/styles/components/divider.css +63 -0
  425. package/src/styles/components/dose-calculator.css +301 -0
  426. package/src/styles/components/dropdown-button.css +280 -0
  427. package/src/styles/components/empty-state.css +151 -0
  428. package/src/styles/components/experiment-code-badge.css +33 -0
  429. package/src/styles/components/experiment-data-viewer.css +138 -0
  430. package/src/styles/components/experiment-popover.css +562 -0
  431. package/src/styles/components/experiment-selector-modal.css +285 -0
  432. package/src/styles/components/experiment-timeline.css +529 -0
  433. package/src/styles/components/file-uploader.css +310 -0
  434. package/src/styles/components/fit-panel.css +67 -0
  435. package/src/styles/components/form-builder.css +69 -0
  436. package/src/styles/components/form-field.css +48 -0
  437. package/src/styles/components/formula-input.css +103 -0
  438. package/src/styles/components/group-assigner.css +200 -0
  439. package/src/styles/components/grouping-modal.css +323 -0
  440. package/src/styles/components/icon-button.css +192 -0
  441. package/src/styles/components/input.css +66 -0
  442. package/src/styles/components/loading-spinner.css +67 -0
  443. package/src/styles/components/modal.css +350 -0
  444. package/src/styles/components/molecule-input.css +186 -0
  445. package/src/styles/components/multi-select.css +131 -0
  446. package/src/styles/components/number-input.css +199 -0
  447. package/src/styles/components/pill.css +188 -0
  448. package/src/styles/components/plate-map-editor.css +464 -0
  449. package/src/styles/components/progress-bar.css +133 -0
  450. package/src/styles/components/protocol-step-editor.css +449 -0
  451. package/src/styles/components/rack-editor.css +265 -0
  452. package/src/styles/components/radio-group.css +240 -0
  453. package/src/styles/components/reagent-editor.css +510 -0
  454. package/src/styles/components/reagent-list.css +407 -0
  455. package/src/styles/components/resource-card.css +360 -0
  456. package/src/styles/components/sample-hierarchy-tree.css +314 -0
  457. package/src/styles/components/sample-legend.css +201 -0
  458. package/src/styles/components/sample-selector.css +751 -0
  459. package/src/styles/components/schedule-calendar.css +478 -0
  460. package/src/styles/components/scientific-number.css +63 -0
  461. package/src/styles/components/segmented-control.css +197 -0
  462. package/src/styles/components/select.css +77 -0
  463. package/src/styles/components/sequence-input.css +184 -0
  464. package/src/styles/components/settings-button.css +94 -0
  465. package/src/styles/components/settings-modal.css +95 -0
  466. package/src/styles/components/skeleton.css +49 -0
  467. package/src/styles/components/slider.css +74 -0
  468. package/src/styles/components/status-indicator.css +66 -0
  469. package/src/styles/components/step-wizard.css +192 -0
  470. package/src/styles/components/tabs.css +95 -0
  471. package/src/styles/components/tags-input.css +195 -0
  472. package/src/styles/components/textarea.css +82 -0
  473. package/src/styles/components/theme-toggle.css +69 -0
  474. package/src/styles/components/time-picker.css +171 -0
  475. package/src/styles/components/time-range-input.css +42 -0
  476. package/src/styles/components/toast.css +91 -0
  477. package/src/styles/components/toggle.css +146 -0
  478. package/src/styles/components/tooltip.css +91 -0
  479. package/src/styles/components/unit-input.css +123 -0
  480. package/src/styles/components/well-edit-popup.css +252 -0
  481. package/src/styles/components/well-plate.css +307 -0
  482. package/src/styles/index.css +87 -0
  483. package/src/styles/variables.css +1117 -0
  484. package/src/tailwind.preset.ts +61 -0
  485. package/src/types/auth.ts +55 -0
  486. package/src/types/auto-group.ts +40 -0
  487. package/src/types/components.ts +710 -0
  488. package/src/types/form-builder.ts +197 -0
  489. package/src/types/index.ts +207 -0
  490. package/src/types/platform.ts +116 -0
  491. package/src/utils/color.ts +96 -0
@@ -0,0 +1,370 @@
1
+ import {
2
+ useConcentrationUnits,
3
+ type ConcentrationValue,
4
+ type MolarityUnit,
5
+ type MassVolumeUnit,
6
+ } from './useConcentrationUnits'
7
+
8
+ // Volume units
9
+ export type VolumeUnit = 'µL' | 'mL' | 'L'
10
+
11
+ export interface VolumeValue {
12
+ value: number
13
+ unit: VolumeUnit
14
+ }
15
+
16
+ // Dilution calculation types
17
+ export interface DilutionParams {
18
+ stockConcentration: ConcentrationValue
19
+ finalConcentration: ConcentrationValue
20
+ finalVolume: VolumeValue
21
+ }
22
+
23
+ export interface DilutionResult {
24
+ stockVolume: VolumeValue
25
+ diluentVolume: VolumeValue
26
+ dilutionFactor: number
27
+ valid: boolean
28
+ error?: string
29
+ }
30
+
31
+ // Serial dilution types
32
+ export interface SerialDilutionParams {
33
+ startingConcentration: ConcentrationValue
34
+ dilutionFactor: number
35
+ numberOfDilutions: number
36
+ volumePerWell: VolumeValue
37
+ }
38
+
39
+ export interface SerialDilutionStep {
40
+ stepNumber: number
41
+ concentration: ConcentrationValue
42
+ transferVolume: VolumeValue
43
+ diluentVolume: VolumeValue
44
+ }
45
+
46
+ export interface SerialDilutionResult {
47
+ steps: SerialDilutionStep[]
48
+ totalStockVolume: VolumeValue
49
+ valid: boolean
50
+ error?: string
51
+ }
52
+
53
+ // Conversion types
54
+ export interface ConversionResult {
55
+ result: ConcentrationValue
56
+ valid: boolean
57
+ error?: string
58
+ }
59
+
60
+ // Well concentration for plate integration
61
+ export interface WellConcentration {
62
+ wellId: string
63
+ concentration: ConcentrationValue
64
+ volume?: VolumeValue
65
+ }
66
+
67
+ export interface UseDoseCalculatorReturn {
68
+ volumeUnits: VolumeUnit[]
69
+ calculateDilution: (params: DilutionParams) => DilutionResult
70
+ calculateSerialDilution: (params: SerialDilutionParams) => SerialDilutionResult
71
+ convertMassToMolar: (
72
+ mass: number,
73
+ massUnit: MassVolumeUnit,
74
+ mw: number
75
+ ) => ConcentrationValue
76
+ convertMolarToMass: (
77
+ molar: number,
78
+ molarUnit: MolarityUnit,
79
+ mw: number
80
+ ) => ConcentrationValue
81
+ convertVolume: (value: number, from: VolumeUnit, to: VolumeUnit) => number
82
+ formatVolume: (volume: VolumeValue, precision?: number) => string
83
+ generateWellConcentrations: (
84
+ result: SerialDilutionResult,
85
+ wellIds: string[]
86
+ ) => WellConcentration[]
87
+ }
88
+
89
+ const VOLUME_UNITS: VolumeUnit[] = ['µL', 'mL', 'L']
90
+
91
+ const VOLUME_FACTORS: Record<VolumeUnit, number> = {
92
+ 'µL': 1e-6,
93
+ 'mL': 1e-3,
94
+ 'L': 1,
95
+ }
96
+
97
+ /** Calculates single dilutions, serial dilution series, and plate-filling volumes for dose-response experiments. */
98
+ export function useDoseCalculator(): UseDoseCalculatorReturn {
99
+ const { convert } = useConcentrationUnits()
100
+
101
+ function convertVolume(value: number, from: VolumeUnit, to: VolumeUnit): number {
102
+ if (from === to) return value
103
+ const baseValue = value * VOLUME_FACTORS[from]
104
+ return baseValue / VOLUME_FACTORS[to]
105
+ }
106
+
107
+ function formatVolume(volume: VolumeValue, precision: number = 3): string {
108
+ const { value, unit } = volume
109
+ if (value === 0) return `0 ${unit}`
110
+
111
+ let formattedValue: string
112
+ if (Math.abs(value) >= 1000) {
113
+ formattedValue = value.toExponential(precision - 1)
114
+ } else if (Math.abs(value) < 0.001) {
115
+ formattedValue = value.toExponential(precision - 1)
116
+ } else {
117
+ formattedValue = value.toPrecision(precision)
118
+ }
119
+
120
+ // Remove trailing zeros
121
+ formattedValue = formattedValue.replace(/\.?0+$/, '')
122
+ formattedValue = formattedValue.replace(/\.?0+e/, 'e')
123
+
124
+ return `${formattedValue} ${unit}`
125
+ }
126
+
127
+ function calculateDilution(params: DilutionParams): DilutionResult {
128
+ const { stockConcentration, finalConcentration, finalVolume } = params
129
+
130
+ // Validate inputs
131
+ if (stockConcentration.value <= 0) {
132
+ return {
133
+ stockVolume: { value: 0, unit: 'µL' },
134
+ diluentVolume: { value: 0, unit: 'µL' },
135
+ dilutionFactor: 0,
136
+ valid: false,
137
+ error: 'Stock concentration must be positive',
138
+ }
139
+ }
140
+
141
+ if (finalConcentration.value <= 0) {
142
+ return {
143
+ stockVolume: { value: 0, unit: 'µL' },
144
+ diluentVolume: { value: 0, unit: 'µL' },
145
+ dilutionFactor: 0,
146
+ valid: false,
147
+ error: 'Final concentration must be positive',
148
+ }
149
+ }
150
+
151
+ if (finalVolume.value <= 0) {
152
+ return {
153
+ stockVolume: { value: 0, unit: 'µL' },
154
+ diluentVolume: { value: 0, unit: 'µL' },
155
+ dilutionFactor: 0,
156
+ valid: false,
157
+ error: 'Final volume must be positive',
158
+ }
159
+ }
160
+
161
+ // Convert concentrations to same unit
162
+ const convertedFinal = convert(
163
+ finalConcentration.value,
164
+ finalConcentration.unit,
165
+ stockConcentration.unit
166
+ )
167
+
168
+ if (convertedFinal === null) {
169
+ return {
170
+ stockVolume: { value: 0, unit: 'µL' },
171
+ diluentVolume: { value: 0, unit: 'µL' },
172
+ dilutionFactor: 0,
173
+ valid: false,
174
+ error: 'Cannot convert between concentration units. Provide molecular weight for mass↔molarity conversion.',
175
+ }
176
+ }
177
+
178
+ // Check that stock is more concentrated than final
179
+ if (convertedFinal >= stockConcentration.value) {
180
+ return {
181
+ stockVolume: { value: 0, unit: 'µL' },
182
+ diluentVolume: { value: 0, unit: 'µL' },
183
+ dilutionFactor: 0,
184
+ valid: false,
185
+ error: 'Stock concentration must be higher than final concentration',
186
+ }
187
+ }
188
+
189
+ // Calculate using C1V1 = C2V2
190
+ const dilutionFactor = stockConcentration.value / convertedFinal
191
+ const finalVolumeInL = convertVolume(finalVolume.value, finalVolume.unit, 'L')
192
+ const stockVolumeInL = finalVolumeInL / dilutionFactor
193
+ const diluentVolumeInL = finalVolumeInL - stockVolumeInL
194
+
195
+ // Convert to appropriate unit (µL for small volumes, mL for larger)
196
+ let outputUnit: VolumeUnit = 'µL'
197
+ if (stockVolumeInL >= 0.001) outputUnit = 'mL'
198
+ if (stockVolumeInL >= 1) outputUnit = 'L'
199
+
200
+ return {
201
+ stockVolume: {
202
+ value: convertVolume(stockVolumeInL, 'L', outputUnit),
203
+ unit: outputUnit,
204
+ },
205
+ diluentVolume: {
206
+ value: convertVolume(diluentVolumeInL, 'L', outputUnit),
207
+ unit: outputUnit,
208
+ },
209
+ dilutionFactor,
210
+ valid: true,
211
+ }
212
+ }
213
+
214
+ function calculateSerialDilution(params: SerialDilutionParams): SerialDilutionResult {
215
+ const { startingConcentration, dilutionFactor, numberOfDilutions, volumePerWell } = params
216
+
217
+ // Validate inputs
218
+ if (startingConcentration.value <= 0) {
219
+ return {
220
+ steps: [],
221
+ totalStockVolume: { value: 0, unit: 'µL' },
222
+ valid: false,
223
+ error: 'Starting concentration must be positive',
224
+ }
225
+ }
226
+
227
+ if (dilutionFactor <= 1) {
228
+ return {
229
+ steps: [],
230
+ totalStockVolume: { value: 0, unit: 'µL' },
231
+ valid: false,
232
+ error: 'Dilution factor must be greater than 1',
233
+ }
234
+ }
235
+
236
+ if (numberOfDilutions < 1) {
237
+ return {
238
+ steps: [],
239
+ totalStockVolume: { value: 0, unit: 'µL' },
240
+ valid: false,
241
+ error: 'Number of dilutions must be at least 1',
242
+ }
243
+ }
244
+
245
+ if (volumePerWell.value <= 0) {
246
+ return {
247
+ steps: [],
248
+ totalStockVolume: { value: 0, unit: 'µL' },
249
+ valid: false,
250
+ error: 'Volume per well must be positive',
251
+ }
252
+ }
253
+
254
+ const steps: SerialDilutionStep[] = []
255
+ let currentConcentration = startingConcentration.value
256
+ const transferVolume = volumePerWell.value / (dilutionFactor - 1)
257
+ const diluentVolume = volumePerWell.value - transferVolume
258
+
259
+ for (let i = 0; i < numberOfDilutions; i++) {
260
+ steps.push({
261
+ stepNumber: i + 1,
262
+ concentration: {
263
+ value: currentConcentration,
264
+ unit: startingConcentration.unit,
265
+ },
266
+ transferVolume: {
267
+ value: i === 0 ? volumePerWell.value : transferVolume,
268
+ unit: volumePerWell.unit,
269
+ },
270
+ diluentVolume: {
271
+ value: i === 0 ? 0 : diluentVolume,
272
+ unit: volumePerWell.unit,
273
+ },
274
+ })
275
+ currentConcentration /= dilutionFactor
276
+ }
277
+
278
+ // Total stock needed for first well
279
+ const totalStockVolume: VolumeValue = {
280
+ value: volumePerWell.value + (transferVolume * (numberOfDilutions - 1)),
281
+ unit: volumePerWell.unit,
282
+ }
283
+
284
+ return {
285
+ steps,
286
+ totalStockVolume,
287
+ valid: true,
288
+ }
289
+ }
290
+
291
+ function convertMassToMolar(
292
+ mass: number,
293
+ massUnit: MassVolumeUnit,
294
+ mw: number
295
+ ): ConcentrationValue {
296
+ // Convert mass to g/mL, then to M using MW
297
+ const converted = convert(mass, massUnit, 'µM', mw)
298
+ if (converted !== null) {
299
+ return { value: converted, unit: 'µM' }
300
+ }
301
+ // Fallback to manual calculation
302
+ // mass (g/mL) / MW (g/mol) = mol/L = M
303
+ const massInGML = mass * getMassVolumeFactor(massUnit)
304
+ const molarConc = massInGML / mw
305
+ return { value: molarConc * 1e6, unit: 'µM' }
306
+ }
307
+
308
+ function convertMolarToMass(
309
+ molar: number,
310
+ molarUnit: MolarityUnit,
311
+ mw: number
312
+ ): ConcentrationValue {
313
+ // Convert M to mass using MW
314
+ const converted = convert(molar, molarUnit, 'µg/mL', mw)
315
+ if (converted !== null) {
316
+ return { value: converted, unit: 'µg/mL' }
317
+ }
318
+ // Fallback to manual calculation
319
+ // mol/L * MW (g/mol) = g/L = mg/mL
320
+ const molarInM = molar * getMolarityFactor(molarUnit)
321
+ const massConc = molarInM * mw * 1e6 // µg/mL
322
+ return { value: massConc, unit: 'µg/mL' }
323
+ }
324
+
325
+ function getMassVolumeFactor(unit: MassVolumeUnit): number {
326
+ const factors: Record<MassVolumeUnit, number> = {
327
+ 'pg/mL': 1e-12,
328
+ 'ng/mL': 1e-9,
329
+ 'µg/mL': 1e-6,
330
+ 'mg/mL': 1e-3,
331
+ 'g/mL': 1,
332
+ }
333
+ return factors[unit]
334
+ }
335
+
336
+ function getMolarityFactor(unit: MolarityUnit): number {
337
+ const factors: Record<MolarityUnit, number> = {
338
+ 'pM': 1e-12,
339
+ 'nM': 1e-9,
340
+ 'µM': 1e-6,
341
+ 'mM': 1e-3,
342
+ 'M': 1,
343
+ }
344
+ return factors[unit]
345
+ }
346
+
347
+ function generateWellConcentrations(
348
+ result: SerialDilutionResult,
349
+ wellIds: string[]
350
+ ): WellConcentration[] {
351
+ if (!result.valid || result.steps.length === 0) return []
352
+
353
+ return result.steps.slice(0, wellIds.length).map((step, index) => ({
354
+ wellId: wellIds[index],
355
+ concentration: step.concentration,
356
+ volume: step.transferVolume,
357
+ }))
358
+ }
359
+
360
+ return {
361
+ volumeUnits: VOLUME_UNITS,
362
+ calculateDilution,
363
+ calculateSerialDilution,
364
+ convertMassToMolar,
365
+ convertMolarToMass,
366
+ convertVolume,
367
+ formatVolume,
368
+ generateWellConcentrations,
369
+ }
370
+ }
@@ -0,0 +1,86 @@
1
+ import { ref, computed, type Ref, type ComputedRef } from 'vue'
2
+ import { useApi } from './useApi'
3
+ import type { TreeNode, SummaryData } from '../types'
4
+
5
+ export interface UseExperimentDataOptions {
6
+ apiBaseUrl?: string
7
+ immediate?: boolean
8
+ }
9
+
10
+ export interface UseExperimentDataReturn {
11
+ data: Ref<Record<string, unknown> | null>
12
+ treeData: ComputedRef<TreeNode[]>
13
+ tableData: ComputedRef<Record<string, unknown>[]>
14
+ summaryData: ComputedRef<SummaryData | null>
15
+ isLoading: Ref<boolean>
16
+ error: Ref<string | null>
17
+ fetch: (experimentId: number) => Promise<void>
18
+ refresh: () => Promise<void>
19
+ }
20
+
21
+ /** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */
22
+ export function useExperimentData(
23
+ options: UseExperimentDataOptions = {},
24
+ ): UseExperimentDataReturn {
25
+ const api = useApi({ baseUrl: options.apiBaseUrl })
26
+
27
+ const data = ref<Record<string, unknown> | null>(null)
28
+ const isLoading = ref(false)
29
+ const error = ref<string | null>(null)
30
+ let lastExperimentId: number | null = null
31
+
32
+ const treeData = computed<TreeNode[]>(() => {
33
+ if (!data.value) return []
34
+ const tree = data.value.tree_data ?? data.value.treeData
35
+ return Array.isArray(tree) ? tree as TreeNode[] : []
36
+ })
37
+
38
+ const tableData = computed<Record<string, unknown>[]>(() => {
39
+ if (!data.value) return []
40
+ const table = data.value.table_data ?? data.value.tableData
41
+ return Array.isArray(table) ? table as Record<string, unknown>[] : []
42
+ })
43
+
44
+ const summaryData = computed<SummaryData | null>(() => {
45
+ if (!data.value) return null
46
+ const summary = data.value.summary_data ?? data.value.summaryData
47
+ if (summary && typeof summary === 'object' && 'metadata' in (summary as Record<string, unknown>)) {
48
+ return summary as SummaryData
49
+ }
50
+ return null
51
+ })
52
+
53
+ async function fetchData(experimentId: number): Promise<void> {
54
+ lastExperimentId = experimentId
55
+ isLoading.value = true
56
+ error.value = null
57
+ try {
58
+ const result = await api.get<Record<string, unknown>>(
59
+ `/experiments/${experimentId}/data`,
60
+ )
61
+ data.value = result
62
+ } catch (e) {
63
+ error.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'
64
+ data.value = null
65
+ } finally {
66
+ isLoading.value = false
67
+ }
68
+ }
69
+
70
+ async function refresh(): Promise<void> {
71
+ if (lastExperimentId !== null) {
72
+ await fetchData(lastExperimentId)
73
+ }
74
+ }
75
+
76
+ return {
77
+ data,
78
+ treeData,
79
+ tableData,
80
+ summaryData,
81
+ isLoading,
82
+ error,
83
+ fetch: fetchData,
84
+ refresh,
85
+ }
86
+ }
@@ -0,0 +1,192 @@
1
+ import { ref, type Ref } from 'vue'
2
+ import { useApi } from './useApi'
3
+
4
+ export interface UseExperimentSaveOptions {
5
+ /** Default plugin_id for all save/load calls. */
6
+ pluginId?: string
7
+ /** Default schema version for design saves (defaults to "1.0"). */
8
+ schemaVersion?: string
9
+ /** Override API base URL. */
10
+ apiBaseUrl?: string
11
+ }
12
+
13
+ export interface UseExperimentSaveReturn {
14
+ /** Whether a save operation is in progress. */
15
+ isSaving: Ref<boolean>
16
+ /** Error message from the last failed operation, or null. */
17
+ error: Ref<string | null>
18
+ /** Timestamp of the last successful save, or null. */
19
+ lastSavedAt: Ref<Date | null>
20
+ /** Save design data for an experiment. */
21
+ saveDesign: (experimentId: number, data: Record<string, unknown>) => Promise<boolean>
22
+ /** Save analysis result for an experiment. */
23
+ saveAnalysis: (experimentId: number, result: Record<string, unknown>) => Promise<boolean>
24
+ /** Save design and/or analysis in one call. */
25
+ save: (experimentId: number, opts: {
26
+ design?: Record<string, unknown>
27
+ analysis?: Record<string, unknown>
28
+ }) => Promise<boolean>
29
+ /** Load design data for an experiment. */
30
+ loadDesign: (experimentId: number) => Promise<Record<string, unknown> | null>
31
+ /** Load analysis result for an experiment. */
32
+ loadAnalysis: (experimentId: number) => Promise<Record<string, unknown> | null>
33
+ /** Delete design data for an experiment. */
34
+ deleteDesign: (experimentId: number) => Promise<boolean>
35
+ /** Delete analysis result for an experiment. */
36
+ deleteAnalysis: (experimentId: number) => Promise<boolean>
37
+ }
38
+
39
+ /** Persists and loads experiment design and analysis data via the plugin's API endpoint. */
40
+ export function useExperimentSave(
41
+ options: UseExperimentSaveOptions = {},
42
+ ): UseExperimentSaveReturn {
43
+ const api = useApi({ baseUrl: options.apiBaseUrl })
44
+ const pluginId = options.pluginId
45
+ const schemaVersion = options.schemaVersion ?? '1.0'
46
+
47
+ const isSaving = ref(false)
48
+ const error = ref<string | null>(null)
49
+ const lastSavedAt = ref<Date | null>(null)
50
+
51
+ function setError(e: unknown): void {
52
+ error.value = e instanceof Error ? e.message : 'Unknown error'
53
+ }
54
+
55
+ async function saveDesign(
56
+ experimentId: number,
57
+ data: Record<string, unknown>,
58
+ ): Promise<boolean> {
59
+ if (!pluginId) {
60
+ error.value = 'pluginId is required for saveDesign'
61
+ return false
62
+ }
63
+ isSaving.value = true
64
+ error.value = null
65
+ try {
66
+ await api.put(`/experiments/${experimentId}/data`, {
67
+ plugin_id: pluginId,
68
+ data,
69
+ schema_version: schemaVersion,
70
+ })
71
+ lastSavedAt.value = new Date()
72
+ return true
73
+ } catch (e) {
74
+ setError(e)
75
+ return false
76
+ } finally {
77
+ isSaving.value = false
78
+ }
79
+ }
80
+
81
+ async function saveAnalysis(
82
+ experimentId: number,
83
+ result: Record<string, unknown>,
84
+ ): Promise<boolean> {
85
+ if (!pluginId) {
86
+ error.value = 'pluginId is required for saveAnalysis'
87
+ return false
88
+ }
89
+ isSaving.value = true
90
+ error.value = null
91
+ try {
92
+ await api.put(`/experiments/${experimentId}/results/${pluginId}`, {
93
+ result,
94
+ })
95
+ lastSavedAt.value = new Date()
96
+ return true
97
+ } catch (e) {
98
+ setError(e)
99
+ return false
100
+ } finally {
101
+ isSaving.value = false
102
+ }
103
+ }
104
+
105
+ async function save(
106
+ experimentId: number,
107
+ opts: { design?: Record<string, unknown>; analysis?: Record<string, unknown> },
108
+ ): Promise<boolean> {
109
+ if ((opts.design || opts.analysis) && !pluginId) {
110
+ error.value = 'pluginId is required for save'
111
+ return false
112
+ }
113
+ isSaving.value = true
114
+ error.value = null
115
+ try {
116
+ if (opts.design) {
117
+ await api.put(`/experiments/${experimentId}/data`, {
118
+ plugin_id: pluginId,
119
+ data: opts.design,
120
+ schema_version: schemaVersion,
121
+ })
122
+ }
123
+ if (opts.analysis) {
124
+ await api.put(`/experiments/${experimentId}/results/${pluginId}`, {
125
+ result: opts.analysis,
126
+ })
127
+ }
128
+ lastSavedAt.value = new Date()
129
+ return true
130
+ } catch (e) {
131
+ setError(e)
132
+ return false
133
+ } finally {
134
+ isSaving.value = false
135
+ }
136
+ }
137
+
138
+ async function loadDesign(
139
+ experimentId: number,
140
+ ): Promise<Record<string, unknown> | null> {
141
+ try {
142
+ return await api.get<Record<string, unknown>>(`/experiments/${experimentId}/data`)
143
+ } catch {
144
+ return null
145
+ }
146
+ }
147
+
148
+ async function loadAnalysis(
149
+ experimentId: number,
150
+ ): Promise<Record<string, unknown> | null> {
151
+ if (!pluginId) return null
152
+ try {
153
+ return await api.get<Record<string, unknown>>(
154
+ `/experiments/${experimentId}/results/${pluginId}`,
155
+ )
156
+ } catch {
157
+ return null
158
+ }
159
+ }
160
+
161
+ async function deleteDesign(experimentId: number): Promise<boolean> {
162
+ try {
163
+ await api.delete(`/experiments/${experimentId}/data`)
164
+ return true
165
+ } catch {
166
+ return false
167
+ }
168
+ }
169
+
170
+ async function deleteAnalysis(experimentId: number): Promise<boolean> {
171
+ if (!pluginId) return false
172
+ try {
173
+ await api.delete(`/experiments/${experimentId}/results/${pluginId}`)
174
+ return true
175
+ } catch {
176
+ return false
177
+ }
178
+ }
179
+
180
+ return {
181
+ isSaving,
182
+ error,
183
+ lastSavedAt,
184
+ saveDesign,
185
+ saveAnalysis,
186
+ save,
187
+ loadDesign,
188
+ loadAnalysis,
189
+ deleteDesign,
190
+ deleteAnalysis,
191
+ }
192
+ }