@morscherlab/mint-sdk 1.2.0-beta.9 → 1.2.1
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 +30 -0
- package/dist/{ExperimentPopover-oL0fg_A0.js → ExperimentPopover-CJdNlCZb.js} +21 -14
- package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
- package/dist/{ExperimentSelectorModal-BeEOomw_.js → ExperimentSelectorModal-DHE9k8TP.js} +5 -5
- package/dist/{ExperimentSelectorModal-BeEOomw_.js.map → ExperimentSelectorModal-DHE9k8TP.js.map} +1 -1
- package/dist/{SettingsModal-Dphh3RrI.js → SettingsModal-DOcCf0Vo.js} +41 -18
- package/dist/SettingsModal-DOcCf0Vo.js.map +1 -0
- package/dist/{Skeleton-DxtwJ5gt.js → Skeleton-jhF9wUkU.js} +19 -9
- package/dist/Skeleton-jhF9wUkU.js.map +1 -0
- package/dist/__tests__/apiErrors.test.d.ts +1 -0
- package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
- package/dist/__tests__/components/FormField.test.d.ts +1 -0
- package/dist/__tests__/components/NumberInput.test.d.ts +1 -0
- package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
- package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
- package/dist/__tests__/composables/localFiles.test.d.ts +1 -0
- package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
- package/dist/__tests__/composables/usePickerSelection.test.d.ts +1 -0
- package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
- package/dist/__tests__/styles/tokenOnlyColors.test.d.ts +1 -0
- package/dist/apiErrors.d.ts +26 -0
- package/dist/components/AppSidebar.vue.d.ts +7 -7
- package/dist/components/BaseInput.vue.d.ts +1 -1
- package/dist/components/BasePill.vue.d.ts +3 -0
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseTabs.vue.d.ts +1 -4
- package/dist/components/BaseTextarea.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
- package/dist/components/ConcentrationInput.vue.d.ts +1 -1
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +1 -1
- package/dist/components/FileBrowserModal.vue.d.ts +99 -0
- package/dist/components/FilePicker.vue.d.ts +32 -0
- package/dist/components/FileUploader.vue.d.ts +8 -2
- package/dist/components/FormField.vue.d.ts +8 -1
- package/dist/components/FormulaInput.vue.d.ts +1 -1
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
- package/dist/components/MobileSupportGate.vue.d.ts +1 -1
- package/dist/components/MoleculeInput.vue.d.ts +1 -1
- package/dist/components/NumberInput.vue.d.ts +1 -1
- package/dist/components/PlotlyChart.vue.d.ts +59 -0
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -3
- package/dist/components/ResourceCard.vue.d.ts +1 -1
- package/dist/components/SecretInput.vue.d.ts +1 -1
- package/dist/components/SequenceInput.vue.d.ts +1 -1
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/components/TagsInput.vue.d.ts +1 -1
- package/dist/components/TimePicker.vue.d.ts +1 -1
- package/dist/components/TimeRangeInput.vue.d.ts +1 -1
- package/dist/components/UnitInput.vue.d.ts +1 -1
- package/dist/components/WellPlate.vue.d.ts +1 -1
- package/dist/components/fileBrowserPresentation.d.ts +10 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +8 -8
- package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +34 -0
- package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
- package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
- package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
- package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
- package/dist/{components-CgAUzF8F.js → components-Cp8DcNrU.js} +4363 -1730
- package/dist/components-Cp8DcNrU.js.map +1 -0
- package/dist/composables/filePicker/localFiles.d.ts +3 -0
- package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
- package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
- package/dist/composables/filePicker/validation.d.ts +5 -0
- package/dist/composables/index.d.ts +2 -0
- package/dist/composables/index.js +10 -10
- package/dist/composables/pluginWorkspaceModel.d.ts +0 -3
- package/dist/composables/useApi.d.ts +14 -7
- package/dist/composables/useFileBrowser.d.ts +55 -0
- package/dist/composables/useFilePicker.d.ts +189 -0
- package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
- package/dist/composables/useToast.d.ts +22 -1
- package/dist/{composables-B_pia3hO.js → composables-D_4qqOlp.js} +300 -11
- package/dist/composables-D_4qqOlp.js.map +1 -0
- package/dist/{errors-_W0-LbA6.js → errors-BLuqMee5.js} +78 -15
- package/dist/errors-BLuqMee5.js.map +1 -0
- package/dist/{experiment-Cwl9AYxw.js → experiment-CVDRAnqp.js} +2 -2
- package/dist/{experiment-Cwl9AYxw.js.map → experiment-CVDRAnqp.js.map} +1 -1
- package/dist/{experiment-utils-DV3DrvE_.js → experiment-utils-CV2Wza3z.js} +1 -1
- package/dist/{experiment-utils-DV3DrvE_.js.map → experiment-utils-CV2Wza3z.js.map} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -15
- package/dist/install.js +2 -2
- package/dist/stores/index.js +2 -2
- package/dist/styles.css +2957 -1708
- package/dist/templates/index.js +2 -2
- package/dist/{templates-Qq113pyZ.js → templates-Dd9bBSs6.js} +2 -2
- package/dist/{templates-Qq113pyZ.js.map → templates-Dd9bBSs6.js.map} +1 -1
- package/dist/types/components.d.ts +21 -0
- package/dist/types/fileBrowserTypes.d.ts +77 -0
- package/dist/types/filePicker.d.ts +77 -0
- package/dist/types/form-builder.d.ts +2 -0
- package/dist/types/generated-ui.d.ts +2 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/{useControlSchema-aMZlb4Sc.js → useControlSchema-BB2GRPhW.js} +190 -105
- package/dist/useControlSchema-BB2GRPhW.js.map +1 -0
- package/dist/{useDropdownState-DitCtrTT.js → useDropdownState-C7hRiVQf.js} +2 -2
- package/dist/{useDropdownState-DitCtrTT.js.map → useDropdownState-C7hRiVQf.js.map} +1 -1
- package/dist/{useExperimentSelector-I8HC8hUu.js → useExperimentSelector-G_CAOFTX.js} +4 -4
- package/dist/{useExperimentSelector-I8HC8hUu.js.map → useExperimentSelector-G_CAOFTX.js.map} +1 -1
- package/dist/{useFormBuilder-TCxNnqeb.js → useFormBuilder-BnGSdSRG.js} +2 -2
- package/dist/{useFormBuilder-TCxNnqeb.js.map → useFormBuilder-BnGSdSRG.js.map} +1 -1
- package/dist/{useProtocolTemplates-BmX_iVBR.js → validation-CTxuzLIX.js} +70 -13
- package/dist/validation-CTxuzLIX.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/apiErrors.test.ts +81 -0
- package/src/__tests__/components/AppSidebar.test.ts +84 -94
- package/src/__tests__/components/AppToastContainer.test.ts +55 -1
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +0 -21
- package/src/__tests__/components/ControlWorkspaceView.test.ts +0 -2
- package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +0 -2
- package/src/__tests__/components/ExperimentPopover.test.ts +30 -0
- package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
- package/src/__tests__/components/FilePicker.state.test.ts +287 -0
- package/src/__tests__/components/FilePicker.test.ts +487 -0
- package/src/__tests__/components/FormBuilder.test.ts +26 -0
- package/src/__tests__/components/FormField.test.ts +26 -0
- package/src/__tests__/components/GeneratedPathInput.test.ts +18 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
- package/src/__tests__/components/NumberInput.test.ts +105 -0
- package/src/__tests__/components/PlotlyChart.test.ts +305 -0
- package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +1 -2
- package/src/__tests__/components/PluginWorkspaceView.test.ts +1 -2
- package/src/__tests__/components/SegmentedControl.test.ts +19 -0
- package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
- package/src/__tests__/composables/localFiles.test.ts +57 -0
- package/src/__tests__/composables/useApi.test.ts +92 -9
- package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
- package/src/__tests__/composables/usePickerSelection.test.ts +98 -0
- package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
- package/src/__tests__/composables/usePluginClient.test.ts +3 -0
- package/src/__tests__/generatedUi.test.ts +111 -0
- package/src/__tests__/styles/tokenOnlyColors.test.ts +16 -0
- package/src/apiErrors.ts +85 -0
- package/src/components/AppLayout.story.vue +1 -1
- package/src/components/AppSidebar.story.vue +7 -10
- package/src/components/AppSidebar.vue +178 -58
- package/src/components/AppToastContainer.vue +65 -23
- package/src/components/BasePill.story.vue +2 -0
- package/src/components/BasePill.vue +4 -0
- package/src/components/BaseTabs.story.vue +0 -42
- package/src/components/BaseTabs.vue +3 -6
- package/src/components/ExperimentPopover.vue +10 -2
- package/src/components/FileBrowserModal.story.vue +203 -0
- package/src/components/FileBrowserModal.vue +384 -0
- package/src/components/FilePicker.story.vue +44 -0
- package/src/components/FilePicker.vue +284 -0
- package/src/components/FileUploader.vue +42 -15
- package/src/components/FormField.vue +10 -3
- package/src/components/GeneratedPathInput.vue +11 -7
- package/src/components/GeneratedPlotlyResult.vue +34 -69
- package/src/components/GeneratedResultRenderer.vue +0 -5
- package/src/components/NumberInput.story.vue +32 -13
- package/src/components/NumberInput.vue +116 -55
- package/src/components/PlotlyChart.story.vue +61 -0
- package/src/components/PlotlyChart.vue +233 -0
- package/src/components/PluginWorkspaceView.props.ts +3 -3
- package/src/components/SegmentedControl.vue +2 -2
- package/src/components/fileBrowserPresentation.ts +39 -0
- package/src/components/index.ts +5 -0
- package/src/components/internal/FileBrowserDialogInternal.vue +75 -0
- package/src/components/internal/FileBrowserListingInternal.vue +197 -0
- package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
- package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
- package/src/components/internal/FilePickerTreeInternal.vue +238 -0
- package/src/components/internal/FormFieldRendererInternal.vue +17 -0
- package/src/components/internal/WorkspaceSidebarInternal.vue +1 -2
- package/src/composables/filePicker/localFiles.ts +65 -0
- package/src/composables/filePicker/usePickerNavigation.ts +220 -0
- package/src/composables/filePicker/usePickerSelection.ts +270 -0
- package/src/composables/filePicker/validation.ts +41 -0
- package/src/composables/index.ts +7 -0
- package/src/composables/pluginWorkspaceModel.ts +0 -3
- package/src/composables/useApi.ts +57 -13
- package/src/composables/useFileBrowser.ts +314 -0
- package/src/composables/useFilePicker.ts +366 -0
- package/src/composables/useGeneratedAnalysis.ts +10 -17
- package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
- package/src/composables/usePluginClient.ts +3 -0
- package/src/composables/usePluginWorkspace.ts +1 -2
- package/src/composables/useToast.ts +21 -5
- package/src/generated/schema.ts +58 -18
- package/src/index.ts +3 -0
- package/src/styles/components/app-sidebar.css +282 -121
- package/src/styles/components/file-browser-modal.css +489 -0
- package/src/styles/components/file-picker.css +502 -0
- package/src/styles/components/form-builder.css +1 -1
- package/src/styles/components/form-field.css +28 -0
- package/src/styles/components/icon-button.css +9 -0
- package/src/styles/components/number-input.css +73 -91
- package/src/styles/components/pill.css +49 -51
- package/src/styles/components/segmented-control.css +16 -11
- package/src/styles/components/tabs.css +18 -34
- package/src/styles/components/toast.css +136 -27
- package/src/styles/index.css +3 -0
- package/src/styles/variables.css +25 -5
- package/src/types/components.ts +23 -0
- package/src/types/fileBrowserTypes.ts +85 -0
- package/src/types/filePicker.ts +72 -0
- package/src/types/form-builder.ts +2 -0
- package/src/types/generated-ui.ts +2 -0
- package/src/types/index.ts +16 -0
- package/dist/ExperimentPopover-oL0fg_A0.js.map +0 -1
- package/dist/SettingsModal-Dphh3RrI.js.map +0 -1
- package/dist/Skeleton-DxtwJ5gt.js.map +0 -1
- package/dist/components-CgAUzF8F.js.map +0 -1
- package/dist/composables-B_pia3hO.js.map +0 -1
- package/dist/errors-_W0-LbA6.js.map +0 -1
- package/dist/useControlSchema-aMZlb4Sc.js.map +0 -1
- package/dist/useProtocolTemplates-BmX_iVBR.js.map +0 -1
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from './useJobsStatusTray'
|
|
15
15
|
import { useEventStream, type EventStreamMessage } from './useEventStream'
|
|
16
16
|
import { isActiveJobStatus, normalizeJobState } from '../jobs'
|
|
17
|
+
import { MintApiError, mintApiErrorFromResponse } from '../apiErrors'
|
|
17
18
|
import type {
|
|
18
19
|
GeneratedAnalysisJob,
|
|
19
20
|
GeneratedAnalysisJobInput,
|
|
@@ -129,16 +130,6 @@ type PluginJobStreamData = GeneratedAnalysisJobInput | {
|
|
|
129
130
|
jobs: GeneratedAnalysisJobInput[]
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
class GeneratedAnalysisRequestError extends Error {
|
|
133
|
-
constructor(
|
|
134
|
-
readonly status: number,
|
|
135
|
-
readonly detail: unknown,
|
|
136
|
-
) {
|
|
137
|
-
super(typeof detail === 'string' ? detail : `Request failed (${status})`)
|
|
138
|
-
this.name = 'GeneratedAnalysisRequestError'
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
133
|
export function createGeneratedAnalysisTransport(
|
|
143
134
|
options: GeneratedAnalysisTransportOptions = {},
|
|
144
135
|
): GeneratedAnalysisTransport {
|
|
@@ -838,7 +829,7 @@ function createPluginJobs(
|
|
|
838
829
|
job: GeneratedAnalysisJob,
|
|
839
830
|
cause: unknown,
|
|
840
831
|
): void {
|
|
841
|
-
if (cause instanceof
|
|
832
|
+
if (cause instanceof MintApiError) {
|
|
842
833
|
if (cause.status === 404 || cause.status === 410) {
|
|
843
834
|
discardCachedResult(job.job_id)
|
|
844
835
|
jobs.value = jobs.value.map(candidate => (
|
|
@@ -1037,8 +1028,10 @@ function createPluginJobs(
|
|
|
1037
1028
|
},
|
|
1038
1029
|
})
|
|
1039
1030
|
if (!response.ok) {
|
|
1040
|
-
const payload = await response.json().catch(() => null)
|
|
1041
|
-
throw
|
|
1031
|
+
const payload: unknown = await response.json().catch(() => null)
|
|
1032
|
+
throw mintApiErrorFromResponse(response.status, payload, {
|
|
1033
|
+
requestId: response.headers.get('X-Request-ID'),
|
|
1034
|
+
})
|
|
1042
1035
|
}
|
|
1043
1036
|
return await response.json() as T
|
|
1044
1037
|
}
|
|
@@ -1177,20 +1170,20 @@ function createGeneratedSessionStorage(candidate?: Storage): GeneratedSessionSto
|
|
|
1177
1170
|
}
|
|
1178
1171
|
|
|
1179
1172
|
function isSessionNotFoundError(error: unknown): boolean {
|
|
1180
|
-
return error instanceof
|
|
1173
|
+
return error instanceof MintApiError
|
|
1181
1174
|
&& error.status === 404
|
|
1182
|
-
&& error.detail === SESSION_NOT_FOUND_DETAIL
|
|
1175
|
+
&& (error.detail === SESSION_NOT_FOUND_DETAIL || error.message === SESSION_NOT_FOUND_DETAIL)
|
|
1183
1176
|
}
|
|
1184
1177
|
|
|
1185
1178
|
function isRetryableResultError(error: unknown): boolean {
|
|
1186
|
-
return !(error instanceof
|
|
1179
|
+
return !(error instanceof MintApiError)
|
|
1187
1180
|
|| error.status === 409
|
|
1188
1181
|
|| error.status === 429
|
|
1189
1182
|
|| error.status >= 500
|
|
1190
1183
|
}
|
|
1191
1184
|
|
|
1192
1185
|
function isRetryableJobLookupError(error: unknown): boolean {
|
|
1193
|
-
return !(error instanceof
|
|
1186
|
+
return !(error instanceof MintApiError)
|
|
1194
1187
|
|| error.status === 408
|
|
1195
1188
|
|| error.status === 429
|
|
1196
1189
|
|| error.status >= 500
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { PickerAdapter, PickerNode, PickerRequest } from '../types/filePicker'
|
|
2
|
+
import type { FileDirectoryListing, ServerMount } from '../types/fileBrowserTypes'
|
|
3
|
+
import { useApi } from './useApi'
|
|
4
|
+
import { getInjectedPlatformContext } from './platformContextHelpers'
|
|
5
|
+
import { pickerFormat } from './filePicker/validation'
|
|
6
|
+
|
|
7
|
+
/** Opaque picker identity; decode before passing a selection to a mount-scoped API. */
|
|
8
|
+
export function encodePlatformPickerPath(mountId: string, path = ''): string {
|
|
9
|
+
return JSON.stringify([mountId, path])
|
|
10
|
+
}
|
|
11
|
+
export function decodePlatformPickerPath(path: string): { mountId: string; path: string } {
|
|
12
|
+
const decoded: unknown = JSON.parse(path)
|
|
13
|
+
if (
|
|
14
|
+
!Array.isArray(decoded) ||
|
|
15
|
+
decoded.length !== 2 ||
|
|
16
|
+
!decoded.every((item) => typeof item === 'string') ||
|
|
17
|
+
!decoded[0]
|
|
18
|
+
) {
|
|
19
|
+
throw new Error('Invalid platform picker path.')
|
|
20
|
+
}
|
|
21
|
+
return { mountId: decoded[0], path: decoded[1] }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Adapt the authenticated MINT mount API without exposing absolute host paths. */
|
|
25
|
+
export function usePlatformFilePickerAdapter(options: { apiBaseUrl?: string } = {}): PickerAdapter {
|
|
26
|
+
const api = useApi({
|
|
27
|
+
baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl,
|
|
28
|
+
})
|
|
29
|
+
const mountLabels = new Map<string, string>()
|
|
30
|
+
async function listRoot(request?: PickerRequest): Promise<PickerNode[]> {
|
|
31
|
+
try {
|
|
32
|
+
const { mounts } = await api.get<{ mounts: ServerMount[] }>('/filesystem/mounts', {
|
|
33
|
+
signal: request?.signal,
|
|
34
|
+
})
|
|
35
|
+
return mounts.map((mount) => {
|
|
36
|
+
mountLabels.set(mount.id, mount.label)
|
|
37
|
+
return {
|
|
38
|
+
kind: 'folder',
|
|
39
|
+
path: encodePlatformPickerPath(mount.id),
|
|
40
|
+
name: mount.label,
|
|
41
|
+
parentPath: null,
|
|
42
|
+
parentName: 'Server mounts',
|
|
43
|
+
unavailableReason: mount.available === false ? 'This mount is offline.' : undefined,
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
} catch (cause) {
|
|
47
|
+
if ((cause as { response?: { status?: number } }).response?.status === 404) return []
|
|
48
|
+
throw cause
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function listChildren(path: string, request?: PickerRequest): Promise<PickerNode[]> {
|
|
52
|
+
const location = decodePlatformPickerPath(path)
|
|
53
|
+
const query = new URLSearchParams({ mount_id: location.mountId, path: location.path })
|
|
54
|
+
const listing = await api.get<FileDirectoryListing>(`/filesystem/browse?${query}`, {
|
|
55
|
+
signal: request?.signal,
|
|
56
|
+
})
|
|
57
|
+
if (listing.truncated)
|
|
58
|
+
throw new Error(
|
|
59
|
+
'This directory exceeds the server listing limit. Choose a smaller data directory.',
|
|
60
|
+
)
|
|
61
|
+
return (listing.entries ?? []).map(
|
|
62
|
+
(entry) =>
|
|
63
|
+
({
|
|
64
|
+
kind: entry.kind === 'directory' ? 'folder' : 'file',
|
|
65
|
+
path: encodePlatformPickerPath(location.mountId, entry.path),
|
|
66
|
+
parentPath: path,
|
|
67
|
+
parentName: `${mountLabels.get(location.mountId) ?? location.mountId}${location.path ? ` / ${location.path}` : ''}`,
|
|
68
|
+
name: entry.name,
|
|
69
|
+
bytes: entry.size_bytes,
|
|
70
|
+
...(entry.kind === 'directory'
|
|
71
|
+
? { hasChildren: entry.child_count !== 0 }
|
|
72
|
+
: { format: pickerFormat(entry.name) }),
|
|
73
|
+
}) as PickerNode,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
listRoot,
|
|
78
|
+
listChildren,
|
|
79
|
+
async search(query, scope, request) {
|
|
80
|
+
const matches: PickerNode[] = []
|
|
81
|
+
const queue = [scope.path]
|
|
82
|
+
const visited = new Set<string>()
|
|
83
|
+
while (queue.length) {
|
|
84
|
+
request?.signal?.throwIfAborted()
|
|
85
|
+
const path = queue.shift()!
|
|
86
|
+
if (visited.has(path)) continue
|
|
87
|
+
visited.add(path)
|
|
88
|
+
if (visited.size > 500)
|
|
89
|
+
throw new Error(
|
|
90
|
+
'Search reached its folder limit. Search a smaller location or turn off Subfolders.',
|
|
91
|
+
)
|
|
92
|
+
const nodes = path ? await listChildren(path, request) : await listRoot(request)
|
|
93
|
+
for (const node of nodes) {
|
|
94
|
+
if (node.name.toLowerCase().includes(query.toLowerCase())) matches.push(node)
|
|
95
|
+
if (scope.recursive && node.kind === 'folder' && !node.unavailableReason)
|
|
96
|
+
queue.push(node.path)
|
|
97
|
+
}
|
|
98
|
+
if (matches.length > 10000)
|
|
99
|
+
throw new Error('Too many search results. Use a more specific search.')
|
|
100
|
+
}
|
|
101
|
+
return matches
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -423,6 +423,7 @@ export function createPluginClient<TClient extends object = GeneratedPluginClien
|
|
|
423
423
|
const api = useApi({
|
|
424
424
|
baseUrl: parts.baseUrl,
|
|
425
425
|
withAuth: endpoint.requiresAuth !== false,
|
|
426
|
+
typedErrors: true,
|
|
426
427
|
})
|
|
427
428
|
const config = endpointRequestConfig(endpoint, parts.query, callOptions)
|
|
428
429
|
|
|
@@ -469,6 +470,7 @@ export async function uploadPluginEndpoint<TResponse = unknown>(
|
|
|
469
470
|
const api = useApi({
|
|
470
471
|
baseUrl: parts.baseUrl,
|
|
471
472
|
withAuth: endpoint.requiresAuth !== false,
|
|
473
|
+
typedErrors: true,
|
|
472
474
|
})
|
|
473
475
|
const body = endpoint.hasBody
|
|
474
476
|
? requestBodyFromPayload(payload, parts.requestPayload, endpoint)
|
|
@@ -507,6 +509,7 @@ export async function downloadPluginEndpoint(
|
|
|
507
509
|
const api = useApi({
|
|
508
510
|
baseUrl: parts.baseUrl,
|
|
509
511
|
withAuth: endpoint.requiresAuth !== false,
|
|
512
|
+
typedErrors: true,
|
|
510
513
|
})
|
|
511
514
|
const config = {
|
|
512
515
|
...(endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {}),
|
|
@@ -167,7 +167,6 @@ export function usePluginWorkspace(options: UsePluginWorkspaceOptions): UsePlugi
|
|
|
167
167
|
title: props.sidebarTitle,
|
|
168
168
|
subtitle: props.sidebarSubtitle,
|
|
169
169
|
badge: props.sidebarBadge,
|
|
170
|
-
variant: props.sidebarVariant ?? 'analysis',
|
|
171
170
|
panels: props.panels ?? {},
|
|
172
171
|
activeView: navigationState.resolvedActiveView.value,
|
|
173
172
|
floating: false,
|
|
@@ -187,7 +186,7 @@ export function usePluginWorkspace(options: UsePluginWorkspaceOptions): UsePlugi
|
|
|
187
186
|
formDisabled: props.formDisabled ?? false,
|
|
188
187
|
formReadonly: props.formReadonly ?? false,
|
|
189
188
|
formSize: props.formSize ?? 'sm',
|
|
190
|
-
collapsible: props.sidebarCollapsible,
|
|
189
|
+
collapsible: props.sidebarCollapsible ?? true,
|
|
191
190
|
collapsed: props.sidebarCollapsed,
|
|
192
191
|
defaultCollapsed: props.defaultSidebarCollapsed ?? false,
|
|
193
192
|
collapsedWidth: props.sidebarCollapsedWidth ?? '3rem',
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
import { ref } from 'vue'
|
|
2
|
-
import type { Toast } from '../types'
|
|
2
|
+
import type { Toast, ToastOptions } from '../types'
|
|
3
3
|
|
|
4
4
|
const toasts = ref<Toast[]>([])
|
|
5
5
|
let nextId = 0
|
|
6
6
|
|
|
7
7
|
/** Reactive global toast queue with success/error/warning/info variants and auto-dismiss timers. */
|
|
8
8
|
export function useToast() {
|
|
9
|
-
|
|
9
|
+
/** Push a structured toast. `progress: 'indeterminate'` is the in-progress form and never auto-dismisses. */
|
|
10
|
+
function push(options: ToastOptions): number {
|
|
10
11
|
const id = nextId++
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const duration = options.duration ?? 3500
|
|
13
|
+
toasts.value.push({
|
|
14
|
+
id,
|
|
15
|
+
type: 'success',
|
|
16
|
+
...options,
|
|
17
|
+
message: options.message ?? options.title,
|
|
18
|
+
duration,
|
|
19
|
+
actions: options.actions?.slice(0, 2),
|
|
20
|
+
})
|
|
21
|
+
if (options.progress !== 'indeterminate') {
|
|
22
|
+
setTimeout(() => dismiss(id), duration)
|
|
23
|
+
}
|
|
24
|
+
return id
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function show(message: string, type: Toast['type'] = 'success', duration = 3500) {
|
|
28
|
+
push({ title: message, type, duration })
|
|
13
29
|
}
|
|
14
30
|
|
|
15
31
|
function success(message: string, duration = 3500) {
|
|
@@ -36,5 +52,5 @@ export function useToast() {
|
|
|
36
52
|
toasts.value = []
|
|
37
53
|
}
|
|
38
54
|
|
|
39
|
-
return { toasts, show, success, error, warning, info, dismiss, clear }
|
|
55
|
+
return { toasts, push, show, success, error, warning, info, dismiss, clear }
|
|
40
56
|
}
|
package/src/generated/schema.ts
CHANGED
|
@@ -38,8 +38,9 @@ export function generatedJobFormSchema(definition: GeneratedJobDefinition): Form
|
|
|
38
38
|
export function generatedJobDefaults(definition: GeneratedJobDefinition): Record<string, unknown> {
|
|
39
39
|
return Object.fromEntries(
|
|
40
40
|
Object.entries(definition.input_schema.properties ?? {})
|
|
41
|
-
.
|
|
42
|
-
.
|
|
41
|
+
.map(([name, schema]) => [name, resolveGeneratedField(definition.input_schema, schema)] as const)
|
|
42
|
+
.filter(([, field]) => field.hasDefault)
|
|
43
|
+
.map(([name, field]) => [name, structuredClone(field.defaultValue)]),
|
|
43
44
|
)
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -51,7 +52,17 @@ export function normalizeGeneratedJobInput(
|
|
|
51
52
|
for (const [name, value] of Object.entries(values)) {
|
|
52
53
|
const field = definition.input_schema.properties?.[name]
|
|
53
54
|
if (!field) continue
|
|
54
|
-
|
|
55
|
+
const { schema, reference } = resolveGeneratedField(definition.input_schema, field)
|
|
56
|
+
if (reference !== 'path' && Array.isArray(value) && ['integer', 'number'].includes(String(schema.items?.type))) {
|
|
57
|
+
normalized[name] = value.map(item => {
|
|
58
|
+
const numeric = typeof item === 'string' && item.trim() !== '' ? Number(item) : item
|
|
59
|
+
return typeof numeric === 'number' && Number.isFinite(numeric) ? numeric : item
|
|
60
|
+
})
|
|
61
|
+
} else if (reference !== 'path' && Array.isArray(value) && schema.items?.type === 'boolean') {
|
|
62
|
+
normalized[name] = value.map(item => item === 'true' ? true : item === 'false' ? false : item)
|
|
63
|
+
} else {
|
|
64
|
+
normalized[name] = value === undefined && allowsNull(schema) ? null : value
|
|
65
|
+
}
|
|
55
66
|
}
|
|
56
67
|
return normalized
|
|
57
68
|
}
|
|
@@ -63,7 +74,12 @@ function toFormField(
|
|
|
63
74
|
): FormFieldSchema {
|
|
64
75
|
const { schema, reference } = resolved
|
|
65
76
|
const validation: NonNullable<FormFieldSchema['validation']> = {}
|
|
66
|
-
|
|
77
|
+
const allowsEmpty = schema.type === 'array' || (
|
|
78
|
+
schema.type === 'string' && reference !== 'path'
|
|
79
|
+
&& !(schema.minLength && schema.minLength > 0)
|
|
80
|
+
&& (!schema.enum || schema.enum.includes(''))
|
|
81
|
+
)
|
|
82
|
+
if (required && !allowsNull(schema) && !allowsEmpty) validation.required = true
|
|
67
83
|
if (schema.minimum !== undefined) validation.min = schema.minimum
|
|
68
84
|
if (schema.maximum !== undefined) validation.max = schema.maximum
|
|
69
85
|
if (schema.minLength !== undefined) validation.minLength = schema.minLength
|
|
@@ -71,15 +87,37 @@ function toFormField(
|
|
|
71
87
|
|
|
72
88
|
const props: Record<string, unknown> = {}
|
|
73
89
|
if (schema.enum) props.options = [...schema.enum]
|
|
90
|
+
if (schema.type === 'integer') {
|
|
91
|
+
if (schema.exclusiveMinimum !== undefined) validation.min = Math.max(Number(validation.min ?? -Infinity), Math.floor(schema.exclusiveMinimum) + 1)
|
|
92
|
+
if (schema.exclusiveMaximum !== undefined) validation.max = Math.min(Number(validation.max ?? Infinity), Math.ceil(schema.exclusiveMaximum) - 1)
|
|
93
|
+
if (validation.min !== undefined) validation.min = Math.ceil(Number(validation.min))
|
|
94
|
+
if (validation.max !== undefined) validation.max = Math.floor(Number(validation.max))
|
|
95
|
+
if (schema.multipleOf && Number.isInteger(schema.multipleOf)) {
|
|
96
|
+
if (validation.min !== undefined) validation.min = Math.ceil(Number(validation.min) / schema.multipleOf) * schema.multipleOf
|
|
97
|
+
if (validation.max !== undefined) validation.max = Math.floor(Number(validation.max) / schema.multipleOf) * schema.multipleOf
|
|
98
|
+
}
|
|
99
|
+
props.step = 1
|
|
100
|
+
}
|
|
101
|
+
if (schema.type === 'integer' || schema.type === 'number') {
|
|
102
|
+
if (validation.min !== undefined) props.min = validation.min
|
|
103
|
+
if (validation.max !== undefined) props.max = validation.max
|
|
104
|
+
if (schema.multipleOf !== undefined) props.step = schema.multipleOf
|
|
105
|
+
}
|
|
106
|
+
if (schema.type === 'array' && reference !== 'path') {
|
|
107
|
+
if (schema.items?.enum) props.options = [...schema.items.enum]
|
|
108
|
+
else props.allowDuplicates = true
|
|
109
|
+
}
|
|
74
110
|
if (reference === 'path' && schema.type === 'array') props.multiple = true
|
|
75
111
|
return {
|
|
76
112
|
name,
|
|
77
113
|
label: schema.title || humanize(name),
|
|
78
114
|
type: fieldType(schema, reference),
|
|
79
115
|
defaultValue: resolved.hasDefault ? structuredClone(resolved.defaultValue) : undefined,
|
|
116
|
+
disabled: Object.prototype.hasOwnProperty.call(schema, 'const') || undefined,
|
|
80
117
|
hint: schema.description,
|
|
81
118
|
validation,
|
|
82
119
|
props,
|
|
120
|
+
itemType: schema.items?.type === 'number' || schema.items?.type === 'integer' ? 'number' : undefined,
|
|
83
121
|
}
|
|
84
122
|
}
|
|
85
123
|
|
|
@@ -87,27 +125,24 @@ function resolveGeneratedField(
|
|
|
87
125
|
root: GeneratedJsonSchema,
|
|
88
126
|
field: GeneratedJsonSchema,
|
|
89
127
|
): ResolvedGeneratedField {
|
|
90
|
-
const hasDefault = hasOwnDefault(field)
|
|
91
128
|
const candidate = field.anyOf?.find(item => item.type !== 'null')
|
|
92
129
|
?? field.allOf?.[0]
|
|
93
130
|
?? field
|
|
131
|
+
let schema = { ...candidate, ...field }
|
|
94
132
|
if (candidate.$ref) {
|
|
95
133
|
const referenceParts = candidate.$ref.split('/')
|
|
96
134
|
const key = referenceParts[referenceParts.length - 1]
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
99
|
-
|
|
100
|
-
schema: { ...schema, title: field.title ?? schema.title },
|
|
101
|
-
reference: field['x-mint-component'] ?? schema['x-mint-component'],
|
|
102
|
-
hasDefault,
|
|
103
|
-
defaultValue: field.default,
|
|
104
|
-
}
|
|
135
|
+
const referenced = key ? root.$defs?.[key] : undefined
|
|
136
|
+
if (!referenced) throw new Error(`Generated field reference is missing: ${candidate.$ref}`)
|
|
137
|
+
schema = { ...referenced, ...candidate, ...field }
|
|
105
138
|
}
|
|
139
|
+
if (schema.items) schema.items = resolveGeneratedField(root, schema.items).schema
|
|
140
|
+
const constant = Object.prototype.hasOwnProperty.call(schema, 'const')
|
|
106
141
|
return {
|
|
107
|
-
schema
|
|
108
|
-
reference:
|
|
109
|
-
hasDefault,
|
|
110
|
-
defaultValue: field.default,
|
|
142
|
+
schema,
|
|
143
|
+
reference: schema['x-mint-component'],
|
|
144
|
+
hasDefault: constant || hasOwnDefault(field) || allowsNull(schema),
|
|
145
|
+
defaultValue: constant ? schema.const : hasOwnDefault(field) ? field.default : null,
|
|
111
146
|
}
|
|
112
147
|
}
|
|
113
148
|
|
|
@@ -117,7 +152,7 @@ function fieldType(schema: GeneratedJsonSchema, reference?: GeneratedReference):
|
|
|
117
152
|
const type = Array.isArray(schema.type) ? schema.type.find(value => value !== 'null') : schema.type
|
|
118
153
|
if (type === 'boolean') return 'toggle'
|
|
119
154
|
if (type === 'number' || type === 'integer') return 'number'
|
|
120
|
-
if (type === 'array') return 'tags'
|
|
155
|
+
if (type === 'array') return schema.items?.enum ? 'multiselect' : 'tags'
|
|
121
156
|
return 'text'
|
|
122
157
|
}
|
|
123
158
|
|
|
@@ -125,6 +160,11 @@ function hasOwnDefault(schema: GeneratedJsonSchema): boolean {
|
|
|
125
160
|
return Object.prototype.hasOwnProperty.call(schema, 'default')
|
|
126
161
|
}
|
|
127
162
|
|
|
163
|
+
function allowsNull(schema: GeneratedJsonSchema): boolean {
|
|
164
|
+
return schema.type === 'null' || (Array.isArray(schema.type) && schema.type.includes('null'))
|
|
165
|
+
|| !!schema.anyOf?.some(item => item.type === 'null')
|
|
166
|
+
}
|
|
167
|
+
|
|
128
168
|
function humanize(value: string): string {
|
|
129
169
|
return value.replace(/[_-]+/g, ' ').replace(/^\w/, character => character.toUpperCase())
|
|
130
170
|
}
|