@morscherlab/mint-sdk 1.0.0-beta.1 → 1.0.0-beta.3

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 (421) hide show
  1. package/README.md +218 -6
  2. package/dist/__tests__/components/ActionItem.test.d.ts +1 -0
  3. package/dist/__tests__/components/AppAvatarMenu.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppPageSelector.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppPillNav.test.d.ts +1 -0
  6. package/dist/__tests__/components/AppPluginSwitcher.test.d.ts +1 -0
  7. package/dist/__tests__/components/AppToastContainer.test.d.ts +1 -0
  8. package/dist/__tests__/components/BaseRadioGroup.test.d.ts +1 -0
  9. package/dist/__tests__/components/BaseSelect.test.d.ts +1 -0
  10. package/dist/__tests__/components/BaseTabs.test.d.ts +1 -0
  11. package/dist/__tests__/components/BatchProgressList.test.d.ts +1 -0
  12. package/dist/__tests__/components/BioTemplateExperimentWorkspaceView.test.d.ts +1 -0
  13. package/dist/__tests__/components/BioTemplatePackWorkspaceView.test.d.ts +1 -0
  14. package/dist/__tests__/components/BioTemplatePresetWorkspaceView.test.d.ts +1 -0
  15. package/dist/__tests__/components/BioTemplateRenderer.test.d.ts +1 -0
  16. package/dist/__tests__/components/Breadcrumb.test.d.ts +1 -0
  17. package/dist/__tests__/components/CalendarGridPanel.test.d.ts +1 -0
  18. package/dist/__tests__/components/ConcentrationInput.test.d.ts +1 -0
  19. package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
  20. package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
  21. package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
  22. package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
  23. package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
  24. package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
  25. package/dist/__tests__/components/FormCompatibility.test.d.ts +1 -0
  26. package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
  27. package/dist/__tests__/components/GroupingModal.test.d.ts +1 -0
  28. package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
  29. package/dist/__tests__/components/PluginIcon.test.d.ts +1 -0
  30. package/dist/__tests__/components/ProtocolStepEditor.test.d.ts +1 -0
  31. package/dist/__tests__/components/ReagentList.test.d.ts +1 -0
  32. package/dist/__tests__/components/SampleHierarchyTree.test.d.ts +1 -0
  33. package/dist/__tests__/components/SampleSelector.test.d.ts +1 -0
  34. package/dist/__tests__/components/SegmentedControl.test.d.ts +1 -0
  35. package/dist/__tests__/components/SettingsButton.test.d.ts +1 -0
  36. package/dist/__tests__/components/SettingsModal.test.d.ts +1 -0
  37. package/dist/__tests__/components/TagsInput.test.d.ts +1 -0
  38. package/dist/__tests__/components/ThemeToggle.test.d.ts +1 -0
  39. package/dist/__tests__/components/TimePicker.test.d.ts +1 -0
  40. package/dist/__tests__/composables/useBioTemplatePackWorkspace.test.d.ts +1 -0
  41. package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
  42. package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
  43. package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
  44. package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
  45. package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
  46. package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
  47. package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
  48. package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
  49. package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
  50. package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
  51. package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
  52. package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
  53. package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
  54. package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
  55. package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
  56. package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
  57. package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
  58. package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
  59. package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
  60. package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
  61. package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
  62. package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
  63. package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
  64. package/dist/__tests__/templates/templates.test.d.ts +1 -0
  65. package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
  66. package/dist/auth-QQj2kkze.js.map +1 -0
  67. package/dist/components/ActionItem.vue.d.ts +32 -0
  68. package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
  69. package/dist/components/AppPageSelector.vue.d.ts +3 -6
  70. package/dist/components/AppPillNav.vue.d.ts +2 -2
  71. package/dist/components/AppSidebar.vue.d.ts +56 -3
  72. package/dist/components/AppToastContainer.vue.d.ts +2 -0
  73. package/dist/components/AppTopBar.vue.d.ts +43 -10
  74. package/dist/components/BaseButton.vue.d.ts +2 -2
  75. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  76. package/dist/components/BaseInput.vue.d.ts +2 -2
  77. package/dist/components/BasePill.vue.d.ts +3 -3
  78. package/dist/components/BaseRadioGroup.vue.d.ts +4 -4
  79. package/dist/components/BaseSelect.vue.d.ts +4 -4
  80. package/dist/components/BaseSlider.vue.d.ts +1 -1
  81. package/dist/components/BaseTabs.vue.d.ts +2 -2
  82. package/dist/components/BaseTextarea.vue.d.ts +2 -2
  83. package/dist/components/BaseToggle.vue.d.ts +1 -1
  84. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +117 -0
  85. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +92 -0
  86. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +82 -0
  87. package/dist/components/BioTemplateRenderer.vue.d.ts +29 -0
  88. package/dist/components/Breadcrumb.vue.d.ts +2 -2
  89. package/dist/components/Calendar.vue.d.ts +1 -1
  90. package/dist/components/CalendarGridPanel.vue.d.ts +25 -0
  91. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  92. package/dist/components/ColorSlider.vue.d.ts +1 -1
  93. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  94. package/dist/components/ConfirmDialog.vue.d.ts +2 -2
  95. package/dist/components/ControlWorkspaceView.vue.d.ts +130 -0
  96. package/dist/components/DatePicker.vue.d.ts +2 -2
  97. package/dist/components/DateTimePicker.vue.d.ts +3 -3
  98. package/dist/components/DropdownButton.vue.d.ts +4 -4
  99. package/dist/components/EmptyState.vue.d.ts +1 -2
  100. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  101. package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
  102. package/dist/components/FileUploader.vue.d.ts +2 -2
  103. package/dist/components/FitPanel.vue.d.ts +1 -1
  104. package/dist/components/FormActions.vue.d.ts +4 -4
  105. package/dist/components/FormBuilder.vue.d.ts +22 -8
  106. package/dist/components/FormFieldRenderer.vue.d.ts +7 -10
  107. package/dist/components/FormSection.vue.d.ts +11 -24
  108. package/dist/components/FormulaInput.vue.d.ts +2 -2
  109. package/dist/components/IconButton.vue.d.ts +1 -1
  110. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  111. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  112. package/dist/components/MultiSelect.vue.d.ts +3 -3
  113. package/dist/components/NumberInput.vue.d.ts +2 -2
  114. package/dist/components/PluginIcon.vue.d.ts +11 -0
  115. package/dist/components/ProgressBar.vue.d.ts +2 -2
  116. package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
  117. package/dist/components/RackEditor.vue.d.ts +2 -2
  118. package/dist/components/ReagentEditor.vue.d.ts +1 -1
  119. package/dist/components/ResourceCard.vue.d.ts +1 -1
  120. package/dist/components/SampleLegend.vue.d.ts +2 -2
  121. package/dist/components/SampleSelector.vue.d.ts +1 -1
  122. package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
  123. package/dist/components/ScientificNumber.vue.d.ts +1 -1
  124. package/dist/components/SegmentedControl.vue.d.ts +3 -3
  125. package/dist/components/SequenceInput.vue.d.ts +3 -3
  126. package/dist/components/SettingsButton.vue.d.ts +2 -2
  127. package/dist/components/SettingsModal.vue.d.ts +32 -4
  128. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  129. package/dist/components/TagsInput.vue.d.ts +3 -2
  130. package/dist/components/TimePicker.vue.d.ts +3 -3
  131. package/dist/components/TimeRangeInput.vue.d.ts +2 -2
  132. package/dist/components/UnitInput.vue.d.ts +2 -2
  133. package/dist/components/WellPlate.vue.d.ts +8 -8
  134. package/dist/components/index.d.ts +12 -1
  135. package/dist/components/index.js +3 -3
  136. package/dist/components/internal/FormFieldRendererInternal.vue.d.ts +31 -0
  137. package/dist/components/internal/FormSectionRenderer.vue.d.ts +43 -0
  138. package/dist/{components-CzbQQPCb.js → components-D_Sr0adg.js} +9629 -8647
  139. package/dist/components-D_Sr0adg.js.map +1 -0
  140. package/dist/composables/index.d.ts +21 -2
  141. package/dist/composables/index.js +4 -3
  142. package/dist/composables/platformContextHelpers.d.ts +14 -0
  143. package/dist/composables/useBioTemplateComponents.d.ts +20 -0
  144. package/dist/composables/useBioTemplateControls.d.ts +6 -0
  145. package/dist/composables/useBioTemplatePackWorkspace.d.ts +45 -0
  146. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +74 -0
  147. package/dist/composables/useBioTemplateWorkspace.d.ts +50 -0
  148. package/dist/composables/useCalendarGrid.d.ts +26 -0
  149. package/dist/composables/useControlSchema.d.ts +321 -0
  150. package/dist/composables/useDebouncedWatch.d.ts +20 -0
  151. package/dist/composables/useDropdownState.d.ts +19 -0
  152. package/dist/composables/useEventListener.d.ts +13 -0
  153. package/dist/composables/useExpansionSet.d.ts +21 -0
  154. package/dist/composables/useExperimentData.d.ts +10 -0
  155. package/dist/composables/useExperimentSave.d.ts +31 -2
  156. package/dist/composables/useExperimentSelector.d.ts +20 -0
  157. package/dist/composables/useForm.d.ts +2 -0
  158. package/dist/composables/useGroupAssignment.d.ts +31 -0
  159. package/dist/composables/useListSelection.d.ts +35 -0
  160. package/dist/composables/usePlatformContext.d.ts +24 -3
  161. package/dist/composables/usePluginApi.d.ts +7 -14
  162. package/dist/composables/usePluginClient.d.ts +109 -0
  163. package/dist/composables/usePluginConfig.d.ts +12 -0
  164. package/dist/composables/useRequestSyncState.d.ts +34 -0
  165. package/dist/composables/useSampleGroups.d.ts +32 -0
  166. package/dist/composables/useSelectionLimit.d.ts +17 -0
  167. package/dist/composables/useSortedItems.d.ts +32 -0
  168. package/dist/composables/useTemplateCollection.d.ts +58 -0
  169. package/dist/composables/useTextSearch.d.ts +18 -0
  170. package/dist/composables/useTimeUtils.d.ts +8 -0
  171. package/dist/{composables-BXklV5ii.js → composables-C3dpXQN5.js} +228 -146
  172. package/dist/composables-C3dpXQN5.js.map +1 -0
  173. package/dist/index.d.ts +12 -3
  174. package/dist/index.js +6 -5
  175. package/dist/install.d.ts +7 -2
  176. package/dist/install.js +2 -2
  177. package/dist/install.js.map +1 -1
  178. package/dist/stores/auth.d.ts +1 -1
  179. package/dist/stores/index.js +1 -1
  180. package/dist/stores/settings.d.ts +4 -1
  181. package/dist/styles.css +5255 -5654
  182. package/dist/templates/adapters.d.ts +43 -0
  183. package/dist/templates/builders.d.ts +63 -0
  184. package/dist/templates/catalog.d.ts +188 -0
  185. package/dist/templates/componentBindings.d.ts +58 -0
  186. package/dist/templates/controlSchemas.d.ts +25 -0
  187. package/dist/templates/index.d.ts +15 -0
  188. package/dist/templates/index.js +2 -0
  189. package/dist/templates/lookup.d.ts +4 -0
  190. package/dist/templates/packs.d.ts +18 -0
  191. package/dist/templates/presets.d.ts +90 -0
  192. package/dist/templates/types.d.ts +531 -0
  193. package/dist/templates-50NPjaxL.js +9333 -0
  194. package/dist/templates-50NPjaxL.js.map +1 -0
  195. package/dist/types/components.d.ts +62 -1
  196. package/dist/types/form-builder.d.ts +6 -8
  197. package/dist/types/index.d.ts +2 -2
  198. package/dist/types/platform.d.ts +8 -1
  199. package/dist/useScheduleDrag-D4oWdh41.js +4371 -0
  200. package/dist/useScheduleDrag-D4oWdh41.js.map +1 -0
  201. package/dist/utils/formModelSync.d.ts +5 -0
  202. package/dist/utils/items.d.ts +8 -0
  203. package/dist/utils/options.d.ts +6 -0
  204. package/dist/utils/pluginIcon.d.ts +9 -0
  205. package/package.json +7 -2
  206. package/src/__tests__/components/ActionItem.test.ts +99 -0
  207. package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
  208. package/src/__tests__/components/AppPageSelector.test.ts +134 -0
  209. package/src/__tests__/components/AppPillNav.test.ts +78 -0
  210. package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
  211. package/src/__tests__/components/AppSidebar.test.ts +370 -0
  212. package/src/__tests__/components/AppToastContainer.test.ts +48 -0
  213. package/src/__tests__/components/AppTopBar.test.ts +414 -13
  214. package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
  215. package/src/__tests__/components/BaseSelect.test.ts +21 -0
  216. package/src/__tests__/components/BaseTabs.test.ts +25 -0
  217. package/src/__tests__/components/BatchProgressList.test.ts +52 -0
  218. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +153 -0
  219. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +161 -0
  220. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +281 -0
  221. package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
  222. package/src/__tests__/components/Breadcrumb.test.ts +23 -0
  223. package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
  224. package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
  225. package/src/__tests__/components/ControlWorkspaceView.test.ts +1031 -0
  226. package/src/__tests__/components/DataFrame.test.ts +11 -0
  227. package/src/__tests__/components/DatePicker.test.ts +45 -0
  228. package/src/__tests__/components/DateTimePicker.test.ts +48 -0
  229. package/src/__tests__/components/DropdownButton.test.ts +23 -0
  230. package/src/__tests__/components/EmptyState.test.ts +23 -0
  231. package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
  232. package/src/__tests__/components/FormBuilder.test.ts +296 -0
  233. package/src/__tests__/components/FormCompatibility.test.ts +94 -0
  234. package/src/__tests__/components/GroupAssigner.test.ts +30 -0
  235. package/src/__tests__/components/GroupingModal.test.ts +73 -0
  236. package/src/__tests__/components/MultiSelect.test.ts +48 -0
  237. package/src/__tests__/components/PluginIcon.test.ts +119 -0
  238. package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
  239. package/src/__tests__/components/ReagentList.test.ts +82 -0
  240. package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
  241. package/src/__tests__/components/SampleSelector.test.ts +60 -0
  242. package/src/__tests__/components/SegmentedControl.test.ts +24 -0
  243. package/src/__tests__/components/SettingsButton.test.ts +44 -0
  244. package/src/__tests__/components/SettingsModal.test.ts +296 -0
  245. package/src/__tests__/components/TagsInput.test.ts +75 -0
  246. package/src/__tests__/components/ThemeToggle.test.ts +47 -0
  247. package/src/__tests__/components/TimePicker.test.ts +38 -0
  248. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +122 -0
  249. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
  250. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +99 -0
  251. package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
  252. package/src/__tests__/composables/useControlSchema.test.ts +919 -0
  253. package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
  254. package/src/__tests__/composables/useDropdownState.test.ts +95 -0
  255. package/src/__tests__/composables/useEventListener.test.ts +116 -0
  256. package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
  257. package/src/__tests__/composables/useExperimentData.test.ts +4 -0
  258. package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
  259. package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
  260. package/src/__tests__/composables/useForm.test.ts +58 -0
  261. package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
  262. package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
  263. package/src/__tests__/composables/useListSelection.test.ts +66 -0
  264. package/src/__tests__/composables/usePluginClient.test.ts +444 -0
  265. package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
  266. package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
  267. package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
  268. package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
  269. package/src/__tests__/composables/useSortedItems.test.ts +87 -0
  270. package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
  271. package/src/__tests__/composables/useTextSearch.test.ts +55 -0
  272. package/src/__tests__/composables/useTheme.test.ts +91 -0
  273. package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
  274. package/src/__tests__/docs/frontendDocsCatalog.test.ts +229 -0
  275. package/src/__tests__/fixtures/templates/dose-response.json +81 -0
  276. package/src/__tests__/fixtures/templates/plate-map.json +54 -0
  277. package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
  278. package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
  279. package/src/__tests__/templates/templates.test.ts +1043 -0
  280. package/src/components/ActionItem.vue +82 -0
  281. package/src/components/AppAvatarMenu.vue +15 -69
  282. package/src/components/AppLayout.story.vue +25 -25
  283. package/src/components/AppPageSelector.vue +63 -94
  284. package/src/components/AppPillNav.vue +44 -39
  285. package/src/components/AppPluginSwitcher.vue +41 -145
  286. package/src/components/AppSidebar.story.vue +94 -0
  287. package/src/components/AppSidebar.vue +187 -12
  288. package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
  289. package/src/components/AppToastContainer.vue +62 -0
  290. package/src/components/AppTopBar.story.vue +7 -30
  291. package/src/components/AppTopBar.vue +283 -84
  292. package/src/components/BaseModal.vue +3 -5
  293. package/src/components/BaseRadioGroup.vue +7 -3
  294. package/src/components/BaseSelect.vue +11 -7
  295. package/src/components/BaseTabs.vue +6 -4
  296. package/src/components/BatchProgressList.vue +5 -8
  297. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
  298. package/src/components/BioTemplateExperimentWorkspaceView.vue +337 -0
  299. package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
  300. package/src/components/BioTemplatePackWorkspaceView.vue +176 -0
  301. package/src/components/BioTemplatePresetWorkspaceView.story.vue +151 -0
  302. package/src/components/BioTemplatePresetWorkspaceView.vue +392 -0
  303. package/src/components/BioTemplateRenderer.story.vue +57 -0
  304. package/src/components/BioTemplateRenderer.vue +269 -0
  305. package/src/components/Breadcrumb.vue +14 -8
  306. package/src/components/CalendarGridPanel.vue +120 -0
  307. package/src/components/ConcentrationInput.vue +27 -64
  308. package/src/components/ControlWorkspaceView.story.vue +336 -0
  309. package/src/components/ControlWorkspaceView.vue +347 -0
  310. package/src/components/DataFrame.vue +34 -50
  311. package/src/components/DatePicker.vue +59 -192
  312. package/src/components/DateTimePicker.vue +50 -171
  313. package/src/components/DropdownButton.vue +14 -32
  314. package/src/components/EmptyState.vue +4 -2
  315. package/src/components/ExperimentPopover.vue +5 -22
  316. package/src/components/FormBuilder.vue +124 -27
  317. package/src/components/FormFieldRenderer.vue +15 -38
  318. package/src/components/FormSection.vue +20 -73
  319. package/src/components/GroupAssigner.vue +24 -56
  320. package/src/components/GroupingModal.story.vue +3 -3
  321. package/src/components/GroupingModal.vue +30 -391
  322. package/src/components/MultiSelect.vue +17 -12
  323. package/src/components/PlateMapEditor.vue +3 -8
  324. package/src/components/PluginIcon.story.vue +71 -0
  325. package/src/components/PluginIcon.vue +68 -0
  326. package/src/components/ProtocolStepEditor.vue +13 -22
  327. package/src/components/ReagentList.vue +25 -33
  328. package/src/components/SampleHierarchyTree.vue +12 -23
  329. package/src/components/SampleSelector.vue +42 -122
  330. package/src/components/SegmentedControl.vue +7 -3
  331. package/src/components/SettingsButton.story.vue +1 -1
  332. package/src/components/SettingsButton.vue +15 -27
  333. package/src/components/SettingsModal.story.vue +337 -45
  334. package/src/components/SettingsModal.vue +344 -66
  335. package/src/components/TagsInput.vue +29 -14
  336. package/src/components/ThemeToggle.vue +9 -7
  337. package/src/components/TimePicker.vue +19 -41
  338. package/src/components/ToastNotification.vue +4 -57
  339. package/src/components/Tooltip.vue +7 -12
  340. package/src/components/WellEditPopup.vue +3 -8
  341. package/src/components/WellPlate.vue +4 -10
  342. package/src/components/index.ts +12 -1
  343. package/src/components/internal/FormFieldRendererInternal.vue +50 -0
  344. package/src/components/internal/FormSectionRenderer.vue +78 -0
  345. package/src/composables/index.ts +212 -0
  346. package/src/composables/platformContextHelpers.ts +74 -0
  347. package/src/composables/useBioTemplateComponents.ts +93 -0
  348. package/src/composables/useBioTemplateControls.ts +41 -0
  349. package/src/composables/useBioTemplatePackWorkspace.ts +181 -0
  350. package/src/composables/useBioTemplatePresetWorkspace.ts +337 -0
  351. package/src/composables/useBioTemplateWorkspace.ts +139 -0
  352. package/src/composables/useCalendarGrid.ts +140 -0
  353. package/src/composables/useControlSchema.ts +1274 -0
  354. package/src/composables/useDebouncedWatch.ts +119 -0
  355. package/src/composables/useDropdownState.ts +83 -0
  356. package/src/composables/useEventListener.ts +111 -0
  357. package/src/composables/useExpansionSet.ts +117 -0
  358. package/src/composables/useExperimentData.ts +20 -11
  359. package/src/composables/useExperimentSave.ts +202 -50
  360. package/src/composables/useExperimentSelector.ts +86 -72
  361. package/src/composables/useForm.ts +49 -4
  362. package/src/composables/useFormBuilder.ts +93 -42
  363. package/src/composables/useGroupAssignment.ts +148 -0
  364. package/src/composables/useListSelection.ts +158 -0
  365. package/src/composables/usePluginApi.ts +7 -14
  366. package/src/composables/usePluginClient.ts +425 -0
  367. package/src/composables/usePluginConfig.ts +34 -13
  368. package/src/composables/useRequestSyncState.ts +126 -0
  369. package/src/composables/useSampleGroups.ts +126 -0
  370. package/src/composables/useSelectionLimit.ts +57 -0
  371. package/src/composables/useSortedItems.ts +118 -0
  372. package/src/composables/useTemplateCollection.ts +229 -0
  373. package/src/composables/useTextSearch.ts +60 -0
  374. package/src/composables/useTheme.ts +2 -28
  375. package/src/composables/useTimeUtils.ts +26 -2
  376. package/src/composables/useWellPlateEditor.ts +13 -9
  377. package/src/index.ts +228 -4
  378. package/src/install.ts +11 -4
  379. package/src/stores/settings.ts +13 -9
  380. package/src/styles/components/app-page-selector.css +23 -0
  381. package/src/styles/components/app-pill-nav.css +8 -2
  382. package/src/styles/components/app-top-bar.css +35 -2
  383. package/src/styles/components/button.css +3 -7
  384. package/src/styles/components/concentration-input.css +3 -142
  385. package/src/styles/components/dropdown-button.css +4 -4
  386. package/src/styles/components/empty-state.css +0 -16
  387. package/src/styles/components/input.css +4 -5
  388. package/src/styles/components/number-input.css +3 -3
  389. package/src/styles/components/plugin-icon.css +38 -0
  390. package/src/styles/components/segmented-control.css +4 -7
  391. package/src/styles/components/settings-button.css +3 -66
  392. package/src/styles/components/settings-modal.css +184 -0
  393. package/src/styles/components/tabs.css +1 -2
  394. package/src/styles/components/textarea.css +4 -5
  395. package/src/styles/components/theme-toggle.css +3 -66
  396. package/src/styles/components/unit-input.css +3 -3
  397. package/src/styles/index.css +0 -1
  398. package/src/templates/adapters.ts +785 -0
  399. package/src/templates/builders.ts +2149 -0
  400. package/src/templates/catalog.ts +245 -0
  401. package/src/templates/componentBindings.ts +615 -0
  402. package/src/templates/controlSchemas.ts +718 -0
  403. package/src/templates/index.ts +314 -0
  404. package/src/templates/lookup.ts +18 -0
  405. package/src/templates/packs.ts +156 -0
  406. package/src/templates/presets.ts +146 -0
  407. package/src/templates/types.ts +668 -0
  408. package/src/types/components.ts +80 -1
  409. package/src/types/form-builder.ts +7 -2
  410. package/src/types/index.ts +17 -0
  411. package/src/types/platform.ts +8 -1
  412. package/src/utils/formModelSync.ts +52 -0
  413. package/src/utils/items.ts +28 -0
  414. package/src/utils/options.ts +23 -0
  415. package/src/utils/pluginIcon.ts +30 -0
  416. package/dist/auth-DsI0rQ7_.js.map +0 -1
  417. package/dist/components-CzbQQPCb.js.map +0 -1
  418. package/dist/composables-BXklV5ii.js.map +0 -1
  419. package/dist/useScheduleDrag-CxBeqYcu.js +0 -7181
  420. package/dist/useScheduleDrag-CxBeqYcu.js.map +0 -1
  421. package/src/styles/components/grouping-modal.css +0 -323
