@morscherlab/mint-sdk 1.0.0-rc.1 → 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/README.md +9 -1
  2. package/dist/__tests__/components/LcmsSequenceTable.test.d.ts +1 -0
  3. package/dist/__tests__/components/ProgressBar.test.d.ts +1 -0
  4. package/dist/__tests__/components/RackEditor.test.d.ts +1 -0
  5. package/dist/__tests__/components/SequenceProgressBar.test.d.ts +1 -0
  6. package/dist/__tests__/composables/useExperimentSamples.test.d.ts +1 -0
  7. package/dist/__tests__/utils/instrument.test.d.ts +1 -0
  8. package/dist/__tests__/utils/lcms.test.d.ts +1 -0
  9. package/dist/__tests__/utils/permissions.test.d.ts +1 -0
  10. package/dist/__tests__/utils/rack.test.d.ts +1 -0
  11. package/dist/{auth-CBG3bWEc.js → auth-B7g4J4ZF.js} +99 -5
  12. package/dist/auth-B7g4J4ZF.js.map +1 -0
  13. package/dist/components/AppSidebar.vue.d.ts +3 -3
  14. package/dist/components/AutoGroupModal.vue.d.ts +1 -1
  15. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  16. package/dist/components/BaseToggle.vue.d.ts +2 -2
  17. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
  18. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
  19. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +1 -1
  20. package/dist/components/ControlWorkspaceView.vue.d.ts +3 -3
  21. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +2 -2
  22. package/dist/components/FormulaInput.vue.d.ts +1 -1
  23. package/dist/components/InstrumentAlertLog.vue.d.ts +22 -0
  24. package/dist/components/InstrumentStateBadge.vue.d.ts +11 -0
  25. package/dist/components/InstrumentStatusCard.vue.d.ts +13 -0
  26. package/dist/components/LcmsSequenceTable.vue.d.ts +26 -0
  27. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -3
  28. package/dist/components/ProgressBar.vue.d.ts +1 -0
  29. package/dist/components/RackEditor.vue.d.ts +41 -3
  30. package/dist/components/ReagentList.vue.d.ts +1 -1
  31. package/dist/components/SampleSelector.vue.d.ts +5 -2
  32. package/dist/components/SegmentedControl.vue.d.ts +2 -0
  33. package/dist/components/SequenceInput.vue.d.ts +1 -1
  34. package/dist/components/SequenceProgressBar.vue.d.ts +15 -0
  35. package/dist/components/SettingsModal.vue.d.ts +3 -1
  36. package/dist/components/TagsInput.vue.d.ts +1 -1
  37. package/dist/components/WellPlate.vue.d.ts +42 -3
  38. package/dist/components/index.d.ts +5 -0
  39. package/dist/components/index.js +3 -3
  40. package/dist/{components-5KSfsVqf.js → components-DafPc4rM.js} +2091 -1051
  41. package/dist/components-DafPc4rM.js.map +1 -0
  42. package/dist/composables/controlComponentBindings.d.ts +7 -0
  43. package/dist/composables/controlSchemaAdapters.d.ts +20 -0
  44. package/dist/composables/controlSchemaFormFields.d.ts +3 -0
  45. package/dist/composables/controlSchemaLayout.d.ts +7 -0
  46. package/dist/composables/controlSchemaNormalize.d.ts +15 -0
  47. package/dist/composables/controlSchemaUtils.d.ts +9 -0
  48. package/dist/composables/controlWorkspaceOptions.d.ts +2 -0
  49. package/dist/composables/experimentDesignData.d.ts +17 -0
  50. package/dist/composables/index.d.ts +2 -0
  51. package/dist/composables/index.js +4 -4
  52. package/dist/composables/useControlSchema.d.ts +15 -30
  53. package/dist/composables/useControlWorkspace.d.ts +5 -0
  54. package/dist/composables/useExperimentData.d.ts +11 -3
  55. package/dist/composables/useExperimentSamples.d.ts +42 -0
  56. package/dist/composables/usePlatformContext.d.ts +54 -0
  57. package/dist/{composables-D4Myb30a.js → composables-BMkPQhVK.js} +5 -3
  58. package/dist/composables-BMkPQhVK.js.map +1 -0
  59. package/dist/index.d.ts +4 -0
  60. package/dist/index.js +168 -6
  61. package/dist/index.js.map +1 -0
  62. package/dist/install.js +2 -2
  63. package/dist/instrument.d.ts +7 -0
  64. package/dist/lcms.d.ts +27 -0
  65. package/dist/permissions.d.ts +46 -0
  66. package/dist/stores/auth.d.ts +74 -2
  67. package/dist/stores/index.js +1 -1
  68. package/dist/styles.css +3316 -1216
  69. package/dist/templates/adapters.d.ts +14 -47
  70. package/dist/templates/assayLookups.d.ts +3 -0
  71. package/dist/templates/assayMatrixAdapters.d.ts +6 -0
  72. package/dist/templates/assayMatrixBuilder.d.ts +2 -0
  73. package/dist/templates/assayNormalizers.d.ts +4 -0
  74. package/dist/templates/builderDefaults.d.ts +1 -0
  75. package/dist/templates/builderIdUtils.d.ts +4 -0
  76. package/dist/templates/builderPresetControls.d.ts +10 -0
  77. package/dist/templates/builderReadUtils.d.ts +8 -0
  78. package/dist/templates/builders.d.ts +26 -63
  79. package/dist/templates/calibrationCurveAdapters.d.ts +4 -0
  80. package/dist/templates/calibrationCurveBuilder.d.ts +2 -0
  81. package/dist/templates/calibrationNormalizers.d.ts +5 -0
  82. package/dist/templates/componentBindingCatalog.d.ts +25 -0
  83. package/dist/templates/componentBindingHelpers.d.ts +17 -0
  84. package/dist/templates/componentDoseResponseProps.d.ts +3 -0
  85. package/dist/templates/componentGenericProps.d.ts +8 -0
  86. package/dist/templates/componentPlateHelpers.d.ts +5 -0
  87. package/dist/templates/componentPlateMapProps.d.ts +3 -0
  88. package/dist/templates/componentPropsFactory.d.ts +3 -0
  89. package/dist/templates/componentQpcrPlateProps.d.ts +3 -0
  90. package/dist/templates/componentTargetResolvers.d.ts +5 -0
  91. package/dist/templates/componentTemplateProps.d.ts +3 -0
  92. package/dist/templates/controlSchemaClone.d.ts +4 -0
  93. package/dist/templates/controlSchemaConstants.d.ts +10 -0
  94. package/dist/templates/controlSchemaMerge.d.ts +3 -0
  95. package/dist/templates/controlSchemaTargets.d.ts +17 -0
  96. package/dist/templates/controlSchemaTypes.d.ts +4 -0
  97. package/dist/templates/controlSchemas.d.ts +4 -4
  98. package/dist/templates/defaultBioTemplateBuilder.d.ts +2 -0
  99. package/dist/templates/doseResponseAdapters.d.ts +4 -0
  100. package/dist/templates/doseResponseBuilder.d.ts +2 -0
  101. package/dist/templates/elisaAssayCollectionBuilder.d.ts +2 -0
  102. package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +2 -0
  103. package/dist/templates/flowCytometryPanelBuilder.d.ts +2 -0
  104. package/dist/templates/flowNormalizers.d.ts +8 -0
  105. package/dist/templates/flowPanelAdapters.d.ts +4 -0
  106. package/dist/templates/index.d.ts +2 -2
  107. package/dist/templates/index.js +2 -2
  108. package/dist/templates/instrumentRunAdapterHelpers.d.ts +8 -0
  109. package/dist/templates/instrumentRunAdapters.d.ts +8 -0
  110. package/dist/templates/instrumentRunBuilder.d.ts +2 -0
  111. package/dist/templates/lcmsBatchCollectionBuilder.d.ts +2 -0
  112. package/dist/templates/plateGeometry.d.ts +4 -0
  113. package/dist/templates/plateMapAdapters.d.ts +3 -0
  114. package/dist/templates/plateMapBuilder.d.ts +2 -0
  115. package/dist/templates/presetControlSchemas.d.ts +534 -0
  116. package/dist/templates/presets.d.ts +12 -0
  117. package/dist/templates/protocolAdapters.d.ts +5 -0
  118. package/dist/templates/protocolNormalizers.d.ts +6 -0
  119. package/dist/templates/protocolStepsBuilder.d.ts +2 -0
  120. package/dist/templates/qpcrAdapters.d.ts +5 -0
  121. package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +2 -0
  122. package/dist/templates/qpcrPlateBuilder.d.ts +2 -0
  123. package/dist/templates/reagentAdapters.d.ts +5 -0
  124. package/dist/templates/reagentListBuilder.d.ts +2 -0
  125. package/dist/templates/runNormalizers.d.ts +10 -0
  126. package/dist/templates/sampleNormalizers.d.ts +4 -0
  127. package/dist/templates/samplePrepAdapters.d.ts +4 -0
  128. package/dist/templates/samplePrepBuilder.d.ts +2 -0
  129. package/dist/templates/sampleSheetAdapters.d.ts +5 -0
  130. package/dist/templates/sampleSheetBuilder.d.ts +2 -0
  131. package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +2 -0
  132. package/dist/templates/targetedMetabolomicsHelpers.d.ts +5 -0
  133. package/dist/templates/templateControlSchemas.d.ts +400 -0
  134. package/dist/templates/templateEnvelopes.d.ts +9 -0
  135. package/dist/templates/templatePackCollectionBuilder.d.ts +2 -0
  136. package/dist/templates/templatePresetCollectionBuilder.d.ts +18 -0
  137. package/dist/templates/templateValidators.d.ts +13 -0
  138. package/dist/templates/timeCourseAdapters.d.ts +5 -0
  139. package/dist/templates/timeCourseBuilder.d.ts +2 -0
  140. package/dist/templates/types.d.ts +16 -1
  141. package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
  142. package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
  143. package/dist/{templates-BSlxwV2c.js → templates-bUAWMn5L.js} +3610 -3154
  144. package/dist/templates-bUAWMn5L.js.map +1 -0
  145. package/dist/types/auth.d.ts +2 -0
  146. package/dist/types/components.d.ts +32 -3
  147. package/dist/types/form-builder.d.ts +2 -1
  148. package/dist/types/index.d.ts +4 -1
  149. package/dist/types/instrument.d.ts +56 -0
  150. package/dist/types/platform.d.ts +3 -0
  151. package/dist/{useExperimentData-BbbdI5xT.js → useProtocolTemplates-QZtHFFH2.js} +534 -359
  152. package/dist/useProtocolTemplates-QZtHFFH2.js.map +1 -0
  153. package/dist/utils/rack.d.ts +47 -0
  154. package/package.json +1 -1
  155. package/src/__tests__/components/AppTopBar.test.ts +15 -0
  156. package/src/__tests__/components/BaseTabs.test.ts +15 -0
  157. package/src/__tests__/components/LcmsSequenceTable.test.ts +57 -0
  158. package/src/__tests__/components/ProgressBar.test.ts +18 -0
  159. package/src/__tests__/components/RackEditor.test.ts +125 -0
  160. package/src/__tests__/components/SampleSelector.test.ts +25 -0
  161. package/src/__tests__/components/SegmentedControl.test.ts +45 -0
  162. package/src/__tests__/components/SequenceProgressBar.test.ts +39 -0
  163. package/src/__tests__/components/SettingsModal.test.ts +83 -2
  164. package/src/__tests__/composables/useControlSchema.test.ts +4 -0
  165. package/src/__tests__/composables/useExperimentData.test.ts +23 -0
  166. package/src/__tests__/composables/useExperimentSamples.test.ts +91 -0
  167. package/src/__tests__/templates/templates.test.ts +86 -0
  168. package/src/__tests__/utils/instrument.test.ts +47 -0
  169. package/src/__tests__/utils/lcms.test.ts +73 -0
  170. package/src/__tests__/utils/permissions.test.ts +50 -0
  171. package/src/__tests__/utils/rack.test.ts +120 -0
  172. package/src/components/AppTopBar.vue +1 -0
  173. package/src/components/BaseTabs.vue +22 -1
  174. package/src/components/InstrumentAlertLog.vue +191 -0
  175. package/src/components/InstrumentStateBadge.vue +50 -0
  176. package/src/components/InstrumentStatusCard.vue +188 -0
  177. package/src/components/LcmsSequenceTable.vue +191 -0
  178. package/src/components/ProgressBar.vue +3 -0
  179. package/src/components/RackEditor.vue +73 -2
  180. package/src/components/SampleSelector.vue +28 -9
  181. package/src/components/SegmentedControl.story.vue +17 -0
  182. package/src/components/SegmentedControl.vue +14 -3
  183. package/src/components/SequenceProgressBar.vue +71 -0
  184. package/src/components/SettingsModal.vue +42 -2
  185. package/src/components/WellPlate.vue +142 -21
  186. package/src/components/index.ts +5 -0
  187. package/src/components/internal/WellEditPopupInternal.vue +1 -0
  188. package/src/composables/controlComponentBindings.ts +80 -0
  189. package/src/composables/controlSchemaAdapters.ts +196 -0
  190. package/src/composables/controlSchemaFormFields.ts +61 -0
  191. package/src/composables/controlSchemaLayout.ts +59 -0
  192. package/src/composables/controlSchemaNormalize.ts +101 -0
  193. package/src/composables/controlSchemaUtils.ts +36 -0
  194. package/src/composables/controlWorkspaceOptions.ts +21 -0
  195. package/src/composables/experimentDesignData.ts +182 -0
  196. package/src/composables/index.ts +14 -0
  197. package/src/composables/useAuth.ts +4 -0
  198. package/src/composables/useAutoGroup.ts +5 -1
  199. package/src/composables/useControlSchema.ts +62 -618
  200. package/src/composables/useControlWorkspace.ts +207 -0
  201. package/src/composables/useExperimentData.ts +57 -16
  202. package/src/composables/useExperimentSamples.ts +142 -0
  203. package/src/index.ts +27 -0
  204. package/src/instrument.ts +90 -0
  205. package/src/lcms.ts +108 -0
  206. package/src/permissions.ts +143 -0
  207. package/src/stores/auth.ts +31 -3
  208. package/src/styles/components/instrument-monitor.css +478 -0
  209. package/src/styles/components/lcms-sequence-table.css +189 -0
  210. package/src/styles/components/sequence-progress-bar.css +63 -0
  211. package/src/styles/components/tabs.css +9 -0
  212. package/src/styles/components/well-edit-popup.css +7 -1
  213. package/src/styles/components/well-plate.css +5 -0
  214. package/src/styles/index.css +3 -0
  215. package/src/templates/adapters.ts +89 -930
  216. package/src/templates/assayLookups.ts +33 -0
  217. package/src/templates/assayMatrixAdapters.ts +78 -0
  218. package/src/templates/assayMatrixBuilder.ts +59 -0
  219. package/src/templates/assayNormalizers.ts +34 -0
  220. package/src/templates/builderDefaults.ts +11 -0
  221. package/src/templates/builderIdUtils.ts +20 -0
  222. package/src/templates/builderPresetControls.ts +165 -0
  223. package/src/templates/builderReadUtils.ts +57 -0
  224. package/src/templates/builders.ts +122 -2149
  225. package/src/templates/calibrationCurveAdapters.ts +59 -0
  226. package/src/templates/calibrationCurveBuilder.ts +99 -0
  227. package/src/templates/calibrationNormalizers.ts +60 -0
  228. package/src/templates/componentBindingCatalog.ts +90 -0
  229. package/src/templates/componentBindingHelpers.ts +93 -0
  230. package/src/templates/componentBindings.ts +12 -461
  231. package/src/templates/componentDoseResponseProps.ts +42 -0
  232. package/src/templates/componentGenericProps.ts +77 -0
  233. package/src/templates/componentPlateHelpers.ts +29 -0
  234. package/src/templates/componentPlateMapProps.ts +32 -0
  235. package/src/templates/componentPropsFactory.ts +21 -0
  236. package/src/templates/componentQpcrPlateProps.ts +28 -0
  237. package/src/templates/componentTargetResolvers.ts +69 -0
  238. package/src/templates/componentTemplateProps.ts +78 -0
  239. package/src/templates/controlSchemaClone.ts +32 -0
  240. package/src/templates/controlSchemaConstants.ts +11 -0
  241. package/src/templates/controlSchemaMerge.ts +40 -0
  242. package/src/templates/controlSchemaTargets.ts +87 -0
  243. package/src/templates/controlSchemaTypes.ts +20 -0
  244. package/src/templates/controlSchemas.ts +22 -636
  245. package/src/templates/defaultBioTemplateBuilder.ts +124 -0
  246. package/src/templates/doseResponseAdapters.ts +45 -0
  247. package/src/templates/doseResponseBuilder.ts +44 -0
  248. package/src/templates/elisaAssayCollectionBuilder.ts +62 -0
  249. package/src/templates/flowCytometryAssayCollectionBuilder.ts +41 -0
  250. package/src/templates/flowCytometryPanelBuilder.ts +53 -0
  251. package/src/templates/flowNormalizers.ts +58 -0
  252. package/src/templates/flowPanelAdapters.ts +58 -0
  253. package/src/templates/index.ts +2 -0
  254. package/src/templates/instrumentRunAdapterHelpers.ts +94 -0
  255. package/src/templates/instrumentRunAdapters.ts +163 -0
  256. package/src/templates/instrumentRunBuilder.ts +97 -0
  257. package/src/templates/lcmsBatchCollectionBuilder.ts +38 -0
  258. package/src/templates/plateGeometry.ts +62 -0
  259. package/src/templates/plateMapAdapters.ts +36 -0
  260. package/src/templates/plateMapBuilder.ts +43 -0
  261. package/src/templates/presetControlSchemas.ts +258 -0
  262. package/src/templates/presets.ts +23 -0
  263. package/src/templates/protocolAdapters.ts +69 -0
  264. package/src/templates/protocolNormalizers.ts +37 -0
  265. package/src/templates/protocolStepsBuilder.ts +36 -0
  266. package/src/templates/qpcrAdapters.ts +104 -0
  267. package/src/templates/qpcrExpressionCollectionBuilder.ts +33 -0
  268. package/src/templates/qpcrPlateBuilder.ts +96 -0
  269. package/src/templates/reagentAdapters.ts +77 -0
  270. package/src/templates/reagentListBuilder.ts +30 -0
  271. package/src/templates/runNormalizers.ts +63 -0
  272. package/src/templates/sampleNormalizers.ts +58 -0
  273. package/src/templates/samplePrepAdapters.ts +63 -0
  274. package/src/templates/samplePrepBuilder.ts +51 -0
  275. package/src/templates/sampleSheetAdapters.ts +75 -0
  276. package/src/templates/sampleSheetBuilder.ts +23 -0
  277. package/src/templates/targetedMetabolomicsCollectionBuilder.ts +79 -0
  278. package/src/templates/targetedMetabolomicsHelpers.ts +102 -0
  279. package/src/templates/templateControlSchemas.ts +320 -0
  280. package/src/templates/templateEnvelopes.ts +137 -0
  281. package/src/templates/templatePackCollectionBuilder.ts +23 -0
  282. package/src/templates/templatePresetCollectionBuilder.ts +139 -0
  283. package/src/templates/templateValidators.ts +414 -0
  284. package/src/templates/timeCourseAdapters.ts +73 -0
  285. package/src/templates/timeCourseBuilder.ts +64 -0
  286. package/src/templates/types.ts +17 -0
  287. package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
  288. package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -0
  289. package/src/types/auth.ts +3 -0
  290. package/src/types/components.ts +45 -3
  291. package/src/types/form-builder.ts +2 -1
  292. package/src/types/index.ts +35 -0
  293. package/src/types/instrument.ts +61 -0
  294. package/src/types/platform.ts +4 -0
  295. package/src/utils/rack.ts +209 -0
  296. package/dist/auth-CBG3bWEc.js.map +0 -1
  297. package/dist/components-5KSfsVqf.js.map +0 -1
  298. package/dist/composables-D4Myb30a.js.map +0 -1
  299. package/dist/templates-BSlxwV2c.js.map +0 -1
  300. package/dist/useExperimentData-BbbdI5xT.js.map +0 -1
