@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
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
import type { PickerAdapter, PickerNode, PickerRequest } from '../types/filePicker'
|
|
2
|
-
import type {
|
|
1
|
+
import type { PickerAdapter, PickerNode, PickerRequest, PickerSearchResult } from '../types/filePicker'
|
|
2
|
+
import type {
|
|
3
|
+
FileBrowserLocation,
|
|
4
|
+
FileDirectoryListing,
|
|
5
|
+
FileDirectoryTree,
|
|
6
|
+
FileEntry,
|
|
7
|
+
FileSearchResult,
|
|
8
|
+
ServerMount,
|
|
9
|
+
} from '../types/fileBrowserTypes'
|
|
3
10
|
import { pickerFormat } from './filePicker/validation'
|
|
4
11
|
|
|
5
12
|
export interface FilePickerTransport {
|
|
6
13
|
listMounts(request?: PickerRequest): Promise<ServerMount[]>
|
|
7
14
|
browse(location: FileBrowserLocation, request?: PickerRequest): Promise<FileDirectoryListing>
|
|
15
|
+
/** `GET …/filesystem/tree`: one request fills the cache for the subfolders too. */
|
|
16
|
+
tree?(location: FileBrowserLocation, request?: PickerRequest): Promise<FileDirectoryTree>
|
|
17
|
+
/** `GET …/filesystem/search`: replaces the folder-by-folder search walk. */
|
|
18
|
+
search?(
|
|
19
|
+
location: FileBrowserLocation,
|
|
20
|
+
query: string,
|
|
21
|
+
recursive: boolean,
|
|
22
|
+
request?: PickerRequest,
|
|
23
|
+
): Promise<FileSearchResult>
|
|
8
24
|
}
|
|
9
25
|
export interface FilePickerAdapterOptions {
|
|
10
26
|
/** Show this directory's contents directly and prevent navigation outside it. */
|
|
@@ -82,31 +98,75 @@ export function createFilePickerAdapter(
|
|
|
82
98
|
unavailableReason: mount.available === false ? 'This mount is offline.' : undefined,
|
|
83
99
|
}
|
|
84
100
|
})
|
|
101
|
+
} else if (transport.tree) {
|
|
102
|
+
const tree = await transport.tree(location, request)
|
|
103
|
+
check(request, token)
|
|
104
|
+
for (const listing of tree.listings ?? []) {
|
|
105
|
+
const at = { mount_id: listing.mount_id, path: listing.path }
|
|
106
|
+
validateLocation(at)
|
|
107
|
+
if (at.mount_id !== location.mount_id) throw new Error('Invalid file picker directory entry.')
|
|
108
|
+
// A capped subfolder is read on its own when opened, and reports the cap then.
|
|
109
|
+
if (!listing.truncated) listings.set(encodePlatformPickerPath(at.mount_id, at.path), toNodes(at, listing))
|
|
110
|
+
}
|
|
111
|
+
const cached = listings.get(key)
|
|
112
|
+
if (!cached)
|
|
113
|
+
throw new Error('This directory exceeds the server listing limit. Choose a smaller data directory.')
|
|
114
|
+
return cached
|
|
85
115
|
} else {
|
|
86
116
|
const listing = await transport.browse(location, request)
|
|
87
117
|
check(request, token)
|
|
88
118
|
if (listing.truncated)
|
|
89
119
|
throw new Error('This directory exceeds the server listing limit. Choose a smaller data directory.')
|
|
90
|
-
|
|
91
|
-
nodes = (listing.entries ?? []).map((entry): PickerNode => {
|
|
92
|
-
if (!isRelativePath(entry.path) || !entry.name ||
|
|
93
|
-
entry.path !== `${location.path ? `${location.path}/` : ''}${entry.name}` ||
|
|
94
|
-
entry.name.includes('/')) throw new Error('Invalid file picker directory entry.')
|
|
95
|
-
const common = {
|
|
96
|
-
path: encodePlatformPickerPath(location.mount_id, entry.path),
|
|
97
|
-
parentPath: atRoot ? null : key,
|
|
98
|
-
parentName: `${mountLabels.get(location.mount_id) ?? location.mount_id}${location.path ? ` / ${location.path}` : ''}`,
|
|
99
|
-
name: entry.name, bytes: entry.size_bytes, modifiedAt: entry.modified_at,
|
|
100
|
-
}
|
|
101
|
-
return entry.kind === 'directory'
|
|
102
|
-
// Counts may include only matching formats, not nested directories.
|
|
103
|
-
? { ...common, kind: 'folder' }
|
|
104
|
-
: { ...common, kind: 'file', format: pickerFormat(entry.name) }
|
|
105
|
-
})
|
|
120
|
+
nodes = toNodes(location, listing)
|
|
106
121
|
}
|
|
107
122
|
listings.set(key, nodes)
|
|
108
123
|
return nodes
|
|
109
124
|
}
|
|
125
|
+
function toNodes(location: FileBrowserLocation, listing: FileDirectoryListing): PickerNode[] {
|
|
126
|
+
return (listing.entries ?? []).map((entry) => {
|
|
127
|
+
if (entry.path !== `${location.path ? `${location.path}/` : ''}${entry.name}`)
|
|
128
|
+
throw new Error('Invalid file picker directory entry.')
|
|
129
|
+
return toNode(location.mount_id, entry)
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
/** One entry, parented on the folder its mount-relative path names. */
|
|
133
|
+
function toNode(mountId: string, entry: FileEntry): PickerNode {
|
|
134
|
+
if (!isRelativePath(entry.path) || !entry.name || entry.name.includes('/') ||
|
|
135
|
+
(entry.path !== entry.name && !entry.path.endsWith(`/${entry.name}`)))
|
|
136
|
+
throw new Error('Invalid file picker directory entry.')
|
|
137
|
+
const parent = entry.path.slice(0, Math.max(0, entry.path.length - entry.name.length - 1))
|
|
138
|
+
const atRoot = root && mountId === root.mount_id && parent === root.path
|
|
139
|
+
const common = {
|
|
140
|
+
path: encodePlatformPickerPath(mountId, entry.path),
|
|
141
|
+
parentPath: atRoot ? null : encodePlatformPickerPath(mountId, parent),
|
|
142
|
+
parentName: `${mountLabels.get(mountId) ?? mountId}${parent ? ` / ${parent}` : ''}`,
|
|
143
|
+
name: entry.name, bytes: entry.size_bytes, modifiedAt: entry.modified_at,
|
|
144
|
+
}
|
|
145
|
+
return entry.kind === 'directory'
|
|
146
|
+
// Counts may include only matching formats, not nested directories.
|
|
147
|
+
? { ...common, kind: 'folder' }
|
|
148
|
+
: { ...common, kind: 'file', format: pickerFormat(entry.name) }
|
|
149
|
+
}
|
|
150
|
+
async function serverSearch(
|
|
151
|
+
query: string,
|
|
152
|
+
scope: { path: string; recursive: boolean },
|
|
153
|
+
request: PickerRequest | undefined,
|
|
154
|
+
): Promise<PickerNode[] | PickerSearchResult> {
|
|
155
|
+
const token = begin(request)
|
|
156
|
+
const decoded = scope.path ? decodePlatformPickerPath(scope.path) : undefined
|
|
157
|
+
const location = decoded ? { mount_id: decoded.mountId, path: decoded.path } : root!
|
|
158
|
+
validateLocation(location)
|
|
159
|
+
const result = await transport.search!(location, query, scope.recursive, request)
|
|
160
|
+
check(request, token)
|
|
161
|
+
const prefix = location.path ? `${location.path}/` : ''
|
|
162
|
+
const nodes = (result.entries ?? []).map((entry) => {
|
|
163
|
+
// "This folder" searches may return direct children only.
|
|
164
|
+
if (!entry.path.startsWith(prefix) || (!scope.recursive && entry.path.slice(prefix.length).includes('/')))
|
|
165
|
+
throw new Error('Invalid file picker directory entry.')
|
|
166
|
+
return toNode(location.mount_id, entry)
|
|
167
|
+
})
|
|
168
|
+
return result.truncated ? { nodes, truncated: true } : nodes
|
|
169
|
+
}
|
|
110
170
|
return {
|
|
111
171
|
mounts: !root,
|
|
112
172
|
mountOf(path) {
|
|
@@ -120,6 +180,8 @@ export function createFilePickerAdapter(
|
|
|
120
180
|
listRoot: async (request) => read('', request, begin(request)),
|
|
121
181
|
listChildren: async (path, request) => read(path, request, begin(request)),
|
|
122
182
|
async search(query, scope, request) {
|
|
183
|
+
// The mount list itself has no server to search, so it keeps the walk.
|
|
184
|
+
if (transport.search && (scope.path || root)) return serverSearch(query, scope, request)
|
|
123
185
|
const token = begin(request)
|
|
124
186
|
const matches: PickerNode[] = []
|
|
125
187
|
const queue = [scope.path]
|
|
@@ -131,8 +193,7 @@ export function createFilePickerAdapter(
|
|
|
131
193
|
const path = queue.shift()!
|
|
132
194
|
if (visited.has(path)) continue
|
|
133
195
|
visited.add(path)
|
|
134
|
-
if (visited.size > 500)
|
|
135
|
-
throw new Error('Search reached its folder limit. Search a smaller location or turn off Subfolders.')
|
|
196
|
+
if (visited.size > 500) return { nodes: matches, truncated: true }
|
|
136
197
|
const nodes = await read(path, currentRequest, token)
|
|
137
198
|
check(request, token)
|
|
138
199
|
if (currentRequest?.refresh) {
|
|
@@ -144,8 +205,7 @@ export function createFilePickerAdapter(
|
|
|
144
205
|
if (scope.recursive && node.kind === 'folder' && !node.unavailableReason)
|
|
145
206
|
queue.push(node.path)
|
|
146
207
|
}
|
|
147
|
-
if (matches.length > 10000)
|
|
148
|
-
throw new Error('Too many search results. Use a more specific search.')
|
|
208
|
+
if (matches.length > 10000) return { nodes: matches.slice(0, 10000), truncated: true }
|
|
149
209
|
}
|
|
150
210
|
return matches
|
|
151
211
|
},
|
|
@@ -3,13 +3,33 @@ import type {
|
|
|
3
3
|
PickerAdapter,
|
|
4
4
|
PickerNode,
|
|
5
5
|
PickerRequest,
|
|
6
|
+
PickerSort,
|
|
6
7
|
PickerTreeRow,
|
|
7
8
|
} from '../../types/filePicker'
|
|
8
9
|
|
|
10
|
+
const byName = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
|
|
11
|
+
/** Folders first; undated rows go last in either time order. */
|
|
12
|
+
function ordered(items: PickerNode[], sort: PickerSort | undefined): PickerNode[] {
|
|
13
|
+
if (!sort) return items
|
|
14
|
+
// Parse each timestamp once rather than on every comparison.
|
|
15
|
+
const times = new Map(items.map((node) => [node, node.modifiedAt ? Date.parse(node.modifiedAt) : NaN]))
|
|
16
|
+
const time = (node: PickerNode) => times.get(node)!
|
|
17
|
+
return [...items].sort((a, b) => {
|
|
18
|
+
if (a.kind !== b.kind) return a.kind === 'folder' ? -1 : 1
|
|
19
|
+
if (sort === 'name') return byName(a.name, b.name)
|
|
20
|
+
if (sort === 'name-desc') return byName(b.name, a.name)
|
|
21
|
+
const [ta, tb] = [time(a), time(b)]
|
|
22
|
+
if (Number.isNaN(ta) || Number.isNaN(tb))
|
|
23
|
+
return Number.isNaN(ta) === Number.isNaN(tb) ? byName(a.name, b.name) : Number.isNaN(ta) ? 1 : -1
|
|
24
|
+
return (sort === 'newest' ? tb - ta : ta - tb) || byName(a.name, b.name)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
export function usePickerNavigation(
|
|
10
29
|
adapter: () => PickerAdapter,
|
|
11
30
|
request: () => PickerRequest,
|
|
12
31
|
systemFilter: (node: PickerNode) => boolean = () => true,
|
|
32
|
+
sort: () => PickerSort | undefined = () => undefined,
|
|
13
33
|
) {
|
|
14
34
|
const path = shallowRef('')
|
|
15
35
|
const trail = ref<{ path: string; label: string }[]>([])
|
|
@@ -21,6 +41,8 @@ export function usePickerNavigation(
|
|
|
21
41
|
const expansionErrors = ref(new Map<string, string>())
|
|
22
42
|
const loading = shallowRef(false)
|
|
23
43
|
const error = shallowRef('')
|
|
44
|
+
/** True when the current search results stopped at an adapter limit. */
|
|
45
|
+
const truncated = shallowRef(false)
|
|
24
46
|
let epoch = 0
|
|
25
47
|
let navigation = 0
|
|
26
48
|
let controller: AbortController | undefined
|
|
@@ -154,14 +176,17 @@ export function usePickerNavigation(
|
|
|
154
176
|
const options = { ...request(), ...(refresh && { refresh }), signal: controller.signal }
|
|
155
177
|
loading.value = true
|
|
156
178
|
error.value = ''
|
|
179
|
+
truncated.value = false
|
|
157
180
|
entries.value = []
|
|
158
181
|
try {
|
|
159
|
-
const
|
|
182
|
+
const found = query.trim()
|
|
160
183
|
? await adapter().search(query.trim(), { path: path.value, recursive }, options)
|
|
161
184
|
: path.value
|
|
162
185
|
? await loadChildren(path.value, [], refresh)
|
|
163
186
|
: await adapter().listRoot(options)
|
|
164
187
|
if (token !== navigation) return
|
|
188
|
+
const items = Array.isArray(found) ? found : found.nodes
|
|
189
|
+
truncated.value = !Array.isArray(found) && found.truncated
|
|
165
190
|
register(items, query.trim() ? undefined : path.value)
|
|
166
191
|
entries.value = items
|
|
167
192
|
if (!query.trim()) prefetch(items)
|
|
@@ -215,8 +240,9 @@ export function usePickerNavigation(
|
|
|
215
240
|
}
|
|
216
241
|
const rows = computed(() => {
|
|
217
242
|
const result: PickerTreeRow[] = []
|
|
243
|
+
const order = sort()
|
|
218
244
|
function visit(items: PickerNode[], depth: number, ancestors: Set<string>) {
|
|
219
|
-
for (const item of items) {
|
|
245
|
+
for (const item of ordered(items, order)) {
|
|
220
246
|
if (ancestors.has(item.path) || depth > 30) continue
|
|
221
247
|
const node = nodes.value.get(item.path) ?? item
|
|
222
248
|
if (!isAllowed(node)) continue
|
|
@@ -246,6 +272,7 @@ export function usePickerNavigation(
|
|
|
246
272
|
expansionErrors,
|
|
247
273
|
loading,
|
|
248
274
|
error,
|
|
275
|
+
truncated,
|
|
249
276
|
rows,
|
|
250
277
|
isAllowed,
|
|
251
278
|
clear,
|
package/src/composables/index.ts
CHANGED
|
@@ -4,8 +4,6 @@ export {
|
|
|
4
4
|
type ApiClientOptions,
|
|
5
5
|
type UseApiReturn,
|
|
6
6
|
} from './useApi'
|
|
7
|
-
export { useAuth } from './useAuth'
|
|
8
|
-
export { usePasskey } from './usePasskey'
|
|
9
7
|
export { useTheme } from './useTheme'
|
|
10
8
|
export { useToast } from './useToast'
|
|
11
9
|
export { usePlatformContext } from './usePlatformContext'
|
|
@@ -32,11 +30,6 @@ export {
|
|
|
32
30
|
type FieldState,
|
|
33
31
|
type UseFormReturn,
|
|
34
32
|
} from './useForm'
|
|
35
|
-
export {
|
|
36
|
-
useWellPlateEditor,
|
|
37
|
-
type UseWellPlateEditorOptions,
|
|
38
|
-
type UseWellPlateEditorReturn,
|
|
39
|
-
} from './useWellPlateEditor'
|
|
40
33
|
export {
|
|
41
34
|
useConcentrationUnits,
|
|
42
35
|
type ConcentrationValue,
|
|
@@ -62,7 +62,7 @@ export { parseCSV } from './autoGroup/csv-shim'
|
|
|
62
62
|
/**
|
|
63
63
|
* Sample-grouping engine behind `AutoGroupModal`.
|
|
64
64
|
*
|
|
65
|
-
* @deprecated Scheduled for removal in MINT 1.4;
|
|
65
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
66
66
|
*/
|
|
67
67
|
export function useAutoGroup() {
|
|
68
68
|
const inputMode = ref<InputMode>('paste')
|
|
@@ -67,14 +67,22 @@ export interface UseBioTemplateComponentsReturn {
|
|
|
67
67
|
) => Record<string, unknown> | undefined
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Return component props grouped by SDK component name for direct WellPlate/DoseCalculator binding.
|
|
72
|
+
*
|
|
73
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
74
|
+
*/
|
|
71
75
|
export function toBioTemplateComponentPropsByComponent(
|
|
72
76
|
target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope,
|
|
73
77
|
): BioTemplateComponentPropsByComponent {
|
|
74
78
|
return toTemplateComponentPropsByComponent(target)
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Return the first props object for an SDK component, optionally restricted by template id or binding id.
|
|
83
|
+
*
|
|
84
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
85
|
+
*/
|
|
78
86
|
export function getBioTemplateComponentProps(
|
|
79
87
|
target: BioTemplateEnvelope<unknown> | TemplateCollectionEnvelope,
|
|
80
88
|
component: string,
|
|
@@ -83,7 +91,11 @@ export function getBioTemplateComponentProps(
|
|
|
83
91
|
return getTemplateComponentProps(target, component, options)
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Map built-in biology templates to SDK components such as WellPlate, DataFrame, DoseCalculator, and PlateEditor.
|
|
96
|
+
*
|
|
97
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
98
|
+
*/
|
|
87
99
|
export function useBioTemplateComponents(
|
|
88
100
|
target: BioTemplateComponentTarget
|
|
89
101
|
): UseBioTemplateComponentsReturn {
|
|
@@ -21,7 +21,11 @@ export type {
|
|
|
21
21
|
BioTemplateControlTarget,
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Build FormBuilder schema, AppSidebar panels, and defaults for a built-in biology template or preset.
|
|
26
|
+
*
|
|
27
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
28
|
+
*/
|
|
25
29
|
export function useBioTemplateControls(
|
|
26
30
|
target: BioTemplateControlTarget,
|
|
27
31
|
options: ControlWorkspaceOptions = {},
|
|
@@ -90,6 +90,8 @@ export interface UseBioTemplatePackWorkspaceReturn {
|
|
|
90
90
|
* const pack = useBioTemplatePackWorkspace('cell-culture-screen')
|
|
91
91
|
* await pack.loadCurrent()
|
|
92
92
|
* ```
|
|
93
|
+
*
|
|
94
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
93
95
|
*/
|
|
94
96
|
export function useBioTemplatePackWorkspace(
|
|
95
97
|
packId: TemplatePackId | string,
|
|
@@ -119,7 +119,11 @@ export interface BioTemplatePresetWorkspaceBindings {
|
|
|
119
119
|
getComponentProps: UseBioTemplateWorkspaceReturn['getComponentProps']
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Create a complete editable WellPlate/DoseCalculator workspace for dose design from a built-in biology template preset with current-experiment save wiring.
|
|
124
|
+
*
|
|
125
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
126
|
+
*/
|
|
123
127
|
export function useBioTemplatePresetWorkspace(
|
|
124
128
|
presetId: TemplatePresetId,
|
|
125
129
|
options: UseBioTemplatePresetWorkspaceOptions = {},
|
|
@@ -60,7 +60,11 @@ export interface UseBioTemplateWorkspaceReturn extends BioTemplateWorkspaceCompo
|
|
|
60
60
|
renderer: BioTemplateRendererBinding | null
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* One-stop wiring for BioTemplateRenderer, FormBuilder, AppSidebar, and template-aware SDK components.
|
|
65
|
+
*
|
|
66
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
67
|
+
*/
|
|
64
68
|
export function useBioTemplateWorkspace(
|
|
65
69
|
target: BioTemplateWorkspaceTarget,
|
|
66
70
|
options: ControlWorkspaceOptions = {}
|
|
@@ -86,7 +86,11 @@ export interface UseExperimentSaveReturn {
|
|
|
86
86
|
requireCurrentExperimentId: () => number
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Persists experiment design, analysis, and built-in template preset data through the platform API.
|
|
91
|
+
*
|
|
92
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
93
|
+
*/
|
|
90
94
|
export function useExperimentSave(
|
|
91
95
|
options: UseExperimentSaveOptions = {},
|
|
92
96
|
): UseExperimentSaveReturn {
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
PickerNode,
|
|
6
6
|
PickerSelection,
|
|
7
7
|
PickerSelectionMode,
|
|
8
|
+
PickerSort,
|
|
8
9
|
PickerSource,
|
|
9
10
|
} from '../types/filePicker'
|
|
10
11
|
import type { SegmentedOption } from '../types'
|
|
@@ -20,6 +21,7 @@ interface PickerStateProps {
|
|
|
20
21
|
capabilities: PickerCapabilities
|
|
21
22
|
initialPath: string
|
|
22
23
|
initialSelection: PickerSelection | null
|
|
24
|
+
defaultSort: PickerSort
|
|
23
25
|
systemFilter?: (node: PickerNode, source: PickerSource) => boolean
|
|
24
26
|
}
|
|
25
27
|
type PickerEmit = {
|
|
@@ -50,6 +52,7 @@ export function useFilePicker(
|
|
|
50
52
|
)
|
|
51
53
|
const query = shallowRef('')
|
|
52
54
|
const recursive = shallowRef(false)
|
|
55
|
+
const sort = shallowRef<PickerSort>(props.defaultSort)
|
|
53
56
|
const skipBlanks = shallowRef(false)
|
|
54
57
|
const reviewing = shallowRef(false)
|
|
55
58
|
const localFiles = shallowRef<File[]>([])
|
|
@@ -61,6 +64,7 @@ export function useFilePicker(
|
|
|
61
64
|
() => (source.value === 'localFile' ? localAdapter.value : props.adapter),
|
|
62
65
|
() => ({ source: source.value }),
|
|
63
66
|
(node) => props.systemFilter?.(node, source.value) !== false,
|
|
67
|
+
() => sort.value,
|
|
64
68
|
)
|
|
65
69
|
const capability = computed(() => props.capabilities[source.value])
|
|
66
70
|
const selection = usePickerSelection(
|
|
@@ -182,6 +186,7 @@ export function useFilePicker(
|
|
|
182
186
|
(open) => {
|
|
183
187
|
clearTimeout(timer)
|
|
184
188
|
if (open) {
|
|
189
|
+
sort.value = props.defaultSort
|
|
185
190
|
if (!initialized) {
|
|
186
191
|
path.value = props.initialPath
|
|
187
192
|
if (path.value) trail.value = [{ path: path.value, label: 'Current folder' }]
|
|
@@ -348,9 +353,10 @@ export function useFilePicker(
|
|
|
348
353
|
}
|
|
349
354
|
async function selectLocalFiles() {
|
|
350
355
|
const token = openGeneration
|
|
351
|
-
const
|
|
356
|
+
const found = props.selectionMode === 'single-file' || props.selectionMode === 'multi-file'
|
|
352
357
|
? await localAdapter.value.search('', { path: '', recursive: true })
|
|
353
358
|
: nav.entries.value
|
|
359
|
+
const items = Array.isArray(found) ? found : found.nodes
|
|
354
360
|
if (token !== openGeneration || !props.open || source.value !== 'localFile') return
|
|
355
361
|
nav.register(items)
|
|
356
362
|
const candidates = items.filter((node) => selection.selectable(node))
|
|
@@ -411,6 +417,7 @@ export function useFilePicker(
|
|
|
411
417
|
source,
|
|
412
418
|
query,
|
|
413
419
|
recursive,
|
|
420
|
+
sort,
|
|
414
421
|
skipBlanks,
|
|
415
422
|
reviewing,
|
|
416
423
|
nav,
|
|
@@ -37,7 +37,11 @@ export interface UseGroupAssignmentReturn {
|
|
|
37
37
|
clearAll: () => GroupAssignmentState
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Shared two-zone group assignment model for control/treatment style workflows.
|
|
42
|
+
*
|
|
43
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
44
|
+
*/
|
|
41
45
|
export function useGroupAssignment(options: UseGroupAssignmentOptions): UseGroupAssignmentReturn {
|
|
42
46
|
const groups = computed(() => [...toValue(options.groups)])
|
|
43
47
|
const group1 = computed(() => [...toValue(options.group1)])
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import type { PickerAdapter } from '../types/filePicker'
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
FileBrowserLocation,
|
|
4
|
+
FileDirectoryListing,
|
|
5
|
+
FileDirectoryTree,
|
|
6
|
+
FileSearchResult,
|
|
7
|
+
ServerMount,
|
|
8
|
+
} from '../types/fileBrowserTypes'
|
|
9
|
+
import type { PickerRequest } from '../types/filePicker'
|
|
3
10
|
import { useApi } from './useApi'
|
|
4
11
|
import { getInjectedPlatformContext } from './platformContextHelpers'
|
|
5
12
|
import { createFilePickerAdapter, type FilePickerAdapterOptions } from './createFilePickerAdapter'
|
|
6
13
|
|
|
7
14
|
export { encodePlatformPickerPath, decodePlatformPickerPath } from './createFilePickerAdapter'
|
|
8
15
|
|
|
16
|
+
function query(location: FileBrowserLocation, request?: PickerRequest): URLSearchParams {
|
|
17
|
+
const params = new URLSearchParams({ mount_id: location.mount_id, path: location.path })
|
|
18
|
+
if (request?.refresh) params.set('refresh', 'true')
|
|
19
|
+
return params
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
/** Adapt the authenticated MINT mount API without exposing absolute host paths. */
|
|
10
23
|
export function usePlatformFilePickerAdapter(
|
|
11
24
|
options: FilePickerAdapterOptions & { apiBaseUrl?: string } = {},
|
|
@@ -27,11 +40,20 @@ export function usePlatformFilePickerAdapter(
|
|
|
27
40
|
}
|
|
28
41
|
},
|
|
29
42
|
browse(location, request) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
return api.get<FileDirectoryListing>(`/filesystem/browse?${query(location, request)}`, {
|
|
44
|
+
signal: request?.signal,
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
tree(location, request) {
|
|
48
|
+
return api.get<FileDirectoryTree>(`/filesystem/tree?${query(location, request)}`, {
|
|
33
49
|
signal: request?.signal,
|
|
34
50
|
})
|
|
35
51
|
},
|
|
52
|
+
search(location, text, recursive, request) {
|
|
53
|
+
const params = query(location, request)
|
|
54
|
+
params.set('q', text)
|
|
55
|
+
params.set('recursive', String(recursive))
|
|
56
|
+
return api.get<FileSearchResult>(`/filesystem/search?${params}`, { signal: request?.signal })
|
|
57
|
+
},
|
|
36
58
|
}, options)
|
|
37
59
|
}
|
|
@@ -72,7 +72,11 @@ export interface UseTemplateCollectionReturn {
|
|
|
72
72
|
requireCurrentExperimentId: () => number
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Manages a biology template collection in the current experiment's design_data.
|
|
77
|
+
*
|
|
78
|
+
* @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
|
|
79
|
+
*/
|
|
76
80
|
export function useTemplateCollection(
|
|
77
81
|
options: UseTemplateCollectionOptions = {},
|
|
78
82
|
): UseTemplateCollectionReturn {
|
package/src/index.ts
CHANGED
|
@@ -34,7 +34,7 @@ export * from './jobs'
|
|
|
34
34
|
// Experimental generated analysis workspace
|
|
35
35
|
export * from './generated/schema'
|
|
36
36
|
|
|
37
|
-
// LCMS sequence helpers. Deprecated: scheduled for removal in MINT 1.4
|
|
37
|
+
// LCMS sequence helpers. Deprecated: scheduled for removal in MINT 1.4, no SDK replacement.
|
|
38
38
|
export * from './lcms'
|
|
39
39
|
|
|
40
40
|
// Utils
|
package/src/lcms.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { LcmsPlateCell, LcmsPlateType } from './utils/rack'
|
|
2
2
|
|
|
3
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
3
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
4
4
|
export type LcmsPolarity = 'POS' | 'NEG' | 'BOTH'
|
|
5
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
5
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
6
6
|
export type LcmsContainerType = 'VIAL' | 'PLATE'
|
|
7
7
|
|
|
8
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
8
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
9
9
|
export interface LcmsSequenceItem {
|
|
10
10
|
sample_type: string
|
|
11
11
|
file_name: string
|
|
@@ -16,14 +16,14 @@ export interface LcmsSequenceItem {
|
|
|
16
16
|
injection_volume: number
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
19
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
20
20
|
export interface LcmsSequenceTableColumn {
|
|
21
21
|
key: keyof LcmsSequenceItem | 'index' | 'actions'
|
|
22
22
|
label: string
|
|
23
23
|
align?: 'left' | 'right'
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
26
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
27
27
|
export const DEFAULT_LCMS_SEQUENCE_COLUMNS: LcmsSequenceTableColumn[] = [
|
|
28
28
|
{ key: 'index', label: '#' },
|
|
29
29
|
{ key: 'sample_type', label: 'Type' },
|
|
@@ -33,7 +33,7 @@ export const DEFAULT_LCMS_SEQUENCE_COLUMNS: LcmsSequenceTableColumn[] = [
|
|
|
33
33
|
{ key: 'injection_volume', label: 'Inj Vol', align: 'right' },
|
|
34
34
|
]
|
|
35
35
|
|
|
36
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
36
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
37
37
|
export function extractLcmsCommonPrefix(fileNames: readonly string[]): string {
|
|
38
38
|
if (fileNames.length === 0) return ''
|
|
39
39
|
const stripped = fileNames.map(fileName => fileName.replace(/_\d{3}$/, ''))
|
|
@@ -52,7 +52,7 @@ export function extractLcmsCommonPrefix(fileNames: readonly string[]): string {
|
|
|
52
52
|
return common
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
55
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
56
56
|
export function extractLcmsSampleName(fileName: string, prefix = ''): string {
|
|
57
57
|
let name = fileName.replace(/_\d{3}$/, '')
|
|
58
58
|
if (prefix && name.startsWith(prefix)) {
|
|
@@ -62,12 +62,12 @@ export function extractLcmsSampleName(fileName: string, prefix = ''): string {
|
|
|
62
62
|
return name || fileName
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
65
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
66
66
|
export function lcmsWellIdFromPosition(position: string): string {
|
|
67
67
|
return position.includes(':') ? position.split(':').pop() ?? position : position
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
70
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
71
71
|
export function inferLcmsPlateTypeFromWellIds(wellIds: readonly string[]): LcmsPlateType | null {
|
|
72
72
|
let maxCol = 0
|
|
73
73
|
let maxRow = 0
|
|
@@ -81,7 +81,7 @@ export function inferLcmsPlateTypeFromWellIds(wellIds: readonly string[]): LcmsP
|
|
|
81
81
|
return maxCol <= 9 && maxRow <= 6 ? '54vial' : '96well'
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
84
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
85
85
|
export function reconstructLcmsPlateCellsFromSequenceItems(
|
|
86
86
|
items: readonly LcmsSequenceItem[],
|
|
87
87
|
): { cells: LcmsPlateCell[]; plateType: LcmsPlateType } | null {
|
|
@@ -112,7 +112,7 @@ export function reconstructLcmsPlateCellsFromSequenceItems(
|
|
|
112
112
|
return { cells, plateType }
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
/** @deprecated Scheduled for removal in MINT 1.4;
|
|
115
|
+
/** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
|
|
116
116
|
export function basenameFromWindowsPath(path: string | null | undefined): string {
|
|
117
117
|
if (!path) return ''
|
|
118
118
|
return path.split(/[\\/]/).pop() ?? path
|
|
@@ -18,3 +18,13 @@
|
|
|
18
18
|
min-width: 10rem;
|
|
19
19
|
max-width: min(18rem, calc(100vw - 1rem));
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
.mint-action-menu__check {
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
color: var(--color-primary);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Unchecked radio rows keep the check's width so labels stay aligned. */
|
|
28
|
+
.mint-action-menu__check--off {
|
|
29
|
+
visibility: hidden;
|
|
30
|
+
}
|