@@ -0,0 +1,425 @@
1
+ import { computed, shallowRef, watch, type ComputedRef, type Ref } from 'vue'
2
+ import { useApi } from './useApi'
3
+ import { usePlatformContext } from './usePlatformContext'
4
+ import { useRequestSyncState } from './useRequestSyncState'
5
+ import {
6
+ currentExperimentFromContext,
7
+ getInjectedPlatformContext,
8
+ resolveCurrentExperimentId,
9
+ } from './platformContextHelpers'
10
+
11
+ export type PluginHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'
12
+
13
+ export interface PluginEndpointContract {
14
+ name: string
15
+ method: PluginHttpMethod
16
+ path: string
17
+ pathParams?: string[]
18
+ queryParams?: PluginEndpointParamContract[]
19
+ requestType?: string | null
20
+ responseType?: string | null
21
+ }
22
+
23
+ export interface PluginEndpointParamContract {
24
+ name: string
25
+ fieldName: string
26
+ type?: string
27
+ required?: boolean
28
+ }
29
+
30
+ export interface PluginNavItemContract {
31
+ path: string
32
+ label: string
33
+ id?: string
34
+ icon?: string
35
+ description?: string
36
+ requiresAuth?: boolean
37
+ requiresAdmin?: boolean
38
+ requiresFeature?: string
39
+ }
40
+
41
+ export interface PluginContract {
42
+ schemaVersion: number
43
+ plugin: {
44
+ name?: string
45
+ packageName?: string
46
+ version?: string
47
+ description?: string
48
+ routesPrefix: string
49
+ apiPrefix: string
50
+ type: 'analysis' | 'experiment-design' | string
51
+ analysisType?: string
52
+ icon?: string
53
+ color?: string
54
+ navItems?: PluginNavItemContract[]
55
+ capabilities?: Record<string, unknown>
56
+ }
57
+ endpoints: PluginEndpointContract[]
58
+ hash: string
59
+ }
60
+
61
+ export interface PluginEndpointDefinition {
62
+ method: PluginHttpMethod
63
+ path: string
64
+ pathParams?: string[]
65
+ queryParams?: PluginEndpointParamDefinition[]
66
+ hasBody?: boolean
67
+ }
68
+
69
+ export type PluginEndpointParamDefinition = string | {
70
+ name: string
71
+ fieldName?: string
72
+ type?: string
73
+ required?: boolean
74
+ }
75
+
76
+ export interface CreatePluginClientOptions {
77
+ endpoints: Record<string, PluginEndpointDefinition>
78
+ baseUrl?: string
79
+ }
80
+
81
+ export interface BuildPluginEndpointUrlOptions {
82
+ /** Override the generated or platform-injected API base URL. */
83
+ baseUrl?: string
84
+ /** Return only the endpoint path and query string instead of base URL + path. */
85
+ includeBaseUrl?: boolean
86
+ }
87
+
88
+ export interface UseCurrentExperimentOptions {
89
+ apiBaseUrl?: string
90
+ immediate?: boolean
91
+ }
92
+
93
+ export interface UseCurrentExperimentReturn<TExperiment = unknown> {
94
+ /** Current experiment id resolved from platform injection or URL conventions. */
95
+ experimentId: ComputedRef<number | undefined>
96
+ /** Whether a current experiment id is available. */
97
+ hasExperiment: ComputedRef<boolean>
98
+ /** Current experiment payload, if injected or successfully fetched. */
99
+ experiment: Ref<TExperiment | undefined>
100
+ /** Whether the current experiment is currently loading. */
101
+ isLoading: Ref<boolean>
102
+ /** Error message from the last failed current-experiment fetch, or null. */
103
+ error: Ref<string | null>
104
+ /** Timestamp of the last successful current-experiment fetch, or null. */
105
+ lastLoadedAt: Ref<Date | null>
106
+ /** Return the current experiment id or throw a clear SDK error when absent. */
107
+ requireExperimentId: () => number
108
+ /** Fetch a specific experiment id, defaulting to the current experiment id. */
109
+ fetch: (experimentId?: number) => Promise<TExperiment | undefined>
110
+ /** Refetch the currently resolved experiment id. */
111
+ refresh: () => Promise<TExperiment | undefined>
112
+ }
113
+
114
+ export type PluginEndpointCaller = (payload?: unknown) => Promise<unknown>
115
+ export type GeneratedPluginClient = Record<string, (...args: any[]) => Promise<any>>
116
+
117
+ function normalizeApiPrefix(prefix: string | undefined): string | undefined {
118
+ if (!prefix) return undefined
119
+ if (prefix.startsWith('/api/')) return prefix
120
+ if (prefix === '/api') return prefix
121
+ if (prefix.startsWith('/')) return `/api${prefix}`
122
+ return `/api/${prefix}`
123
+ }
124
+
125
+ /** Resolve the runtime plugin API base URL from env, explicit options, platform injection, or contract metadata. */
126
+ export function resolvePluginBaseUrl(contract: PluginContract, explicitBaseUrl?: string): string {
127
+ const envPrefix = (import.meta.env?.VITE_API_PREFIX as string | undefined) || undefined
128
+ if (envPrefix) return envPrefix
129
+ if (explicitBaseUrl) return explicitBaseUrl
130
+
131
+ const injected = getInjectedPlatformContext()
132
+ const platformPrefix =
133
+ injected?.plugin?.api_prefix ||
134
+ normalizeApiPrefix(injected?.plugin?.route_prefix)
135
+ if (platformPrefix) return platformPrefix
136
+
137
+ return contract.plugin.apiPrefix || '/api'
138
+ }
139
+
140
+ function encodePath(path: string, payload: Record<string, unknown>, pathParams: string[]): string {
141
+ let nextPath = path
142
+ for (const param of pathParams) {
143
+ const value = payload[param]
144
+ if (value === undefined || value === null) {
145
+ throw new Error(`[MINT SDK] Missing path parameter '${param}' for plugin endpoint ${path}`)
146
+ }
147
+ nextPath = nextPath.replace(`{${param}}`, encodeURIComponent(String(value)))
148
+ nextPath = nextPath.replace(`:${param}`, encodeURIComponent(String(value)))
149
+ }
150
+ return nextPath
151
+ }
152
+
153
+ function withDefaultPathParams(
154
+ payload: Record<string, unknown>,
155
+ pathParams: string[],
156
+ ): Record<string, unknown> {
157
+ if (!pathParams.length) return payload
158
+ const nextPayload = { ...payload }
159
+ for (const param of pathParams) {
160
+ if (nextPayload[param] !== undefined && nextPayload[param] !== null) continue
161
+ if (isExperimentPathParam(param)) {
162
+ const experimentId = resolveCurrentExperimentId()
163
+ if (experimentId !== undefined) {
164
+ nextPayload[param] = experimentId
165
+ }
166
+ }
167
+ }
168
+ return nextPayload
169
+ }
170
+
171
+ function isExperimentPathParam(param: string): boolean {
172
+ return param === 'experimentId' || param === 'experiment_id'
173
+ }
174
+
175
+ function withoutPathParams(payload: Record<string, unknown>, pathParams: string[]) {
176
+ const copy = { ...payload }
177
+ for (const param of pathParams) {
178
+ delete copy[param]
179
+ }
180
+ return copy
181
+ }
182
+
183
+ function queryParamsFromPayload(
184
+ payload: Record<string, unknown>,
185
+ pathParams: string[],
186
+ queryParams?: PluginEndpointParamDefinition[],
187
+ endpointPath?: string,
188
+ includeUnspecifiedParams = false,
189
+ ) {
190
+ if (!queryParams?.length) {
191
+ return includeUnspecifiedParams ? withoutPathParams(payload, pathParams) : {}
192
+ }
193
+
194
+ const query: Record<string, unknown> = {}
195
+ for (const param of queryParams) {
196
+ const name = typeof param === 'string' ? param : param.name
197
+ const fieldName = typeof param === 'string' ? param : param.fieldName ?? param.name
198
+ const required = typeof param !== 'string' && param.required === true
199
+ const value = payload[fieldName]
200
+ if (value === undefined || value === null) {
201
+ if (required) {
202
+ throw new Error(
203
+ `[MINT SDK] Missing query parameter '${fieldName}' for plugin endpoint ${endpointPath ?? ''}`,
204
+ )
205
+ }
206
+ continue
207
+ }
208
+ query[name] = value
209
+ }
210
+ return query
211
+ }
212
+
213
+ function hasKeys(value: Record<string, unknown>): boolean {
214
+ return Object.keys(value).length > 0
215
+ }
216
+
217
+ function requestBodyFromPayload(
218
+ payload: unknown,
219
+ requestPayload: Record<string, unknown>,
220
+ endpoint: PluginEndpointDefinition,
221
+ ): unknown {
222
+ if (!endpoint.hasBody) return undefined
223
+ const body = endpoint.pathParams?.length || endpoint.queryParams?.length
224
+ ? requestPayload.body
225
+ : payload
226
+ if (body === undefined || body === null) {
227
+ throw new Error(`[MINT SDK] Missing request body for plugin endpoint ${endpoint.path}`)
228
+ }
229
+ return body
230
+ }
231
+
232
+ function pluginEndpointRequestParts(
233
+ contract: PluginContract,
234
+ endpoint: PluginEndpointDefinition,
235
+ payload?: unknown,
236
+ explicitBaseUrl?: string,
237
+ ) {
238
+ const payloadObject =
239
+ payload && typeof payload === 'object' && !Array.isArray(payload)
240
+ ? payload as Record<string, unknown>
241
+ : {}
242
+ const pathParams = endpoint.pathParams ?? []
243
+ const requestPayload = withDefaultPathParams(payloadObject, pathParams)
244
+ const path = encodePath(endpoint.path, requestPayload, pathParams)
245
+ const query = queryParamsFromPayload(
246
+ requestPayload,
247
+ pathParams,
248
+ endpoint.queryParams,
249
+ endpoint.path,
250
+ endpoint.method === 'get',
251
+ )
252
+
253
+ return {
254
+ baseUrl: resolvePluginBaseUrl(contract, explicitBaseUrl),
255
+ path,
256
+ query,
257
+ requestPayload,
258
+ }
259
+ }
260
+
261
+ function appendQueryValue(params: URLSearchParams, key: string, value: unknown): void {
262
+ if (value === undefined || value === null) return
263
+ if (Array.isArray(value)) {
264
+ for (const item of value) {
265
+ appendQueryValue(params, key, item)
266
+ }
267
+ return
268
+ }
269
+ if (typeof value === 'object') {
270
+ params.append(key, JSON.stringify(value))
271
+ return
272
+ }
273
+ params.append(key, String(value))
274
+ }
275
+
276
+ function queryString(query: Record<string, unknown>): string {
277
+ const params = new URLSearchParams()
278
+ for (const [key, value] of Object.entries(query)) {
279
+ appendQueryValue(params, key, value)
280
+ }
281
+ const serialized = params.toString()
282
+ return serialized ? `?${serialized}` : ''
283
+ }
284
+
285
+ function joinBaseUrl(baseUrl: string, path: string): string {
286
+ if (!baseUrl) return path
287
+ const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl
288
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`
289
+ return `${normalizedBase}${normalizedPath}`
290
+ }
291
+
292
+ /** Build the concrete URL for a generated plugin endpoint without making a request. */
293
+ export function buildPluginEndpointUrl(
294
+ contract: PluginContract,
295
+ endpoint: PluginEndpointDefinition,
296
+ payload?: unknown,
297
+ options: BuildPluginEndpointUrlOptions = {},
298
+ ): string {
299
+ const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
300
+ const pathWithQuery = `${parts.path}${queryString(parts.query)}`
301
+ if (options.includeBaseUrl === false) return pathWithQuery
302
+ return joinBaseUrl(parts.baseUrl, pathWithQuery)
303
+ }
304
+
305
+ /** Create a typed plugin API client from a generated MINT plugin contract. */
306
+ export function createPluginClient<TClient extends object = GeneratedPluginClient>(
307
+ contract: PluginContract,
308
+ options: CreatePluginClientOptions,
309
+ ): TClient {
310
+ const client: Record<string, PluginEndpointCaller> = {}
311
+
312
+ for (const [name, endpoint] of Object.entries(options.endpoints)) {
313
+ client[name] = async (payload?: unknown) => {
314
+ const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
315
+ const api = useApi({ baseUrl: parts.baseUrl })
316
+ const queryConfig = hasKeys(parts.query) ? { params: parts.query } : undefined
317
+
318
+ if (endpoint.method === 'get') {
319
+ return api.get(parts.path, queryConfig)
320
+ }
321
+
322
+ if (endpoint.method === 'delete') {
323
+ const body = requestBodyFromPayload(payload, parts.requestPayload, endpoint)
324
+ const config = body === undefined
325
+ ? queryConfig
326
+ : { ...queryConfig, data: body }
327
+ return api.delete(parts.path, config)
328
+ }
329
+
330
+ const body = requestBodyFromPayload(payload, parts.requestPayload, endpoint)
331
+
332
+ if (endpoint.method === 'post') return api.post(parts.path, body, queryConfig)
333
+ if (endpoint.method === 'put') return api.put(parts.path, body, queryConfig)
334
+ if (endpoint.method === 'patch') return api.patch(parts.path, body, queryConfig)
335
+ throw new Error(`[MINT SDK] Unsupported plugin endpoint method: ${endpoint.method}`)
336
+ }
337
+ }
338
+
339
+ return client as TClient
340
+ }
341
+
342
+ /** Return a generated plugin client from setup code with a stable typed identity. */
343
+ export function usePluginClient<TClient>(client: TClient): TClient {
344
+ return client
345
+ }
346
+
347
+ /** Read plugin settings exposed through the platform context. */
348
+ export function usePluginSettings<TSettings = Record<string, unknown>>() {
349
+ const { plugin } = usePlatformContext()
350
+ const settings = computed(() => plugin.value?.settings as TSettings | undefined)
351
+ return { settings }
352
+ }
353
+
354
+ /** Read and optionally load the current platform experiment for integrated plugin views. */
355
+ export function useCurrentExperiment<TExperiment = unknown>(
356
+ options: UseCurrentExperimentOptions = {},
357
+ ): UseCurrentExperimentReturn<TExperiment> {
358
+ const api = useApi({ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl })
359
+ const experiment = shallowRef<TExperiment | undefined>(currentExperimentFromContext<TExperiment>())
360
+ const request = useRequestSyncState('Failed to load current experiment')
361
+ const isLoading = request.loading
362
+ const error = request.error
363
+ const lastLoadedAt = request.lastLoadedAt
364
+
365
+ const experimentId = computed<number | undefined>(() => {
366
+ return resolveCurrentExperimentId()
367
+ })
368
+ const hasExperiment = computed(() => experimentId.value !== undefined)
369
+
370
+ function requireExperimentId(): number {
371
+ const id = experimentId.value
372
+ if (id === undefined) {
373
+ throw new Error('[MINT SDK] No current experiment is selected.')
374
+ }
375
+ return id
376
+ }
377
+
378
+ async function fetchExperiment(id = experimentId.value): Promise<TExperiment | undefined> {
379
+ if (id === undefined) {
380
+ experiment.value = currentExperimentFromContext<TExperiment>()
381
+ return experiment.value
382
+ }
383
+
384
+ try {
385
+ const result = await request.run(
386
+ () => api.get<TExperiment>(`/experiments/${id}`),
387
+ { success: 'load', errorMessage: 'Failed to load current experiment' },
388
+ )
389
+ experiment.value = result
390
+ return result
391
+ } catch (e) {
392
+ experiment.value = undefined
393
+ return undefined
394
+ }
395
+ }
396
+
397
+ async function refresh(): Promise<TExperiment | undefined> {
398
+ return fetchExperiment()
399
+ }
400
+
401
+ watch(
402
+ experimentId,
403
+ (id) => {
404
+ if (options.immediate === false) return
405
+ if (id === undefined) {
406
+ experiment.value = currentExperimentFromContext<TExperiment>()
407
+ return
408
+ }
409
+ void fetchExperiment(id)
410
+ },
411
+ { immediate: options.immediate !== false },
412
+ )
413
+
414
+ return {
415
+ experimentId,
416
+ hasExperiment,
417
+ experiment,
418
+ isLoading,
419
+ error,
420
+ lastLoadedAt,
421
+ requireExperimentId,
422
+ fetch: fetchExperiment,
423
+ refresh,
424
+ }
425
+ }
@@ -1,15 +1,28 @@
1
1
  import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
2
2
  import { useApi } from './useApi'
3
3
  import { usePlatformContext } from './usePlatformContext'
4
+ import { useRequestSyncState } from './useRequestSyncState'
4
5
 
5
6
  export interface UsePluginConfigReturn {
7
+ /** Editable plugin configuration values. */
6
8
  config: Ref<Record<string, unknown>>
9
+ /** Whether the config is currently loading. */
7
10
  isLoading: Ref<boolean>
11
+ /** Whether the config is currently saving. */
8
12
  isSaving: Ref<boolean>
13
+ /** Error message from the last failed operation, or null. */
9
14
  error: Ref<string | null>
15
+ /** Timestamp of the last successful load, or null. */
16
+ lastLoadedAt: Ref<Date | null>
17
+ /** Timestamp of the last successful save, or null. */
18
+ lastSavedAt: Ref<Date | null>
19
+ /** Whether local config differs from the last loaded or saved snapshot. */
10
20
  isDirty: ComputedRef<boolean>
21
+ /** Load plugin configuration from the platform. */
11
22
  load: () => Promise<void>
23
+ /** Save plugin configuration to the platform. */
12
24
  save: () => Promise<boolean>
25
+ /** Restore the last loaded or saved configuration snapshot. */
13
26
  reset: () => void
14
27
  }
15
28
 
@@ -22,9 +35,12 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
22
35
 
23
36
  const config = ref<Record<string, unknown>>({})
24
37
  const savedConfig = ref<Record<string, unknown>>({})
38
+ const request = useRequestSyncState('Plugin config request failed.')
25
39
  const isLoading = ref(false)
26
40
  const isSaving = ref(false)
27
- const error = ref<string | null>(null)
41
+ const error = request.error
42
+ const lastLoadedAt = request.lastLoadedAt
43
+ const lastSavedAt = request.lastSavedAt
28
44
 
29
45
  const isDirty = computed(() => {
30
46
  return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value)
@@ -35,15 +51,17 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
35
51
  if (!name) return
36
52
 
37
53
  isLoading.value = true
38
- error.value = null
39
54
  try {
40
- const response = await api.get<{ plugin_name: string; config: Record<string, unknown> }>(
41
- `/plugins/${encodeURIComponent(name)}/config`,
55
+ const response = await request.run(
56
+ () => api.get<{ plugin_name: string; config: Record<string, unknown> }>(
57
+ `/plugins/${encodeURIComponent(name)}/config`,
58
+ ),
59
+ { success: 'load', errorMessage: 'Failed to load plugin config' },
42
60
  )
43
61
  config.value = { ...response.config }
44
62
  savedConfig.value = { ...response.config }
45
- } catch (e) {
46
- error.value = e instanceof Error ? e.message : 'Failed to load plugin config'
63
+ } catch {
64
+ // Error state is handled by request.run().
47
65
  } finally {
48
66
  isLoading.value = false
49
67
  }
@@ -54,17 +72,18 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
54
72
  if (!name) return false
55
73
 
56
74
  isSaving.value = true
57
- error.value = null
58
75
  try {
59
- const response = await api.patch<{ plugin_name: string; config: Record<string, unknown> }>(
60
- `/plugins/${encodeURIComponent(name)}/config`,
61
- { config: config.value },
76
+ const response = await request.run(
77
+ () => api.patch<{ plugin_name: string; config: Record<string, unknown> }>(
78
+ `/plugins/${encodeURIComponent(name)}/config`,
79
+ { config: config.value },
80
+ ),
81
+ { success: 'save', errorMessage: 'Failed to save plugin config' },
62
82
  )
63
83
  config.value = { ...response.config }
64
84
  savedConfig.value = { ...response.config }
65
85
  return true
66
- } catch (e) {
67
- error.value = e instanceof Error ? e.message : 'Failed to save plugin config'
86
+ } catch {
68
87
  return false
69
88
  } finally {
70
89
  isSaving.value = false
@@ -73,7 +92,7 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
73
92
 
74
93
  function reset(): void {
75
94
  config.value = { ...savedConfig.value }
76
- error.value = null
95
+ request.clearError()
77
96
  }
78
97
 
79
98
  onMounted(() => {
@@ -85,6 +104,8 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
85
104
  isLoading,
86
105
  isSaving,
87
106
  error,
107
+ lastLoadedAt,
108
+ lastSavedAt,
88
109
  isDirty,
89
110
  load,
90
111
  save,
@@ -0,0 +1,126 @@
1
+ import { ref, type Ref } from 'vue'
2
+
3
+ export type RequestSyncSuccessKind = 'load' | 'save' | 'run'
4
+
5
+ export interface RequestSyncRunOptions {
6
+ success?: RequestSyncSuccessKind
7
+ errorMessage?: string
8
+ }
9
+
10
+ export interface UseRequestSyncStateReturn {
11
+ /** Whether a request is in progress. */
12
+ loading: Ref<boolean>
13
+ /** Message from the last failed request, or null. */
14
+ error: Ref<string | null>
15
+ /** Timestamp of the last successful load operation, or null. */
16
+ lastLoadedAt: Ref<Date | null>
17
+ /** Timestamp of the last successful save operation, or null. */
18
+ lastSavedAt: Ref<Date | null>
19
+ /** Timestamp of the last successful run operation, or null. */
20
+ lastRunAt: Ref<Date | null>
21
+ /** Clear the current error without changing loading or timestamps. */
22
+ clearError: () => void
23
+ /** Convert thrown values into a developer-facing error message. */
24
+ readErrorMessage: (value: unknown, fallback?: string) => string
25
+ /** Store and return a normalized error message. */
26
+ setError: (value: unknown, fallback?: string) => string
27
+ /** Mark the resource as successfully loaded. */
28
+ markLoaded: (date?: Date) => void
29
+ /** Mark the resource as successfully saved. */
30
+ markSaved: (date?: Date) => void
31
+ /** Mark the operation as successfully run. */
32
+ markRun: (date?: Date) => void
33
+ /** Run a request with shared loading/error handling and optional sync marking. */
34
+ run: <T>(operation: () => Promise<T>, options?: RequestSyncRunOptions) => Promise<T>
35
+ }
36
+
37
+ /** Shared loading/error/timestamp state for generated plugin request helpers. */
38
+ export function useRequestSyncState(
39
+ defaultErrorMessage = 'Request failed.',
40
+ ): UseRequestSyncStateReturn {
41
+ const loading = ref(false)
42
+ const error = ref<string | null>(null)
43
+ const lastLoadedAt = ref<Date | null>(null)
44
+ const lastSavedAt = ref<Date | null>(null)
45
+ const lastRunAt = ref<Date | null>(null)
46
+
47
+ function clearError(): void {
48
+ error.value = null
49
+ }
50
+
51
+ function readErrorMessage(value: unknown, fallback = defaultErrorMessage): string {
52
+ if (value instanceof Error && value.message) {
53
+ return value.message
54
+ }
55
+ if (typeof value === 'string' && value.trim()) {
56
+ return value
57
+ }
58
+ if (
59
+ typeof value === 'object'
60
+ && value !== null
61
+ && 'message' in value
62
+ && typeof (value as { message?: unknown }).message === 'string'
63
+ && (value as { message: string }).message
64
+ ) {
65
+ return (value as { message: string }).message
66
+ }
67
+ return fallback
68
+ }
69
+
70
+ function setError(value: unknown, fallback?: string): string {
71
+ const message = readErrorMessage(value, fallback)
72
+ error.value = message
73
+ return message
74
+ }
75
+
76
+ function markLoaded(date = new Date()): void {
77
+ lastLoadedAt.value = date
78
+ }
79
+
80
+ function markSaved(date = new Date()): void {
81
+ lastSavedAt.value = date
82
+ }
83
+
84
+ function markRun(date = new Date()): void {
85
+ lastRunAt.value = date
86
+ }
87
+
88
+ async function run<T>(
89
+ operation: () => Promise<T>,
90
+ options: RequestSyncRunOptions = {},
91
+ ): Promise<T> {
92
+ loading.value = true
93
+ clearError()
94
+ try {
95
+ const response = await operation()
96
+ if (options.success === 'load') {
97
+ markLoaded()
98
+ } else if (options.success === 'save') {
99
+ markSaved()
100
+ } else if (options.success === 'run') {
101
+ markRun()
102
+ }
103
+ return response
104
+ } catch (err) {
105
+ setError(err, options.errorMessage)
106
+ throw err
107
+ } finally {
108
+ loading.value = false
109
+ }
110
+ }
111
+
112
+ return {
113
+ loading,
114
+ error,
115
+ lastLoadedAt,
116
+ lastSavedAt,
117
+ lastRunAt,
118
+ clearError,
119
+ readErrorMessage,
120
+ setError,
121
+ markLoaded,
122
+ markSaved,
123
+ markRun,
124
+ run,
125
+ }
126
+ }