@morscherlab/mint-sdk 1.0.15 → 1.0.16
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/{BaseSelect-DksaKYq_.js → BaseSelect-ekgr9fDo.js} +4 -1
- package/dist/BaseSelect-ekgr9fDo.js.map +1 -0
- package/dist/{ExperimentSelectorModal-DIFyL5ta.js → ExperimentSelectorModal-BOzDs8TU.js} +2 -2
- package/dist/{ExperimentSelectorModal-CHsU-LIh.js → ExperimentSelectorModal-CX0oBzpV.js} +2 -2
- package/dist/{ExperimentSelectorModal-CHsU-LIh.js.map → ExperimentSelectorModal-CX0oBzpV.js.map} +1 -1
- package/dist/{SettingsModal-LEKI6Ebl.js → SettingsModal-BTyXD0uP.js} +3 -3
- package/dist/{SettingsModal-LEKI6Ebl.js.map → SettingsModal-BTyXD0uP.js.map} +1 -1
- package/dist/SettingsModal-DXcSKk9D.js +5 -0
- package/dist/__tests__/components/MobileSupportGate.test.d.ts +1 -0
- package/dist/__tests__/composables/useMobileSupportGate.test.d.ts +1 -0
- package/dist/components/AutoGroupModal.vue.d.ts +6 -0
- package/dist/components/BaseInput.vue.d.ts +1 -0
- package/dist/components/MobileSupportGate.vue.d.ts +40 -0
- package/dist/components/SampleSelector.colors.d.ts +2 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +6 -6
- package/dist/{components-Cyk8QEyL.js → components-D0CzE0XK.js} +1061 -473
- package/dist/components-D0CzE0XK.js.map +1 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.js +7 -7
- package/dist/composables/useMobileSupportGate.d.ts +14 -0
- package/dist/{composables-D9mexHSW.js → composables-Da-4XOe2.js} +3 -3
- package/dist/{composables-D9mexHSW.js.map → composables-Da-4XOe2.js.map} +1 -1
- package/dist/index.js +10 -10
- package/dist/install.js +5 -5
- package/dist/styles.css +3705 -2286
- package/dist/templates/index.js +3 -3
- package/dist/{templates-Do43ZIMb.js → templates-Dnf8UNxg.js} +2 -2
- package/dist/{templates-Do43ZIMb.js.map → templates-Dnf8UNxg.js.map} +1 -1
- package/dist/{useControlSchema-0n8Bcftq.js → useControlSchema-Dkm-W_lg.js} +2 -2
- package/dist/{useControlSchema-0n8Bcftq.js.map → useControlSchema-Dkm-W_lg.js.map} +1 -1
- package/dist/{useFormBuilder-COfYWDuC.js → useFormBuilder-BOJ52N4M.js} +2 -2
- package/dist/{useFormBuilder-COfYWDuC.js.map → useFormBuilder-BOJ52N4M.js.map} +1 -1
- package/dist/{useProtocolTemplates-DODHlhxr.js → useProtocolTemplates-r2GOnnH1.js} +55 -5
- package/dist/useProtocolTemplates-r2GOnnH1.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/MobileSupportGate.test.ts +120 -0
- package/src/__tests__/components/SampleSelector.test.ts +119 -0
- package/src/__tests__/composables/useMobileSupportGate.test.ts +74 -0
- package/src/components/AutoGroupModal.story.vue +46 -0
- package/src/components/AutoGroupModal.vue +578 -2
- package/src/components/BaseInput.vue +2 -0
- package/src/components/MobileSupportGate.story.vue +52 -0
- package/src/components/MobileSupportGate.vue +115 -0
- package/src/components/SampleSelector.colors.ts +7 -2
- package/src/components/SampleSelector.story.vue +34 -0
- package/src/components/SampleSelector.vue +22 -1
- package/src/components/index.ts +1 -0
- package/src/composables/index.ts +8 -0
- package/src/composables/useMobileSupportGate.ts +80 -0
- package/src/styles/components/auto-group-modal.css +744 -0
- package/src/styles/components/mobile-support-gate.css +119 -0
- package/dist/BaseSelect-DksaKYq_.js.map +0 -1
- package/dist/SettingsModal-L7Ejny45.js +0 -5
- package/dist/components-Cyk8QEyL.js.map +0 -1
- package/dist/useProtocolTemplates-DODHlhxr.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { nextTick } from 'vue'
|
|
4
|
+
import MobileSupportGate from '../../components/MobileSupportGate.vue'
|
|
5
|
+
|
|
6
|
+
function mockMatchMedia(initialMatches: boolean) {
|
|
7
|
+
const listeners = new Set<(event: MediaQueryListEvent) => void>()
|
|
8
|
+
const queryList = {
|
|
9
|
+
matches: initialMatches,
|
|
10
|
+
media: '',
|
|
11
|
+
onchange: null,
|
|
12
|
+
addEventListener: vi.fn((_event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
13
|
+
listeners.add(listener)
|
|
14
|
+
}),
|
|
15
|
+
removeEventListener: vi.fn((_event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
16
|
+
listeners.delete(listener)
|
|
17
|
+
}),
|
|
18
|
+
addListener: vi.fn((listener: (event: MediaQueryListEvent) => void) => {
|
|
19
|
+
listeners.add(listener)
|
|
20
|
+
}),
|
|
21
|
+
removeListener: vi.fn((listener: (event: MediaQueryListEvent) => void) => {
|
|
22
|
+
listeners.delete(listener)
|
|
23
|
+
}),
|
|
24
|
+
dispatchEvent: vi.fn(),
|
|
25
|
+
} as unknown as MediaQueryList
|
|
26
|
+
|
|
27
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
28
|
+
configurable: true,
|
|
29
|
+
value: vi.fn(() => queryList),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
setMatches(matches: boolean) {
|
|
34
|
+
Object.defineProperty(queryList, 'matches', {
|
|
35
|
+
configurable: true,
|
|
36
|
+
value: matches,
|
|
37
|
+
})
|
|
38
|
+
listeners.forEach(listener => listener({ matches } as MediaQueryListEvent))
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('MobileSupportGate', () => {
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.restoreAllMocks()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('shows the unsupported message on mobile when the route is unsupported', async () => {
|
|
49
|
+
mockMatchMedia(true)
|
|
50
|
+
|
|
51
|
+
const wrapper = mount(MobileSupportGate, {
|
|
52
|
+
props: {
|
|
53
|
+
supported: false,
|
|
54
|
+
},
|
|
55
|
+
slots: {
|
|
56
|
+
default: '<div data-testid="content">Workspace content</div>',
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
await nextTick()
|
|
60
|
+
|
|
61
|
+
expect(wrapper.find('[data-testid="mobile-unsupported"]').exists()).toBe(true)
|
|
62
|
+
expect(wrapper.text()).toContain('Desktop workspace recommended')
|
|
63
|
+
expect(wrapper.find('[data-testid="content"]').exists()).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('renders content on desktop even when the route is mobile-unsupported', async () => {
|
|
67
|
+
mockMatchMedia(false)
|
|
68
|
+
|
|
69
|
+
const wrapper = mount(MobileSupportGate, {
|
|
70
|
+
props: {
|
|
71
|
+
supported: false,
|
|
72
|
+
},
|
|
73
|
+
slots: {
|
|
74
|
+
default: '<div data-testid="content">Workspace content</div>',
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
await nextTick()
|
|
78
|
+
|
|
79
|
+
expect(wrapper.find('[data-testid="mobile-unsupported"]').exists()).toBe(false)
|
|
80
|
+
expect(wrapper.find('[data-testid="content"]').exists()).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('renders content on mobile when the route is supported', async () => {
|
|
84
|
+
mockMatchMedia(true)
|
|
85
|
+
|
|
86
|
+
const wrapper = mount(MobileSupportGate, {
|
|
87
|
+
props: {
|
|
88
|
+
supported: true,
|
|
89
|
+
},
|
|
90
|
+
slots: {
|
|
91
|
+
default: '<div data-testid="content">Workspace content</div>',
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
await nextTick()
|
|
95
|
+
|
|
96
|
+
expect(wrapper.find('[data-testid="mobile-unsupported"]').exists()).toBe(false)
|
|
97
|
+
expect(wrapper.find('[data-testid="content"]').exists()).toBe(true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('reacts when the viewport moves into the mobile query', async () => {
|
|
101
|
+
const media = mockMatchMedia(false)
|
|
102
|
+
|
|
103
|
+
const wrapper = mount(MobileSupportGate, {
|
|
104
|
+
props: {
|
|
105
|
+
supported: false,
|
|
106
|
+
},
|
|
107
|
+
slots: {
|
|
108
|
+
default: '<div data-testid="content">Workspace content</div>',
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
await nextTick()
|
|
112
|
+
|
|
113
|
+
expect(wrapper.find('[data-testid="content"]').exists()).toBe(true)
|
|
114
|
+
|
|
115
|
+
media.setMatches(true)
|
|
116
|
+
await nextTick()
|
|
117
|
+
|
|
118
|
+
expect(wrapper.find('[data-testid="mobile-unsupported"]').exists()).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { createPinia } from 'pinia'
|
|
2
3
|
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import AutoGroupModal from '../../components/AutoGroupModal.vue'
|
|
3
5
|
import SampleSelector from '../../components/SampleSelector.vue'
|
|
4
6
|
import type { AutoGroupResult, SampleGroup } from '../../types'
|
|
5
7
|
|
|
@@ -86,6 +88,123 @@ describe('SampleSelector', () => {
|
|
|
86
88
|
expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('2 samples')
|
|
87
89
|
expect(wrapper.findAll('.mint-sample-selector__flat-name').map(item => item.text())).toEqual(['S1', 'S2'])
|
|
88
90
|
})
|
|
91
|
+
|
|
92
|
+
it('opens the grouping modal in manual mode', async () => {
|
|
93
|
+
const wrapper = mount(SampleSelector, {
|
|
94
|
+
props: {
|
|
95
|
+
samples: ['S1', 'S2'],
|
|
96
|
+
modelValue: [],
|
|
97
|
+
groups: [],
|
|
98
|
+
enableSmartGroup: false,
|
|
99
|
+
},
|
|
100
|
+
global: {
|
|
101
|
+
stubs: {
|
|
102
|
+
AutoGroupModal: true,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
await wrapper.findAllComponents({ name: 'BaseButton' })[0].trigger('click')
|
|
108
|
+
|
|
109
|
+
const modal = wrapper.findComponent({ name: 'AutoGroupModal' })
|
|
110
|
+
expect(modal.props('initialMode')).toBe('manual')
|
|
111
|
+
expect(modal.props('groups')).toEqual([])
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('AutoGroupModal manual workflow', () => {
|
|
116
|
+
it('assigns selected samples to a manual group and subgroup', async () => {
|
|
117
|
+
const wrapper = mount(AutoGroupModal, {
|
|
118
|
+
props: {
|
|
119
|
+
modelValue: true,
|
|
120
|
+
initialMode: 'manual',
|
|
121
|
+
samples: ['S1', 'S2'],
|
|
122
|
+
groups: [],
|
|
123
|
+
},
|
|
124
|
+
global: {
|
|
125
|
+
plugins: [createPinia()],
|
|
126
|
+
stubs: {
|
|
127
|
+
Teleport: true,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
await wrapper.findAll('.mint-auto-group__manual-sample input')[0].trigger('change')
|
|
133
|
+
const fields = wrapper.findAll('.mint-auto-group__manual-field input')
|
|
134
|
+
await fields[0].setValue('Treatment')
|
|
135
|
+
await fields[1].setValue('Baseline')
|
|
136
|
+
await wrapper.find('.mint-auto-group__manual-assign').trigger('click')
|
|
137
|
+
await wrapper.find('.mint-auto-group__nav--manual .mint-button--primary').trigger('click')
|
|
138
|
+
|
|
139
|
+
const result = wrapper.emitted('apply')?.at(-1)?.[0] as AutoGroupResult
|
|
140
|
+
expect(result.groups).toHaveLength(1)
|
|
141
|
+
expect(result.groups[0]).toMatchObject({
|
|
142
|
+
name: 'Treatment/Baseline',
|
|
143
|
+
samples: ['S1'],
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('moves manual selections out of existing groups before assigning them', async () => {
|
|
148
|
+
const wrapper = mount(AutoGroupModal, {
|
|
149
|
+
props: {
|
|
150
|
+
modelValue: true,
|
|
151
|
+
initialMode: 'manual',
|
|
152
|
+
samples: ['S1', 'S2'],
|
|
153
|
+
groups: [
|
|
154
|
+
{ name: 'Control', color: '#3B82F6', samples: ['S1'] },
|
|
155
|
+
{ name: 'Treatment', color: '#10B981', samples: [] },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
global: {
|
|
159
|
+
plugins: [createPinia()],
|
|
160
|
+
stubs: {
|
|
161
|
+
Teleport: true,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
await wrapper.find('.mint-auto-group__manual-filter input').setValue(false)
|
|
167
|
+
await wrapper.findAll('.mint-auto-group__manual-sample input')[0].trigger('change')
|
|
168
|
+
await wrapper.findAll('.mint-auto-group__manual-chip')[1].trigger('click')
|
|
169
|
+
await wrapper.find('.mint-auto-group__manual-assign').trigger('click')
|
|
170
|
+
await wrapper.find('.mint-auto-group__nav--manual .mint-button--primary').trigger('click')
|
|
171
|
+
|
|
172
|
+
const result = wrapper.emitted('apply')?.at(-1)?.[0] as AutoGroupResult
|
|
173
|
+
expect(result.groups).toEqual([
|
|
174
|
+
{ name: 'Control', color: '#3B82F6', samples: [] },
|
|
175
|
+
{ name: 'Treatment', color: '#10B981', samples: ['S1'] },
|
|
176
|
+
])
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('preserves an existing cohort color when the target is typed manually', async () => {
|
|
180
|
+
const wrapper = mount(AutoGroupModal, {
|
|
181
|
+
props: {
|
|
182
|
+
modelValue: true,
|
|
183
|
+
initialMode: 'manual',
|
|
184
|
+
samples: ['S1', 'S2'],
|
|
185
|
+
groups: [
|
|
186
|
+
{ name: 'Control', color: '#10B981', samples: ['S1'] },
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
global: {
|
|
190
|
+
plugins: [createPinia()],
|
|
191
|
+
stubs: {
|
|
192
|
+
Teleport: true,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
await wrapper.findAll('.mint-auto-group__manual-sample input')[0].trigger('change')
|
|
198
|
+
const fields = wrapper.findAll('.mint-auto-group__manual-field input')
|
|
199
|
+
await fields[0].setValue('Control')
|
|
200
|
+
await wrapper.find('.mint-auto-group__manual-assign').trigger('click')
|
|
201
|
+
await wrapper.find('.mint-auto-group__nav--manual .mint-button--primary').trigger('click')
|
|
202
|
+
|
|
203
|
+
const result = wrapper.emitted('apply')?.at(-1)?.[0] as AutoGroupResult
|
|
204
|
+
expect(result.groups).toEqual([
|
|
205
|
+
{ name: 'Control', color: '#10B981', samples: ['S1', 'S2'] },
|
|
206
|
+
])
|
|
207
|
+
})
|
|
89
208
|
})
|
|
90
209
|
|
|
91
210
|
describe('SampleSelector — QC overlay regression', () => {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { computed, defineComponent, nextTick, ref } from 'vue'
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import { useMobileSupportGate } from '../../composables/useMobileSupportGate'
|
|
5
|
+
|
|
6
|
+
function mockMatchMedia(initialMatches: boolean) {
|
|
7
|
+
const listeners = new Set<(event: MediaQueryListEvent) => void>()
|
|
8
|
+
const queryList = {
|
|
9
|
+
matches: initialMatches,
|
|
10
|
+
media: '',
|
|
11
|
+
onchange: null,
|
|
12
|
+
addEventListener: vi.fn((_event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
13
|
+
listeners.add(listener)
|
|
14
|
+
}),
|
|
15
|
+
removeEventListener: vi.fn((_event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
16
|
+
listeners.delete(listener)
|
|
17
|
+
}),
|
|
18
|
+
addListener: vi.fn((listener: (event: MediaQueryListEvent) => void) => {
|
|
19
|
+
listeners.add(listener)
|
|
20
|
+
}),
|
|
21
|
+
removeListener: vi.fn((listener: (event: MediaQueryListEvent) => void) => {
|
|
22
|
+
listeners.delete(listener)
|
|
23
|
+
}),
|
|
24
|
+
dispatchEvent: vi.fn(),
|
|
25
|
+
} as unknown as MediaQueryList
|
|
26
|
+
|
|
27
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
28
|
+
configurable: true,
|
|
29
|
+
value: vi.fn(() => queryList),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
setMatches(matches: boolean) {
|
|
34
|
+
Object.defineProperty(queryList, 'matches', {
|
|
35
|
+
configurable: true,
|
|
36
|
+
value: matches,
|
|
37
|
+
})
|
|
38
|
+
listeners.forEach(listener => listener({ matches } as MediaQueryListEvent))
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('useMobileSupportGate', () => {
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.restoreAllMocks()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('computes the unsupported state from viewport and supported sources', async () => {
|
|
49
|
+
const media = mockMatchMedia(true)
|
|
50
|
+
const supported = ref(false)
|
|
51
|
+
|
|
52
|
+
const wrapper = mount(defineComponent({
|
|
53
|
+
setup() {
|
|
54
|
+
const gate = useMobileSupportGate({
|
|
55
|
+
supported: computed(() => supported.value),
|
|
56
|
+
})
|
|
57
|
+
return gate
|
|
58
|
+
},
|
|
59
|
+
template: '<div>{{ shouldShowUnsupported }}</div>',
|
|
60
|
+
}))
|
|
61
|
+
await nextTick()
|
|
62
|
+
|
|
63
|
+
expect(wrapper.text()).toBe('true')
|
|
64
|
+
|
|
65
|
+
supported.value = true
|
|
66
|
+
await nextTick()
|
|
67
|
+
expect(wrapper.text()).toBe('false')
|
|
68
|
+
|
|
69
|
+
supported.value = false
|
|
70
|
+
media.setMatches(false)
|
|
71
|
+
await nextTick()
|
|
72
|
+
expect(wrapper.text()).toBe('false')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import AutoGroupModal from './AutoGroupModal.vue'
|
|
4
4
|
import type { AutoGroupResult } from '../types/auto-group'
|
|
5
|
+
import type { SampleGroup } from '../types'
|
|
5
6
|
import mixedLcMsRaw from '../__tests__/fixtures/auto-group/mixed-lc-ms-batch.txt?raw'
|
|
6
7
|
|
|
7
8
|
const mixedLcMsSamples = mixedLcMsRaw.trim().split('\n')
|
|
@@ -92,6 +93,29 @@ const hyphenSamples = [
|
|
|
92
93
|
'KO-Drug-1', 'KO-Drug-2', 'KO-Drug-3',
|
|
93
94
|
]
|
|
94
95
|
|
|
96
|
+
const manualSamples = [
|
|
97
|
+
'Pt001_TumorA_d7_rep1',
|
|
98
|
+
'Pt001_TumorA_d7_rep1_reseq2024',
|
|
99
|
+
'Pt001_TumorA_d14_rep1',
|
|
100
|
+
'Pt002_TumorB_d7_rep1',
|
|
101
|
+
'Pt002_tumorb_d7_rep2',
|
|
102
|
+
'Pt002_TumorB_d14_rep1',
|
|
103
|
+
'Pt003_NoID_d7_rep1',
|
|
104
|
+
'Pt003_TumorC_d14_rep1_redo',
|
|
105
|
+
'Ctrl_pooled_old_naming',
|
|
106
|
+
'control_2_(redo)',
|
|
107
|
+
'Pt004_TumorA_d7_2024batch2',
|
|
108
|
+
'Pt004_TumorA_d14',
|
|
109
|
+
'QC_pool_01',
|
|
110
|
+
'blank_run3',
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
const manualGroups: SampleGroup[] = [
|
|
114
|
+
{ name: 'Responder/Day 7', color: '#0EA5A4', samples: ['Pt001_TumorA_d7_rep1'] },
|
|
115
|
+
{ name: 'Responder/Day 14', color: '#0EA5A4', samples: ['Pt001_TumorA_d14_rep1'] },
|
|
116
|
+
{ name: 'Non-responder/Day 7', color: '#F43F5E', samples: ['Pt002_TumorB_d7_rep1'] },
|
|
117
|
+
]
|
|
118
|
+
|
|
95
119
|
const isOpen = ref(false)
|
|
96
120
|
const lastResult = ref<AutoGroupResult | null>(null)
|
|
97
121
|
|
|
@@ -239,6 +263,28 @@ function initState(samples: string[] = proteomicsSamples) {
|
|
|
239
263
|
</template>
|
|
240
264
|
</Variant>
|
|
241
265
|
|
|
266
|
+
<Variant title="Manual Workflow" :init-state="() => ({ isOpen: true })">
|
|
267
|
+
<template #default="{ state }">
|
|
268
|
+
<div style="padding: 2rem;">
|
|
269
|
+
<button
|
|
270
|
+
v-if="!state.isOpen"
|
|
271
|
+
type="button"
|
|
272
|
+
style="padding: 0.5rem 1rem; background: var(--bg-card, #fff); border: 1px solid var(--border-color, #e5e7eb); border-radius: 0.375rem; cursor: pointer; font-size: 0.875rem;"
|
|
273
|
+
@click="state.isOpen = true"
|
|
274
|
+
>
|
|
275
|
+
Re-open Modal
|
|
276
|
+
</button>
|
|
277
|
+
<AutoGroupModal
|
|
278
|
+
v-model="state.isOpen"
|
|
279
|
+
:samples="manualSamples"
|
|
280
|
+
:groups="manualGroups"
|
|
281
|
+
initial-mode="manual"
|
|
282
|
+
@apply="handleApply"
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
</template>
|
|
286
|
+
</Variant>
|
|
287
|
+
|
|
242
288
|
<Variant title="From Experiment Metadata" :init-state="() => ({ isOpen: true, lastResult: null as AutoGroupResult | null })">
|
|
243
289
|
<template #default="{ state }">
|
|
244
290
|
<div style="padding: 2rem;">
|