@morscherlab/mint-sdk 1.3.0-beta.4 → 1.3.0-beta.6

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 (95) 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/WellPlate.colors.d.ts +1 -5
  5. package/dist/components/WellPlate.rendering.d.ts +5 -2
  6. package/dist/components/index.d.ts +32 -17
  7. package/dist/components/index.js +3 -3
  8. package/dist/{components-CF0XMy8W.js → components-C-CAD2Ma.js} +84 -134
  9. package/dist/components-C-CAD2Ma.js.map +1 -0
  10. package/dist/composables/index.d.ts +0 -3
  11. package/dist/composables/index.js +4 -4
  12. package/dist/composables/useAutoGroup.d.ts +1 -1
  13. package/dist/composables/useBioTemplateComponents.d.ts +15 -3
  14. package/dist/composables/useBioTemplateControls.d.ts +5 -1
  15. package/dist/composables/useBioTemplatePackWorkspace.d.ts +2 -0
  16. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +5 -1
  17. package/dist/composables/useBioTemplateWorkspace.d.ts +5 -1
  18. package/dist/composables/useExperimentSave.d.ts +5 -1
  19. package/dist/composables/useGroupAssignment.d.ts +5 -1
  20. package/dist/composables/usePlatformContext.d.ts +3 -0
  21. package/dist/composables/useTemplateCollection.d.ts +5 -1
  22. package/dist/composables-BKhsoONv.js +591 -0
  23. package/dist/composables-BKhsoONv.js.map +1 -0
  24. package/dist/index.js +24 -24
  25. package/dist/index.js.map +1 -1
  26. package/dist/install.js +1 -1
  27. package/dist/lcms.d.ts +11 -11
  28. package/dist/styles.css +50 -143
  29. package/dist/templates/index.js +1 -1
  30. package/dist/{templates-Bj9hZ7iV.js → templates-DcxyATPT.js} +31 -44
  31. package/dist/templates-DcxyATPT.js.map +1 -0
  32. package/dist/types/auth.d.ts +0 -31
  33. package/dist/types/index.d.ts +1 -1
  34. package/dist/types/platform.d.ts +2 -0
  35. package/dist/utils/rack.d.ts +16 -16
  36. package/dist/{validation-B4Y8_eEe.js → validation-kbTxfKYD.js} +50 -12
  37. package/dist/validation-kbTxfKYD.js.map +1 -0
  38. package/package.json +1 -6
  39. package/src/__tests__/components/AppTopBar.test.ts +24 -8
  40. package/src/__tests__/components/EmptyState.test.ts +15 -0
  41. package/src/__tests__/components/FileUploader.test.ts +6 -9
  42. package/src/__tests__/components/WellPlate.decoration.test.ts +4 -28
  43. package/src/__tests__/components/WellPlate.rendering.test.ts +20 -33
  44. package/src/__tests__/components/WellPlate.sampleTypeIndicator.test.ts +7 -7
  45. package/src/__tests__/docs/frontendDocsCatalog.test.ts +2 -2
  46. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  47. package/src/__tests__/styles/menusShellRefresh.test.ts +8 -2
  48. package/src/__tests__/templates/templatesDeprecation.test.ts +58 -11
  49. package/src/components/AppTopBar.vue +5 -4
  50. package/src/components/EmptyState.vue +3 -1
  51. package/src/components/FileUploader.vue +30 -46
  52. package/src/components/WellPlate.colors.ts +6 -10
  53. package/src/components/WellPlate.legend.ts +4 -4
  54. package/src/components/WellPlate.rendering.ts +9 -8
  55. package/src/components/WellPlate.vue +5 -22
  56. package/src/components/index.ts +32 -17
  57. package/src/composables/autoGroup/index.ts +1 -1
  58. package/src/composables/index.ts +0 -7
  59. package/src/composables/useAutoGroup.ts +1 -1
  60. package/src/composables/useBioTemplateComponents.ts +15 -3
  61. package/src/composables/useBioTemplateControls.ts +5 -1
  62. package/src/composables/useBioTemplatePackWorkspace.ts +2 -0
  63. package/src/composables/useBioTemplatePresetWorkspace.ts +5 -1
  64. package/src/composables/useBioTemplateWorkspace.ts +5 -1
  65. package/src/composables/useExperimentSave.ts +5 -1
  66. package/src/composables/useGroupAssignment.ts +5 -1
  67. package/src/composables/useTemplateCollection.ts +5 -1
  68. package/src/index.ts +1 -1
  69. package/src/lcms.ts +11 -11
  70. package/src/styles/components/app-top-bar.css +7 -31
  71. package/src/styles/components/empty-state.css +3 -2
  72. package/src/styles/components/file-uploader.css +22 -55
  73. package/src/styles/components/well-plate.css +18 -55
  74. package/src/types/auth.ts +0 -41
  75. package/src/types/index.ts +0 -5
  76. package/src/types/platform.ts +2 -0
  77. package/src/utils/rack.ts +16 -16
  78. package/dist/ResourcePickerListInternal-c3IT8PIu.js.map +0 -1
  79. package/dist/__tests__/composables/useAuth.test.d.ts +0 -1
  80. package/dist/__tests__/composables/usePasskey.test.d.ts +0 -1
  81. package/dist/components-CF0XMy8W.js.map +0 -1
  82. package/dist/composables/useAuth.d.ts +0 -47
  83. package/dist/composables/usePasskey.d.ts +0 -10
  84. package/dist/composables/useWellPlateEditor.d.ts +0 -33
  85. package/dist/composables-Dlyz_Aiv.js +0 -1256
  86. package/dist/composables-Dlyz_Aiv.js.map +0 -1
  87. package/dist/plotly.min-DDhZCNEM.js +0 -168582
  88. package/dist/plotly.min-DDhZCNEM.js.map +0 -1
  89. package/dist/templates-Bj9hZ7iV.js.map +0 -1
  90. package/dist/validation-B4Y8_eEe.js.map +0 -1
  91. package/src/__tests__/composables/useAuth.test.ts +0 -290
  92. package/src/__tests__/composables/usePasskey.test.ts +0 -38
  93. package/src/composables/useAuth.ts +0 -470
  94. package/src/composables/usePasskey.ts +0 -216
  95. package/src/composables/useWellPlateEditor.ts +0 -415
