@morscherlab/mint-sdk 1.1.7 → 1.1.9

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 (90) hide show
  1. package/dist/__tests__/components/RetiredStyles.test.d.ts +1 -0
  2. package/dist/__tests__/composables/autoGroup/groupByOrder.test.d.ts +1 -0
  3. package/dist/__tests__/composables/useSampleGroups.tree.test.d.ts +1 -0
  4. package/dist/components/AppTopBar.vue.d.ts +6 -11
  5. package/dist/components/AutoGroupModal.adapter.d.ts +10 -6
  6. package/dist/components/PluginIcon.vue.d.ts +2 -2
  7. package/dist/components/PluginWorkspaceView.props.d.ts +5 -7
  8. package/dist/components/PluginWorkspaceView.vue.d.ts +0 -3
  9. package/dist/components/SampleSelector.groups.d.ts +7 -7
  10. package/dist/components/SampleSelector.selection.d.ts +6 -12
  11. package/dist/components/SampleSelector.tree.d.ts +39 -0
  12. package/dist/components/SampleSelectorGroupNode.vue.d.ts +9 -0
  13. package/dist/components/SmartGroup.types.d.ts +5 -0
  14. package/dist/components/SmartGroupFieldRecipe.groups.d.ts +3 -2
  15. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -0
  16. package/dist/components/index.js +1 -1
  17. package/dist/{components-CDomhGSj.js → components-BbtUpCTv.js} +913 -943
  18. package/dist/components-BbtUpCTv.js.map +1 -0
  19. package/dist/composables/index.d.ts +1 -1
  20. package/dist/composables/index.js +1 -1
  21. package/dist/composables/useAutoGroup.d.ts +3 -0
  22. package/dist/composables/usePlatformContext.d.ts +3 -0
  23. package/dist/composables/useSampleGroups.d.ts +25 -0
  24. package/dist/index.js +2 -2
  25. package/dist/install.js +1 -1
  26. package/dist/styles.css +130 -557
  27. package/dist/types/platform.d.ts +1 -0
  28. package/dist/{useProtocolTemplates-BMt0LlXf.js → useProtocolTemplates-BWfNaynA.js} +199 -24
  29. package/dist/useProtocolTemplates-BWfNaynA.js.map +1 -0
  30. package/dist/utils/pluginIcon.d.ts +1 -1
  31. package/dist/utils/sampleGroupPath.d.ts +6 -0
  32. package/package.json +1 -1
  33. package/src/__tests__/components/AppAvatarMenu.test.ts +11 -0
  34. package/src/__tests__/components/AppTopBar.test.ts +84 -10
  35. package/src/__tests__/components/AppTopBarPageSelector.test.ts +1 -1
  36. package/src/__tests__/components/AutoGroupModal.adapter.test.ts +16 -6
  37. package/src/__tests__/components/PluginIcon.test.ts +19 -0
  38. package/src/__tests__/components/PluginWorkspaceView.test.ts +8 -0
  39. package/src/__tests__/components/RetiredStyles.test.ts +82 -0
  40. package/src/__tests__/components/SampleSelector.drag.test.ts +26 -0
  41. package/src/__tests__/components/SampleSelector.groups.test.ts +19 -6
  42. package/src/__tests__/components/SampleSelector.selection.test.ts +9 -23
  43. package/src/__tests__/components/SmartGroupFieldRecipe.groups.test.ts +5 -0
  44. package/src/__tests__/composables/autoGroup/compose.test.ts +9 -10
  45. package/src/__tests__/composables/autoGroup/groupByOrder.test.ts +249 -0
  46. package/src/__tests__/composables/autoGroup/integration.test.ts +3 -7
  47. package/src/__tests__/composables/useSampleGroups.tree.test.ts +145 -0
  48. package/src/components/AppTopBar.story.vue +9 -7
  49. package/src/components/AppTopBar.vue +79 -72
  50. package/src/components/AutoGroupModal.adapter.ts +17 -12
  51. package/src/components/AutoGroupModal.story.vue +89 -0
  52. package/src/components/AutoGroupModal.vue +16 -8
  53. package/src/components/PluginIcon.story.vue +11 -9
  54. package/src/components/PluginIcon.vue +3 -3
  55. package/src/components/PluginWorkspaceView.props.ts +5 -7
  56. package/src/components/PluginWorkspaceView.vue +0 -4
  57. package/src/components/SampleSelector.drag.ts +5 -5
  58. package/src/components/SampleSelector.groups.ts +15 -15
  59. package/src/components/SampleSelector.selection.ts +7 -46
  60. package/src/components/SampleSelector.story.vue +37 -0
  61. package/src/components/SampleSelector.tree.ts +48 -0
  62. package/src/components/SampleSelector.vue +52 -320
  63. package/src/components/SampleSelectorGroupNode.vue +197 -0
  64. package/src/components/SmartGroup.types.ts +5 -0
  65. package/src/components/SmartGroupFieldRecipe.groups.ts +6 -2
  66. package/src/components/SmartGroupFieldRecipe.vue +76 -4
  67. package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -2
  68. package/src/composables/autoGroup/compose.ts +31 -10
  69. package/src/composables/index.ts +1 -0
  70. package/src/composables/useAutoGroup.ts +108 -5
  71. package/src/composables/useSampleGroups.ts +118 -33
  72. package/src/styles/components/app-avatar-menu.css +2 -1
  73. package/src/styles/components/app-plugin-switcher.css +0 -13
  74. package/src/styles/components/app-top-bar.css +13 -54
  75. package/src/styles/components/dose-calculator.css +0 -4
  76. package/src/styles/components/experiment-popover.css +2 -104
  77. package/src/styles/components/experiment-selector-modal.css +0 -7
  78. package/src/styles/components/plugin-icon.css +3 -0
  79. package/src/styles/components/protocol-step-editor.css +0 -6
  80. package/src/styles/components/reagent-editor.css +0 -5
  81. package/src/styles/components/sample-hierarchy-tree.css +0 -4
  82. package/src/styles/components/sample-selector.css +0 -96
  83. package/src/styles/components/schedule-calendar.css +0 -6
  84. package/src/styles/components/smart-group.css +22 -0
  85. package/src/styles/components/well-plate.css +1 -27
  86. package/src/types/platform.ts +1 -0
  87. package/src/utils/pluginIcon.ts +5 -2
  88. package/src/utils/sampleGroupPath.ts +19 -0
  89. package/dist/components-CDomhGSj.js.map +0 -1
  90. package/dist/useProtocolTemplates-BMt0LlXf.js.map +0 -1
