@morscherlab/mint-sdk 1.3.0-beta.3 → 1.3.0-beta.5
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/README.md +23 -140
- package/dist/{ResourcePickerListInternal-c3IT8PIu.js → ResourcePickerListInternal-BEXnyBNz.js} +7 -3
- package/dist/ResourcePickerListInternal-BEXnyBNz.js.map +1 -0
- package/dist/components/FilePicker.vue.d.ts +4 -1
- package/dist/components/WellPlate.colors.d.ts +1 -5
- package/dist/components/WellPlate.rendering.d.ts +5 -2
- package/dist/components/index.d.ts +32 -17
- package/dist/components/index.js +3 -3
- package/dist/components/internal/FilePickerListInternal.vue.d.ts +2 -0
- package/dist/{components-CvY105kJ.js → components-BTzcqrJp.js} +400 -358
- package/dist/components-BTzcqrJp.js.map +1 -0
- package/dist/composables/createFilePickerAdapter.d.ts +5 -1
- package/dist/composables/filePicker/usePickerNavigation.d.ts +3 -2
- package/dist/composables/index.d.ts +0 -3
- package/dist/composables/index.js +4 -4
- package/dist/composables/useAutoGroup.d.ts +1 -1
- package/dist/composables/useBioTemplateComponents.d.ts +15 -3
- package/dist/composables/useBioTemplateControls.d.ts +5 -1
- package/dist/composables/useBioTemplatePackWorkspace.d.ts +2 -0
- package/dist/composables/useBioTemplatePresetWorkspace.d.ts +5 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +5 -1
- package/dist/composables/useExperimentSave.d.ts +5 -1
- package/dist/composables/useFilePicker.d.ts +4 -1
- package/dist/composables/useGroupAssignment.d.ts +5 -1
- package/dist/composables/useTemplateCollection.d.ts +5 -1
- package/dist/composables-Doqb7g1z.js +591 -0
- package/dist/composables-Doqb7g1z.js.map +1 -0
- package/dist/index.js +24 -24
- package/dist/index.js.map +1 -1
- package/dist/install.js +1 -1
- package/dist/lcms.d.ts +11 -11
- package/dist/styles.css +60 -89
- package/dist/templates/index.js +1 -1
- package/dist/{templates-Bj9hZ7iV.js → templates-DgAG5kMq.js} +2 -2
- package/dist/{templates-Bj9hZ7iV.js.map → templates-DgAG5kMq.js.map} +1 -1
- package/dist/types/auth.d.ts +0 -27
- package/dist/types/components.d.ts +4 -0
- package/dist/types/fileBrowserTypes.d.ts +13 -0
- package/dist/types/filePicker.d.ts +8 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/rack.d.ts +16 -16
- package/dist/{validation-B4Y8_eEe.js → validation-DILksa0c.js} +50 -12
- package/dist/validation-DILksa0c.js.map +1 -0
- package/package.json +1 -6
- package/src/__tests__/components/ActionMenu.test.ts +10 -0
- package/src/__tests__/components/AppTopBar.test.ts +16 -8
- package/src/__tests__/components/EmptyState.test.ts +15 -0
- package/src/__tests__/components/FilePicker.test.ts +65 -1
- package/src/__tests__/components/WellPlate.decoration.test.ts +4 -28
- package/src/__tests__/components/WellPlate.rendering.test.ts +20 -33
- package/src/__tests__/components/WellPlate.sampleTypeIndicator.test.ts +7 -7
- package/src/__tests__/composables/createFilePickerAdapter.test.ts +92 -1
- package/src/__tests__/composables/usePickerNavigation.test.ts +61 -1
- package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +18 -6
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +2 -2
- package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
- package/src/__tests__/styles/menusShellRefresh.test.ts +8 -2
- package/src/__tests__/templates/templatesDeprecation.test.ts +51 -11
- package/src/components/ActionMenu.vue +10 -2
- package/src/components/AppTopBar.vue +2 -4
- package/src/components/EmptyState.vue +3 -1
- package/src/components/FilePicker.vue +42 -0
- package/src/components/WellPlate.colors.ts +6 -10
- package/src/components/WellPlate.legend.ts +4 -4
- package/src/components/WellPlate.rendering.ts +9 -8
- package/src/components/WellPlate.vue +5 -22
- package/src/components/index.ts +32 -17
- package/src/components/internal/FilePickerListInternal.vue +11 -2
- package/src/composables/autoGroup/index.ts +1 -1
- package/src/composables/createFilePickerAdapter.ts +82 -22
- package/src/composables/filePicker/usePickerNavigation.ts +29 -2
- package/src/composables/index.ts +0 -7
- package/src/composables/useAutoGroup.ts +1 -1
- package/src/composables/useBioTemplateComponents.ts +15 -3
- package/src/composables/useBioTemplateControls.ts +5 -1
- package/src/composables/useBioTemplatePackWorkspace.ts +2 -0
- package/src/composables/useBioTemplatePresetWorkspace.ts +5 -1
- package/src/composables/useBioTemplateWorkspace.ts +5 -1
- package/src/composables/useExperimentSave.ts +5 -1
- package/src/composables/useFilePicker.ts +8 -1
- package/src/composables/useGroupAssignment.ts +5 -1
- package/src/composables/usePlatformFilePickerAdapter.ts +26 -4
- package/src/composables/useTemplateCollection.ts +5 -1
- package/src/index.ts +1 -1
- package/src/lcms.ts +11 -11
- package/src/styles/components/action-menu.css +10 -0
- package/src/styles/components/app-top-bar.css +7 -31
- package/src/styles/components/empty-state.css +3 -2
- package/src/styles/components/file-picker.css +23 -1
- package/src/styles/components/well-plate.css +18 -55
- package/src/types/auth.ts +0 -37
- package/src/types/components.ts +4 -0
- package/src/types/fileBrowserTypes.ts +15 -0
- package/src/types/filePicker.ts +8 -1
- package/src/types/index.ts +2 -5
- package/src/utils/rack.ts +16 -16
- package/dist/ResourcePickerListInternal-c3IT8PIu.js.map +0 -1
- package/dist/__tests__/composables/useAuth.test.d.ts +0 -1
- package/dist/__tests__/composables/usePasskey.test.d.ts +0 -1
- package/dist/components-CvY105kJ.js.map +0 -1
- package/dist/composables/useAuth.d.ts +0 -47
- package/dist/composables/usePasskey.d.ts +0 -10
- package/dist/composables/useWellPlateEditor.d.ts +0 -33
- package/dist/composables-DL4Vca7L.js +0 -1193
- package/dist/composables-DL4Vca7L.js.map +0 -1
- package/dist/plotly.min-D6Ge6qgX.js +0 -168582
- package/dist/plotly.min-D6Ge6qgX.js.map +0 -1
- package/dist/validation-B4Y8_eEe.js.map +0 -1
- package/src/__tests__/composables/useAuth.test.ts +0 -290
- package/src/__tests__/composables/usePasskey.test.ts +0 -38
- package/src/composables/useAuth.ts +0 -470
- package/src/composables/usePasskey.ts +0 -216
- package/src/composables/useWellPlateEditor.ts +0 -415
|
@@ -25,8 +25,14 @@ describe('menus and app shell styles', () => {
|
|
|
25
25
|
expect(css('app-top-bar.css')).toMatch(/\.mint-topbar\s*\{[^}]*padding:\s*0\.625rem 0\.75rem;/)
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// Shrinking the tile beside the identity also shrank structured icons inside their solid chip, drawing a ring.
|
|
29
|
+
it('should keep the 32px plugin tile beside the static plugin identity (5a)', () => {
|
|
30
|
+
expect(css('app-top-bar.css')).not.toMatch(/:has\(\+ \.mint-topbar__plugin-identity\)/)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// 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 32px tile (5a)', () => {
|
|
35
|
+
expect(css('app-top-bar.css')).toMatch(/\.mint-topbar__identity > \*\s*\{[^}]*max-width:\s*100%;[^}]*max-height:\s*100%;/)
|
|
30
36
|
})
|
|
31
37
|
|
|
32
38
|
// 15px x 1.15 + 12px x 1.2 = 31.7px, so the title group fits the 32px control row.
|
|
@@ -1,31 +1,71 @@
|
|
|
1
1
|
import { dirname, resolve } from 'node:path'
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { beforeAll, describe, expect, it } from 'vitest'
|
|
4
4
|
import ts from 'typescript'
|
|
5
5
|
|
|
6
6
|
const TEMPLATES_INDEX = resolve(dirname(fileURLToPath(import.meta.url)), '../../templates/index.ts')
|
|
7
|
+
const COMPONENTS_INDEX = resolve(dirname(fileURLToPath(import.meta.url)), '../../components/index.ts')
|
|
8
|
+
const COMPOSABLES_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../../composables')
|
|
7
9
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Composables removed in MINT 1.4 with the templates and sample grouping.
|
|
11
|
+
const REMOVED_COMPOSABLE_FILES = [
|
|
12
|
+
'useBioTemplateComponents.ts',
|
|
13
|
+
'useBioTemplateControls.ts',
|
|
14
|
+
'useBioTemplatePackWorkspace.ts',
|
|
15
|
+
'useBioTemplatePresetWorkspace.ts',
|
|
16
|
+
'useBioTemplateWorkspace.ts',
|
|
17
|
+
'useTemplateCollection.ts',
|
|
18
|
+
'useExperimentSave.ts',
|
|
19
|
+
'useGroupAssignment.ts',
|
|
20
|
+
].map(file => resolve(COMPOSABLES_DIR, file))
|
|
21
|
+
|
|
22
|
+
// One program for every file: building one per file took longer than the test timeout on CI.
|
|
23
|
+
let program: ts.Program
|
|
24
|
+
let checker: ts.TypeChecker
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
program = ts.createProgram([TEMPLATES_INDEX, COMPONENTS_INDEX, ...REMOVED_COMPOSABLE_FILES], {
|
|
12
28
|
target: ts.ScriptTarget.ES2022,
|
|
13
29
|
module: ts.ModuleKind.ESNext,
|
|
14
30
|
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
15
31
|
noEmit: true,
|
|
16
32
|
})
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
checker = program.getTypeChecker()
|
|
34
|
+
}, 60_000)
|
|
35
|
+
|
|
36
|
+
function exportsOf(file: string): ts.Symbol[] {
|
|
37
|
+
return checker.getExportsOfModule(checker.getSymbolAtLocation(program.getSourceFile(file)!)!)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isDeprecated(symbol: ts.Symbol): boolean {
|
|
41
|
+
return symbol.getJsDocTags(checker).some(tag => tag.name === 'deprecated')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// TypeScript ignores @deprecated on `export { ... } from` blocks; an import
|
|
45
|
+
// site only shows the deprecation when the original declaration carries it.
|
|
46
|
+
function undeprecatedValueExports(file: string): string[] {
|
|
47
|
+
return exportsOf(file)
|
|
21
48
|
.map(symbol => (symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol))
|
|
22
49
|
.filter(symbol => symbol.flags & ts.SymbolFlags.Value)
|
|
23
|
-
.filter(symbol => !symbol
|
|
50
|
+
.filter(symbol => !isDeprecated(symbol))
|
|
24
51
|
.map(symbol => symbol.getName())
|
|
25
52
|
}
|
|
26
53
|
|
|
27
54
|
describe('templates deprecation', () => {
|
|
55
|
+
it('should expose deprecated component tags to TypeScript consumers', () => {
|
|
56
|
+
const names = ['BioTemplateRenderer', 'SmartGroupModal', 'SampleSelector', 'DoseDesignWorkspaceView']
|
|
57
|
+
const tagged = exportsOf(COMPONENTS_INDEX).filter(isDeprecated).map(symbol => symbol.getName())
|
|
58
|
+
|
|
59
|
+
expect(names.filter(name => !tagged.includes(name))).toEqual([])
|
|
60
|
+
})
|
|
61
|
+
|
|
28
62
|
it('should tag every template value export when the templates move out in MINT 1.4', () => {
|
|
29
|
-
expect(
|
|
63
|
+
expect(undeprecatedValueExports(TEMPLATES_INDEX)).toEqual([])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should tag every value export of the template and grouping composables when they are removed in MINT 1.4', () => {
|
|
67
|
+
const untagged = REMOVED_COMPOSABLE_FILES.flatMap(undeprecatedValueExports)
|
|
68
|
+
|
|
69
|
+
expect(untagged).toEqual([])
|
|
30
70
|
})
|
|
31
71
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Compact overflow actions with an IconButton trigger and a viewport-safe teleported menu. */
|
|
3
3
|
import { computePosition, flip, offset, shift } from '@floating-ui/dom'
|
|
4
|
-
import { Ellipsis } from 'lucide-vue-next'
|
|
4
|
+
import { Check, Ellipsis } from 'lucide-vue-next'
|
|
5
5
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
|
6
6
|
import { useDropdownState } from '../composables/useDropdownState'
|
|
7
7
|
import { useMenuKeyboard } from '../composables/useMenuKeyboard'
|
|
@@ -167,10 +167,18 @@ watch(isOpen, async (opened) => {
|
|
|
167
167
|
v-else
|
|
168
168
|
type="button"
|
|
169
169
|
:class="['mint-menu-item mint-action-menu__item', { 'mint-menu-item--danger mint-action-menu__item--danger': item.danger }]"
|
|
170
|
-
role="menuitem"
|
|
170
|
+
:role="item.checked === undefined ? 'menuitem' : 'menuitemradio'"
|
|
171
|
+
:aria-checked="item.checked"
|
|
171
172
|
:disabled="item.disabled"
|
|
172
173
|
@click="handleSelect(item)"
|
|
173
174
|
>
|
|
175
|
+
<Check
|
|
176
|
+
v-if="item.checked !== undefined"
|
|
177
|
+
class="mint-action-menu__check"
|
|
178
|
+
:class="{ 'mint-action-menu__check--off': !item.checked }"
|
|
179
|
+
:size="14"
|
|
180
|
+
aria-hidden="true"
|
|
181
|
+
/>
|
|
174
182
|
{{ item.label }}
|
|
175
183
|
</button>
|
|
176
184
|
</template>
|
|
@@ -144,8 +144,8 @@ const hasPlatformPageSelector = computed(() =>
|
|
|
144
144
|
props.pageSelector === undefined && (plugin.value?.nav_items?.length ?? 0) > 1,
|
|
145
145
|
)
|
|
146
146
|
const hasPageSelector = computed(() => (props.pageSelector?.length ?? 0) > 1 || hasPlatformPageSelector.value)
|
|
147
|
-
/** Integrated plugins show their name
|
|
148
|
-
const identityName = computed(() => (isIntegrated.value ? plugin.value?.name : undefined))
|
|
147
|
+
/** Integrated plugins show their name as a static identity in place of the title; the page selector replaces it. */
|
|
148
|
+
const identityName = computed(() => (isIntegrated.value && !hasPageSelector.value ? plugin.value?.name : undefined))
|
|
149
149
|
const hasPillNav = computed(() => !!props.pillNav?.length)
|
|
150
150
|
const hasAccountMenu = computed(() => !!props.accountMenu?.length)
|
|
151
151
|
const hasTitleGroup = computed(
|
|
@@ -304,9 +304,7 @@ const effectiveCurrentPageSelectorId = computed(() =>
|
|
|
304
304
|
<!-- Left: static plugin identity (not a control) -->
|
|
305
305
|
<span v-if="identityName" class="mint-topbar__plugin-identity">
|
|
306
306
|
<span class="mint-topbar__plugin-name">{{ identityName }}</span>
|
|
307
|
-
<span v-if="plugin?.version" class="mint-topbar__plugin-version">v{{ plugin.version }}</span>
|
|
308
307
|
</span>
|
|
309
|
-
<span v-if="identityName && hasPageSelector" class="mint-topbar__divider" aria-hidden="true" />
|
|
310
308
|
|
|
311
309
|
<!-- Left: page selector -->
|
|
312
310
|
<AppTopBarPageSelectorInternal
|
|
@@ -56,7 +56,9 @@ const defaultIconPaths = [
|
|
|
56
56
|
<div class="mint-empty-state__body">
|
|
57
57
|
<h3 v-if="title" class="mint-empty-state__title">{{ title }}</h3>
|
|
58
58
|
<p v-if="description" class="mint-empty-state__description">{{ description }}</p>
|
|
59
|
-
<
|
|
59
|
+
<div v-if="$slots.default" class="mint-empty-state__content">
|
|
60
|
+
<slot />
|
|
61
|
+
</div>
|
|
60
62
|
</div>
|
|
61
63
|
<div v-if="actionLabel" class="mint-empty-state__action">
|
|
62
64
|
<BaseButton variant="secondary" @click="emit('action')">
|
|
@@ -12,6 +12,7 @@ import { computed, ref, useTemplateRef } from 'vue'
|
|
|
12
12
|
import BaseModal from './BaseModal.vue'
|
|
13
13
|
import BaseButton from './BaseButton.vue'
|
|
14
14
|
import BaseInput from './BaseInput.vue'
|
|
15
|
+
import ActionMenu from './ActionMenu.vue'
|
|
15
16
|
import SegmentedControl from './SegmentedControl.vue'
|
|
16
17
|
import FileUploader from './FileUploader.vue'
|
|
17
18
|
import FilePickerListInternal from './internal/FilePickerListInternal.vue'
|
|
@@ -24,6 +25,7 @@ import type {
|
|
|
24
25
|
PickerNode,
|
|
25
26
|
PickerSelection,
|
|
26
27
|
PickerSelectionMode,
|
|
28
|
+
PickerSort,
|
|
27
29
|
PickerSource,
|
|
28
30
|
PickerView,
|
|
29
31
|
} from '../types/filePicker'
|
|
@@ -39,6 +41,8 @@ interface Props {
|
|
|
39
41
|
title?: string
|
|
40
42
|
/** `list` shows one folder at a time; `tree` adds disclosure triangles. */
|
|
41
43
|
view?: PickerView
|
|
44
|
+
/** Row order on each open; the user can change it from the path bar until closing. */
|
|
45
|
+
defaultSort?: PickerSort
|
|
42
46
|
/** Host-owned visibility/selection filter; backend must still enforce access. */
|
|
43
47
|
systemFilter?: (node: PickerNode, source: PickerSource) => boolean
|
|
44
48
|
}
|
|
@@ -50,6 +54,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
50
54
|
initialSelection: null,
|
|
51
55
|
title: 'Choose data',
|
|
52
56
|
view: 'list',
|
|
57
|
+
defaultSort: 'newest',
|
|
53
58
|
})
|
|
54
59
|
const emit = defineEmits<{
|
|
55
60
|
'update:open': [open: boolean]
|
|
@@ -68,6 +73,7 @@ const {
|
|
|
68
73
|
source,
|
|
69
74
|
query,
|
|
70
75
|
recursive,
|
|
76
|
+
sort,
|
|
71
77
|
skipBlanks,
|
|
72
78
|
reviewing,
|
|
73
79
|
nav,
|
|
@@ -109,6 +115,19 @@ const sourceLabel = computed(() => {
|
|
|
109
115
|
}
|
|
110
116
|
return mounts.value.find((item) => item.path === props.adapter.mountOf?.(node.path))?.name ?? sourceNames.server
|
|
111
117
|
})
|
|
118
|
+
const SORT_LABELS: Record<PickerSort, string> = {
|
|
119
|
+
newest: 'Newest first',
|
|
120
|
+
oldest: 'Oldest first',
|
|
121
|
+
name: 'Name A–Z',
|
|
122
|
+
'name-desc': 'Name Z–A',
|
|
123
|
+
}
|
|
124
|
+
const sortItems = computed(() =>
|
|
125
|
+
(Object.keys(SORT_LABELS) as PickerSort[]).map((id) => ({
|
|
126
|
+
id,
|
|
127
|
+
label: SORT_LABELS[id],
|
|
128
|
+
checked: sort.value === id,
|
|
129
|
+
})),
|
|
130
|
+
)
|
|
112
131
|
const accepts = computed(() =>
|
|
113
132
|
(capability.value?.formats ?? []).map((format) => `.${format.replace(/^\./, '')}`).join(' '),
|
|
114
133
|
)
|
|
@@ -198,6 +217,28 @@ const summary = computed(() => {
|
|
|
198
217
|
</template>
|
|
199
218
|
</nav>
|
|
200
219
|
<span v-if="accepts" class="mint-picker__accepts">Accepts {{ accepts }}</span>
|
|
220
|
+
<ActionMenu
|
|
221
|
+
label="Sort"
|
|
222
|
+
:items="sortItems"
|
|
223
|
+
@select="sort = $event.id as PickerSort"
|
|
224
|
+
>
|
|
225
|
+
<template #trigger="{ open: menuOpen, toggle, keydown }">
|
|
226
|
+
<button
|
|
227
|
+
type="button"
|
|
228
|
+
class="mint-picker__sort"
|
|
229
|
+
aria-haspopup="menu"
|
|
230
|
+
:aria-expanded="menuOpen"
|
|
231
|
+
:aria-label="`Sort: ${SORT_LABELS[sort]}`"
|
|
232
|
+
@click="toggle"
|
|
233
|
+
@keydown="keydown"
|
|
234
|
+
>
|
|
235
|
+
{{ SORT_LABELS[sort] }}
|
|
236
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
|
237
|
+
<path d="M4 6l4 4 4-4" />
|
|
238
|
+
</svg>
|
|
239
|
+
</button>
|
|
240
|
+
</template>
|
|
241
|
+
</ActionMenu>
|
|
201
242
|
<button
|
|
202
243
|
type="button"
|
|
203
244
|
class="mint-picker__scope"
|
|
@@ -255,6 +296,7 @@ const summary = computed(() => {
|
|
|
255
296
|
:loading="loading"
|
|
256
297
|
:error="error"
|
|
257
298
|
:query="query"
|
|
299
|
+
:truncated="nav.truncated.value"
|
|
258
300
|
:mode="selectionMode"
|
|
259
301
|
:selected="selection.selected"
|
|
260
302
|
:selectable="selection.selectable"
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import type { HeatmapConfig } from '../types'
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Group color for the built-in sample types when `sampleColors` has no entry.
|
|
5
|
-
* Color encodes the group and shape encodes the type (3a): blank and QC render
|
|
6
|
-
* neutral markers and ignore it; iQC takes it like a sample.
|
|
7
|
-
*/
|
|
3
|
+
/** Color for the built-in sample types when `sampleColors` has no entry. */
|
|
8
4
|
export const DEFAULT_SAMPLE_TYPE_COLORS: Record<string, string> = {
|
|
9
|
-
sample: '
|
|
10
|
-
control: '
|
|
11
|
-
blank: '
|
|
12
|
-
qc: '
|
|
13
|
-
iqc: '
|
|
5
|
+
sample: '#10b981',
|
|
6
|
+
control: '#3b82f6',
|
|
7
|
+
blank: '#f97316',
|
|
8
|
+
qc: '#8b5cf6',
|
|
9
|
+
iqc: '#ec4899',
|
|
14
10
|
}
|
|
15
11
|
|
|
16
12
|
export const HEATMAP_COLORS: Record<string, string[]> = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { WellLegendItem } from '../types'
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_WELL_LEGEND_ITEMS: WellLegendItem[] = [
|
|
4
|
-
{ type: 'sample', label: 'Sample', color: '
|
|
5
|
-
{ type: 'blank', label: 'Blank', color: '
|
|
6
|
-
{ type: 'qc', label: 'QC', color: '
|
|
7
|
-
{ type: 'iqc', label: 'IQC', color: '
|
|
4
|
+
{ type: 'sample', label: 'Sample', color: '#10b981' },
|
|
5
|
+
{ type: 'blank', label: 'Blank', color: '#f97316' },
|
|
6
|
+
{ type: 'qc', label: 'QC', color: '#8b5cf6' },
|
|
7
|
+
{ type: 'iqc', label: 'IQC', color: '#ec4899' },
|
|
8
8
|
]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { HeatmapConfig, Well, WellPlateSelectionMode, WellShape } from '../types'
|
|
2
|
-
import { sampleTypeMarker } from '../utils/sampleTypeMarker'
|
|
3
2
|
import {
|
|
4
3
|
DEFAULT_SAMPLE_TYPE_COLORS,
|
|
5
4
|
getHeatmapColor,
|
|
@@ -55,13 +54,15 @@ export function wellStyle(well: Well, state: WellStyleState): Record<string, str
|
|
|
55
54
|
?? state.sampleColors[well.sampleType] ?? DEFAULT_SAMPLE_TYPE_COLORS[well.sampleType])
|
|
56
55
|
if (!groupColor) return {} // empty / filled wells are styled by well-plate.css
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
return { backgroundColor: background, border }
|
|
57
|
+
return sampleTint(groupColor)
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
/**
|
|
63
|
-
export function
|
|
64
|
-
return
|
|
60
|
+
/** 15% fill and 40% outline of a sample color; color-mix accepts hex and `var()` tokens alike. */
|
|
61
|
+
export function sampleTint(color: string): { backgroundColor: string; border: string } {
|
|
62
|
+
return {
|
|
63
|
+
backgroundColor: `color-mix(in srgb, ${color} 15%, transparent)`,
|
|
64
|
+
border: `1px solid color-mix(in srgb, ${color} 40%, transparent)`,
|
|
65
|
+
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
let sampleTypeIndicatorWarned = false
|
|
@@ -89,10 +90,10 @@ export function wellBadge(
|
|
|
89
90
|
if (!showBadges || !well.metadata) return null
|
|
90
91
|
const count = well.metadata.injectionCount as number | undefined
|
|
91
92
|
if (count && count > 1) {
|
|
92
|
-
return { text: String(count), color: '
|
|
93
|
+
return { text: String(count), color: '#6366f1' }
|
|
93
94
|
}
|
|
94
95
|
if (well.metadata.customMethod) {
|
|
95
|
-
return { text: '', color: '
|
|
96
|
+
return { text: '+', color: '#ec4899' }
|
|
96
97
|
}
|
|
97
98
|
return null
|
|
98
99
|
}
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from './WellPlate.geometry'
|
|
26
26
|
import { useWellPlateInteraction } from './WellPlate.interaction'
|
|
27
27
|
import {
|
|
28
|
-
|
|
28
|
+
sampleTint,
|
|
29
29
|
warnSampleTypeIndicatorDeprecated,
|
|
30
30
|
wellBadge,
|
|
31
31
|
wellClasses,
|
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
} from './WellPlate.rendering'
|
|
35
35
|
import { DEFAULT_WELL_LEGEND_ITEMS } from './WellPlate.legend'
|
|
36
36
|
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
37
|
-
import { sampleTypeMarker } from '../utils/sampleTypeMarker'
|
|
38
37
|
|
|
39
38
|
interface Props {
|
|
40
39
|
modelValue?: string[]
|
|
@@ -476,27 +475,13 @@ const tableStyle = computed(() => ({
|
|
|
476
475
|
>
|
|
477
476
|
{{ well.id }}
|
|
478
477
|
</span>
|
|
479
|
-
<!-- Sample-type marker glyph (Q / i) -->
|
|
480
|
-
<span
|
|
481
|
-
v-else-if="sampleTypeGlyph(well)"
|
|
482
|
-
class="mint-well-plate__indicator"
|
|
483
|
-
>
|
|
484
|
-
{{ sampleTypeGlyph(well) }}
|
|
485
|
-
</span>
|
|
486
|
-
<!-- The glyph moves to a corner when a label or well ID takes the centre -->
|
|
487
|
-
<span
|
|
488
|
-
v-if="sampleTypeGlyph(well) && (getWellLabel(well) || props.showWellIds)"
|
|
489
|
-
class="mint-well-plate__corner-glyph"
|
|
490
|
-
>
|
|
491
|
-
{{ sampleTypeGlyph(well) }}
|
|
492
|
-
</span>
|
|
493
478
|
|
|
494
479
|
<!-- Badge (injection count or custom method) -->
|
|
495
480
|
<span
|
|
496
481
|
v-if="getWellBadge(well)"
|
|
497
|
-
|
|
482
|
+
class="mint-well-plate__badge"
|
|
498
483
|
:style="{ backgroundColor: getWellBadge(well)?.color }"
|
|
499
|
-
:title="getWellBadge(well)?.text ? `${getWellBadge(well)?.text}x injections`
|
|
484
|
+
:title="getWellBadge(well)?.text === '+' ? 'Custom method' : `${getWellBadge(well)?.text ?? ''}x injections`"
|
|
500
485
|
>
|
|
501
486
|
{{ getWellBadge(well)?.text }}
|
|
502
487
|
</span>
|
|
@@ -531,11 +516,9 @@ const tableStyle = computed(() => ({
|
|
|
531
516
|
class="mint-well-plate__sample-legend-item"
|
|
532
517
|
>
|
|
533
518
|
<span
|
|
534
|
-
v-for="marker in [sampleTypeMarker(item.type, item.color)]"
|
|
535
|
-
:key="item.type"
|
|
536
519
|
class="mint-well-plate__sample-legend-dot"
|
|
537
|
-
:style="
|
|
538
|
-
|
|
520
|
+
:style="sampleTint(item.color)"
|
|
521
|
+
/>
|
|
539
522
|
<span class="mint-well-plate__sample-legend-text">{{ item.label }}</span>
|
|
540
523
|
</div>
|
|
541
524
|
</div>
|
package/src/components/index.ts
CHANGED
|
@@ -48,7 +48,8 @@ export { default as GeneratedResultRenderer } from './GeneratedResultRenderer.vu
|
|
|
48
48
|
export { default as GeneratedPlotlyResult } from './GeneratedPlotlyResult.vue'
|
|
49
49
|
export { default as ComponentBindingRenderer } from './ComponentBindingRenderer.vue'
|
|
50
50
|
export { default as ControlWorkspaceView } from './ControlWorkspaceView.vue'
|
|
51
|
-
|
|
51
|
+
/** Complete ControlWorkspaceView page shell for WellPlate + DoseCalculator dose design. @deprecated Scheduled for removal in MINT 1.4; use `ControlWorkspaceView` instead, with `defineDoseDesignControlModel()`. */
|
|
52
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as DoseDesignWorkspaceView } from './DoseDesignWorkspaceView.vue'
|
|
52
53
|
export { default as AppContainer } from './AppContainer.vue'
|
|
53
54
|
export { default as PluginIcon } from './PluginIcon.vue'
|
|
54
55
|
export { default as LayoutResizeHandle } from './LayoutResizeHandle.vue'
|
|
@@ -69,30 +70,43 @@ export { default as AdductText } from './AdductText.vue'
|
|
|
69
70
|
export { default as SettingsModal } from './SettingsModal.vue'
|
|
70
71
|
|
|
71
72
|
// Biological experiment components
|
|
72
|
-
|
|
73
|
-
export { default as
|
|
74
|
-
|
|
75
|
-
export { default as
|
|
73
|
+
/** Render biology template envelopes with matching SDK components such as WellPlate, DoseCalculator, and DataFrame. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
74
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as BioTemplateRenderer } from './BioTemplateRenderer.vue'
|
|
75
|
+
/** Current-experiment template workspace with load/save/reset controls and WellPlate/DataFrame/DoseCalculator previews. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
76
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as BioTemplateExperimentWorkspaceView } from './BioTemplateExperimentWorkspaceView.vue'
|
|
77
|
+
/** Complete current-experiment workspace for a curated biology template pack with save/load controls and component-prop slots. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
78
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as BioTemplatePackWorkspaceView } from './BioTemplatePackWorkspaceView.vue'
|
|
79
|
+
/** Complete editable biology template preset workspace for WellPlate/DoseCalculator, LC-MS, qPCR, generated controls, preview components, and current-experiment save wiring. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
80
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as BioTemplatePresetWorkspaceView } from './BioTemplatePresetWorkspaceView.vue'
|
|
76
81
|
export { default as WellPlate } from './WellPlate.vue'
|
|
77
82
|
export { default as PlateEditor } from './PlateEditor.vue'
|
|
78
|
-
|
|
79
|
-
export { default as
|
|
83
|
+
/** Clickable color-swatch legend for plate sample types, supporting selection, count display, add/remove, and custom color assignment. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
84
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as SampleLegend } from './SampleLegend.vue'
|
|
85
|
+
/** Horizontal or vertical timeline of ordered protocol steps with status indicators, drag-to-reorder, and step-type iconography. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
86
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as ExperimentTimeline } from './ExperimentTimeline.vue'
|
|
80
87
|
|
|
81
88
|
// Sample management components
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
export { default as
|
|
86
|
-
|
|
87
|
-
export { default as
|
|
88
|
-
|
|
89
|
+
/** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
90
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as SampleSelector } from './SampleSelector.vue'
|
|
91
|
+
/** Smart Group modal wired to the `useAutoGroup` engine. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
92
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as AutoGroupModal } from './AutoGroupModal.vue'
|
|
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. */
|
|
94
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as GroupAssigner } from './GroupAssigner.vue'
|
|
95
|
+
/** Smart Group modal with the auto Field Recipe and the manual cohort builder. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
96
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as SmartGroupModal } from './SmartGroupModal.vue'
|
|
97
|
+
/** Smart Group auto mode: groups samples by a field recipe. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
98
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as SmartGroupFieldRecipe } from './SmartGroupFieldRecipe.vue'
|
|
99
|
+
/** Smart Group manual mode: builds cohorts from selected samples. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
100
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as SmartGroupManual } from './SmartGroupManual.vue'
|
|
89
101
|
|
|
90
102
|
// Lab/Experiment components
|
|
91
103
|
export { default as MoleculeInput } from './MoleculeInput.vue'
|
|
92
104
|
export { default as ConcentrationInput } from './ConcentrationInput.vue'
|
|
93
105
|
export { default as DoseCalculator } from './DoseCalculator.vue'
|
|
94
|
-
|
|
95
|
-
export { default as
|
|
106
|
+
/** Editor for a single reagent definition with axis orientation, dilution-series generator, and position-count validation for 96/384-well plates. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
107
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as ReagentEditor, type ReagentDefinition } from './ReagentEditor.vue'
|
|
108
|
+
/** Sortable, searchable table of experiment reagents showing lot number, expiry, storage conditions, and stock level with low-stock warnings. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
109
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as ReagentList } from './ReagentList.vue'
|
|
96
110
|
export { default as SampleHierarchyTree } from './SampleHierarchyTree.vue'
|
|
97
111
|
export { default as ProtocolStepEditor } from './ProtocolStepEditor.vue'
|
|
98
112
|
|
|
@@ -138,6 +152,7 @@ export { default as ArtifactSelectorModal } from './ArtifactSelectorModal.vue'
|
|
|
138
152
|
export { default as GeneratedPathInput } from './GeneratedPathInput.vue'
|
|
139
153
|
export { default as ArtifactSaveDialog } from './ArtifactSaveDialog.vue'
|
|
140
154
|
export { default as ExperimentPopover } from './ExperimentPopover.vue'
|
|
141
|
-
|
|
155
|
+
/** Control panel for running a curve-fit job with progress bar, results table, and error display. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
156
|
+
export { /** @deprecated Removed in MINT 1.4. */ default as FitPanel } from './FitPanel.vue'
|
|
142
157
|
|
|
143
158
|
export { default as FilePicker } from './FilePicker.vue'
|
|
@@ -22,6 +22,8 @@ const props = defineProps<{
|
|
|
22
22
|
loading: boolean
|
|
23
23
|
error: string
|
|
24
24
|
query: string
|
|
25
|
+
/** Search results stopped at a limit; the rows are the first matches only. */
|
|
26
|
+
truncated?: boolean
|
|
25
27
|
mode: PickerSelectionMode
|
|
26
28
|
selected: (node: PickerNode) => boolean
|
|
27
29
|
selectable: (node: PickerNode) => boolean
|
|
@@ -171,6 +173,9 @@ function parts(name: string) {
|
|
|
171
173
|
<div class="mint-picker__list-head" aria-hidden="true">
|
|
172
174
|
<span /><span>Name</span><span>Size</span><span>Modified</span>
|
|
173
175
|
</div>
|
|
176
|
+
<p v-if="truncated && !loading && !error && rows.length" class="mint-picker__truncated" role="status">
|
|
177
|
+
Showing the first {{ rows.length }} matches. Search a smaller folder or use a more specific search to see the rest.
|
|
178
|
+
</p>
|
|
174
179
|
<div v-if="loading" class="mint-picker__skeleton">
|
|
175
180
|
<Skeleton v-for="n in 6" :key="n" width="100%" height="1.25rem" />
|
|
176
181
|
</div>
|
|
@@ -180,10 +185,14 @@ function parts(name: string) {
|
|
|
180
185
|
</div>
|
|
181
186
|
<div v-else-if="!rows.length" class="mint-picker__empty" role="status">
|
|
182
187
|
<strong>{{
|
|
183
|
-
|
|
188
|
+
truncated
|
|
189
|
+
? 'Search stopped before finding a match'
|
|
190
|
+
: query ? 'No matching folders or files' : emptyTitle || 'No readable files'
|
|
184
191
|
}}</strong>
|
|
185
192
|
<span>{{
|
|
186
|
-
|
|
193
|
+
truncated
|
|
194
|
+
? 'Search a smaller folder or use a more specific search.'
|
|
195
|
+
: query ? 'Try a different search or include subfolders.' : 'Choose another location.'
|
|
187
196
|
}}</span>
|
|
188
197
|
</div>
|
|
189
198
|
<template v-for="row in rows" v-else :key="row.node.path">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Internal engine for `useAutoGroup` / `AutoGroupModal`. Deprecated: scheduled
|
|
2
|
-
// for removal in MINT 1.4 together with `useAutoGroup`;
|
|
2
|
+
// for removal in MINT 1.4 together with `useAutoGroup`; no SDK replacement.
|
|
3
3
|
|
|
4
4
|
export { splitMulti, pickPrimaryDelimiter } from './tokenize'
|
|
5
5
|
export type { Delimiter } from './tokenize'
|