@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
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Adapter-driven file picker with Finder-style selection, disclosure and metadata preview. */
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
import BaseButton from './BaseButton.vue'
|
|
5
|
+
import BaseInput from './BaseInput.vue'
|
|
6
|
+
import IconButton from './IconButton.vue'
|
|
7
|
+
import FileUploader from './FileUploader.vue'
|
|
8
|
+
import FileBrowserDialogInternal from './internal/FileBrowserDialogInternal.vue'
|
|
9
|
+
import FilePickerTreeInternal from './internal/FilePickerTreeInternal.vue'
|
|
10
|
+
import FilePickerPreviewInternal from './internal/FilePickerPreviewInternal.vue'
|
|
11
|
+
import { formatBytes } from './fileBrowserPresentation'
|
|
12
|
+
import { useFilePicker } from '../composables/useFilePicker'
|
|
13
|
+
import { pickerReason } from '../composables/filePicker/validation'
|
|
14
|
+
import type {
|
|
15
|
+
PickerAdapter,
|
|
16
|
+
PickerCapabilities,
|
|
17
|
+
PickerNode,
|
|
18
|
+
PickerSelection,
|
|
19
|
+
PickerSelectionMode,
|
|
20
|
+
PickerSource,
|
|
21
|
+
} from '../types/filePicker'
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
open: boolean
|
|
25
|
+
adapter: PickerAdapter
|
|
26
|
+
selectionMode?: PickerSelectionMode
|
|
27
|
+
sources?: PickerSource[]
|
|
28
|
+
capabilities?: PickerCapabilities
|
|
29
|
+
initialPath?: string
|
|
30
|
+
initialSelection?: PickerSelection | null
|
|
31
|
+
title?: string
|
|
32
|
+
/** Host-owned visibility/selection filter; backend must still enforce access. */
|
|
33
|
+
systemFilter?: (node: PickerNode, source: PickerSource) => boolean
|
|
34
|
+
}
|
|
35
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
36
|
+
selectionMode: 'folder+files',
|
|
37
|
+
sources: () => ['server'],
|
|
38
|
+
capabilities: () => ({}),
|
|
39
|
+
initialPath: '',
|
|
40
|
+
initialSelection: null,
|
|
41
|
+
title: 'Choose data',
|
|
42
|
+
})
|
|
43
|
+
const emit = defineEmits<{
|
|
44
|
+
'update:open': [open: boolean]
|
|
45
|
+
select: [selection: PickerSelection]
|
|
46
|
+
cancel: []
|
|
47
|
+
}>()
|
|
48
|
+
const uploadError = ref('')
|
|
49
|
+
const {
|
|
50
|
+
previewNode,
|
|
51
|
+
sourceNames,
|
|
52
|
+
sourceIcons,
|
|
53
|
+
allowedSources,
|
|
54
|
+
source,
|
|
55
|
+
query,
|
|
56
|
+
recursive,
|
|
57
|
+
skipBlanks,
|
|
58
|
+
reviewing,
|
|
59
|
+
nav,
|
|
60
|
+
selection,
|
|
61
|
+
capability,
|
|
62
|
+
path,
|
|
63
|
+
trail,
|
|
64
|
+
loading,
|
|
65
|
+
error,
|
|
66
|
+
rows,
|
|
67
|
+
roots,
|
|
68
|
+
chosen,
|
|
69
|
+
focused,
|
|
70
|
+
resolving,
|
|
71
|
+
resolutionError,
|
|
72
|
+
searchArea,
|
|
73
|
+
confirmLabel,
|
|
74
|
+
folderCount,
|
|
75
|
+
canConfirm,
|
|
76
|
+
switchSource,
|
|
77
|
+
selectNode,
|
|
78
|
+
enter,
|
|
79
|
+
up,
|
|
80
|
+
refresh,
|
|
81
|
+
selectParent,
|
|
82
|
+
chooseLocal,
|
|
83
|
+
cancel,
|
|
84
|
+
confirm,
|
|
85
|
+
keyboard,
|
|
86
|
+
} = useFilePicker(props, emit)
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<FileBrowserDialogInternal
|
|
91
|
+
:open="open"
|
|
92
|
+
:label="title"
|
|
93
|
+
size="full"
|
|
94
|
+
variant="picker"
|
|
95
|
+
@close="cancel"
|
|
96
|
+
>
|
|
97
|
+
<template #header>
|
|
98
|
+
<div v-if="source === 'server'" ref="searchArea" class="mint-picker__search">
|
|
99
|
+
<svg
|
|
100
|
+
viewBox="0 0 24 24"
|
|
101
|
+
fill="none"
|
|
102
|
+
stroke="currentColor"
|
|
103
|
+
stroke-width="1.8"
|
|
104
|
+
aria-hidden="true"
|
|
105
|
+
>
|
|
106
|
+
<circle cx="10.5" cy="10.5" r="7" />
|
|
107
|
+
<path d="m16 16 5 5" />
|
|
108
|
+
</svg>
|
|
109
|
+
<BaseInput
|
|
110
|
+
v-model="query"
|
|
111
|
+
aria-label="Search folders and files"
|
|
112
|
+
placeholder="Search folders and files…"
|
|
113
|
+
/>
|
|
114
|
+
<kbd>/</kbd>
|
|
115
|
+
</div>
|
|
116
|
+
<h2 v-else class="mint-file-browser__title mint-picker__local-title">{{ title }}</h2>
|
|
117
|
+
<div
|
|
118
|
+
v-if="allowedSources.length > 1"
|
|
119
|
+
class="mint-picker__sources"
|
|
120
|
+
role="group"
|
|
121
|
+
aria-label="Data source"
|
|
122
|
+
:style="{ '--picker-source-index': allowedSources.indexOf(source) }"
|
|
123
|
+
>
|
|
124
|
+
<span class="mint-picker__source-indicator" aria-hidden="true" />
|
|
125
|
+
<IconButton
|
|
126
|
+
v-for="item in allowedSources"
|
|
127
|
+
:key="item"
|
|
128
|
+
size="sm"
|
|
129
|
+
variant="ghost"
|
|
130
|
+
:label="sourceNames[item]"
|
|
131
|
+
:aria-pressed="item === source"
|
|
132
|
+
@click="switchSource(item)"
|
|
133
|
+
>
|
|
134
|
+
<svg
|
|
135
|
+
viewBox="0 0 24 24"
|
|
136
|
+
fill="none"
|
|
137
|
+
stroke="currentColor"
|
|
138
|
+
stroke-width="1.8"
|
|
139
|
+
aria-hidden="true"
|
|
140
|
+
>
|
|
141
|
+
<path :d="sourceIcons[item]" />
|
|
142
|
+
</svg>
|
|
143
|
+
</IconButton>
|
|
144
|
+
</div>
|
|
145
|
+
</template>
|
|
146
|
+
<div :key="source" class="mint-picker mint-picker__source-panel" @keydown="keyboard">
|
|
147
|
+
<div v-if="source === 'server'" class="mint-picker__pathbar">
|
|
148
|
+
<IconButton
|
|
149
|
+
label="Up one level"
|
|
150
|
+
size="sm"
|
|
151
|
+
variant="secondary"
|
|
152
|
+
:disabled="!path"
|
|
153
|
+
@click="up()"
|
|
154
|
+
><svg
|
|
155
|
+
viewBox="0 0 24 24"
|
|
156
|
+
fill="none"
|
|
157
|
+
stroke="currentColor"
|
|
158
|
+
stroke-width="2"
|
|
159
|
+
aria-hidden="true"
|
|
160
|
+
>
|
|
161
|
+
<path d="M11 17l-5-5 5-5M6 12h13" /></svg
|
|
162
|
+
></IconButton>
|
|
163
|
+
<nav aria-label="Breadcrumb" class="mint-picker__breadcrumbs">
|
|
164
|
+
<BaseButton variant="ghost" size="sm" @click="up('')">{{
|
|
165
|
+
sourceNames[source]
|
|
166
|
+
}}</BaseButton>
|
|
167
|
+
<template v-for="crumb in trail" :key="crumb.path"
|
|
168
|
+
><span>/</span
|
|
169
|
+
><BaseButton variant="ghost" size="sm" @click="up(crumb.path)">{{
|
|
170
|
+
crumb.label
|
|
171
|
+
}}</BaseButton></template
|
|
172
|
+
>
|
|
173
|
+
</nav>
|
|
174
|
+
<BaseButton
|
|
175
|
+
variant="ghost"
|
|
176
|
+
size="sm"
|
|
177
|
+
class="mint-picker__scope"
|
|
178
|
+
:aria-pressed="recursive"
|
|
179
|
+
@click="recursive = !recursive"
|
|
180
|
+
>{{ recursive ? 'Subfolders' : 'This folder' }}</BaseButton
|
|
181
|
+
>
|
|
182
|
+
<IconButton size="sm" label="Refresh location" :disabled="loading" @click="refresh()"
|
|
183
|
+
><svg
|
|
184
|
+
viewBox="0 0 24 24"
|
|
185
|
+
fill="none"
|
|
186
|
+
stroke="currentColor"
|
|
187
|
+
stroke-width="1.8"
|
|
188
|
+
aria-hidden="true"
|
|
189
|
+
>
|
|
190
|
+
<path d="M20 7v5h-5M4 17v-5h5M6 8a7 7 0 0 1 12-2l2 3M4 15l2 3a7 7 0 0 0 12-2" /></svg
|
|
191
|
+
></IconButton>
|
|
192
|
+
</div>
|
|
193
|
+
<div v-if="!allowedSources.length" class="mint-picker__empty">
|
|
194
|
+
No data sources are enabled.
|
|
195
|
+
</div>
|
|
196
|
+
<template v-else>
|
|
197
|
+
<div v-if="source === 'localFile'" class="mint-picker__local-upload">
|
|
198
|
+
<FileUploader
|
|
199
|
+
size="lg"
|
|
200
|
+
:mode="selectionMode === 'folder' ? 'folder' : selectionMode === 'single-file' ? 'file' : 'file+folder'"
|
|
201
|
+
:multiple="selectionMode !== 'single-file'"
|
|
202
|
+
:show-files="false"
|
|
203
|
+
@upload="(...args) => { uploadError = ''; chooseLocal(...args) }"
|
|
204
|
+
@error="uploadError = $event"
|
|
205
|
+
/>
|
|
206
|
+
<p v-if="uploadError || error || resolutionError" role="alert">
|
|
207
|
+
{{ uploadError || error || resolutionError }}
|
|
208
|
+
</p>
|
|
209
|
+
</div>
|
|
210
|
+
<div v-else class="mint-picker__workspace">
|
|
211
|
+
<FilePickerTreeInternal
|
|
212
|
+
:empty-title="
|
|
213
|
+
path
|
|
214
|
+
? 'No readable files'
|
|
215
|
+
: 'No server locations configured'
|
|
216
|
+
"
|
|
217
|
+
:rows="rows"
|
|
218
|
+
:loading="loading"
|
|
219
|
+
:error="error"
|
|
220
|
+
:query="query"
|
|
221
|
+
:mode="selectionMode"
|
|
222
|
+
:selected="selection.selected"
|
|
223
|
+
:reason="(node) => pickerReason(selection.exclusion(node), node)"
|
|
224
|
+
:progress="selection.folderProgress"
|
|
225
|
+
:focus-path="selection.focusPath.value"
|
|
226
|
+
@select="selectNode"
|
|
227
|
+
@enter="enter"
|
|
228
|
+
@expand="nav.expand"
|
|
229
|
+
@up="up"
|
|
230
|
+
@retry="refresh"
|
|
231
|
+
@parent="selectParent"
|
|
232
|
+
/>
|
|
233
|
+
<FilePickerPreviewInternal
|
|
234
|
+
:node="previewNode"
|
|
235
|
+
:total-count="focused?.kind === 'folder' ? focused.fileCount : undefined"
|
|
236
|
+
:source="source"
|
|
237
|
+
:exclusions="chosen.excluded"
|
|
238
|
+
:busy="resolving"
|
|
239
|
+
:error="resolutionError"
|
|
240
|
+
:reviewing="reviewing"
|
|
241
|
+
@review="reviewing = !reviewing"
|
|
242
|
+
@retry="selection.resolve"
|
|
243
|
+
/>
|
|
244
|
+
</div>
|
|
245
|
+
</template>
|
|
246
|
+
</div>
|
|
247
|
+
<template #footer>
|
|
248
|
+
<div class="mint-picker__footer">
|
|
249
|
+
<div class="mint-picker__receipt" role="status">
|
|
250
|
+
<span v-if="folderCount" class="mint-picker__badge"
|
|
251
|
+
>{{ folderCount }} folder{{ folderCount === 1 ? '' : 's' }}</span
|
|
252
|
+
>
|
|
253
|
+
<span v-if="chosen.files.length" class="mint-picker__badge"
|
|
254
|
+
>{{ chosen.files.length }} {{ chosen.files.length === 1 ? 'file' : 'files' }}</span
|
|
255
|
+
>
|
|
256
|
+
<span v-if="roots.length">{{ resolving ? 'Checking…' : formatBytes(chosen.bytes) }}</span>
|
|
257
|
+
<span v-else>No selection</span>
|
|
258
|
+
<BaseButton
|
|
259
|
+
v-if="chosen.excluded.length && source === 'server'"
|
|
260
|
+
variant="ghost"
|
|
261
|
+
size="sm"
|
|
262
|
+
@click="reviewing = !reviewing"
|
|
263
|
+
>{{ chosen.excluded.length }} skipped</BaseButton
|
|
264
|
+
>
|
|
265
|
+
<span v-else-if="chosen.excluded.length">{{ chosen.excluded.length }} skipped</span>
|
|
266
|
+
<BaseButton
|
|
267
|
+
v-if="capability?.isBlank"
|
|
268
|
+
size="sm"
|
|
269
|
+
variant="ghost"
|
|
270
|
+
:aria-pressed="skipBlanks"
|
|
271
|
+
@click="skipBlanks = !skipBlanks"
|
|
272
|
+
>Skip blanks {{ skipBlanks ? 'on' : 'off' }}</BaseButton
|
|
273
|
+
>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="mint-picker__actions">
|
|
276
|
+
<BaseButton variant="ghost" size="sm" @click="cancel">Cancel</BaseButton
|
|
277
|
+
><BaseButton size="sm" :disabled="!canConfirm" @click="confirm">{{
|
|
278
|
+
confirmLabel
|
|
279
|
+
}}</BaseButton>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</template>
|
|
283
|
+
</FileBrowserDialogInternal>
|
|
284
|
+
</template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Drag-and-drop file upload zone with type filtering, size validation, and folder mode. */
|
|
3
|
-
import { ref, computed } from 'vue'
|
|
3
|
+
import { ref, computed, watch } from 'vue'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
accept?: string
|
|
@@ -8,7 +8,12 @@ interface Props {
|
|
|
8
8
|
maxSize?: number
|
|
9
9
|
disabled?: boolean
|
|
10
10
|
size?: 'sm' | 'md' | 'lg'
|
|
11
|
-
mode?: 'file' | 'folder'
|
|
11
|
+
mode?: 'file' | 'folder' | 'file+folder'
|
|
12
|
+
/** Hide the selection list when a parent presents the files itself. */
|
|
13
|
+
showFiles?: boolean
|
|
14
|
+
/** Restore selections owned by a parent form; omitted for internal selection state. */
|
|
15
|
+
files?: File[]
|
|
16
|
+
rootName?: string
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -16,6 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
16
21
|
disabled: false,
|
|
17
22
|
size: 'md',
|
|
18
23
|
mode: 'file',
|
|
24
|
+
showFiles: true,
|
|
19
25
|
})
|
|
20
26
|
|
|
21
27
|
const emit = defineEmits<{
|
|
@@ -29,10 +35,17 @@ const inputRef = ref<HTMLInputElement>()
|
|
|
29
35
|
const selectedFiles = ref<File[]>([])
|
|
30
36
|
const folderName = ref<string | null>(null)
|
|
31
37
|
|
|
38
|
+
watch(() => [props.files, props.rootName] as const, ([files, rootName]) => {
|
|
39
|
+
if (files === undefined) return
|
|
40
|
+
selectedFiles.value = [...files]
|
|
41
|
+
folderName.value = rootName ?? extractFolderName(files)
|
|
42
|
+
}, { immediate: true })
|
|
43
|
+
|
|
32
44
|
const isFolder = computed(() => props.mode === 'folder')
|
|
45
|
+
const isMixed = computed(() => props.mode === 'file+folder')
|
|
33
46
|
|
|
34
47
|
const acceptLabel = computed(() => {
|
|
35
|
-
if (!props.accept) return 'any file'
|
|
48
|
+
if (!props.accept) return isFolder.value ? 'a folder' : 'any file'
|
|
36
49
|
return props.accept.split(',').map(a => a.trim().replace('.', '').toUpperCase()).join(', ')
|
|
37
50
|
})
|
|
38
51
|
|
|
@@ -154,15 +167,19 @@ function extractFolderName(files: File[]): string | null {
|
|
|
154
167
|
return null
|
|
155
168
|
}
|
|
156
169
|
|
|
157
|
-
function handleDrop(event: DragEvent) {
|
|
170
|
+
async function handleDrop(event: DragEvent) {
|
|
158
171
|
event.preventDefault()
|
|
159
172
|
isDragOver.value = false
|
|
160
173
|
if (props.disabled) return
|
|
161
174
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
try {
|
|
176
|
+
if (props.mode !== 'file' && event.dataTransfer?.items?.length) {
|
|
177
|
+
await handleFolderDrop(event.dataTransfer.items)
|
|
178
|
+
} else if (event.dataTransfer?.files) {
|
|
179
|
+
handleFiles(event.dataTransfer.files)
|
|
180
|
+
}
|
|
181
|
+
} catch (cause) {
|
|
182
|
+
emit('error', cause instanceof Error ? cause.message : 'Could not read the dropped folder.')
|
|
166
183
|
}
|
|
167
184
|
}
|
|
168
185
|
|
|
@@ -171,8 +188,12 @@ async function handleFolderDrop(items: DataTransferItemList) {
|
|
|
171
188
|
const relativePaths: string[] = []
|
|
172
189
|
let folderNameFromDrop: string | undefined
|
|
173
190
|
|
|
174
|
-
|
|
191
|
+
// Capture live drag data before awaiting: the browser protects it after the drop handler.
|
|
192
|
+
const dropped = Array.from(items, (item) => {
|
|
175
193
|
const entry = item.webkitGetAsEntry?.()
|
|
194
|
+
return { entry, file: entry ? null : item.getAsFile?.() }
|
|
195
|
+
})
|
|
196
|
+
for (const { entry, file: droppedFile } of dropped) {
|
|
176
197
|
if (entry?.isDirectory) {
|
|
177
198
|
folderNameFromDrop = entry.name
|
|
178
199
|
await readDirectory(
|
|
@@ -181,8 +202,10 @@ async function handleFolderDrop(items: DataTransferItemList) {
|
|
|
181
202
|
relativePaths,
|
|
182
203
|
entry.name,
|
|
183
204
|
)
|
|
184
|
-
} else
|
|
185
|
-
const file =
|
|
205
|
+
} else {
|
|
206
|
+
const file = entry?.isFile
|
|
207
|
+
? await getFile(entry as FileSystemFileEntry)
|
|
208
|
+
: droppedFile
|
|
186
209
|
if (file) {
|
|
187
210
|
files.push(file)
|
|
188
211
|
relativePaths.push(file.name)
|
|
@@ -291,7 +314,11 @@ function formatFileSize(bytes: number): string {
|
|
|
291
314
|
<div
|
|
292
315
|
role="button"
|
|
293
316
|
tabindex="0"
|
|
294
|
-
:aria-label="disabled
|
|
317
|
+
:aria-label="disabled
|
|
318
|
+
? 'File upload disabled'
|
|
319
|
+
: isMixed
|
|
320
|
+
? 'Drop files or a folder, or click to choose files'
|
|
321
|
+
: isFolder ? 'Click or drag a folder to upload' : 'Click or drag files to upload'"
|
|
295
322
|
:aria-disabled="disabled"
|
|
296
323
|
:class="[
|
|
297
324
|
'mint-file-uploader__dropzone',
|
|
@@ -345,7 +372,7 @@ function formatFileSize(bytes: number): string {
|
|
|
345
372
|
</svg>
|
|
346
373
|
|
|
347
374
|
<p class="mint-file-uploader__text">
|
|
348
|
-
Drop {
|
|
375
|
+
{{ isMixed ? 'Drop files or a folder, or click to choose files' : `Drop ${acceptLabel} or click` }}
|
|
349
376
|
</p>
|
|
350
377
|
</div>
|
|
351
378
|
</div>
|
|
@@ -356,7 +383,7 @@ function formatFileSize(bytes: number): string {
|
|
|
356
383
|
</div>
|
|
357
384
|
|
|
358
385
|
<!-- Folder summary display -->
|
|
359
|
-
<div v-if="isFolder && folderName && selectedFiles.length > 0" class="mint-file-uploader__folder-summary">
|
|
386
|
+
<div v-if="showFiles && isFolder && folderName && selectedFiles.length > 0" class="mint-file-uploader__folder-summary">
|
|
360
387
|
<div class="mint-file-uploader__folder-info">
|
|
361
388
|
<div class="mint-file-uploader__folder-icon-wrapper">
|
|
362
389
|
<svg class="mint-file-uploader__folder-icon" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
|
|
@@ -382,7 +409,7 @@ function formatFileSize(bytes: number): string {
|
|
|
382
409
|
|
|
383
410
|
<!-- File list display -->
|
|
384
411
|
<TransitionGroup
|
|
385
|
-
v-if="!isFolder && selectedFiles.length > 0"
|
|
412
|
+
v-if="showFiles && !isFolder && selectedFiles.length > 0"
|
|
386
413
|
tag="ul"
|
|
387
414
|
name="mint-file-uploader-item"
|
|
388
415
|
class="mint-file-uploader__list"
|
|
@@ -10,9 +10,13 @@ interface Props {
|
|
|
10
10
|
showOptional?: boolean
|
|
11
11
|
htmlFor?: string
|
|
12
12
|
fieldId?: string
|
|
13
|
+
/** `row` puts the label left and the control in a fixed 7.5rem column. Use for numbers, toggles, short enums. */
|
|
14
|
+
layout?: 'stacked' | 'row'
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
const props = defineProps<Props>()
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
layout: 'stacked',
|
|
19
|
+
})
|
|
16
20
|
|
|
17
21
|
const errorId = computed(() =>
|
|
18
22
|
props.error && props.fieldId ? `${props.fieldId}-error` : undefined
|
|
@@ -26,7 +30,7 @@ const describedBy = computed(() => errorId.value ?? hintId.value)
|
|
|
26
30
|
</script>
|
|
27
31
|
|
|
28
32
|
<template>
|
|
29
|
-
<div class="mint-form-field">
|
|
33
|
+
<div :class="['mint-form-field', layout === 'row' ? 'mint-form-field--row' : '']">
|
|
30
34
|
<div v-if="label" class="mint-form-field__label-row">
|
|
31
35
|
<label
|
|
32
36
|
:for="htmlFor"
|
|
@@ -43,7 +47,10 @@ const describedBy = computed(() => errorId.value ?? hintId.value)
|
|
|
43
47
|
</span>
|
|
44
48
|
</div>
|
|
45
49
|
|
|
46
|
-
<
|
|
50
|
+
<div v-if="layout === 'row'" class="mint-form-field__control">
|
|
51
|
+
<slot :described-by="describedBy" />
|
|
52
|
+
</div>
|
|
53
|
+
<slot v-else :described-by="describedBy" />
|
|
47
54
|
|
|
48
55
|
<p v-if="error" :id="errorId" class="mint-form-field__error" role="alert">
|
|
49
56
|
{{ error }}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, ref } from 'vue'
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
3
|
|
|
4
4
|
import { createGeneratedAnalysisTransport } from '../composables/useGeneratedAnalysis'
|
|
5
5
|
import {
|
|
@@ -32,6 +32,13 @@ const emit = defineEmits<{
|
|
|
32
32
|
}>()
|
|
33
33
|
|
|
34
34
|
const mode = ref<'file' | 'folder'>('file')
|
|
35
|
+
const selections = computed(() => Array.isArray(props.modelValue)
|
|
36
|
+
? props.modelValue
|
|
37
|
+
: props.modelValue ? [props.modelValue] : [])
|
|
38
|
+
const selectedFiles = computed(() => selections.value.flatMap(selection => selection.files))
|
|
39
|
+
watch(selections, (current) => {
|
|
40
|
+
if (current.length) mode.value = current.every(selection => selection.rootName) ? 'folder' : 'file'
|
|
41
|
+
}, { immediate: true })
|
|
35
42
|
const artifactOpen = ref(false)
|
|
36
43
|
const artifactLoading = ref(false)
|
|
37
44
|
const artifactError = ref('')
|
|
@@ -73,12 +80,7 @@ function selectFiles(
|
|
|
73
80
|
|
|
74
81
|
function removeFiles(files: File[]): void {
|
|
75
82
|
const removed = new Set(files)
|
|
76
|
-
const
|
|
77
|
-
? props.modelValue
|
|
78
|
-
: props.modelValue
|
|
79
|
-
? [props.modelValue]
|
|
80
|
-
: []
|
|
81
|
-
const remaining = current.flatMap((selection) => {
|
|
83
|
+
const remaining = selections.value.flatMap((selection) => {
|
|
82
84
|
const indexes = selection.files
|
|
83
85
|
.map((file, index) => removed.has(file) ? -1 : index)
|
|
84
86
|
.filter(index => index >= 0)
|
|
@@ -186,6 +188,8 @@ function artifactFileReference(detail: AnalysisArtifactDetail): ArtifactFileRefe
|
|
|
186
188
|
<FileUploader
|
|
187
189
|
:key="mode"
|
|
188
190
|
:mode="mode"
|
|
191
|
+
:files="selectedFiles"
|
|
192
|
+
:root-name="selections[0]?.rootName"
|
|
189
193
|
:multiple="multiple && mode === 'file'"
|
|
190
194
|
:disabled="disabled"
|
|
191
195
|
@upload="selectFiles"
|
|
@@ -1,87 +1,52 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
3
2
|
import type * as PlotlyType from 'plotly.js'
|
|
3
|
+
import { computed, toRaw } from 'vue'
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import PlotlyChart from './PlotlyChart.vue'
|
|
6
6
|
|
|
7
7
|
const props = defineProps<{
|
|
8
8
|
spec: Record<string, unknown>
|
|
9
9
|
fallback?: { media_type: string; data: string }
|
|
10
10
|
}>()
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
async function loadPlotly(): Promise<typeof PlotlyType> {
|
|
17
|
-
const module = await import('plotly.js-dist-min')
|
|
18
|
-
return ('default' in module ? module.default : module) as unknown as typeof PlotlyType
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function renderChart(): Promise<void> {
|
|
22
|
-
if (!chartElement.value) return
|
|
23
|
-
failed.value = false
|
|
24
|
-
await nextTick()
|
|
25
|
-
try {
|
|
26
|
-
const renderer = plotly ?? await loadPlotly()
|
|
27
|
-
plotly = renderer
|
|
28
|
-
const data = Array.isArray(props.spec.data) ? props.spec.data : []
|
|
29
|
-
const layout = props.spec.layout && typeof props.spec.layout === 'object'
|
|
30
|
-
? props.spec.layout
|
|
31
|
-
: {}
|
|
32
|
-
await renderer.react(
|
|
33
|
-
chartElement.value,
|
|
34
|
-
data as PlotlyType.Data[],
|
|
35
|
-
layout as Partial<PlotlyType.Layout>,
|
|
36
|
-
{ responsive: true, displaylogo: false },
|
|
37
|
-
)
|
|
38
|
-
failed.value = false
|
|
39
|
-
} catch {
|
|
40
|
-
failed.value = true
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
onMounted(renderChart)
|
|
45
|
-
watch(() => props.spec, async () => {
|
|
46
|
-
await nextTick()
|
|
47
|
-
await renderChart()
|
|
48
|
-
})
|
|
49
|
-
onBeforeUnmount(() => {
|
|
50
|
-
if (plotly && chartElement.value) plotly.purge(chartElement.value)
|
|
12
|
+
const data = computed<PlotlyType.Data[]>(() => {
|
|
13
|
+
const traces = toRaw(props.spec).data
|
|
14
|
+
return Array.isArray(traces) ? [...toRaw(traces)] as PlotlyType.Data[] : []
|
|
51
15
|
})
|
|
16
|
+
const layout = computed<Partial<PlotlyType.Layout>>(() => (
|
|
17
|
+
props.spec.layout && typeof props.spec.layout === 'object'
|
|
18
|
+
? props.spec.layout as Partial<PlotlyType.Layout>
|
|
19
|
+
: {}
|
|
20
|
+
))
|
|
21
|
+
const config = computed<Partial<PlotlyType.Config>>(() => (
|
|
22
|
+
props.spec.config && typeof props.spec.config === 'object'
|
|
23
|
+
? props.spec.config as Partial<PlotlyType.Config>
|
|
24
|
+
: {}
|
|
25
|
+
))
|
|
52
26
|
</script>
|
|
53
27
|
|
|
54
28
|
<template>
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<div
|
|
70
|
-
ref="chartElement"
|
|
71
|
-
v-show="!failed"
|
|
72
|
-
class="mint-generated-result__plotly"
|
|
73
|
-
aria-label="Interactive Plotly chart"
|
|
74
|
-
/>
|
|
75
|
-
</ChartContainer>
|
|
29
|
+
<PlotlyChart
|
|
30
|
+
title="Interactive chart"
|
|
31
|
+
:data="data"
|
|
32
|
+
:layout="layout"
|
|
33
|
+
:config="config"
|
|
34
|
+
>
|
|
35
|
+
<template v-if="fallback" #error>
|
|
36
|
+
<img
|
|
37
|
+
class="mint-generated-result__image"
|
|
38
|
+
:src="`data:${fallback.media_type};base64,${fallback.data}`"
|
|
39
|
+
alt="Static Plotly chart fallback"
|
|
40
|
+
>
|
|
41
|
+
</template>
|
|
42
|
+
</PlotlyChart>
|
|
76
43
|
</template>
|
|
77
44
|
|
|
78
45
|
<style scoped>
|
|
79
|
-
.mint-generated-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
background: var(--mint-error-bg);
|
|
85
|
-
color: var(--mint-error);
|
|
46
|
+
.mint-generated-result__image {
|
|
47
|
+
display: block;
|
|
48
|
+
max-width: 100%;
|
|
49
|
+
height: auto;
|
|
50
|
+
margin: 0 auto;
|
|
86
51
|
}
|
|
87
52
|
</style>
|