@@ -2,7 +2,7 @@ import { ref } from 'vue'
2
2
  import type { SampleGroup } from '../types'
3
3
  import {
4
4
  detectSampleGroupSeparator,
5
- getSampleGroupMajorPrefix,
5
+ getSampleGroupParentPath,
6
6
  moveSampleToGroup,
7
7
  reorderSampleGroup,
8
8
  reorderSampleMajorGroup,
@@ -93,13 +93,13 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
93
93
  if (!draggingGroup.value || draggingGroupKind.value !== kind) return
94
94
  if (draggingGroup.value === name) return
95
95
 
96
- // Sub-group reorder is restricted to siblings under the same major prefix;
97
- // crossing majors would silently rename the group, which is too magical.
96
+ // Leaf reorder is restricted to siblings under the same immediate parent;
97
+ // crossing any hierarchy boundary would silently rename the group.
98
98
  if (kind === 'sub') {
99
99
  const separator = detectSampleGroupSeparator(groups.value)
100
100
  if (
101
- getSampleGroupMajorPrefix(draggingGroup.value, separator) !==
102
- getSampleGroupMajorPrefix(name, separator)
101
+ getSampleGroupParentPath(draggingGroup.value, separator) !==
102
+ getSampleGroupParentPath(name, separator)
103
103
  ) return
104
104
  }
105
105
 
@@ -1,4 +1,11 @@
1
1
  import type { SampleGroup } from '../types'
2
+ import { getSampleGroupMajorPrefix } from '../utils/sampleGroupPath'
3
+
4
+ export {
5
+ getSampleGroupMajorPrefix,
6
+ getSampleGroupParentPath,
7
+ splitSampleGroupPath,
8
+ } from '../utils/sampleGroupPath'
2
9
 
3
10
  export type GroupDragKind = 'major' | 'sub' | 'flat'
4
11
  export type GroupReorderPosition = 'before' | 'after'
@@ -7,11 +14,6 @@ export function detectSampleGroupSeparator(groups: SampleGroup[]): string {
7
14
  return groups.some(group => group.name.includes('/')) ? '/' : '_'
8
15
  }
9
16
 
10
- export function getSampleGroupMajorPrefix(groupName: string, separator: string): string {
11
- const parts = groupName.split(separator)
12
- return parts.length > 1 ? parts[0] : groupName
13
- }
14
-
15
17
  export function moveSampleToGroup(
16
18
  groups: SampleGroup[],
17
19
  sample: string,
@@ -31,16 +33,14 @@ export function moveSampleToGroup(
31
33
  })
32
34
  }