@@ -0,0 +1,591 @@
1
+ import { ar as getInjectedPlatformContext } from "./templates-DcxyATPT.js";
2
+ import { n as pickerFormat, st as useRequestSyncState } from "./validation-kbTxfKYD.js";
3
+ import { a as useApi } from "./experiment-DgllIc3I.js";
4
+ import { computed, getCurrentScope, onBeforeUnmount, onScopeDispose, ref, shallowRef, toValue, watch } from "vue";
5
+ //#region src/composables/pluginFrontendBase.ts
6
+ function normalizePluginRoutePrefix(prefix) {
7
+ const candidate = prefix?.trim();
8
+ if (!candidate) return void 0;
9
+ const path = candidate.split("/").filter(Boolean).join("/");
10
+ return path ? `/${path}/` : "/";
11
+ }
12
+ /**
13
+ * Resolve the Vue Router base for a plugin frontend.
14
+ *
15
+ * Integrated plugins use the platform-injected mount prefix. Standalone and
16
+ * development builds retain the caller's Vite base unchanged.
17
+ */
18
+ function resolvePluginFrontendBase(fallbackBase = "/") {
19
+ const context = getInjectedPlatformContext();
20
+ if (!context?.isIntegrated) return fallbackBase;
21
+ return normalizePluginRoutePrefix(context.plugin?.route_prefix) ?? fallbackBase;
22
+ }
23
+ //#endregion
24
+ //#region src/composables/useManualLayoutResize.ts
25
+ function clamp(value, min, max) {
26
+ return Math.min(max, Math.max(min, value));
27
+ }
28
+ function availablePanelWidth(containerWidth, constraints) {
29
+ return Math.min(constraints.maxWidth, containerWidth - (constraints.mainMinWidth ?? 0) - (constraints.layoutGap ?? 0));
30
+ }
31
+ function resizedLeadingPanelWidth(startWidth, deltaX, containerWidth, constraints) {
32
+ return Math.round(clamp(startWidth + deltaX, constraints.minWidth, Math.max(constraints.minWidth, availablePanelWidth(containerWidth, constraints))));
33
+ }
34
+ function resizedTrailingPanelWidth(startWidth, deltaX, containerWidth, constraints) {
35
+ return resizedLeadingPanelWidth(startWidth, -deltaX, containerWidth, constraints);
36
+ }
37
+ function resizedVerticalSplitPercent(startPercent, deltaY, containerHeight, constraints) {
38
+ if (containerHeight <= 0) return startPercent;
39
+ return Math.round(clamp(startPercent + deltaY / containerHeight * 100, constraints.minPercent, constraints.maxPercent));
40
+ }
41
+ /** Coordinates pointer-driven resizing for one or more layout splitters. */
42
+ function useManualLayoutResize(options) {
43
+ const activeResize = shallowRef(null);
44
+ const isResizing = computed(() => activeResize.value !== null);
45
+ let previousBodyCursor = "";
46
+ let previousBodyUserSelect = "";
47
+ function stopResize() {
48
+ if (!activeResize.value) return;
49
+ activeResize.value = null;
50
+ window.removeEventListener("pointermove", handlePointerMove);
51
+ window.removeEventListener("pointerup", stopResize);
52
+ window.removeEventListener("pointercancel", stopResize);
53
+ if (typeof document !== "undefined") {
54
+ document.body.style.cursor = previousBodyCursor;
55
+ document.body.style.userSelect = previousBodyUserSelect;
56
+ }
57
+ }
58
+ function handlePointerMove(event) {
59
+ const state = activeResize.value;
60
+ if (!state) return;
61
+ options.resolveTarget(state.kind)?.onResize({
62
+ kind: state.kind,
63
+ startValue: state.startValue,
64
+ deltaX: event.clientX - state.startClientX,
65
+ deltaY: event.clientY - state.startClientY,
66
+ containerRect: state.containerRect,
67
+ secondaryRect: state.secondaryRect
68
+ });
69
+ }
70
+ function startResize(kind, event) {
71
+ if (!toValue(options.enabled) || event.button !== 0) return;
72
+ const target = options.resolveTarget(kind);
73
+ const containerRect = target?.getContainerElement()?.getBoundingClientRect();
74
+ if (!target || !containerRect) return;
75
+ event.preventDefault();
76
+ if (event.currentTarget instanceof HTMLElement) event.currentTarget.setPointerCapture(event.pointerId);
77
+ const secondary = target.getSecondaryElement?.() ?? null;
78
+ activeResize.value = {
79
+ kind,
80
+ startClientX: event.clientX,
81
+ startClientY: event.clientY,
82
+ startValue: target.getStartValue(),
83
+ containerRect,
84
+ secondaryRect: secondary?.getBoundingClientRect() ?? null,
85
+ cursor: target.cursor ?? "col-resize"
86
+ };
87
+ if (typeof document !== "undefined") {
88
+ previousBodyCursor = document.body.style.cursor;
89
+ previousBodyUserSelect = document.body.style.userSelect;
90
+ document.body.style.cursor = activeResize.value.cursor;
91
+ document.body.style.userSelect = "none";
92
+ }
93
+ window.addEventListener("pointermove", handlePointerMove);
94
+ window.addEventListener("pointerup", stopResize);
95
+ window.addEventListener("pointercancel", stopResize);
96
+ }
97
+ function isResizingKind(kind) {
98
+ return activeResize.value?.kind === kind;
99
+ }
100
+ watch(() => toValue(options.enabled), (enabled) => {
101
+ if (!enabled) stopResize();
102
+ });
103
+ onBeforeUnmount(stopResize);
104
+ return {
105
+ activeResize,
106
+ isResizing,
107
+ isResizingKind,
108
+ startResize,
109
+ stopResize
110
+ };
111
+ }
112
+ //#endregion
113
+ //#region src/composables/useFileBrowser.ts
114
+ /**
115
+ * Browses the platform's `/api/filesystem` routes for a custom file UI. For the
116
+ * ready-made dialog, use `FilePicker` with `usePlatformFilePickerAdapter()`.
117
+ *
118
+ * Requires the `filesystem.browse` permission and a deployment that declares
119
+ * mounts; with none configured the routes are absent and `mounts` stays empty.
120
+ */
121
+ function useFileBrowser(options = {}) {
122
+ const injectedContext = getInjectedPlatformContext();
123
+ const api = useApi({ baseUrl: options.apiBaseUrl ?? injectedContext?.platformApiUrl });
124
+ const sync = useRequestSyncState("Could not read the server mounts.");
125
+ const mounts = ref([]);
126
+ const mountId = shallowRef("");
127
+ const path = shallowRef("");
128
+ const parent = ref(null);
129
+ const entries = ref([]);
130
+ const breadcrumbs = ref([]);
131
+ const totalCount = ref(null);
132
+ const matchCount = ref(null);
133
+ const shownCount = ref(null);
134
+ const truncated = ref(false);
135
+ const search = shallowRef("");
136
+ const sort = shallowRef("name");
137
+ const selected = ref([]);
138
+ const mountsLoading = shallowRef(false);
139
+ const listingLoading = shallowRef(false);
140
+ const listingReady = shallowRef(false);
141
+ const isLoading = computed(() => mountsLoading.value || listingLoading.value);
142
+ let listToken = 0;
143
+ let mountsToken = 0;
144
+ let disposed = false;
145
+ let listingController;
146
+ let mountsController;
147
+ let searchTimer;
148
+ function resetListing() {
149
+ listingReady.value = false;
150
+ entries.value = [];
151
+ breadcrumbs.value = [];
152
+ parent.value = null;
153
+ totalCount.value = null;
154
+ matchCount.value = null;
155
+ shownCount.value = null;
156
+ truncated.value = false;
157
+ }
158
+ function typeRuleParams() {
159
+ return (toValue(options.typeRules) ?? []).map((rule) => `&type_rules=${encodeURIComponent(rule)}`).join("");
160
+ }
161
+ /** Prefer the API's own `detail` over axios's "Request failed with status N". */
162
+ function describe(cause, fallback) {
163
+ const detail = cause?.response?.data?.detail;
164
+ return typeof detail === "string" && detail ? detail : sync.readErrorMessage(cause, fallback);
165
+ }
166
+ function fail(cause, fallback) {
167
+ sync.setError(new Error(describe(cause, fallback)), fallback);
168
+ }
169
+ function statusOf(cause) {
170
+ return cause?.response?.status;
171
+ }
172
+ async function loadMounts() {
173
+ const token = ++mountsToken;
174
+ mountsController?.abort();
175
+ mountsController = new AbortController();
176
+ mountsLoading.value = true;
177
+ try {
178
+ const data = await api.get("/filesystem/mounts", { signal: mountsController.signal });
179
+ if (disposed || token !== mountsToken) return false;
180
+ mounts.value = data.mounts ?? [];
181
+ sync.clearError();
182
+ sync.markLoaded();
183
+ return true;
184
+ } catch (cause) {
185
+ if (disposed || token !== mountsToken) return false;
186
+ if (statusOf(cause) === 404) {
187
+ mounts.value = [];
188
+ sync.clearError();
189
+ sync.markLoaded();
190
+ return true;
191
+ }
192
+ mounts.value = [];
193
+ fail(cause, "Could not read the server mounts.");
194
+ return false;
195
+ } finally {
196
+ if (token === mountsToken) mountsLoading.value = false;
197
+ }
198
+ }
199
+ async function navigate(location, refresh = false) {
200
+ if (disposed || !location.mount_id) return;
201
+ clearTimeout(searchTimer);
202
+ const token = ++listToken;
203
+ listingController?.abort();
204
+ listingController = new AbortController();
205
+ listingLoading.value = true;
206
+ mountId.value = location.mount_id;
207
+ path.value = location.path;
208
+ resetListing();
209
+ sync.clearError();
210
+ try {
211
+ const query = `mount_id=${encodeURIComponent(location.mount_id)}&path=${encodeURIComponent(location.path)}&sort=${sort.value}` + (refresh ? "&refresh=true" : "") + (search.value ? `&search=${encodeURIComponent(search.value)}` : "") + typeRuleParams();
212
+ const listing = await api.get(`/filesystem/browse?${query}`, { signal: listingController.signal });
213
+ if (disposed || token !== listToken) return;
214
+ mountId.value = listing.mount_id;
215
+ path.value = listing.path;
216
+ parent.value = listing.parent ?? null;
217
+ entries.value = listing.entries ?? [];
218
+ breadcrumbs.value = listing.breadcrumbs ?? [];
219
+ totalCount.value = listing.total_count ?? null;
220
+ matchCount.value = listing.match_count ?? null;
221
+ shownCount.value = listing.shown_count ?? listing.entries?.length ?? null;
222
+ truncated.value = listing.truncated ?? false;
223
+ listingReady.value = true;
224
+ sync.clearError();
225
+ sync.markLoaded();
226
+ } catch (cause) {
227
+ if (disposed || token !== listToken) return;
228
+ entries.value = [];
229
+ fail(cause, "Could not read that folder.");
230
+ } finally {
231
+ if (token === listToken) listingLoading.value = false;
232
+ }
233
+ }
234
+ async function init() {
235
+ if (disposed) return;
236
+ const navigationToken = listToken;
237
+ if (!await loadMounts() || disposed || navigationToken !== listToken) return;
238
+ const preferred = toValue(options.initialMountId);
239
+ const reachable = (mount) => mount.available !== false;
240
+ const target = mounts.value.find((mount) => mount.id === preferred && reachable(mount)) ?? mounts.value.find(reachable);
241
+ if (target) await navigate({
242
+ mount_id: target.id,
243
+ path: ""
244
+ });
245
+ else {
246
+ mountId.value = "";
247
+ path.value = "";
248
+ resetListing();
249
+ }
250
+ }
251
+ async function refresh() {
252
+ if (disposed) return;
253
+ if (mountId.value) await navigate({
254
+ mount_id: mountId.value,
255
+ path: path.value
256
+ }, true);
257
+ else await init();
258
+ }
259
+ function selectCurrentFolder() {
260
+ if (!mountId.value || !listingReady.value || isLoading.value || sync.error.value) return;
261
+ const crumb = breadcrumbs.value[breadcrumbs.value.length - 1];
262
+ const key = JSON.stringify([mountId.value, path.value]);
263
+ if (selected.value.some((item) => JSON.stringify([item.mount_id, item.path]) === key)) return;
264
+ selected.value = [...selected.value, {
265
+ mount_id: mountId.value,
266
+ path: path.value,
267
+ name: crumb?.label ?? path.value,
268
+ kind: "directory",
269
+ child_count: totalCount.value
270
+ }];
271
+ }
272
+ function clear() {
273
+ selected.value = [];
274
+ }
275
+ watch(search, () => {
276
+ if (!mountId.value) return;
277
+ clearTimeout(searchTimer);
278
+ searchTimer = setTimeout(() => {
279
+ navigate({
280
+ mount_id: mountId.value,
281
+ path: path.value
282
+ });
283
+ }, 250);
284
+ });
285
+ watch(sort, () => {
286
+ if (mountId.value) navigate({
287
+ mount_id: mountId.value,
288
+ path: path.value
289
+ });
290
+ });
291
+ watch(() => toValue(options.typeRules), () => {
292
+ if (mountId.value) navigate({
293
+ mount_id: mountId.value,
294
+ path: path.value
295
+ });
296
+ }, { deep: true });
297
+ if (getCurrentScope()) onScopeDispose(() => {
298
+ disposed = true;
299
+ ++listToken;
300
+ ++mountsToken;
301
+ clearTimeout(searchTimer);
302
+ listingController?.abort();
303
+ mountsController?.abort();
304
+ });
305
+ return {
306
+ mounts,
307
+ mountId,
308
+ path,
309
+ parent,
310
+ entries,
311
+ breadcrumbs,
312
+ totalCount,
313
+ matchCount,
314
+ shownCount,
315
+ truncated,
316
+ search,
317
+ sort,
318
+ selected,
319
+ isLoading,
320
+ error: sync.error,
321
+ init,
322
+ navigate,
323
+ refresh,
324
+ selectCurrentFolder,
325
+ clear
326
+ };
327
+ }
328
+ //#endregion
329
+ //#region src/composables/analysisArtifactTarget.ts
330
+ var EXPERIMENT_ID = "experiment_id";
331
+ var ARTIFACT_PLUGIN_ID = "artifact_plugin_id";
332
+ var ARTIFACT_KEY = "artifact_key";
333
+ var ARTIFACT_ID = "artifact_id";
334
+ function parsePositiveInteger(value) {
335
+ if (value === null || !/^\d+$/.test(value)) return void 0;
336
+ const parsed = Number(value);
337
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
338
+ }
339
+ /** Build the canonical query used to open one analysis artifact in a plugin. */
340
+ function buildAnalysisArtifactTargetQuery(target) {
341
+ const query = new URLSearchParams();
342
+ query.set(EXPERIMENT_ID, String(target.experimentId));
343
+ query.set(ARTIFACT_PLUGIN_ID, target.artifactPluginId);
344
+ query.set(ARTIFACT_KEY, target.artifactKey);
345
+ if (target.artifactId !== void 0) query.set(ARTIFACT_ID, String(target.artifactId));
346
+ return query;
347
+ }
348
+ /** Parse a complete canonical target. Incomplete or invalid input is not guessed. */
349
+ function parseAnalysisArtifactTarget(query) {
350
+ const experimentId = parsePositiveInteger(query.get(EXPERIMENT_ID));
351
+ const artifactPluginId = query.get(ARTIFACT_PLUGIN_ID)?.trim();
352
+ const artifactKey = query.get(ARTIFACT_KEY)?.trim();
353
+ const rawArtifactId = query.get(ARTIFACT_ID);
354
+ const artifactId = parsePositiveInteger(rawArtifactId);
355
+ if (experimentId === void 0 || !artifactPluginId || !artifactKey || rawArtifactId !== null && artifactId === void 0) return null;
356
+ return {
357
+ experimentId,
358
+ artifactPluginId,
359
+ artifactKey,
360
+ ...artifactId === void 0 ? {} : { artifactId }
361
+ };
362
+ }
363
+ //#endregion
364
+ //#region src/composables/createFilePickerAdapter.ts
365
+ /** Opaque picker identity; decode before passing a selection to a mount-scoped API. */
366
+ function encodePlatformPickerPath(mountId, path = "") {
367
+ return JSON.stringify([mountId, path]);
368
+ }
369
+ function decodePlatformPickerPath(path) {
370
+ const decoded = JSON.parse(path);
371
+ if (!Array.isArray(decoded) || decoded.length !== 2 || !decoded.every((item) => typeof item === "string") || !decoded[0]) throw new Error("Invalid platform picker path.");
372
+ return {
373
+ mountId: decoded[0],
374
+ path: decoded[1]
375
+ };
376
+ }
377
+ function isRelativePath(path) {
378
+ return path === "" || !/^[a-z]:/i.test(path) && !/[\\\x00-\x1f\x7f]/.test(path) && path.split("/").every((part) => part !== "" && part !== "." && part !== "..");
379
+ }
380
+ /** Mount browsing and search shared by platform and generated plugin clients. */
381
+ function createFilePickerAdapter(transport, options = {}) {
382
+ const root = options.rootLocation && { ...options.rootLocation };
383
+ const mountLabels = /* @__PURE__ */ new Map();
384
+ const listings = /* @__PURE__ */ new Map();
385
+ let generation = 0;
386
+ function invalidate() {
387
+ generation++;
388
+ listings.clear();
389
+ mountLabels.clear();
390
+ }
391
+ function validateLocation(location) {
392
+ if (!location.mount_id || !isRelativePath(location.path) || root && (location.mount_id !== root.mount_id || root.path !== "" && location.path !== root.path && !location.path.startsWith(`${root.path}/`))) throw new Error("Invalid file picker location.");
393
+ }
394
+ if (root) validateLocation(root);
395
+ function begin(request) {
396
+ request?.signal?.throwIfAborted();
397
+ if (request?.refresh) invalidate();
398
+ return generation;
399
+ }
400
+ function check(request, token) {
401
+ request?.signal?.throwIfAborted();
402
+ if (token !== generation) throw new DOMException("Cancelled", "AbortError");
403
+ }
404
+ async function read(path, request, token) {
405
+ check(request, token);
406
+ const decoded = path ? decodePlatformPickerPath(path) : void 0;
407
+ const location = decoded ? {
408
+ mount_id: decoded.mountId,
409
+ path: decoded.path
410
+ } : root;
411
+ if (location) validateLocation(location);
412
+ const key = location ? encodePlatformPickerPath(location.mount_id, location.path) : "";
413
+ const cached = listings.get(key);
414
+ if (cached) return cached;
415
+ let nodes;
416
+ if (!location) {
417
+ const mounts = await transport.listMounts(request);
418
+ check(request, token);
419
+ nodes = mounts.map((mount) => {
420
+ mountLabels.set(mount.id, mount.label);
421
+ return {
422
+ kind: "folder",
423
+ path: encodePlatformPickerPath(mount.id),
424
+ name: mount.label,
425
+ parentPath: null,
426
+ parentName: "Server mounts",
427
+ unavailableReason: mount.available === false ? "This mount is offline." : void 0
428
+ };
429
+ });
430
+ } else if (transport.tree) {
431
+ const tree = await transport.tree(location, request);
432
+ check(request, token);
433
+ for (const listing of tree.listings ?? []) {
434
+ const at = {
435
+ mount_id: listing.mount_id,
436
+ path: listing.path
437
+ };
438
+ validateLocation(at);
439
+ if (at.mount_id !== location.mount_id) throw new Error("Invalid file picker directory entry.");
440
+ if (!listing.truncated) listings.set(encodePlatformPickerPath(at.mount_id, at.path), toNodes(at, listing));
441
+ }
442
+ const cached = listings.get(key);
443
+ if (!cached) throw new Error("This directory exceeds the server listing limit. Choose a smaller data directory.");
444
+ return cached;
445
+ } else {
446
+ const listing = await transport.browse(location, request);
447
+ check(request, token);
448
+ if (listing.truncated) throw new Error("This directory exceeds the server listing limit. Choose a smaller data directory.");
449
+ nodes = toNodes(location, listing);
450
+ }
451
+ listings.set(key, nodes);
452
+ return nodes;
453
+ }
454
+ function toNodes(location, listing) {
455
+ return (listing.entries ?? []).map((entry) => {
456
+ if (entry.path !== `${location.path ? `${location.path}/` : ""}${entry.name}`) throw new Error("Invalid file picker directory entry.");
457
+ return toNode(location.mount_id, entry);
458
+ });
459
+ }
460
+ /** One entry, parented on the folder its mount-relative path names. */
461
+ function toNode(mountId, entry) {
462
+ if (!isRelativePath(entry.path) || !entry.name || entry.name.includes("/") || entry.path !== entry.name && !entry.path.endsWith(`/${entry.name}`)) throw new Error("Invalid file picker directory entry.");
463
+ const parent = entry.path.slice(0, Math.max(0, entry.path.length - entry.name.length - 1));
464
+ const atRoot = root && mountId === root.mount_id && parent === root.path;
465
+ const common = {
466
+ path: encodePlatformPickerPath(mountId, entry.path),
467
+ parentPath: atRoot ? null : encodePlatformPickerPath(mountId, parent),
468
+ parentName: `${mountLabels.get(mountId) ?? mountId}${parent ? ` / ${parent}` : ""}`,
469
+ name: entry.name,
470
+ bytes: entry.size_bytes,
471
+ modifiedAt: entry.modified_at
472
+ };
473
+ return entry.kind === "directory" ? {
474
+ ...common,
475
+ kind: "folder"
476
+ } : {
477
+ ...common,
478
+ kind: "file",
479
+ format: pickerFormat(entry.name)
480
+ };
481
+ }
482
+ async function serverSearch(query, scope, request) {
483
+ const token = begin(request);
484
+ const decoded = scope.path ? decodePlatformPickerPath(scope.path) : void 0;
485
+ const location = decoded ? {
486
+ mount_id: decoded.mountId,
487
+ path: decoded.path
488
+ } : root;
489
+ validateLocation(location);
490
+ const result = await transport.search(location, query, scope.recursive, request);
491
+ check(request, token);
492
+ const prefix = location.path ? `${location.path}/` : "";
493
+ const nodes = (result.entries ?? []).map((entry) => {
494
+ if (!entry.path.startsWith(prefix) || !scope.recursive && entry.path.slice(prefix.length).includes("/")) throw new Error("Invalid file picker directory entry.");
495
+ return toNode(location.mount_id, entry);
496
+ });
497
+ return result.truncated ? {
498
+ nodes,
499
+ truncated: true
500
+ } : nodes;
501
+ }
502
+ return {
503
+ mounts: !root,
504
+ mountOf(path) {
505
+ try {
506
+ return encodePlatformPickerPath(decodePlatformPickerPath(path).mountId);
507
+ } catch {
508
+ return;
509
+ }
510
+ },
511
+ invalidate,
512
+ listRoot: async (request) => read("", request, begin(request)),
513
+ listChildren: async (path, request) => read(path, request, begin(request)),
514
+ async search(query, scope, request) {
515
+ if (transport.search && (scope.path || root)) return serverSearch(query, scope, request);
516
+ const token = begin(request);
517
+ const matches = [];
518
+ const queue = [scope.path];
519
+ const visited = /* @__PURE__ */ new Set();
520
+ const needle = query.toLowerCase();
521
+ let currentRequest = request;
522
+ while (queue.length) {
523
+ check(request, token);
524
+ const path = queue.shift();
525
+ if (visited.has(path)) continue;
526
+ visited.add(path);
527
+ if (visited.size > 500) return {
528
+ nodes: matches,
529
+ truncated: true
530
+ };
531
+ const nodes = await read(path, currentRequest, token);
532
+ check(request, token);
533
+ if (currentRequest?.refresh) {
534
+ currentRequest = { ...currentRequest };
535
+ delete currentRequest.refresh;
536
+ }
537
+ for (const node of nodes) {
538
+ if (node.name.toLowerCase().includes(needle)) matches.push(node);
539
+ if (scope.recursive && node.kind === "folder" && !node.unavailableReason) queue.push(node.path);
540
+ }
541
+ if (matches.length > 1e4) return {
542
+ nodes: matches.slice(0, 1e4),
543
+ truncated: true
544
+ };
545
+ }
546
+ return matches;
547
+ }
548
+ };
549
+ }
550
+ //#endregion
551
+ //#region src/composables/usePlatformFilePickerAdapter.ts
552
+ function query(location, request) {
553
+ const params = new URLSearchParams({
554
+ mount_id: location.mount_id,
555
+ path: location.path
556
+ });
557
+ if (request?.refresh) params.set("refresh", "true");
558
+ return params;
559
+ }
560
+ /** Adapt the authenticated MINT mount API without exposing absolute host paths. */
561
+ function usePlatformFilePickerAdapter(options = {}) {
562
+ const api = useApi({ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl });
563
+ return createFilePickerAdapter({
564
+ async listMounts(request) {
565
+ try {
566
+ const url = `/filesystem/mounts${request?.refresh ? "?refresh=true" : ""}`;
567
+ const { mounts } = await api.get(url, { signal: request?.signal });
568
+ return mounts;
569
+ } catch (cause) {
570
+ if (cause.response?.status === 404) return [];
571
+ throw cause;
572
+ }
573
+ },
574
+ browse(location, request) {
575
+ return api.get(`/filesystem/browse?${query(location, request)}`, { signal: request?.signal });
576
+ },
577
+ tree(location, request) {
578
+ return api.get(`/filesystem/tree?${query(location, request)}`, { signal: request?.signal });
579
+ },
580
+ search(location, text, recursive, request) {
581
+ const params = query(location, request);
582
+ params.set("q", text);
583
+ params.set("recursive", String(recursive));
584
+ return api.get(`/filesystem/search?${params}`, { signal: request?.signal });
585
+ }
586
+ }, options);
587
+ }
588
+ //#endregion
589
+ export { buildAnalysisArtifactTargetQuery as a, resizedLeadingPanelWidth as c, useManualLayoutResize as d, resolvePluginFrontendBase as f, encodePlatformPickerPath as i, resizedTrailingPanelWidth as l, createFilePickerAdapter as n, parseAnalysisArtifactTarget as o, decodePlatformPickerPath as r, useFileBrowser as s, usePlatformFilePickerAdapter as t, resizedVerticalSplitPercent as u };
590
+
591
+ //# sourceMappingURL=composables-BKhsoONv.js.map