@morscherlab/mint-sdk 1.3.0-beta.5 → 1.3.0-beta.7
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/{ResourcePickerListInternal-BEXnyBNz.js → ResourcePickerListInternal-CFAw4vTZ.js} +20 -17
- package/dist/ResourcePickerListInternal-CFAw4vTZ.js.map +1 -0
- package/dist/components/SettingsModal.schema.d.ts +1 -1
- package/dist/components/SettingsModal.vue.d.ts +2 -0
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +3 -3
- package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +9 -3
- package/dist/{components-BTzcqrJp.js → components-GDDvof8D.js} +19 -15
- package/dist/components-GDDvof8D.js.map +1 -0
- package/dist/composables/index.js +3 -3
- package/dist/composables/usePlatformContext.d.ts +3 -0
- package/dist/{composables-Doqb7g1z.js → composables-D6uqp0UF.js} +3 -3
- package/dist/{composables-Doqb7g1z.js.map → composables-D6uqp0UF.js.map} +1 -1
- package/dist/index.js +5 -5
- package/dist/install.js +1 -1
- package/dist/styles.css +114 -115
- package/dist/templates/index.js +1 -1
- package/dist/{templates-DgAG5kMq.js → templates-BU1pet28.js} +31 -44
- package/dist/templates-BU1pet28.js.map +1 -0
- package/dist/types/components.d.ts +4 -0
- package/dist/types/platform.d.ts +2 -0
- package/dist/{validation-DILksa0c.js → validation-Dmp9Q5W0.js} +2 -2
- package/dist/{validation-DILksa0c.js.map → validation-Dmp9Q5W0.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.test.ts +8 -0
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +12 -5
- package/src/__tests__/components/FileUploader.test.ts +6 -9
- package/src/__tests__/components/ResourcePickerListInternal.test.ts +23 -4
- package/src/__tests__/components/SettingsModal.test.ts +22 -0
- package/src/__tests__/styles/controlRefreshRules.test.ts +22 -3
- package/src/__tests__/styles/menusShellRefresh.test.ts +9 -5
- package/src/__tests__/templates/templatesDeprecation.test.ts +8 -1
- package/src/components/AppTopBar.vue +5 -1
- package/src/components/ExperimentSelectorModal.vue +4 -1
- package/src/components/FileUploader.vue +30 -46
- package/src/components/SettingsModal.schema.ts +4 -2
- package/src/components/SettingsModal.vue +4 -2
- package/src/components/index.ts +3 -3
- package/src/components/internal/ResourcePickerListInternal.vue +21 -27
- package/src/styles/components/app-page-selector.css +3 -3
- package/src/styles/components/app-top-bar.css +16 -12
- package/src/styles/components/experiment-selector-modal.css +3 -9
- package/src/styles/components/file-uploader.css +22 -55
- package/src/styles/components/resource-picker-list.css +70 -36
- package/src/types/components.ts +4 -0
- package/src/types/platform.ts +2 -0
- package/dist/ResourcePickerListInternal-BEXnyBNz.js.map +0 -1
- package/dist/components-BTzcqrJp.js.map +0 -1
- package/dist/templates-DgAG5kMq.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.3.0-beta.
|
|
3
|
+
"version": "1.3.0-beta.7",
|
|
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",
|
|
@@ -1211,6 +1211,14 @@ describe('AppTopBar', () => {
|
|
|
1211
1211
|
expect(wrapper.get('.mint-topbar__plugin-name').text()).toBe('LC-MS Analysis')
|
|
1212
1212
|
})
|
|
1213
1213
|
|
|
1214
|
+
it('should render the platform display name when integrated', () => {
|
|
1215
|
+
mockPlatformCtx({ isIntegrated: true, plugin: { ...integratedPlugin, display_name: 'LC-MS Peak Review' } })
|
|
1216
|
+
|
|
1217
|
+
const wrapper = createWrapper()
|
|
1218
|
+
|
|
1219
|
+
expect(wrapper.get('.mint-topbar__plugin-name').text()).toBe('LC-MS Peak Review')
|
|
1220
|
+
})
|
|
1221
|
+
|
|
1214
1222
|
it('should not render the plugin version when integrated', () => {
|
|
1215
1223
|
mockPlatformCtx({ isIntegrated: true, plugin: integratedPlugin })
|
|
1216
1224
|
|
|
@@ -129,25 +129,32 @@ describe('ExperimentSelectorModal', () => {
|
|
|
129
129
|
expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
|
|
130
130
|
})
|
|
131
131
|
|
|
132
|
-
it('should
|
|
132
|
+
it('should show the experiment code as a pill on the second line', () => {
|
|
133
133
|
experiments.value = [experiment()]
|
|
134
134
|
const wrapper = mountModal({ modelValue: true })
|
|
135
135
|
|
|
136
136
|
expect(wrapper.get('.mint-resource-picker__code').text()).toBe('EXP-007')
|
|
137
137
|
})
|
|
138
138
|
|
|
139
|
-
it('should state the status as text
|
|
139
|
+
it('should state the status as text in the right-hand column', () => {
|
|
140
140
|
experiments.value = [experiment()]
|
|
141
141
|
const wrapper = mountModal({ modelValue: true })
|
|
142
142
|
|
|
143
|
-
expect(wrapper.get('.mint-resource-
|
|
143
|
+
expect(wrapper.get('.mint-resource-picker__aside').text()).toContain('Ongoing')
|
|
144
144
|
})
|
|
145
145
|
|
|
146
|
-
it('should
|
|
146
|
+
it('should tint the status with the experiment status variant', () => {
|
|
147
|
+
experiments.value = [experiment()]
|
|
148
|
+
const wrapper = mountModal({ modelValue: true })
|
|
149
|
+
|
|
150
|
+
expect(wrapper.get('.mint-resource-picker__status').classes()).toContain('mint-resource-picker__status--primary')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should fill the current experiment row', () => {
|
|
147
154
|
experiments.value = [experiment()]
|
|
148
155
|
const wrapper = mountModal({ modelValue: true, currentExperimentId: 7 })
|
|
149
156
|
|
|
150
|
-
expect(wrapper.find('.mint-resource-
|
|
157
|
+
expect(wrapper.find('.mint-resource-picker__row--selected').exists()).toBe(true)
|
|
151
158
|
})
|
|
152
159
|
|
|
153
160
|
it('clears the shared store from the clear-selection footer', async () => {
|
|
@@ -3,16 +3,13 @@ import { describe, expect, it } from 'vitest'
|
|
|
3
3
|
import FileUploader from '../../components/FileUploader.vue'
|
|
4
4
|
|
|
5
5
|
describe('FileUploader', () => {
|
|
6
|
-
it('should
|
|
7
|
-
const wrapper = mount(FileUploader, { props: {
|
|
8
|
-
|
|
9
|
-
dataTransfer: { items: { length: 3 } },
|
|
10
|
-
})
|
|
11
|
-
expect(wrapper.get('.mint-file-uploader__text').text()).toBe('Release to add 3 files')
|
|
6
|
+
it('should name the accepted formats in the prompt when accept is set', () => {
|
|
7
|
+
const wrapper = mount(FileUploader, { props: { accept: '.raw, .mzML' } })
|
|
8
|
+
expect(wrapper.get('.mint-file-uploader__text').text()).toBe('Drop RAW, MZML or click')
|
|
12
9
|
})
|
|
13
10
|
|
|
14
|
-
it('should
|
|
15
|
-
const wrapper = mount(FileUploader
|
|
16
|
-
expect(wrapper.get('.mint-file-
|
|
11
|
+
it('should prompt for any file when accept is not set', () => {
|
|
12
|
+
const wrapper = mount(FileUploader)
|
|
13
|
+
expect(wrapper.get('.mint-file-uploader__text').text()).toBe('Drop any file or click')
|
|
17
14
|
})
|
|
18
15
|
})
|
|
@@ -40,8 +40,27 @@ describe('ResourcePickerListInternal', () => {
|
|
|
40
40
|
expect(setup().get('.mint-resource-picker__code').text()).toBe('EXP-042')
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
it('should
|
|
44
|
-
expect(setup().get('.mint-resource-
|
|
43
|
+
it('should render the code as the shared gray code pill', () => {
|
|
44
|
+
expect(setup().get('.mint-resource-picker__code').classes()).toContain('mint-exp-code')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should tint the status by its variant', () => {
|
|
48
|
+
const wrapper = setup({
|
|
49
|
+
describe: (item: unknown) => ({ title: (item as Item).name, status: { label: 'Completed', variant: 'success' } }),
|
|
50
|
+
})
|
|
51
|
+
expect(wrapper.get('.mint-resource-picker__status').classes()).toContain('mint-resource-picker__status--success')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should keep the title line to the title alone', () => {
|
|
55
|
+
expect(setup().get('.mint-resource-picker__title').text()).toBe('Liver microsome stability')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should lead the second line with the code and meta', () => {
|
|
59
|
+
expect(setup().get('.mint-resource-picker__meta').text()).toBe('EXP-042Zhang')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should put the date in the right-hand column', () => {
|
|
63
|
+
expect(setup().get('.mint-resource-picker__aside .mint-resource-picker__date').text()).toBe('09-24')
|
|
45
64
|
})
|
|
46
65
|
|
|
47
66
|
it('should mark the selected row', () => {
|
|
@@ -49,8 +68,8 @@ describe('ResourcePickerListInternal', () => {
|
|
|
49
68
|
expect(wrapper.findAll('[role="option"]')[1]!.attributes('aria-selected')).toBe('true')
|
|
50
69
|
})
|
|
51
70
|
|
|
52
|
-
it('should
|
|
53
|
-
expect(setup({ selectedKey: 2 }).findAll('.mint-resource-
|
|
71
|
+
it('should fill the selected row only', () => {
|
|
72
|
+
expect(setup({ selectedKey: 2 }).findAll('.mint-resource-picker__row--selected')).toHaveLength(1)
|
|
54
73
|
})
|
|
55
74
|
|
|
56
75
|
it('should emit select when a row is clicked', async () => {
|
|
@@ -540,6 +540,28 @@ describe('SettingsModal', () => {
|
|
|
540
540
|
expect(wrapper.findAll('.mint-modal__rail-section').map(heading => heading.text())).toEqual(['Plugin', 'Personal'])
|
|
541
541
|
})
|
|
542
542
|
|
|
543
|
+
it('should list and open Appearance first when appearanceFirst is set', () => {
|
|
544
|
+
const wrapper = mountSettingsModal({
|
|
545
|
+
tabs: [{ id: 'plugins', label: 'Plugins' }],
|
|
546
|
+
showAppearance: true,
|
|
547
|
+
appearanceFirst: true,
|
|
548
|
+
layout: 'vertical',
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
expect(wrapper.get('.mint-modal__rail-item--active').text()).toContain('Appearance')
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
it('should label a manual tab rail section from its section', () => {
|
|
555
|
+
const wrapper = mountSettingsModal({
|
|
556
|
+
tabs: [{ id: 'plugins', label: 'Plugins', section: 'Platform' }],
|
|
557
|
+
showAppearance: true,
|
|
558
|
+
appearanceFirst: true,
|
|
559
|
+
layout: 'vertical',
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
expect(wrapper.findAll('.mint-modal__rail-section').map(heading => heading.text())).toEqual(['Personal', 'Platform'])
|
|
563
|
+
})
|
|
564
|
+
|
|
543
565
|
it('should not render rail section headings when appearance is hidden', () => {
|
|
544
566
|
const wrapper = mountSettingsModal({ schema: railSchema })
|
|
545
567
|
|
|
@@ -184,11 +184,30 @@ describe('SDK control refresh style rules', () => {
|
|
|
184
184
|
expect(rule).toMatch(/flex-wrap:\s*wrap/)
|
|
185
185
|
})
|
|
186
186
|
|
|
187
|
-
it('should pad resource picker rows
|
|
188
|
-
// 16.25px title + 1px gap + 15px meta + 10px padding + 1px border = 43.25px, under min-height.
|
|
187
|
+
it('should pad resource picker rows to the roomier two-column layout when scanning resource-picker-list.css', () => {
|
|
189
188
|
const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
|
|
190
189
|
const rule = /\.mint-resource-picker__row\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
|
|
191
|
-
expect(rule).toMatch(/padding:\s*
|
|
190
|
+
expect(rule).toMatch(/padding:\s*10px 12px/)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// Group bars and the list frame were inset by the modal body padding; the list now runs edge to edge.
|
|
194
|
+
it('should drop the modal body padding around the resource picker when scanning resource-picker-list.css', () => {
|
|
195
|
+
const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
|
|
196
|
+
const rule = /\.mint-modal__body:has\(> \.mint-resource-picker\)\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
|
|
197
|
+
expect(rule).toMatch(/padding:\s*0/)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should pad resource picker group bars to the modal gutter when scanning resource-picker-list.css', () => {
|
|
201
|
+
const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
|
|
202
|
+
const rule = /\.mint-resource-picker__group\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
|
|
203
|
+
expect(rule).toMatch(/padding:\s*0 1\.25rem/)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// The list sat in a bordered card inside the modal card.
|
|
207
|
+
it('should draw no frame around the resource picker list when scanning resource-picker-list.css', () => {
|
|
208
|
+
const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
|
|
209
|
+
const rule = /\.mint-resource-picker__frame\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
|
|
210
|
+
expect(rule).not.toMatch(/(^|;)\s*border:/)
|
|
192
211
|
})
|
|
193
212
|
|
|
194
213
|
it('should paint the clear-selection link with primary ink when scanning experiment-selector-modal.css (8b)', () => {
|
|
@@ -21,22 +21,26 @@ describe('menus and app shell styles', () => {
|
|
|
21
21
|
expect(css('step-wizard.css')).not.toMatch(/\.mint-wizard__nav-btn/)
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
it('should pad the top bar to
|
|
25
|
-
expect(css('app-top-bar.css')).toMatch(/\.mint-topbar\s*\{[^}]*padding:\s*0\.
|
|
24
|
+
it('should pad the top bar to the 1.2 height (62px)', () => {
|
|
25
|
+
expect(css('app-top-bar.css')).toMatch(/\.mint-topbar\s*\{[^}]*padding:\s*0\.75rem 1rem;/)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should size the page selector trigger to the 36px identity tile', () => {
|
|
29
|
+
expect(css('app-page-selector.css')).toMatch(/\.mint-page-selector__trigger\s*\{[^}]*height:\s*2\.25rem;/)
|
|
26
30
|
})
|
|
27
31
|
|
|
28
32
|
// Shrinking the tile beside the identity also shrank structured icons inside their solid chip, drawing a ring.
|
|
29
|
-
it('should keep the
|
|
33
|
+
it('should keep the plugin tile beside the static plugin identity (5a)', () => {
|
|
30
34
|
expect(css('app-top-bar.css')).not.toMatch(/:has\(\+ \.mint-topbar__plugin-identity\)/)
|
|
31
35
|
})
|
|
32
36
|
|
|
33
37
|
// A slotted 36px logo shrank to 32px wide but kept its height, drawing a 32x36 tile.
|
|
34
|
-
it('should fit slotted identity content inside the
|
|
38
|
+
it('should fit slotted identity content inside the identity tile (5a)', () => {
|
|
35
39
|
expect(css('app-top-bar.css')).toMatch(/\.mint-topbar__identity > \*\s*\{[^}]*max-width:\s*100%;[^}]*max-height:\s*100%;/)
|
|
36
40
|
})
|
|
37
41
|
|
|
38
42
|
// 15px x 1.15 + 12px x 1.2 = 31.7px, so the title group fits the 32px control row.
|
|
39
|
-
it('should fit the top-bar title and subtitle inside the
|
|
43
|
+
it('should fit the top-bar title and subtitle inside the control row (5a)', () => {
|
|
40
44
|
expect(css('app-top-bar.css')).toMatch(/\.mint-topbar-title\s*\{[^}]*line-height:\s*1\.15;/)
|
|
41
45
|
})
|
|
42
46
|
|
|
@@ -53,12 +53,19 @@ function undeprecatedValueExports(file: string): string[] {
|
|
|
53
53
|
|
|
54
54
|
describe('templates deprecation', () => {
|
|
55
55
|
it('should expose deprecated component tags to TypeScript consumers', () => {
|
|
56
|
-
const names = ['BioTemplateRenderer', 'SmartGroupModal', '
|
|
56
|
+
const names = ['BioTemplateRenderer', 'SmartGroupModal', 'DoseDesignWorkspaceView']
|
|
57
57
|
const tagged = exportsOf(COMPONENTS_INDEX).filter(isDeprecated).map(symbol => symbol.getName())
|
|
58
58
|
|
|
59
59
|
expect(names.filter(name => !tagged.includes(name))).toEqual([])
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
+
it('should not tag components that stay public in MINT 1.4', () => {
|
|
63
|
+
const names = ['SampleSelector', 'SequenceProgressBar', 'InstrumentStateBadge']
|
|
64
|
+
const tagged = exportsOf(COMPONENTS_INDEX).filter(isDeprecated).map(symbol => symbol.getName())
|
|
65
|
+
|
|
66
|
+
expect(names.filter(name => tagged.includes(name))).toEqual([])
|
|
67
|
+
})
|
|
68
|
+
|
|
62
69
|
it('should tag every template value export when the templates move out in MINT 1.4', () => {
|
|
63
70
|
expect(undeprecatedValueExports(TEMPLATES_INDEX)).toEqual([])
|
|
64
71
|
})
|
|
@@ -145,7 +145,10 @@ const hasPlatformPageSelector = computed(() =>
|
|
|
145
145
|
)
|
|
146
146
|
const hasPageSelector = computed(() => (props.pageSelector?.length ?? 0) > 1 || hasPlatformPageSelector.value)
|
|
147
147
|
/** Integrated plugins show their name as a static identity in place of the title; the page selector replaces it. */
|
|
148
|
-
|
|
148
|
+
// Platforms before 1.3 send no display_name.
|
|
149
|
+
const identityName = computed(() =>
|
|
150
|
+
isIntegrated.value && !hasPageSelector.value ? plugin.value?.display_name || plugin.value?.name : undefined,
|
|
151
|
+
)
|
|
149
152
|
const hasPillNav = computed(() => !!props.pillNav?.length)
|
|
150
153
|
const hasAccountMenu = computed(() => !!props.accountMenu?.length)
|
|
151
154
|
const hasTitleGroup = computed(
|
|
@@ -441,6 +444,7 @@ const effectiveCurrentPageSelectorId = computed(() =>
|
|
|
441
444
|
:title="settingsConfig?.title"
|
|
442
445
|
:tabs="normalizedSettingsTabs"
|
|
443
446
|
:show-appearance="settingsConfig?.showAppearance ?? true"
|
|
447
|
+
:appearance-first="settingsConfig?.appearanceFirst"
|
|
444
448
|
:size="settingsConfig?.size"
|
|
445
449
|
:layout="settingsConfig?.layout"
|
|
446
450
|
:schema="settingsConfig?.schema"
|
|
@@ -14,6 +14,7 @@ import { useExperimentStore } from '../stores/experiment'
|
|
|
14
14
|
import {
|
|
15
15
|
formatExperimentDate,
|
|
16
16
|
formatExperimentStatus,
|
|
17
|
+
getExperimentStatusVariant,
|
|
17
18
|
EXPERIMENT_STATUS_OPTIONS,
|
|
18
19
|
DATE_PRESET_OPTIONS,
|
|
19
20
|
SORT_OPTIONS,
|
|
@@ -105,8 +106,10 @@ function describe(experiment: ExperimentSummary) {
|
|
|
105
106
|
meta: [
|
|
106
107
|
...(groupToggle.value ? [] : [experiment.project_name || experiment.project || '']),
|
|
107
108
|
owner ? [owner.first_name, owner.last_name].filter(Boolean).join(' ') || owner.username : '',
|
|
108
|
-
formatExperimentStatus(experiment.status),
|
|
109
109
|
].filter(Boolean),
|
|
110
|
+
status: experiment.status
|
|
111
|
+
? { label: formatExperimentStatus(experiment.status), variant: getExperimentStatusVariant(experiment.status) }
|
|
112
|
+
: undefined,
|
|
110
113
|
date: formatExperimentDate(experiment.created_at),
|
|
111
114
|
}
|
|
112
115
|
}
|
|
@@ -31,7 +31,6 @@ const emit = defineEmits<{
|
|
|
31
31
|
}>()
|
|
32
32
|
|
|
33
33
|
const isDragOver = ref(false)
|
|
34
|
-
const dragCount = ref(0)
|
|
35
34
|
const inputRef = ref<HTMLInputElement>()
|
|
36
35
|
const selectedFiles = ref<File[]>([])
|
|
37
36
|
const folderName = ref<string | null>(null)
|
|
@@ -45,14 +44,9 @@ watch(() => [props.files, props.rootName] as const, ([files, rootName]) => {
|
|
|
45
44
|
const isFolder = computed(() => props.mode === 'folder')
|
|
46
45
|
const isMixed = computed(() => props.mode === 'file+folder')
|
|
47
46
|
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
return `Release to add ${dragCount.value} file${dragCount.value === 1 ? '' : 's'}`
|
|
52
|
-
}
|
|
53
|
-
if (isMixed.value) return 'Drop files or a folder, or click to choose files'
|
|
54
|
-
if (isFolder.value) return 'Drop a folder, or click to choose'
|
|
55
|
-
return props.multiple ? 'Drop files, or click to choose' : 'Drop a file, or click to choose'
|
|
47
|
+
const acceptLabel = computed(() => {
|
|
48
|
+
if (!props.accept) return isFolder.value ? 'a folder' : 'any file'
|
|
49
|
+
return props.accept.split(',').map(a => a.trim().replace('.', '').toUpperCase()).join(', ')
|
|
56
50
|
})
|
|
57
51
|
|
|
58
52
|
const maxSizeLabel = computed(() => {
|
|
@@ -69,12 +63,6 @@ const maxSizeLabel = computed(() => {
|
|
|
69
63
|
return `${props.maxSize} bytes`
|
|
70
64
|
})
|
|
71
65
|
|
|
72
|
-
/** Accepted formats exactly as the caller wrote them, plus the size cap. */
|
|
73
|
-
const formatsLabel = computed(() => [
|
|
74
|
-
props.accept?.split(',').map(a => a.trim()).filter(Boolean).join(' '),
|
|
75
|
-
maxSizeLabel.value && `max ${maxSizeLabel.value}`,
|
|
76
|
-
].filter(Boolean).join(' · '))
|
|
77
|
-
|
|
78
66
|
const fileTypeMap: Record<string, { color: string; label: string }> = {
|
|
79
67
|
csv: { color: '#10B981', label: 'CSV' },
|
|
80
68
|
tsv: { color: '#10B981', label: 'TSV' },
|
|
@@ -272,7 +260,6 @@ function handleDragOver(event: DragEvent) {
|
|
|
272
260
|
event.preventDefault()
|
|
273
261
|
if (!props.disabled) {
|
|
274
262
|
isDragOver.value = true
|
|
275
|
-
dragCount.value = event.dataTransfer?.items?.length ?? 0
|
|
276
263
|
}
|
|
277
264
|
}
|
|
278
265
|
|
|
@@ -359,37 +346,34 @@ function formatFileSize(bytes: number): string {
|
|
|
359
346
|
/>
|
|
360
347
|
|
|
361
348
|
<div class="mint-file-uploader__content">
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
</svg>
|
|
387
|
-
</span>
|
|
349
|
+
<svg
|
|
350
|
+
v-if="isFolder"
|
|
351
|
+
class="mint-file-uploader__icon"
|
|
352
|
+
fill="none"
|
|
353
|
+
stroke="currentColor"
|
|
354
|
+
stroke-width="1.5"
|
|
355
|
+
stroke-linecap="round"
|
|
356
|
+
stroke-linejoin="round"
|
|
357
|
+
viewBox="0 0 24 24"
|
|
358
|
+
>
|
|
359
|
+
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
|
|
360
|
+
</svg>
|
|
361
|
+
<svg
|
|
362
|
+
v-else
|
|
363
|
+
class="mint-file-uploader__icon"
|
|
364
|
+
fill="none"
|
|
365
|
+
stroke="currentColor"
|
|
366
|
+
stroke-width="1.5"
|
|
367
|
+
stroke-linecap="round"
|
|
368
|
+
stroke-linejoin="round"
|
|
369
|
+
viewBox="0 0 24 24"
|
|
370
|
+
>
|
|
371
|
+
<path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3" /><path d="m16 16-4-4-4 4" /><path d="M12 12v9" />
|
|
372
|
+
</svg>
|
|
388
373
|
|
|
389
|
-
<
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
</div>
|
|
374
|
+
<p class="mint-file-uploader__text">
|
|
375
|
+
{{ isMixed ? 'Drop files or a folder, or click to choose files' : `Drop ${acceptLabel} or click` }}
|
|
376
|
+
</p>
|
|
393
377
|
</div>
|
|
394
378
|
</div>
|
|
395
379
|
|
|
@@ -68,7 +68,9 @@ export function normalizeVisibleSettingsTabs(
|
|
|
68
68
|
|
|
69
69
|
export function buildSettingsTabs(
|
|
70
70
|
baseTabs: SettingsTab[],
|
|
71
|
-
showAppearance: boolean
|
|
71
|
+
showAppearance: boolean,
|
|
72
|
+
appearanceFirst = false
|
|
72
73
|
): SettingsTab[] {
|
|
73
|
-
|
|
74
|
+
if (!showAppearance) return baseTabs
|
|
75
|
+
return appearanceFirst ? [APPEARANCE_TAB, ...baseTabs] : [...baseTabs, APPEARANCE_TAB]
|
|
74
76
|
}
|
|
@@ -90,6 +90,8 @@ interface Props {
|
|
|
90
90
|
/** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */
|
|
91
91
|
tabs?: SettingsTabInput[]
|
|
92
92
|
showAppearance?: boolean
|
|
93
|
+
/** List Appearance before the other tabs and open on it. */
|
|
94
|
+
appearanceFirst?: boolean
|
|
93
95
|
size?: 'md' | 'lg' | 'xl'
|
|
94
96
|
layout?: SettingsModalLayout
|
|
95
97
|
/** Declarative schema — fields auto-render via SDK form components. */
|
|
@@ -238,7 +240,7 @@ const allTabs = computed<SettingsTab[]>(() => {
|
|
|
238
240
|
const base: SettingsTab[] = settingsSchema.value
|
|
239
241
|
? visibleSchemaGroups.value.map(settingsGroupToTab)
|
|
240
242
|
: manualTabs.value
|
|
241
|
-
return buildSettingsTabs(base, props.showAppearance)
|
|
243
|
+
return buildSettingsTabs(base, props.showAppearance, props.appearanceFirst)
|
|
242
244
|
})
|
|
243
245
|
|
|
244
246
|
const activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)
|
|
@@ -272,7 +274,7 @@ const modalTabs = computed<ModalTab[]>(() =>
|
|
|
272
274
|
description: tab.description,
|
|
273
275
|
meta: group ? (modified ? String(modified) : undefined) : tab.meta,
|
|
274
276
|
// Sections only split plugin groups from Appearance; without it there is nothing to split.
|
|
275
|
-
section: props.showAppearance ? (tab.id === APPEARANCE_TAB_ID ? 'Personal' : 'Plugin') : undefined,
|
|
277
|
+
section: props.showAppearance ? (tab.id === APPEARANCE_TAB_ID ? 'Personal' : tab.section ?? 'Plugin') : undefined,
|
|
276
278
|
}
|
|
277
279
|
}),
|
|
278
280
|
)
|
package/src/components/index.ts
CHANGED
|
@@ -86,8 +86,8 @@ export { /** @deprecated Removed in MINT 1.4. */ default as SampleLegend } from
|
|
|
86
86
|
export { /** @deprecated Removed in MINT 1.4. */ default as ExperimentTimeline } from './ExperimentTimeline.vue'
|
|
87
87
|
|
|
88
88
|
// Sample management components
|
|
89
|
-
/** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support.
|
|
90
|
-
export {
|
|
89
|
+
/** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support. */
|
|
90
|
+
export { default as SampleSelector } from './SampleSelector.vue'
|
|
91
91
|
/** Smart Group modal wired to the `useAutoGroup` engine. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
92
92
|
export { /** @deprecated Removed in MINT 1.4. */ default as AutoGroupModal } from './AutoGroupModal.vue'
|
|
93
93
|
/** Drag-and-drop two-zone assigner for splitting sample groups into control and treatment arms. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
@@ -114,7 +114,7 @@ export { default as ProtocolStepEditor } from './ProtocolStepEditor.vue'
|
|
|
114
114
|
export { default as PlotlyChart } from './PlotlyChart.vue'
|
|
115
115
|
export { default as ScientificNumber } from './ScientificNumber.vue'
|
|
116
116
|
export { default as ChemicalFormula } from './ChemicalFormula.vue'
|
|
117
|
-
/** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels.
|
|
117
|
+
/** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */
|
|
118
118
|
export { default as SequenceProgressBar } from './SequenceProgressBar.vue'
|
|
119
119
|
/** Status badge for instrument states: a tone dot (or dash / hollow shape for offline, never-reported and inactive) plus a label. */
|
|
120
120
|
export { default as InstrumentStateBadge } from './InstrumentStateBadge.vue'
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
<script setup lang="ts" generic="T">
|
|
2
2
|
/**
|
|
3
3
|
* Searchable, optionally grouped single-select list shared by the experiment and
|
|
4
|
-
* artifact selector modals
|
|
4
|
+
* artifact selector modals: two-line rows (title, then code + meta) with status and date
|
|
5
|
+
* in a right-hand column, a filled selected row, skeleton,
|
|
5
6
|
* error and empty states, and the combobox keyboard model (↑ ↓ Enter).
|
|
6
7
|
*/
|
|
7
8
|
import { computed, nextTick, reactive, ref, useId, watch } from 'vue'
|
|
8
9
|
import BaseInput from '../BaseInput.vue'
|
|
9
10
|
import Skeleton from '../Skeleton.vue'
|
|
10
11
|
import EmptyState from '../EmptyState.vue'
|
|
12
|
+
import type { PillVariant } from '../../types'
|
|
11
13
|
|
|
12
14
|
export interface ResourcePickerRow {
|
|
13
|
-
/** Identifier shown
|
|
15
|
+
/** Identifier shown as a gray pill at the start of the second line, e.g. an experiment code. */
|
|
14
16
|
code?: string
|
|
15
17
|
title: string
|
|
16
|
-
/** Second-line facts, joined with " · ". */
|
|
18
|
+
/** Second-line facts after the code, joined with " · ". */
|
|
17
19
|
meta?: string[]
|
|
18
|
-
/**
|
|
20
|
+
/** Right column, above the date, tinted by its variant. */
|
|
21
|
+
status?: { label: string; variant: PillVariant }
|
|
22
|
+
/** Right column, in mono. */
|
|
19
23
|
date?: string
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -206,36 +210,26 @@ watch(
|
|
|
206
210
|
@mouseenter="activeIndex = indexOf(item)"
|
|
207
211
|
>
|
|
208
212
|
<span class="mint-resource-picker__text">
|
|
209
|
-
<span class="mint-resource-picker__title"
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
+
<span class="mint-resource-picker__title">{{ describe(item).title }}</span>
|
|
214
|
+
<span v-if="describe(item).code || describe(item).meta?.length" class="mint-resource-picker__meta"
|
|
215
|
+
><span v-if="describe(item).code" class="mint-resource-picker__code mint-exp-code mint-exp-code--sm">{{
|
|
216
|
+
describe(item).code
|
|
217
|
+
}}</span
|
|
218
|
+
><span v-if="describe(item).meta?.length">{{ describe(item).meta!.join(' · ') }}</span></span
|
|
213
219
|
>
|
|
220
|
+
</span>
|
|
221
|
+
<span v-if="describe(item).status || describe(item).date" class="mint-resource-picker__aside">
|
|
214
222
|
<span
|
|
215
|
-
v-if="describe(item).
|
|
216
|
-
class="mint-resource-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
>{{ describe(item).meta?.length ? ' · ' : ''
|
|
220
|
-
}}<span class="mint-resource-picker__date">{{ describe(item).date }}</span></template
|
|
221
|
-
></span
|
|
223
|
+
v-if="describe(item).status"
|
|
224
|
+
class="mint-resource-picker__status"
|
|
225
|
+
:class="`mint-resource-picker__status--${describe(item).status!.variant}`"
|
|
226
|
+
>{{ describe(item).status!.label }}</span
|
|
222
227
|
>
|
|
228
|
+
<span v-if="describe(item).date" class="mint-resource-picker__date">{{ describe(item).date }}</span>
|
|
223
229
|
</span>
|
|
224
230
|
<span v-if="$slots.actions" class="mint-resource-picker__actions" @click.stop>
|
|
225
231
|
<slot name="actions" :item="item" />
|
|
226
232
|
</span>
|
|
227
|
-
<span class="mint-resource-picker__check-slot" aria-hidden="true">
|
|
228
|
-
<svg
|
|
229
|
-
v-if="itemKey(item) === selectedKey"
|
|
230
|
-
class="mint-resource-picker__check"
|
|
231
|
-
viewBox="0 0 16 16"
|
|
232
|
-
fill="none"
|
|
233
|
-
stroke="currentColor"
|
|
234
|
-
stroke-width="2"
|
|
235
|
-
>
|
|
236
|
-
<path d="M3.5 8.5l3 3 6-7" />
|
|
237
|
-
</svg>
|
|
238
|
-
</span>
|
|
239
233
|
</div>
|
|
240
234
|
</template>
|
|
241
235
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* AppTopBar page selector — left-side route/page dropdown.
|
|
2
|
-
Trigger speaks the field language (5a):
|
|
2
|
+
Trigger speaks the field language (5a): 36px (the identity tile height), hover changes the background
|
|
3
3
|
only, open = field focus, inline 12px chevron. Menu = .mint-popover +
|
|
4
4
|
.mint-menu-item (2d); __menu / __item stay as aliases. */
|
|
5
5
|
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
align-items: center;
|
|
14
14
|
gap: 0.5rem;
|
|
15
15
|
width: 100%;
|
|
16
|
-
height:
|
|
17
|
-
padding: 0 0.5rem 0 0.
|
|
16
|
+
height: 2.25rem;
|
|
17
|
+
padding: 0 0.5rem 0 0.4375rem;
|
|
18
18
|
border: 1px solid var(--border-color);
|
|
19
19
|
background: var(--bg-card);
|
|
20
20
|
color: var(--text-primary);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
.mint-topbar {
|
|
10
10
|
display: block;
|
|
11
11
|
width: 100%;
|
|
12
|
-
padding: 0.
|
|
12
|
+
padding: 0.75rem 1rem;
|
|
13
13
|
background-color: var(--bg-secondary);
|
|
14
14
|
box-sizing: border-box;
|
|
15
15
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
display: flex;
|
|
20
20
|
align-items: center;
|
|
21
21
|
gap: 0.5rem;
|
|
22
|
-
min-height:
|
|
22
|
+
min-height: 2.375rem;
|
|
23
23
|
position: relative;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -31,24 +31,28 @@
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.mint-topbar__identity {
|
|
34
|
-
width:
|
|
35
|
-
height:
|
|
34
|
+
width: 2.25rem;
|
|
35
|
+
height: 2.25rem;
|
|
36
36
|
display: inline-flex;
|
|
37
37
|
align-items: center;
|
|
38
38
|
justify-content: center;
|
|
39
39
|
flex-shrink: 0;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/* Slotted logos sized above
|
|
42
|
+
/* Slotted logos sized above 36px shrink to fit instead of stretching the tile. */
|
|
43
43
|
.mint-topbar__identity > * {
|
|
44
44
|
max-width: 100%;
|
|
45
45
|
max-height: 100%;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/*
|
|
49
|
-
.mint-
|
|
50
|
-
|
|
51
|
-
height:
|
|
48
|
+
/* Left and right fields share the 36px identity tile height. */
|
|
49
|
+
.mint-topbar .mint-experiment-popover__trigger,
|
|
50
|
+
.mint-topbar .mint-experiment-popover__save-trigger {
|
|
51
|
+
height: 2.25rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.mint-topbar .mint-experiment-popover__save-trigger {
|
|
55
|
+
width: 2.25rem;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/* Centered section — absolute so asymmetric left/right weight never shifts the
|
|
@@ -87,8 +91,8 @@
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
.mint-topbar__logo-icon {
|
|
90
|
-
width:
|
|
91
|
-
height:
|
|
94
|
+
width: 2.25rem;
|
|
95
|
+
height: 2.25rem;
|
|
92
96
|
background-color: var(--color-primary);
|
|
93
97
|
border-radius: var(--radius-md);
|
|
94
98
|
display: flex;
|
|
@@ -151,7 +155,7 @@
|
|
|
151
155
|
opacity: 0.8;
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
/* Title + subtitle stay inside the
|
|
158
|
+
/* Title + subtitle stay inside the 36px control row. */
|
|
155
159
|
.mint-topbar-title-group {
|
|
156
160
|
display: flex;
|
|
157
161
|
flex-direction: column;
|