@@ -0,0 +1,191 @@
1
+ <script setup lang="ts">
2
+ /** Filterable instrument alert/event list with optional acknowledge actions. */
3
+ import { computed, ref } from 'vue'
4
+ import type { InstrumentAlert, InstrumentAlertLevel } from '../types'
5
+
6
+ interface Props {
7
+ alerts?: InstrumentAlert[]
8
+ title?: string
9
+ acknowledgeable?: boolean
10
+ emptyMessage?: string
11
+ filteredEmptyMessage?: string
12
+ locale?: string
13
+ }
14
+
15
+ const props = withDefaults(defineProps<Props>(), {
16
+ alerts: () => [],
17
+ title: 'Event Log',
18
+ acknowledgeable: true,
19
+ emptyMessage: 'No alerts received yet',
20
+ filteredEmptyMessage: 'No alerts match current filters',
21
+ locale: undefined,
22
+ })
23
+
24
+ const emit = defineEmits<{
25
+ acknowledge: [alert: InstrumentAlert]
26
+ }>()
27
+
28
+ const levelConfig: Record<InstrumentAlertLevel, { label: string; cssClass: string; color: string }> = {
29
+ critical: { label: 'Critical', cssClass: 'mint-instrument-alert--critical', color: '#ef4444' },
30
+ warning: { label: 'Warning', cssClass: 'mint-instrument-alert--warning', color: '#eab308' },
31
+ info: { label: 'Info', cssClass: 'mint-instrument-alert--info', color: '#3b82f6' },
32
+ }
33
+
34
+ const levels: InstrumentAlertLevel[] = ['critical', 'warning', 'info']
35
+ const activeLevels = ref<Set<InstrumentAlertLevel>>(new Set(levels))
36
+ const activeInstrument = ref<string | null>(null)
37
+
38
+ const instruments = computed(() => {
39
+ const counts = new Map<string, number>()
40
+ for (const alert of props.alerts) {
41
+ const name = instrumentName(alert)
42
+ counts.set(name, (counts.get(name) ?? 0) + 1)
43
+ }
44
+ return [...counts.entries()].map(([name, count]) => ({ name, count }))
45
+ })
46
+
47
+ const alertCounts = computed(() => {
48
+ const counts: Record<InstrumentAlertLevel, number> = { critical: 0, warning: 0, info: 0 }
49
+ for (const alert of props.alerts) {
50
+ if (alert.level in counts) counts[alert.level]++
51
+ }
52
+ return counts
53
+ })
54
+
55
+ const filteredAlerts = computed(() =>
56
+ [...props.alerts]
57
+ .filter(alert => activeLevels.value.has(alert.level))
58
+ .filter(alert => !activeInstrument.value || instrumentName(alert) === activeInstrument.value)
59
+ .sort((a, b) => alertTime(b) - alertTime(a))
60
+ )
61
+
62
+ function toggleLevel(level: InstrumentAlertLevel) {
63
+ const next = new Set(activeLevels.value)
64
+ if (next.has(level)) {
65
+ if (next.size > 1) next.delete(level)
66
+ } else {
67
+ next.add(level)
68
+ }
69
+ activeLevels.value = next
70
+ }
71
+
72
+ function toggleInstrument(name: string) {
73
+ activeInstrument.value = activeInstrument.value === name ? null : name
74
+ }
75
+
76
+ function instrumentName(alert: InstrumentAlert): string {
77
+ return alert.instrument_name || alert.instrument_id
78
+ }
79
+
80
+ function alertTime(alert: InstrumentAlert): number {
81
+ if (!alert.timestamp) return 0
82
+ const date = alert.timestamp instanceof Date ? alert.timestamp : new Date(alert.timestamp)
83
+ return Number.isNaN(date.getTime()) ? 0 : date.getTime()
84
+ }
85
+
86
+ function timeAgo(value: string | Date | undefined): string {
87
+ if (!value) return ''
88
+ const date = value instanceof Date ? value : new Date(value)
89
+ if (Number.isNaN(date.getTime())) return ''
90
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
91
+ if (seconds < 60) return 'just now'
92
+ const minutes = Math.floor(seconds / 60)
93
+ if (minutes < 60) return `${minutes}m ago`
94
+ const hours = Math.floor(minutes / 60)
95
+ if (hours < 24) return `${hours}h ago`
96
+ const days = Math.floor(hours / 24)
97
+ if (days < 7) return `${days}d ago`
98
+ return date.toLocaleDateString(props.locale, { month: 'short', day: 'numeric' })
99
+ }
100
+
101
+ function alertDetail(alert: InstrumentAlert): string {
102
+ return alert.body?.detail ?? alert.message ?? alert.rule ?? ''
103
+ }
104
+ </script>
105
+
106
+ <template>
107
+ <div class="mint-instrument-alert-log">
108
+ <div class="mint-instrument-alert-log__header">
109
+ <span class="mint-instrument-alert-log__title">{{ title }}</span>
110
+ </div>
111
+
112
+ <div class="mint-instrument-alert-log__filters">
113
+ <div class="mint-instrument-alert-log__filter-row">
114
+ <button
115
+ v-for="level in levels"
116
+ :key="level"
117
+ type="button"
118
+ class="mint-instrument-alert-log__filter"
119
+ :class="{ 'mint-instrument-alert-log__filter--active': activeLevels.has(level) }"
120
+ :style="activeLevels.has(level) ? { '--filter-color': levelConfig[level].color } : undefined"
121
+ @click="toggleLevel(level)"
122
+ >
123
+ <span
124
+ class="mint-instrument-alert-log__filter-dot"
125
+ :style="{ backgroundColor: levelConfig[level].color }"
126
+ />
127
+ {{ levelConfig[level].label }}
128
+ <span class="mint-instrument-alert-log__filter-count">{{ alertCounts[level] }}</span>
129
+ </button>
130
+ </div>
131
+ <div v-if="instruments.length > 1" class="mint-instrument-alert-log__filter-row">
132
+ <button
133
+ v-for="instrument in instruments"
134
+ :key="instrument.name"
135
+ type="button"
136
+ class="mint-instrument-alert-log__filter"
137
+ :class="{ 'mint-instrument-alert-log__filter--active': activeInstrument === instrument.name }"
138
+ @click="toggleInstrument(instrument.name)"
139
+ >
140
+ {{ instrument.name }}
141
+ <span class="mint-instrument-alert-log__filter-count">{{ instrument.count }}</span>
142
+ </button>
143
+ </div>
144
+ </div>
145
+
146
+ <TransitionGroup name="mint-instrument-alert" tag="div" class="mint-instrument-alert-log__list">
147
+ <div
148
+ v-for="alert in filteredAlerts"
149
+ :key="alert.id ?? `${alert.instrument_id}-${alert.rule ?? alert.message}-${alert.timestamp ?? ''}`"
150
+ class="mint-instrument-alert"
151
+ :class="[
152
+ levelConfig[alert.level]?.cssClass,
153
+ { 'mint-instrument-alert--acknowledged': alert.acknowledged },
154
+ ]"
155
+ >
156
+ <div class="mint-instrument-alert__header">
157
+ <span class="mint-instrument-alert__instrument">{{ instrumentName(alert) }}</span>
158
+ <span v-if="alert.body?.source" class="mint-instrument-alert__source">
159
+ {{ alert.body.source }}
160
+ </span>
161
+ <span class="mint-instrument-alert__time">{{ timeAgo(alert.timestamp) }}</span>
162
+ </div>
163
+ <div class="mint-instrument-alert__detail">{{ alertDetail(alert) }}</div>
164
+ <div
165
+ v-if="alert.body?.code != null || (acknowledgeable && !alert.acknowledged && alert.level !== 'info')"
166
+ class="mint-instrument-alert__footer"
167
+ >
168
+ <span v-if="alert.body?.code != null" class="mint-instrument-alert__code">
169
+ Code {{ alert.body.code }}
170
+ </span>
171
+ <button
172
+ v-if="acknowledgeable && !alert.acknowledged && alert.level !== 'info'"
173
+ type="button"
174
+ class="mint-instrument-alert__ack"
175
+ @click="emit('acknowledge', alert)"
176
+ >
177
+ Acknowledge
178
+ </button>
179
+ </div>
180
+ </div>
181
+
182
+ <div v-if="filteredAlerts.length === 0" key="empty" class="mint-instrument-alert-log__empty">
183
+ {{ alerts.length === 0 ? emptyMessage : filteredEmptyMessage }}
184
+ </div>
185
+ </TransitionGroup>
186
+ </div>
187
+ </template>
188
+
189
+ <style>
190
+ @import '../styles/components/instrument-monitor.css';
191
+ </style>
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ /** Status badge for shared instrument monitor states. */
3
+ import { computed } from 'vue'
4
+ import type { InstrumentState } from '../types'
5
+
6
+ interface Props {
7
+ state: InstrumentState | string
8
+ label?: string
9
+ pulseWhenRunning?: boolean
10
+ }
11
+
12
+ const props = withDefaults(defineProps<Props>(), {
13
+ label: undefined,
14
+ pulseWhenRunning: true,
15
+ })
16
+
17
+ const stateConfig: Record<InstrumentState, { label: string; tone: string }> = {
18
+ running: { label: 'RUNNING', tone: 'success' },
19
+ standby: { label: 'STANDBY', tone: 'warning' },
20
+ idle: { label: 'IDLE', tone: 'muted' },
21
+ error: { label: 'ERROR', tone: 'error' },
22
+ connected: { label: 'CONNECTED', tone: 'info' },
23
+ disconnected: { label: 'OFFLINE', tone: 'muted' },
24
+ }
25
+
26
+ const normalizedState = computed(() => props.state.toLowerCase())
27
+ const config = computed(() => {
28
+ const match = stateConfig[normalizedState.value as InstrumentState]
29
+ return match ?? { label: props.state.toUpperCase(), tone: 'muted' }
30
+ })
31
+ const shouldPulse = computed(() => props.pulseWhenRunning && normalizedState.value === 'running')
32
+ </script>
33
+
34
+ <template>
35
+ <span
36
+ class="mint-instrument-state"
37
+ :class="[
38
+ `mint-instrument-state--${config.tone}`,
39
+ { 'mint-instrument-state--pulse': shouldPulse },
40
+ { 'mint-instrument-state--offline': normalizedState === 'disconnected' },
41
+ ]"
42
+ >
43
+ <span v-if="shouldPulse" class="mint-instrument-state__dot" />
44
+ {{ label ?? config.label }}
45
+ </span>
46
+ </template>
47
+
48
+ <style>
49
+ @import '../styles/components/instrument-monitor.css';
50
+ </style>
@@ -0,0 +1,188 @@
1
+ <script setup lang="ts">
2
+ /** Compact card for live instrument monitor status, sample, method, sequence progress, and ETA. */
3
+ import { computed } from 'vue'
4
+ import {
5
+ estimateSequenceFinishDate,
6
+ formatSequenceRemaining,
7
+ estimateSequenceRemainingSeconds,
8
+ } from '../instrument'
9
+ import type { InstrumentState, InstrumentStatus, SequenceProgress } from '../types'
10
+ import InstrumentStateBadge from './InstrumentStateBadge.vue'
11
+ import SequenceProgressBar from './SequenceProgressBar.vue'
12
+
13
+ interface Props {
14
+ status: InstrumentStatus
15
+ name?: string
16
+ showPlaceholders?: boolean
17
+ locale?: string
18
+ }
19
+
20
+ const props = withDefaults(defineProps<Props>(), {
21
+ name: undefined,
22
+ showPlaceholders: true,
23
+ locale: undefined,
24
+ })
25
+
26
+ const displayName = computed(() =>
27
+ props.name
28
+ ?? props.status.instrument_name
29
+ ?? props.status.instrument_id
30
+ )
31
+ const isRunning = computed(() => props.status.state === 'running')
32
+ const sampleLabel = computed(() => {
33
+ const sample = props.status.current_sample
34
+ return sample ? (sample.sample_name ?? sample.sample_id ?? sample.file_name) : null
35
+ })
36
+
37
+ const etaDisplay = computed(() => {
38
+ const progress = props.status.sequence_progress
39
+ if (!progress) return null
40
+ const finish = estimateSequenceFinishDate(progress)
41
+ if (!finish) return null
42
+
43
+ const now = new Date()
44
+ const sameDay =
45
+ finish.getFullYear() === now.getFullYear()
46
+ && finish.getMonth() === now.getMonth()
47
+ && finish.getDate() === now.getDate()
48
+
49
+ const time = finish.toLocaleTimeString(props.locale, { hour: 'numeric', minute: '2-digit' })
50
+ if (sameDay) return time
51
+
52
+ const sameYear = finish.getFullYear() === now.getFullYear()
53
+ const date = finish.toLocaleDateString(props.locale, {
54
+ month: 'short',
55
+ day: 'numeric',
56
+ ...(sameYear ? {} : { year: 'numeric' }),
57
+ })
58
+ return `${date}, ${time}`
59
+ })
60
+
61
+ const remainingLabel = computed(() => {
62
+ const progress = props.status.sequence_progress
63
+ if (!progress) return null
64
+ return formatSequenceRemaining(progress)
65
+ })
66
+
67
+ const lastSeenAgo = computed(() => {
68
+ const value = props.status.last_seen ?? props.status.timestamp
69
+ if (!value) return null
70
+ return formatRelativeAge(value)
71
+ })
72
+
73
+ const stateClass = computed(() => `mint-instrument-card--${props.status.state}`)
74
+
75
+ function stateTone(state: InstrumentState | string): string {
76
+ if (state === 'running') return 'success'
77
+ if (state === 'standby') return 'warning'
78
+ if (state === 'error') return 'error'
79
+ if (state === 'connected') return 'info'
80
+ return 'muted'
81
+ }
82
+
83
+ function formatRelativeAge(value: string | Date): string | null {
84
+ const date = value instanceof Date ? value : new Date(value)
85
+ if (Number.isNaN(date.getTime())) return null
86
+ const minutes = Math.floor((Date.now() - date.getTime()) / 60_000)
87
+ if (minutes < 1) return 'just now'
88
+ if (minutes < 60) return `${minutes}m ago`
89
+ const hours = Math.floor(minutes / 60)
90
+ if (hours < 24) return `${hours}h ago`
91
+ const days = Math.floor(hours / 24)
92
+ return days === 1 ? '1 day ago' : `${days} days ago`
93
+ }
94
+
95
+ function hasRemaining(progress: SequenceProgress | null | undefined): boolean {
96
+ const remaining = estimateSequenceRemainingSeconds(progress)
97
+ return remaining !== null && remaining > 0
98
+ }
99
+ </script>
100
+
101
+ <template>
102
+ <div class="mint-instrument-card" :class="stateClass">
103
+ <div class="mint-instrument-card__header">
104
+ <span
105
+ class="mint-instrument-card__dot"
106
+ :class="`mint-instrument-card__dot--${stateTone(status.state)}`"
107
+ />
108
+ <strong class="mint-instrument-card__name">{{ displayName }}</strong>
109
+ <InstrumentStateBadge :state="status.state" />
110
+ </div>
111
+
112
+ <template v-if="isRunning">
113
+ <div v-if="status.current_sample" class="mint-instrument-card__sample">
114
+ <span class="mint-instrument-card__sample-label">Currently Running</span>
115
+ <div class="mint-instrument-card__sample-row">
116
+ <span class="mint-instrument-card__sample-dot" />
117
+ <span class="mint-instrument-card__sample-name">{{ sampleLabel }}</span>
118
+ <span
119
+ v-if="status.current_sample.vial_position"
120
+ class="mint-instrument-card__sample-vial"
121
+ >
122
+ {{ status.current_sample.vial_position }}
123
+ </span>
124
+ </div>
125
+ </div>
126
+
127
+ <span
128
+ v-if="status.active_method"
129
+ class="mint-instrument-card__method"
130
+ :title="status.active_method"
131
+ >
132
+ {{ status.active_method }}
133
+ </span>
134
+
135
+ <SequenceProgressBar
136
+ v-if="status.sequence_progress"
137
+ :progress="status.sequence_progress"
138
+ compact
139
+ />
140
+
141
+ <div
142
+ v-if="etaDisplay || hasRemaining(status.sequence_progress)"
143
+ class="mint-instrument-card__eta"
144
+ >
145
+ <span class="mint-instrument-card__eta-label">ETA</span>
146
+ <span v-if="etaDisplay" class="mint-instrument-card__eta-time">{{ etaDisplay }}</span>
147
+ <span v-if="remainingLabel" class="mint-instrument-card__eta-remaining">
148
+ {{ remainingLabel }}
149
+ </span>
150
+ </div>
151
+
152
+ <span
153
+ v-if="status.sequence_progress?.sequence_name"
154
+ class="mint-instrument-card__sequence"
155
+ :title="status.sequence_progress.sequence_name"
156
+ >
157
+ {{ status.sequence_progress.sequence_name }}
158
+ </span>
159
+ </template>
160
+
161
+ <template v-else-if="status.state === 'error'">
162
+ <div class="mint-instrument-card__error">
163
+ <div class="mint-instrument-card__error-message">
164
+ {{ status.active_method ?? 'Instrument error' }}
165
+ </div>
166
+ <div v-if="lastSeenAgo" class="mint-instrument-card__error-detail">
167
+ Last seen {{ lastSeenAgo }}
168
+ </div>
169
+ </div>
170
+ </template>
171
+
172
+ <template v-else-if="showPlaceholders">
173
+ <div class="mint-instrument-card__placeholder">
174
+ <span class="mint-instrument-card__muted">No sample running</span>
175
+ <span class="mint-instrument-card__muted">No method</span>
176
+ <SequenceProgressBar
177
+ :progress="{ current_sample: 0, total_samples: 0 }"
178
+ compact
179
+ />
180
+ <span class="mint-instrument-card__muted">No sequence</span>
181
+ </div>
182
+ </template>
183
+ </div>
184
+ </template>
185
+
186
+ <style>
187
+ @import '../styles/components/instrument-monitor.css';
188
+ </style>
@@ -0,0 +1,191 @@
1
+ <script setup lang="ts">
2
+ /** Table for Xcalibur-compatible LCMS sequence rows with optional reorder/remove/duplicate controls. */
3
+ import { computed, ref } from 'vue'
4
+ import {
5
+ basenameFromWindowsPath,
6
+ DEFAULT_LCMS_SEQUENCE_COLUMNS,
7
+ type LcmsSequenceItem,
8
+ type LcmsSequenceTableColumn,
9
+ } from '../lcms'
10
+
11
+ interface Props {
12
+ items?: LcmsSequenceItem[]
13
+ columns?: LcmsSequenceTableColumn[]
14
+ editable?: boolean
15
+ maxRows?: number
16
+ showMoreLabel?: boolean
17
+ emptyMessage?: string
18
+ }
19
+
20
+ const props = withDefaults(defineProps<Props>(), {
21
+ items: () => [],
22
+ columns: () => DEFAULT_LCMS_SEQUENCE_COLUMNS,
23
+ editable: false,
24
+ maxRows: undefined,
25
+ showMoreLabel: true,
26
+ emptyMessage: 'No sequence items to display',
27
+ })
28
+
29
+ const emit = defineEmits<{
30
+ reorder: [fromIndex: number, toIndex: number]
31
+ remove: [index: number]
32
+ duplicate: [index: number]
33
+ }>()
34
+
35
+ const dragIndex = ref<number | null>(null)
36
+ const dropTargetIndex = ref<number | null>(null)
37
+
38
+ const displayItems = computed(() => {
39
+ if (!props.maxRows) return props.items
40
+ return props.items.slice(0, props.maxRows)
41
+ })
42
+
43
+ const hasMore = computed(() =>
44
+ props.maxRows != null && props.items.length > props.maxRows
45
+ )
46
+
47
+ function sampleTypeClass(type: string): string {
48
+ const normalized = type.toLowerCase()
49
+ if (normalized === 'blank') return 'mint-lcms-sequence-table__type--blank'
50
+ if (normalized === 'qc' || normalized === 'iqc' || normalized === 'eqc') {
51
+ return 'mint-lcms-sequence-table__type--qc'
52
+ }
53
+ return 'mint-lcms-sequence-table__type--sample'
54
+ }
55
+
56
+ function cellValue(item: LcmsSequenceItem, column: LcmsSequenceTableColumn, index: number): string | number {
57
+ if (column.key === 'index') return index + 1
58
+ if (column.key === 'actions') return ''
59
+ if (column.key === 'instrument_method') return basenameFromWindowsPath(item.instrument_method)
60
+ return item[column.key] ?? ''
61
+ }
62
+
63
+ function onDragStart(event: DragEvent, index: number) {
64
+ if (!props.editable) return
65
+ dragIndex.value = index
66
+ if (event.dataTransfer) {
67
+ event.dataTransfer.effectAllowed = 'move'
68
+ event.dataTransfer.setData('text/plain', String(index))
69
+ }
70
+ }
71
+
72
+ function onDragOver(event: DragEvent, index: number) {
73
+ if (!props.editable) return
74
+ event.preventDefault()
75
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'
76
+ if (dragIndex.value !== null && dragIndex.value !== index) {
77
+ dropTargetIndex.value = index
78
+ }
79
+ }
80
+
81
+ function clearDragState() {
82
+ dragIndex.value = null
83
+ dropTargetIndex.value = null
84
+ }
85
+
86
+ function onDrop(event: DragEvent, toIndex: number) {
87
+ if (!props.editable) return
88
+ event.preventDefault()
89
+ if (dragIndex.value !== null && dragIndex.value !== toIndex) {
90
+ emit('reorder', dragIndex.value, toIndex)
91
+ }
92
+ clearDragState()
93
+ }
94
+ </script>
95
+
96
+ <template>
97
+ <div class="mint-lcms-sequence-table">
98
+ <div class="mint-lcms-sequence-table__scroller">
99
+ <table class="mint-lcms-sequence-table__table">
100
+ <thead>
101
+ <tr>
102
+ <th v-if="editable" class="mint-lcms-sequence-table__drag-header" />
103
+ <th
104
+ v-for="column in columns"
105
+ :key="column.key"
106
+ :class="[
107
+ 'mint-lcms-sequence-table__th',
108
+ { 'mint-lcms-sequence-table__th--right': column.align === 'right' },
109
+ ]"
110
+ >
111
+ {{ column.label }}
112
+ </th>
113
+ <th v-if="editable" class="mint-lcms-sequence-table__actions-header" />
114
+ </tr>
115
+ </thead>
116
+ <tbody>
117
+ <tr
118
+ v-for="(item, index) in displayItems"
119
+ :key="`${item.file_name}-${item.position}-${index}`"
120
+ :draggable="editable"
121
+ class="mint-lcms-sequence-table__row"
122
+ :class="{
123
+ 'mint-lcms-sequence-table__row--dragging': dragIndex === index,
124
+ 'mint-lcms-sequence-table__row--drop-above': dropTargetIndex === index && dragIndex !== null && dragIndex > index,
125
+ 'mint-lcms-sequence-table__row--drop-below': dropTargetIndex === index && dragIndex !== null && dragIndex < index,
126
+ }"
127
+ @dragstart="onDragStart($event, index)"
128
+ @dragenter.prevent
129
+ @dragover="onDragOver($event, index)"
130
+ @dragleave="dropTargetIndex = null"
131
+ @drop="onDrop($event, index)"
132
+ @dragend="clearDragState"
133
+ >
134
+ <td v-if="editable" class="mint-lcms-sequence-table__drag-cell">
135
+ <span class="mint-lcms-sequence-table__drag-handle" aria-hidden="true">::</span>
136
+ </td>
137
+ <td
138
+ v-for="column in columns"
139
+ :key="column.key"
140
+ :class="[
141
+ 'mint-lcms-sequence-table__td',
142
+ `mint-lcms-sequence-table__td--${column.key}`,
143
+ { 'mint-lcms-sequence-table__td--right': column.align === 'right' },
144
+ ]"
145
+ :title="column.key === 'instrument_method' ? item.instrument_method : undefined"
146
+ >
147
+ <span
148
+ v-if="column.key === 'sample_type'"
149
+ class="mint-lcms-sequence-table__type"
150
+ :class="sampleTypeClass(item.sample_type)"
151
+ >
152
+ {{ item.sample_type }}
153
+ </span>
154
+ <template v-else>{{ cellValue(item, column, index) }}</template>
155
+ </td>
156
+ <td v-if="editable" class="mint-lcms-sequence-table__actions">
157
+ <button
158
+ type="button"
159
+ class="mint-lcms-sequence-table__action"
160
+ title="Duplicate"
161
+ @click="emit('duplicate', index)"
162
+ >
163
+ +
164
+ </button>
165
+ <button
166
+ type="button"
167
+ class="mint-lcms-sequence-table__action mint-lcms-sequence-table__action--danger"
168
+ title="Delete"
169
+ @click="emit('remove', index)"
170
+ >
171
+ x
172
+ </button>
173
+ </td>
174
+ </tr>
175
+ </tbody>
176
+ </table>
177
+ </div>
178
+
179
+ <div v-if="items.length === 0" class="mint-lcms-sequence-table__empty">
180
+ {{ emptyMessage }}
181
+ </div>
182
+
183
+ <div v-if="hasMore && showMoreLabel" class="mint-lcms-sequence-table__more">
184
+ Showing first {{ maxRows }} of {{ items.length }} items
185
+ </div>
186
+ </div>
187
+ </template>
188
+
189
+ <style>
190
+ @import '../styles/components/lcms-sequence-table.css';
191
+ </style>
@@ -12,6 +12,7 @@ interface Props {
12
12
  indeterminate?: boolean
13
13
  steps?: string[]
14
14
  currentStep?: number
15
+ ariaLabel?: string
15
16
  }
16
17
 
17
18
  const props = withDefaults(defineProps<Props>(), {
@@ -58,6 +59,7 @@ const segmentCount = computed(() => props.steps.length || 0)
58
59
  :aria-valuenow="indeterminate ? undefined : clampedValue"
59
60
  :aria-valuemin="indeterminate ? undefined : 0"
60
61
  :aria-valuemax="indeterminate ? undefined : 100"
62
+ :aria-label="ariaLabel ?? label"
61
63
  >
62
64
  <div
63
65
  :class="[
@@ -77,6 +79,7 @@ const segmentCount = computed(() => props.steps.length || 0)
77
79
  :aria-valuenow="currentStep"
78
80
  :aria-valuemin="0"
79
81
  :aria-valuemax="segmentCount"
82
+ :aria-label="ariaLabel ?? label"
80
83
  >
81
84
  <span
82
85
  v-for="(_step, i) in steps"