@morscherlab/mint-sdk 1.1.8 → 1.1.10
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.
- package/dist/__tests__/components/RetiredStyles.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/groupByOrder.test.d.ts +1 -0
- package/dist/__tests__/composables/useSampleGroups.tree.test.d.ts +1 -0
- package/dist/components/AppTopBar.vue.d.ts +6 -11
- package/dist/components/AutoGroupModal.adapter.d.ts +22 -7
- package/dist/components/PluginIcon.vue.d.ts +2 -2
- package/dist/components/PluginWorkspaceView.props.d.ts +5 -7
- package/dist/components/PluginWorkspaceView.vue.d.ts +0 -3
- package/dist/components/SampleSelector.groups.d.ts +7 -7
- package/dist/components/SampleSelector.selection.d.ts +6 -12
- package/dist/components/SampleSelector.tree.d.ts +39 -0
- package/dist/components/SampleSelectorGroupNode.vue.d.ts +9 -0
- package/dist/components/SmartGroup.types.d.ts +5 -0
- package/dist/components/SmartGroupFieldRecipe.groups.d.ts +3 -2
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -0
- package/dist/components/index.js +1 -1
- package/dist/{components-CDomhGSj.js → components-HXV-ZkBg.js} +939 -948
- package/dist/components-HXV-ZkBg.js.map +1 -0
- package/dist/composables/index.d.ts +1 -1
- package/dist/composables/index.js +1 -1
- package/dist/composables/useAutoGroup.d.ts +3 -0
- package/dist/composables/usePlatformContext.d.ts +3 -0
- package/dist/composables/useSampleGroups.d.ts +25 -0
- package/dist/index.js +2 -2
- package/dist/install.js +1 -1
- package/dist/styles.css +130 -557
- package/dist/types/platform.d.ts +1 -0
- package/dist/{useProtocolTemplates-BMt0LlXf.js → useProtocolTemplates-D3IZKLqr.js} +198 -24
- package/dist/useProtocolTemplates-D3IZKLqr.js.map +1 -0
- package/dist/utils/pluginIcon.d.ts +1 -1
- package/dist/utils/sampleGroupPath.d.ts +6 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppAvatarMenu.test.ts +11 -0
- package/src/__tests__/components/AppTopBar.test.ts +84 -10
- package/src/__tests__/components/AppTopBarPageSelector.test.ts +1 -1
- package/src/__tests__/components/AutoGroupModal.adapter.test.ts +47 -7
- package/src/__tests__/components/PluginIcon.test.ts +19 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +8 -0
- package/src/__tests__/components/RetiredStyles.test.ts +82 -0
- package/src/__tests__/components/SampleSelector.drag.test.ts +26 -0
- package/src/__tests__/components/SampleSelector.groups.test.ts +19 -6
- package/src/__tests__/components/SampleSelector.selection.test.ts +9 -23
- package/src/__tests__/components/SampleSelector.test.ts +35 -0
- package/src/__tests__/components/SmartGroupFieldRecipe.groups.test.ts +5 -0
- package/src/__tests__/composables/autoGroup/compose.test.ts +9 -10
- package/src/__tests__/composables/autoGroup/groupByOrder.test.ts +278 -0
- package/src/__tests__/composables/autoGroup/integration.test.ts +3 -7
- package/src/__tests__/composables/useSampleGroups.tree.test.ts +145 -0
- package/src/components/AppTopBar.story.vue +9 -7
- package/src/components/AppTopBar.vue +79 -72
- package/src/components/AutoGroupModal.adapter.ts +69 -13
- package/src/components/AutoGroupModal.story.vue +89 -0
- package/src/components/AutoGroupModal.vue +18 -8
- package/src/components/PluginIcon.story.vue +11 -9
- package/src/components/PluginIcon.vue +3 -3
- package/src/components/PluginWorkspaceView.props.ts +5 -7
- package/src/components/PluginWorkspaceView.vue +0 -4
- package/src/components/SampleSelector.drag.ts +5 -5
- package/src/components/SampleSelector.groups.ts +15 -15
- package/src/components/SampleSelector.selection.ts +7 -46
- package/src/components/SampleSelector.story.vue +37 -0
- package/src/components/SampleSelector.tree.ts +48 -0
- package/src/components/SampleSelector.vue +52 -320
- package/src/components/SampleSelectorGroupNode.vue +197 -0
- package/src/components/SmartGroup.types.ts +5 -0
- package/src/components/SmartGroupFieldRecipe.groups.ts +6 -2
- package/src/components/SmartGroupFieldRecipe.vue +76 -4
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -2
- package/src/composables/autoGroup/compose.ts +31 -10
- package/src/composables/index.ts +1 -0
- package/src/composables/useAutoGroup.ts +112 -5
- package/src/composables/useSampleGroups.ts +118 -33
- package/src/styles/components/app-avatar-menu.css +2 -1
- package/src/styles/components/app-plugin-switcher.css +0 -13
- package/src/styles/components/app-top-bar.css +13 -54
- package/src/styles/components/dose-calculator.css +0 -4
- package/src/styles/components/experiment-popover.css +2 -104
- package/src/styles/components/experiment-selector-modal.css +0 -7
- package/src/styles/components/plugin-icon.css +3 -0
- package/src/styles/components/protocol-step-editor.css +0 -6
- package/src/styles/components/reagent-editor.css +0 -5
- package/src/styles/components/sample-hierarchy-tree.css +0 -4
- package/src/styles/components/sample-selector.css +0 -96
- package/src/styles/components/schedule-calendar.css +0 -6
- package/src/styles/components/smart-group.css +22 -0
- package/src/styles/components/well-plate.css +1 -27
- package/src/types/platform.ts +1 -0
- package/src/utils/pluginIcon.ts +5 -2
- package/src/utils/sampleGroupPath.ts +19 -0
- package/dist/components-CDomhGSj.js.map +0 -1
- package/dist/useProtocolTemplates-BMt0LlXf.js.map +0 -1
|
@@ -56,7 +56,6 @@ const props = withDefaults(defineProps<PluginWorkspaceViewProps>(), {
|
|
|
56
56
|
hasNotificationDot: false,
|
|
57
57
|
showAdmin: false,
|
|
58
58
|
adminPath: '/admin',
|
|
59
|
-
showProfile: false,
|
|
60
59
|
userName: undefined,
|
|
61
60
|
userInitial: undefined,
|
|
62
61
|
userEmail: undefined,
|
|
@@ -115,7 +114,6 @@ const emit = defineEmits<{
|
|
|
115
114
|
'sign-out': []
|
|
116
115
|
'notifications-click': []
|
|
117
116
|
'settings-values-change': [data: Record<string, unknown>]
|
|
118
|
-
'profile-click': []
|
|
119
117
|
'admin-click': []
|
|
120
118
|
'experiment-select': [experiment: ExperimentSummary]
|
|
121
119
|
'experiment-detach': []
|
|
@@ -292,7 +290,6 @@ function handleFormCancel(sectionId: string) {
|
|
|
292
290
|
:has-notification-dot="hasNotificationDot"
|
|
293
291
|
:show-admin="showAdmin"
|
|
294
292
|
:admin-path="adminPath"
|
|
295
|
-
:show-profile="showProfile"
|
|
296
293
|
:user-name="userName"
|
|
297
294
|
:user-initial="userInitial"
|
|
298
295
|
:user-email="userEmail"
|
|
@@ -305,7 +302,6 @@ function handleFormCancel(sectionId: string) {
|
|
|
305
302
|
@sign-out="emit('sign-out')"
|
|
306
303
|
@notifications-click="emit('notifications-click')"
|
|
307
304
|
@settings-values-change="emit('settings-values-change', $event)"
|
|
308
|
-
@profile-click="emit('profile-click')"
|
|
309
305
|
@admin-click="emit('admin-click')"
|
|
310
306
|
>
|
|
311
307
|
<template
|
|
@@ -2,7 +2,7 @@ import { ref } from 'vue'
|
|
|
2
2
|
import type { SampleGroup } from '../types'
|
|
3
3
|
import {
|
|
4
4
|
detectSampleGroupSeparator,
|
|
5
|
-
|
|
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
|
-
//
|
|
97
|
-
// crossing
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|