@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.
Files changed (113) hide show
  1. package/README.md +23 -140
  2. package/dist/{ResourcePickerListInternal-c3IT8PIu.js → ResourcePickerListInternal-BEXnyBNz.js} +7 -3
  3. package/dist/ResourcePickerListInternal-BEXnyBNz.js.map +1 -0
  4. package/dist/components/FilePicker.vue.d.ts +4 -1
  5. package/dist/components/WellPlate.colors.d.ts +1 -5
  6. package/dist/components/WellPlate.rendering.d.ts +5 -2
  7. package/dist/components/index.d.ts +32 -17
  8. package/dist/components/index.js +3 -3
  9. package/dist/components/internal/FilePickerListInternal.vue.d.ts +2 -0
  10. package/dist/{components-CvY105kJ.js → components-BTzcqrJp.js} +400 -358
  11. package/dist/components-BTzcqrJp.js.map +1 -0
  12. package/dist/composables/createFilePickerAdapter.d.ts +5 -1
  13. package/dist/composables/filePicker/usePickerNavigation.d.ts +3 -2
  14. package/dist/composables/index.d.ts +0 -3
  15. package/dist/composables/index.js +4 -4
  16. package/dist/composables/useAutoGroup.d.ts +1 -1
  17. package/dist/composables/useBioTemplateComponents.d.ts +15 -3
  18. package/dist/composables/useBioTemplateControls.d.ts +5 -1
  19. package/dist/composables/useBioTemplatePackWorkspace.d.ts +2 -0
  20. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +5 -1
  21. package/dist/composables/useBioTemplateWorkspace.d.ts +5 -1
  22. package/dist/composables/useExperimentSave.d.ts +5 -1
  23. package/dist/composables/useFilePicker.d.ts +4 -1
  24. package/dist/composables/useGroupAssignment.d.ts +5 -1
  25. package/dist/composables/useTemplateCollection.d.ts +5 -1
  26. package/dist/composables-Doqb7g1z.js +591 -0
  27. package/dist/composables-Doqb7g1z.js.map +1 -0
  28. package/dist/index.js +24 -24
  29. package/dist/index.js.map +1 -1
  30. package/dist/install.js +1 -1
  31. package/dist/lcms.d.ts +11 -11
  32. package/dist/styles.css +60 -89
  33. package/dist/templates/index.js +1 -1
  34. package/dist/{templates-Bj9hZ7iV.js → templates-DgAG5kMq.js} +2 -2
  35. package/dist/{templates-Bj9hZ7iV.js.map → templates-DgAG5kMq.js.map} +1 -1
  36. package/dist/types/auth.d.ts +0 -27
  37. package/dist/types/components.d.ts +4 -0
  38. package/dist/types/fileBrowserTypes.d.ts +13 -0
  39. package/dist/types/filePicker.d.ts +8 -1
  40. package/dist/types/index.d.ts +2 -2
  41. package/dist/utils/rack.d.ts +16 -16
  42. package/dist/{validation-B4Y8_eEe.js → validation-DILksa0c.js} +50 -12
  43. package/dist/validation-DILksa0c.js.map +1 -0
  44. package/package.json +1 -6
  45. package/src/__tests__/components/ActionMenu.test.ts +10 -0
  46. package/src/__tests__/components/AppTopBar.test.ts +16 -8
  47. package/src/__tests__/components/EmptyState.test.ts +15 -0
  48. package/src/__tests__/components/FilePicker.test.ts +65 -1
  49. package/src/__tests__/components/WellPlate.decoration.test.ts +4 -28
  50. package/src/__tests__/components/WellPlate.rendering.test.ts +20 -33
  51. package/src/__tests__/components/WellPlate.sampleTypeIndicator.test.ts +7 -7
  52. package/src/__tests__/composables/createFilePickerAdapter.test.ts +92 -1
  53. package/src/__tests__/composables/usePickerNavigation.test.ts +61 -1
  54. package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +18 -6
  55. package/src/__tests__/docs/frontendDocsCatalog.test.ts +2 -2
  56. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  57. package/src/__tests__/styles/menusShellRefresh.test.ts +8 -2
  58. package/src/__tests__/templates/templatesDeprecation.test.ts +51 -11
  59. package/src/components/ActionMenu.vue +10 -2
  60. package/src/components/AppTopBar.vue +2 -4
  61. package/src/components/EmptyState.vue +3 -1
  62. package/src/components/FilePicker.vue +42 -0
  63. package/src/components/WellPlate.colors.ts +6 -10
  64. package/src/components/WellPlate.legend.ts +4 -4
  65. package/src/components/WellPlate.rendering.ts +9 -8
  66. package/src/components/WellPlate.vue +5 -22
  67. package/src/components/index.ts +32 -17
  68. package/src/components/internal/FilePickerListInternal.vue +11 -2
  69. package/src/composables/autoGroup/index.ts +1 -1
  70. package/src/composables/createFilePickerAdapter.ts +82 -22
  71. package/src/composables/filePicker/usePickerNavigation.ts +29 -2
  72. package/src/composables/index.ts +0 -7
  73. package/src/composables/useAutoGroup.ts +1 -1
  74. package/src/composables/useBioTemplateComponents.ts +15 -3
  75. package/src/composables/useBioTemplateControls.ts +5 -1
  76. package/src/composables/useBioTemplatePackWorkspace.ts +2 -0
  77. package/src/composables/useBioTemplatePresetWorkspace.ts +5 -1
  78. package/src/composables/useBioTemplateWorkspace.ts +5 -1
  79. package/src/composables/useExperimentSave.ts +5 -1
  80. package/src/composables/useFilePicker.ts +8 -1
  81. package/src/composables/useGroupAssignment.ts +5 -1
  82. package/src/composables/usePlatformFilePickerAdapter.ts +26 -4
  83. package/src/composables/useTemplateCollection.ts +5 -1
  84. package/src/index.ts +1 -1
  85. package/src/lcms.ts +11 -11
  86. package/src/styles/components/action-menu.css +10 -0
  87. package/src/styles/components/app-top-bar.css +7 -31
  88. package/src/styles/components/empty-state.css +3 -2
  89. package/src/styles/components/file-picker.css +23 -1
  90. package/src/styles/components/well-plate.css +18 -55
  91. package/src/types/auth.ts +0 -37
  92. package/src/types/components.ts +4 -0
  93. package/src/types/fileBrowserTypes.ts +15 -0
  94. package/src/types/filePicker.ts +8 -1
  95. package/src/types/index.ts +2 -5
  96. package/src/utils/rack.ts +16 -16
  97. package/dist/ResourcePickerListInternal-c3IT8PIu.js.map +0 -1
  98. package/dist/__tests__/composables/useAuth.test.d.ts +0 -1
  99. package/dist/__tests__/composables/usePasskey.test.d.ts +0 -1
  100. package/dist/components-CvY105kJ.js.map +0 -1
  101. package/dist/composables/useAuth.d.ts +0 -47
  102. package/dist/composables/usePasskey.d.ts +0 -10
  103. package/dist/composables/useWellPlateEditor.d.ts +0 -33
  104. package/dist/composables-DL4Vca7L.js +0 -1193
  105. package/dist/composables-DL4Vca7L.js.map +0 -1
  106. package/dist/plotly.min-D6Ge6qgX.js +0 -168582
  107. package/dist/plotly.min-D6Ge6qgX.js.map +0 -1
  108. package/dist/validation-B4Y8_eEe.js.map +0 -1
  109. package/src/__tests__/composables/useAuth.test.ts +0 -290
  110. package/src/__tests__/composables/usePasskey.test.ts +0 -38
  111. package/src/composables/useAuth.ts +0 -470
  112. package/src/composables/usePasskey.ts +0 -216
  113. package/src/composables/useWellPlateEditor.ts +0 -415