33
35
 
34
- export function removeSampleGroup(groups: SampleGroup[], groupName: string): SampleGroup[] {
35
- return groups.filter(group => group.name !== groupName)
36
- }
37
-
38
- export function removeSampleMajorGroup(
39
- groups: SampleGroup[],
40
- majorGroup: { subGroups: Array<{ name: string }> },
41
- ): SampleGroup[] {
42
- const groupNames = new Set(majorGroup.subGroups.map(group => group.name))
43
- return groups.filter(group => !groupNames.has(group.name))
36
+ /**
37
+ * Drop every named group. Callers pass the concrete group names beneath the
38
+ * node being removed, so one helper covers a single group, a whole family, or
39
+ * any intermediate hierarchy level.
40
+ */
41
+ export function removeSampleGroups(groups: SampleGroup[], groupNames: string[]): SampleGroup[] {
42
+ const removing = new Set(groupNames)
43
+ return groups.filter(group => !removing.has(group.name))
44
44
  }
45
45
 
46
46
  export function removeSampleFromGroup(
@@ -1,15 +1,9 @@
1
1
  import type { ComputedRef } from 'vue'
2
2
  import { useListSelection } from '../composables/useListSelection'
3
- import type { SampleMajorGroup } from '../composables/useSampleGroups'
4
-
5
- interface SampleGroupSelectionSource {
6
- samples: string[]
7
- }
8
3
 
9
4
  export interface UseSampleSelectorSelectionOptions {
10
5
  selected: () => string[]
11
6
  samples: () => string[]
12
- findGroup: (groupName: string) => SampleGroupSelectionSource | undefined
13
7
  emitSelected: (samples: string[]) => void
14
8
  }
15
9
 
@@ -17,16 +11,16 @@ export interface UseSampleSelectorSelectionReturn {
17
11
  isAllSelected: ComputedRef<boolean>
18
12
  toggleSelectAll: () => void
19
13
  toggleSample: (sample: string) => void
20
- toggleGroupSamples: (groupName: string) => void
21
- toggleMajorGroupSamples: (majorGroup: SampleMajorGroup) => void
14
+ toggleSamplesSelection: (samples: string[]) => void
22
15
  isFullySelected: (samples: string[]) => boolean
23
16
  isPartiallySelected: (samples: string[]) => boolean
24
- isGroupFullySelected: (groupName: string) => boolean
25
- isGroupPartiallySelected: (groupName: string) => boolean
26
- isMajorGroupFullySelected: (majorGroup: SampleMajorGroup) => boolean
27
- isMajorGroupPartiallySelected: (majorGroup: SampleMajorGroup) => boolean
28
17
  }
29
18
 
19
+ /**
20
+ * Selection state for the sample selector. Works on plain sample arrays rather
21
+ * than group names, so any level of the group hierarchy can drive it by handing
22
+ * over the samples beneath that node.
23
+ */
30
24
  export function useSampleSelectorSelection(
31
25
  options: UseSampleSelectorSelectionOptions,
32
26
  ): UseSampleSelectorSelectionReturn {
@@ -51,16 +45,6 @@ export function useSampleSelectorSelection(
51
45
  emitSelection(sampleSelection.toggleValues(samples))
52
46
  }
53
47
 
54
- function toggleGroupSamples(groupName: string) {
55
- const group = options.findGroup(groupName)
56
- if (!group) return
57
- toggleSamplesSelection(group.samples)
58
- }
59
-
60
- function toggleMajorGroupSamples(majorGroup: SampleMajorGroup) {
61
- toggleSamplesSelection(majorGroup.allSamples)
62
- }
63
-
64
48
  function isFullySelected(samples: string[]): boolean {
65
49
  return sampleSelection.isFullySelected(samples)
66
50
  }
@@ -69,35 +53,12 @@ export function useSampleSelectorSelection(
69
53
  return sampleSelection.isPartiallySelected(samples)
70
54
  }
71
55
 
72
- function isGroupFullySelected(groupName: string): boolean {
73
- const group = options.findGroup(groupName)
74
- return group ? isFullySelected(group.samples) : false
75
- }
76
-
77
- function isGroupPartiallySelected(groupName: string): boolean {
78
- const group = options.findGroup(groupName)
79
- return group ? isPartiallySelected(group.samples) : false
80
- }
81
-
82
- function isMajorGroupFullySelected(majorGroup: SampleMajorGroup): boolean {
83
- return isFullySelected(majorGroup.allSamples)
84
- }
85
-
86
- function isMajorGroupPartiallySelected(majorGroup: SampleMajorGroup): boolean {
87
- return isPartiallySelected(majorGroup.allSamples)
88
- }
89
-
90
56
  return {
91
57
  isAllSelected: sampleSelection.isAllSelected,
92
58
  toggleSelectAll,
93
59
  toggleSample,
94
- toggleGroupSamples,
95
- toggleMajorGroupSamples,
60
+ toggleSamplesSelection,
96
61
  isFullySelected,
97
62
  isPartiallySelected,
98
- isGroupFullySelected,
99
- isGroupPartiallySelected,
100
- isMajorGroupFullySelected,
101
- isMajorGroupPartiallySelected,
102
63
  }
103
64
  }
@@ -59,6 +59,17 @@ const hierarchicalGroups: SampleGroup[] = [
59
59
  { name: 'Control_Vehicle', color: '#EC4899', samples: ['Control_Vehicle_Rep1', 'Control_Vehicle_Rep2'] },
60
60
  ]
61
61
 
62
+ // Multi-factor LC-MS names (Tissue × Timepoint × Dose × rep) — exercises the
63
+ // auto-group hierarchy layers: each enabled factor becomes one layer, in the
64
+ // order shown on the Field Recipe cards.
65
+ const lcmsSamples = ['Liver', 'Kidney'].flatMap(tissue =>
66
+ ['5min', '10min'].flatMap(time =>
67
+ ['High', 'Low'].flatMap(dose =>
68
+ [1, 2, 3].map(rep => `${tissue}_${time}_${dose}_rep${rep}`),
69
+ ),
70
+ ),
71
+ )
72
+
62
73
  function initState() {
63
74
  return {
64
75
  samples: mockSamples,
@@ -69,6 +80,14 @@ function initState() {
69
80
  }
70
81
  }
71
82
 
83
+ function initLcmsState() {
84
+ return {
85
+ samples: lcmsSamples,
86
+ selected: [] as string[],
87
+ groups: [] as SampleGroup[],
88
+ }
89
+ }
90
+
72
91
  function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[] }) {
73
92
  state.groups = result.groups
74
93
  console.log('Smart group result:', result)
@@ -142,6 +161,24 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
142
161
  </div>
143
162
  </Variant>
144
163
 
164
+ <Variant title="Auto Group Hierarchy (LC-MS)" :init-state="initLcmsState">
165
+ <template #default="{ state }">
166
+ <div style="padding: 2rem; max-width: 420px;">
167
+ <SampleSelector
168
+ v-model="state.selected"
169
+ v-model:groups="state.groups"
170
+ :samples="state.samples"
171
+ @smart-group="(r: AutoGroupResult) => handleSmartGroup(r, state)"
172
+ />
173
+ <p style="font-size: 0.8125rem; color: #64748b; margin-top: 1rem;">
174
+ Open Grouping: each switched-on factor is one hierarchy layer
175
+ (Layer 1 = major group). Use the layer arrows to reorder, e.g.
176
+ promote Dose above Timepoint, then Apply.
177
+ </p>
178
+ </div>
179
+ </template>
180
+ </Variant>
181
+
145
182
  <Variant title="No Grouping">
146
183
  <div style="padding: 2rem; max-width: 400px;">
147
184
  <SampleSelector
@@ -0,0 +1,48 @@
1
+ import type { SampleGroupNode } from '../composables/useSampleGroups'
2
+ import type { GroupDragKind, GroupReorderPosition } from './SampleSelector.groups'
3
+
4
+ /**
5
+ * Handlers and state predicates shared by every level of the group tree.
6
+ * Passed down as a single prop so the recursive row component does not have to
7
+ * re-emit each event at every nesting level.
8
+ */
9
+ export interface SampleGroupTreeContext {
10
+ isExpanded: (key: string) => boolean
11
+ toggleExpanded: (key: string) => void
12
+ isSampleSelected: (sample: string) => boolean
13
+ isFullySelected: (samples: string[]) => boolean
14
+ isPartiallySelected: (samples: string[]) => boolean
15
+ toggleSamples: (samples: string[]) => void
16
+ toggleSample: (sample: string) => void
17
+ removeGroups: (groupNames: string[]) => void
18
+ removeSampleFromGroup: (sample: string, groupName: string) => void
19
+ openColorPicker: (groupNames: string[], event: Event) => void
20
+ // Drag state + handlers, mirroring `useSampleSelectorDrag`.
21
+ draggingSample: () => string | null
22
+ dragOverGroup: () => string | null
23
+ draggingGroup: () => string | null
24
+ draggingGroupKind: () => GroupDragKind | null
25
+ reorderTarget: () => string | null
26
+ reorderPosition: () => GroupReorderPosition | null
27
+ onSampleDragStart: (sample: string, groupName: string | null, event: DragEvent) => void
28
+ onSampleDragEnd: () => void
29
+ onSampleDragOver: (groupName: string, event: DragEvent) => void
30
+ onSampleDragLeave: () => void
31
+ onSampleDrop: (groupName: string, event: DragEvent) => void
32
+ onGroupDragStart: (name: string, kind: GroupDragKind, event: DragEvent) => void
33
+ onGroupDragEnd: () => void
34
+ onGroupDragOver: (name: string, kind: GroupDragKind, event: DragEvent) => void
35
+ onGroupDragLeave: (event: DragEvent) => void
36
+ onGroupDrop: (name: string, kind: GroupDragKind, event: DragEvent) => void
37
+ }
38
+
39
+ /** Every concrete group name beneath a node, in tree order. */
40
+ export function collectLeafGroupNames(node: SampleGroupNode): string[] {
41
+ if (node.group) return [node.group.name]
42
+ return node.children.flatMap(collectLeafGroupNames)
43
+ }
44
+
45
+ /** Expansion keys for a whole tree, used by "expand all". */
46
+ export function collectNodeKeys(nodes: SampleGroupNode[]): string[] {
47
+ return nodes.flatMap(node => [node.key, ...collectNodeKeys(node.children)])
48
+ }