@@ -1,10 +1,26 @@
1
- import type { PickerAdapter, PickerNode, PickerRequest } from '../types/filePicker'
2
- import type { FileBrowserLocation, FileDirectoryListing, ServerMount } from '../types/fileBrowserTypes'
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
- const atRoot = root && location.mount_id === root.mount_id && location.path === root.path
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 items = query.trim()
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,
@@ -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; use `SmartGroupModal` instead.
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
- /** Return component props grouped by SDK component name for direct WellPlate/DoseCalculator binding. */
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
- /** Return the first props object for an SDK component, optionally restricted by template id or binding id. */
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
- /** Map built-in biology templates to SDK components such as WellPlate, DataFrame, DoseCalculator, and PlateEditor. */
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
- /** Build FormBuilder schema, AppSidebar panels, and defaults for a built-in biology template or preset. */
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
- /** Create a complete editable WellPlate/DoseCalculator workspace for dose design from a built-in biology template preset with current-experiment save wiring. */
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
- /** One-stop wiring for BioTemplateRenderer, FormBuilder, AppSidebar, and template-aware SDK components. */
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
- /** Persists experiment design, analysis, and built-in template preset data through the platform API. */
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 items = props.selectionMode === 'single-file' || props.selectionMode === 'multi-file'
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
- /** Shared two-zone group assignment model for control/treatment style workflows. */
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 { FileDirectoryListing, ServerMount } from '../types/fileBrowserTypes'
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
- const query = new URLSearchParams({ mount_id: location.mount_id, path: location.path })
31
- if (request?.refresh) query.set('refresh', 'true')
32
- return api.get<FileDirectoryListing>(`/filesystem/browse?${query}`, {
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
- /** Manages a biology template collection in the current experiment's design_data. */
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 (moves to the mld-ms plugins).
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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; LC-MS helpers move to the mld-ms plugins. */
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
+ }