@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* ToastNotification Component Styles */
|
|
1
|
+
/* ToastNotification Component Styles — neutral surface, accent only on icon + TTL bar */
|
|
2
2
|
|
|
3
3
|
.mint-toast__container {
|
|
4
4
|
position: fixed;
|
|
@@ -14,54 +14,153 @@
|
|
|
14
14
|
.mint-toast__item {
|
|
15
15
|
position: relative;
|
|
16
16
|
display: flex;
|
|
17
|
-
align-items:
|
|
18
|
-
gap: 0.
|
|
19
|
-
|
|
17
|
+
align-items: flex-start;
|
|
18
|
+
gap: 0.625rem;
|
|
19
|
+
width: 22rem;
|
|
20
|
+
max-width: calc(100vw - 2rem);
|
|
21
|
+
padding: 0.625rem 1.75rem 0.6875rem 0.75rem;
|
|
20
22
|
border-radius: var(--radius-md);
|
|
21
23
|
background-color: var(--bg-card);
|
|
22
24
|
border: 1px solid var(--border-color);
|
|
23
|
-
|
|
24
|
-
box-shadow: var(--shadow-md);
|
|
25
|
+
box-shadow: var(--card-shadow);
|
|
25
26
|
pointer-events: auto;
|
|
26
27
|
cursor: pointer;
|
|
27
|
-
max-width: 24rem;
|
|
28
28
|
overflow: hidden;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/* Dark mode: near-black bg
|
|
31
|
+
/* Dark mode: near-black bg so the toast always contrasts with the page */
|
|
32
32
|
html.dark .mint-toast__item {
|
|
33
|
-
background-color:
|
|
34
|
-
|
|
33
|
+
background-color: var(--bg-primary);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Severity reads from the icon and the TTL bar only */
|
|
37
|
+
.mint-toast__item--success { --mint-toast-tone: var(--mint-success); }
|
|
38
|
+
.mint-toast__item--error { --mint-toast-tone: var(--mint-error); }
|
|
39
|
+
.mint-toast__item--warning { --mint-toast-tone: var(--mint-warning); }
|
|
40
|
+
.mint-toast__item--info { --mint-toast-tone: var(--mint-info); }
|
|
41
|
+
|
|
42
|
+
/* Collapsed stack: one offset sheet drawn with box-shadow, not a second node. */
|
|
43
|
+
.mint-toast__item--stacked {
|
|
44
|
+
box-shadow:
|
|
45
|
+
0 3px 0 -1px var(--bg-card),
|
|
46
|
+
0 3px 0 0 var(--border-color),
|
|
47
|
+
var(--card-shadow);
|
|
35
48
|
}
|
|
36
49
|
|
|
37
50
|
.mint-toast__icon {
|
|
38
51
|
width: 1rem;
|
|
39
52
|
height: 1rem;
|
|
40
53
|
flex-shrink: 0;
|
|
54
|
+
margin-top: 0.0625rem;
|
|
55
|
+
color: var(--mint-toast-tone);
|
|
41
56
|
}
|
|
42
57
|
|
|
43
|
-
|
|
44
|
-
html.dark .mint-toast__item--success .mint-toast__icon { color: #34D399 !important; }
|
|
45
|
-
html.dark .mint-toast__item--error .mint-toast__icon { color: #F87171 !important; }
|
|
46
|
-
html.dark .mint-toast__item--warning .mint-toast__icon { color: #FBBF24 !important; }
|
|
47
|
-
html.dark .mint-toast__item--info .mint-toast__icon { color: #60A5FA !important; }
|
|
48
|
-
|
|
49
|
-
.mint-toast__message {
|
|
50
|
-
font-size: 0.875rem;
|
|
51
|
-
color: var(--text-primary);
|
|
58
|
+
.mint-toast__body {
|
|
52
59
|
flex: 1;
|
|
53
60
|
min-width: 0;
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
.mint-toast__title {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
font-size: 0.8125rem;
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
line-height: 1.35;
|
|
70
|
+
color: var(--text-primary);
|
|
71
|
+
}
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
.mint-toast__count {
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
min-width: 1.125rem;
|
|
79
|
+
height: 1rem;
|
|
80
|
+
padding: 0 0.25rem;
|
|
81
|
+
border-radius: 0.1875rem;
|
|
82
|
+
background: var(--bg-tertiary);
|
|
83
|
+
color: var(--text-secondary);
|
|
84
|
+
font-family: var(--font-mono);
|
|
85
|
+
font-size: 0.625rem;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
font-variant-numeric: tabular-nums;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.mint-toast__detail {
|
|
91
|
+
margin-top: 0.125rem;
|
|
92
|
+
font-size: 0.75rem;
|
|
93
|
+
line-height: 1.45;
|
|
94
|
+
color: var(--text-secondary);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.mint-toast__actions {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 0.5rem;
|
|
101
|
+
margin-top: 0.4375rem;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.mint-toast__action {
|
|
105
|
+
padding: 0.1875rem 0.5rem;
|
|
106
|
+
border: 1px solid var(--border-color);
|
|
107
|
+
border-radius: var(--radius-sm);
|
|
108
|
+
background: var(--bg-secondary);
|
|
109
|
+
font-family: inherit;
|
|
110
|
+
font-size: 0.75rem;
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
color: var(--text-secondary-strong);
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
transition: background-color 0.12s ease, border-color 0.12s ease;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.mint-toast__action:hover {
|
|
118
|
+
background: var(--bg-hover);
|
|
119
|
+
border-color: var(--text-muted);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.mint-toast__action--primary {
|
|
123
|
+
background: var(--color-primary);
|
|
124
|
+
border-color: var(--color-primary);
|
|
125
|
+
color: white;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.mint-toast__action--primary:hover {
|
|
129
|
+
background: var(--color-primary-hover);
|
|
130
|
+
border-color: var(--color-primary-hover);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.mint-toast__action:focus-visible,
|
|
134
|
+
.mint-toast__close:focus-visible {
|
|
135
|
+
outline: none;
|
|
136
|
+
box-shadow: var(--focus-ring);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.mint-toast__close {
|
|
140
|
+
position: absolute;
|
|
141
|
+
top: 0.3125rem;
|
|
142
|
+
right: 0.3125rem;
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
justify-content: center;
|
|
146
|
+
width: 1.25rem;
|
|
147
|
+
height: 1.25rem;
|
|
148
|
+
border: 0;
|
|
149
|
+
border-radius: var(--radius-sm);
|
|
150
|
+
background: transparent;
|
|
151
|
+
color: var(--text-muted);
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
transition: color 0.12s ease, background-color 0.12s ease;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.mint-toast__close:hover {
|
|
157
|
+
color: var(--text-primary);
|
|
158
|
+
background: var(--bg-hover);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.mint-toast__close svg {
|
|
162
|
+
width: 0.75rem;
|
|
163
|
+
height: 0.75rem;
|
|
65
164
|
}
|
|
66
165
|
|
|
67
166
|
/* TTL progress bar — shrinks 100% → 0 over duration */
|
|
@@ -71,16 +170,26 @@ html.dark .mint-toast__item .mint-toast__message {
|
|
|
71
170
|
bottom: 0;
|
|
72
171
|
height: 2px;
|
|
73
172
|
width: 100%;
|
|
173
|
+
background: var(--mint-toast-tone);
|
|
74
174
|
transform-origin: left center;
|
|
75
175
|
animation: mint-toast-progress linear forwards;
|
|
76
176
|
/* animation-duration set inline via toast.duration */
|
|
77
177
|
}
|
|
78
178
|
|
|
179
|
+
.mint-toast__progress--indeterminate {
|
|
180
|
+
animation: mint-toast-indeterminate 1.4s ease-in-out infinite;
|
|
181
|
+
}
|
|
182
|
+
|
|
79
183
|
@keyframes mint-toast-progress {
|
|
80
184
|
from { transform: scaleX(1); }
|
|
81
185
|
to { transform: scaleX(0); }
|
|
82
186
|
}
|
|
83
187
|
|
|
188
|
+
@keyframes mint-toast-indeterminate {
|
|
189
|
+
0% { transform: translateX(-100%) scaleX(0.4); }
|
|
190
|
+
100% { transform: translateX(250%) scaleX(0.4); }
|
|
191
|
+
}
|
|
192
|
+
|
|
84
193
|
@media (prefers-reduced-motion: reduce) {
|
|
85
194
|
.mint-toast__progress { animation: none; opacity: 0.4; }
|
|
86
195
|
}
|
package/src/styles/index.css
CHANGED
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
@import './components/experiment-code-badge.css';
|
|
91
91
|
@import './components/experiment-selector-modal.css';
|
|
92
92
|
@import './components/artifact-selector-modal.css';
|
|
93
|
+
@import './components/file-browser-modal.css';
|
|
93
94
|
@import './components/artifact-save-dialog.css';
|
|
94
95
|
@import './components/experiment-popover.css';
|
|
95
96
|
@import './components/fit-panel.css';
|
|
@@ -100,3 +101,5 @@
|
|
|
100
101
|
@import './components/mobile-support-gate.css';
|
|
101
102
|
@import './components/plugin-icon.css';
|
|
102
103
|
@import './components/reagent-editor.css';
|
|
104
|
+
|
|
105
|
+
@import './components/file-picker.css';
|
package/src/styles/variables.css
CHANGED
|
@@ -528,23 +528,43 @@ code, pre {
|
|
|
528
528
|
/* ==========================================================================
|
|
529
529
|
Scrollbar Styling
|
|
530
530
|
========================================================================== */
|
|
531
|
+
/* Hidden until hover: the thumb is transparent by default and appears on hover
|
|
532
|
+
(or focus) of the scroll area. The gutter is always reserved so the reveal
|
|
533
|
+
never reflows content. Standard properties first; the ::-webkit-* rules are
|
|
534
|
+
the Safari fallback (Chrome ignores them once scrollbar-color is set). */
|
|
535
|
+
* {
|
|
536
|
+
scrollbar-width: thin;
|
|
537
|
+
scrollbar-color: transparent transparent;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
*:hover,
|
|
541
|
+
*:focus-within {
|
|
542
|
+
scrollbar-color: var(--scrollbar-thumb) transparent;
|
|
543
|
+
}
|
|
544
|
+
|
|
531
545
|
::-webkit-scrollbar {
|
|
532
546
|
width: 8px;
|
|
533
547
|
height: 8px;
|
|
534
548
|
}
|
|
535
549
|
|
|
536
550
|
::-webkit-scrollbar-track {
|
|
537
|
-
background:
|
|
538
|
-
border-radius: 4px;
|
|
551
|
+
background: transparent;
|
|
539
552
|
}
|
|
540
553
|
|
|
541
554
|
::-webkit-scrollbar-thumb {
|
|
542
|
-
background:
|
|
543
|
-
border
|
|
555
|
+
background: transparent;
|
|
556
|
+
border: 2px solid transparent;
|
|
557
|
+
border-radius: 9999px;
|
|
558
|
+
background-clip: content-box;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
:hover::-webkit-scrollbar-thumb,
|
|
562
|
+
:focus-within::-webkit-scrollbar-thumb {
|
|
563
|
+
background-color: var(--scrollbar-thumb);
|
|
544
564
|
}
|
|
545
565
|
|
|
546
566
|
::-webkit-scrollbar-thumb:hover {
|
|
547
|
-
background: var(--scrollbar-hover);
|
|
567
|
+
background-color: var(--scrollbar-hover);
|
|
548
568
|
}
|
|
549
569
|
|
|
550
570
|
/* ==========================================================================
|
package/src/types/components.ts
CHANGED
|
@@ -54,11 +54,34 @@ export interface ModalTab {
|
|
|
54
54
|
export type AlertType = 'success' | 'error' | 'warning' | 'info'
|
|
55
55
|
|
|
56
56
|
// Toast types
|
|
57
|
+
export interface ToastAction {
|
|
58
|
+
label: string
|
|
59
|
+
onClick: () => void
|
|
60
|
+
primary?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
export interface Toast {
|
|
58
64
|
id: number
|
|
65
|
+
/** Legacy single-line body; kept as an alias of `title`. */
|
|
59
66
|
message: string
|
|
60
67
|
type: 'success' | 'error' | 'warning' | 'info'
|
|
61
68
|
duration?: number
|
|
69
|
+
title?: string
|
|
70
|
+
detail?: string
|
|
71
|
+
/** At most two; extra entries are dropped. */
|
|
72
|
+
actions?: ToastAction[]
|
|
73
|
+
/** Render the close button (default true). */
|
|
74
|
+
dismissible?: boolean
|
|
75
|
+
/** `ttl` (default) shrinks over `duration`; `indeterminate` never auto-dismisses; `none` hides the bar. */
|
|
76
|
+
progress?: 'ttl' | 'indeterminate' | 'none'
|
|
77
|
+
/** Collapsed-stack count; renders the `×N` pill when above 1. */
|
|
78
|
+
count?: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type ToastOptions = Omit<Toast, 'id' | 'message' | 'type'> & {
|
|
82
|
+
title: string
|
|
83
|
+
type?: Toast['type']
|
|
84
|
+
message?: string
|
|
62
85
|
}
|
|
63
86
|
|
|
64
87
|
// Tab types
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for browsing read-only server mounts.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the `mint_sdk.filesystem` wire models. MINT records the path of
|
|
5
|
+
* data that already sits on a mount; it never copies it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** What a listing row is. */
|
|
9
|
+
export type FileEntryKind = 'file' | 'directory'
|
|
10
|
+
|
|
11
|
+
/** Field a listing is ordered by, after directories are grouped first. */
|
|
12
|
+
export type FileBrowserSortKey = 'name' | 'size' | 'modified'
|
|
13
|
+
|
|
14
|
+
/** One read-only directory tree exposed by the server. */
|
|
15
|
+
export interface ServerMount {
|
|
16
|
+
id: string
|
|
17
|
+
label: string
|
|
18
|
+
read_only?: boolean
|
|
19
|
+
/** False when the mount is configured but currently unreachable. */
|
|
20
|
+
available?: boolean
|
|
21
|
+
/** Child mounts, used to render the sidebar tree. */
|
|
22
|
+
children?: ServerMount[]
|
|
23
|
+
/** Item count shown beside the mount; free-form so callers can send "1.2k". */
|
|
24
|
+
count?: string | number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One step of the trail from a mount root to the current directory. */
|
|
28
|
+
export interface FilePathCrumb {
|
|
29
|
+
label: string
|
|
30
|
+
path: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A single file or directory inside a mount. */
|
|
34
|
+
export interface FileEntry {
|
|
35
|
+
name: string
|
|
36
|
+
/** Mount-relative POSIX path. */
|
|
37
|
+
path: string
|
|
38
|
+
kind: FileEntryKind
|
|
39
|
+
/** Null for directories. */
|
|
40
|
+
size_bytes?: number | null
|
|
41
|
+
modified_at?: string | null
|
|
42
|
+
extension?: string
|
|
43
|
+
/**
|
|
44
|
+
* False when the entry fails the active type rules. Non-matching entries are
|
|
45
|
+
* still listed — they dim rather than vanish.
|
|
46
|
+
*/
|
|
47
|
+
matches_type_rules?: boolean
|
|
48
|
+
/** Directories only. */
|
|
49
|
+
child_count?: number | null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The contents of one directory inside a mount. */
|
|
53
|
+
export interface FileDirectoryListing {
|
|
54
|
+
mount_id: string
|
|
55
|
+
path: string
|
|
56
|
+
/** Null at the mount root. */
|
|
57
|
+
parent?: string | null
|
|
58
|
+
breadcrumbs?: FilePathCrumb[]
|
|
59
|
+
entries?: FileEntry[]
|
|
60
|
+
/** Directory size before any search filter. */
|
|
61
|
+
total_count?: number
|
|
62
|
+
/** How many entries satisfy the type rules. */
|
|
63
|
+
match_count?: number
|
|
64
|
+
/** How many entries this response carries, after search and any cap. */
|
|
65
|
+
shown_count?: number
|
|
66
|
+
/** True when the directory held more entries than the server will return. */
|
|
67
|
+
truncated?: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One item the user has picked. */
|
|
71
|
+
export interface FileSelection {
|
|
72
|
+
mount_id: string
|
|
73
|
+
path: string
|
|
74
|
+
name: string
|
|
75
|
+
kind: FileEntryKind
|
|
76
|
+
/** For a directory pick, how many files it contains. */
|
|
77
|
+
child_count?: number | null
|
|
78
|
+
size_bytes?: number | null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Where the user is standing in the browser. */
|
|
82
|
+
export interface FileBrowserLocation {
|
|
83
|
+
mount_id: string
|
|
84
|
+
path: string
|
|
85
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Sources are supplied by the host; the SDK never infers a host filesystem path. */
|
|
2
|
+
export type PickerSource = 'server' | 'localFile'
|
|
3
|
+
export type PickerSelectionMode = 'single-file' | 'multi-file' | 'folder' | 'folder+files'
|
|
4
|
+
|
|
5
|
+
interface PickerNodeBase {
|
|
6
|
+
path: string
|
|
7
|
+
name: string
|
|
8
|
+
parentPath?: string | null
|
|
9
|
+
parentName?: string
|
|
10
|
+
bytes?: number | null
|
|
11
|
+
/** An explicit adapter failure makes a row inert, including its disclosure. */
|
|
12
|
+
unavailableReason?: string
|
|
13
|
+
}
|
|
14
|
+
export interface PickerFolder extends PickerNodeBase {
|
|
15
|
+
kind: 'folder'
|
|
16
|
+
fileCount?: number | null
|
|
17
|
+
formats?: Record<string, number>
|
|
18
|
+
hasChildren?: boolean
|
|
19
|
+
}
|
|
20
|
+
export interface PickerFile extends PickerNodeBase {
|
|
21
|
+
kind: 'file'
|
|
22
|
+
format: string
|
|
23
|
+
/** Present only for browser-selected files. The picker does not read its bytes. */
|
|
24
|
+
file?: File
|
|
25
|
+
}
|
|
26
|
+
export type PickerNode = PickerFolder | PickerFile
|
|
27
|
+
export interface PickerRequest {
|
|
28
|
+
signal?: AbortSignal
|
|
29
|
+
source?: PickerSource
|
|
30
|
+
/**
|
|
31
|
+
* Set only when the user pressed Refresh. Adapters that cache listings (or
|
|
32
|
+
* ask a server to bypass its own cache) should re-read the location then and
|
|
33
|
+
* serve cached data otherwise; the picker keeps folder contents while it is
|
|
34
|
+
* open and re-reads the current location on each open.
|
|
35
|
+
*/
|
|
36
|
+
refresh?: true
|
|
37
|
+
}
|
|
38
|
+
export interface PickerAdapter {
|
|
39
|
+
listRoot(request?: PickerRequest): Promise<PickerNode[]>
|
|
40
|
+
listChildren(path: string, request?: PickerRequest): Promise<PickerNode[]>
|
|
41
|
+
search(
|
|
42
|
+
query: string,
|
|
43
|
+
scope: { path: string; recursive: boolean },
|
|
44
|
+
request?: PickerRequest,
|
|
45
|
+
): Promise<PickerNode[]>
|
|
46
|
+
}
|
|
47
|
+
export interface PickerCapability {
|
|
48
|
+
/** Case-insensitive suffixes, including compound extensions such as mzML.gz. */
|
|
49
|
+
formats: string[]
|
|
50
|
+
maxBytes?: number
|
|
51
|
+
/** Optional host-owned blank classification; no filename heuristics in the SDK. */
|
|
52
|
+
isBlank?: (node: PickerNode) => boolean
|
|
53
|
+
}
|
|
54
|
+
export type PickerCapabilities = Partial<Record<PickerSource, PickerCapability>>
|
|
55
|
+
export type PickerExclusionReason =
|
|
56
|
+
'deselected' | 'unsupported' | 'tooLarge' | 'blank' | 'unreadable' | 'forbidden'
|
|
57
|
+
export interface PickerExcluded {
|
|
58
|
+
/** Opaque identity prevents collisions between equal filenames in different folders. */
|
|
59
|
+
path: string
|
|
60
|
+
name: string
|
|
61
|
+
reason: PickerExclusionReason
|
|
62
|
+
}
|
|
63
|
+
export type PickerSelection =
|
|
64
|
+
| { source: 'server'; folders: string[]; files: PickerFile[]; excluded: PickerExcluded[] }
|
|
65
|
+
| { source: 'localFile'; files: File[]; relativePaths?: string[] }
|
|
66
|
+
export interface PickerTreeRow {
|
|
67
|
+
node: PickerNode
|
|
68
|
+
depth: number
|
|
69
|
+
expanded: boolean
|
|
70
|
+
loading: boolean
|
|
71
|
+
error?: string
|
|
72
|
+
}
|
|
@@ -93,6 +93,8 @@ export interface FormFieldSchema extends AccessControlled {
|
|
|
93
93
|
colSpan?: number
|
|
94
94
|
/** Extra props passed directly to the underlying component. */
|
|
95
95
|
props?: Record<string, unknown>
|
|
96
|
+
/** Element type for `tags` fields; `number` parses each entered tag back to a number. */
|
|
97
|
+
itemType?: 'string' | 'number'
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
export type FormOptionInput = SelectOption<unknown> | OptionPrimitive
|
package/src/types/index.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type {
|
|
|
11
11
|
ModalTab,
|
|
12
12
|
AlertType,
|
|
13
13
|
Toast,
|
|
14
|
+
ToastAction,
|
|
15
|
+
ToastOptions,
|
|
14
16
|
TabItem,
|
|
15
17
|
TabItemInput,
|
|
16
18
|
OptionPrimitive,
|
|
@@ -208,6 +210,18 @@ export type {
|
|
|
208
210
|
} from './analysisArtifactTypes'
|
|
209
211
|
export { ANALYSIS_FILE_ARTIFACT_SCHEMA } from './analysisArtifactTypes'
|
|
210
212
|
|
|
213
|
+
// File browser types
|
|
214
|
+
export type {
|
|
215
|
+
FileBrowserLocation,
|
|
216
|
+
FileBrowserSortKey,
|
|
217
|
+
FileDirectoryListing,
|
|
218
|
+
FileEntry,
|
|
219
|
+
FileEntryKind,
|
|
220
|
+
FilePathCrumb,
|
|
221
|
+
FileSelection,
|
|
222
|
+
ServerMount,
|
|
223
|
+
} from './fileBrowserTypes'
|
|
224
|
+
|
|
211
225
|
export type {
|
|
212
226
|
JobListPayload,
|
|
213
227
|
JobProgress,
|
|
@@ -297,3 +311,5 @@ export type {
|
|
|
297
311
|
TableDensity,
|
|
298
312
|
UserSummary,
|
|
299
313
|
} from './platform'
|
|
314
|
+
|
|
315
|
+
export type * from './filePicker'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ExperimentPopover-oL0fg_A0.js","names":["$slots"],"sources":["../src/components/ConfirmDialog.vue","../src/components/ConfirmDialog.vue","../src/components/ExperimentPopover.vue","../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport { computed } from 'vue'\nimport type { ButtonVariant } from '../types'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n modelValue: boolean\n title?: string\n subtitle?: string\n message?: string\n variant?: 'danger' | 'warning' | 'info'\n confirmLabel?: string\n cancelLabel?: string\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Confirm',\n variant: 'danger',\n confirmLabel: 'Confirm',\n cancelLabel: 'Cancel',\n loading: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n confirm: []\n cancel: []\n}>()\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n emit('cancel')\n}\n\nfunction handleConfirm() {\n emit('confirm')\n}\n\nconst confirmVariant = computed<ButtonVariant>(() => {\n if (props.variant === 'info') return 'primary'\n if (props.variant === 'warning') return 'cta'\n return 'danger'\n})\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n size=\"sm\"\n :closable=\"!loading\"\n :close-on-overlay=\"!loading\"\n :close-on-escape=\"!loading\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-confirm\">\n <!-- Icon is now opt-in via slot. Refresh design keeps the body focused on\n the message; variant intent is carried by the confirm button's color. -->\n <div\n v-if=\"$slots.icon\"\n :class=\"['mint-confirm__icon', `mint-confirm__icon--${variant}`]\"\n >\n <slot name=\"icon\" />\n </div>\n <p v-if=\"message\" class=\"mint-confirm__message\">{{ message }}</p>\n <slot />\n </div>\n\n <template #footer>\n <div class=\"mint-confirm__footer\">\n <BaseButton\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </BaseButton>\n <BaseButton\n type=\"button\"\n :variant=\"confirmVariant\"\n size=\"md\"\n :loading=\"loading\"\n @click=\"handleConfirm\"\n >\n {{ confirmLabel }}\n </BaseButton>\n </div>\n </template>\n </BaseModal>\n</template>\n","<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport { computed } from 'vue'\nimport type { ButtonVariant } from '../types'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n modelValue: boolean\n title?: string\n subtitle?: string\n message?: string\n variant?: 'danger' | 'warning' | 'info'\n confirmLabel?: string\n cancelLabel?: string\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Confirm',\n variant: 'danger',\n confirmLabel: 'Confirm',\n cancelLabel: 'Cancel',\n loading: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n confirm: []\n cancel: []\n}>()\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n emit('cancel')\n}\n\nfunction handleConfirm() {\n emit('confirm')\n}\n\nconst confirmVariant = computed<ButtonVariant>(() => {\n if (props.variant === 'info') return 'primary'\n if (props.variant === 'warning') return 'cta'\n return 'danger'\n})\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n size=\"sm\"\n :closable=\"!loading\"\n :close-on-overlay=\"!loading\"\n :close-on-escape=\"!loading\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-confirm\">\n <!-- Icon is now opt-in via slot. Refresh design keeps the body focused on\n the message; variant intent is carried by the confirm button's color. -->\n <div\n v-if=\"$slots.icon\"\n :class=\"['mint-confirm__icon', `mint-confirm__icon--${variant}`]\"\n >\n <slot name=\"icon\" />\n </div>\n <p v-if=\"message\" class=\"mint-confirm__message\">{{ message }}</p>\n <slot />\n </div>\n\n <template #footer>\n <div class=\"mint-confirm__footer\">\n <BaseButton\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </BaseButton>\n <BaseButton\n type=\"button\"\n :variant=\"confirmVariant\"\n size=\"md\"\n :loading=\"loading\"\n @click=\"handleConfirm\"\n >\n {{ confirmLabel }}\n </BaseButton>\n </div>\n </template>\n </BaseModal>\n</template>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, computed, watch, onUnmounted } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { formatExperimentStatus } from '../composables/experiment-utils'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** An experiment is bound but the platform has not returned its record yet. */\n isResolving?: boolean\n /** Message explaining why the bound experiment could not be resolved. */\n error?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\n// An experiment is bound but has no name to show — either still resolving or\n// resolution failed. The id is never substituted: a name may only come from the\n// platform, so these states say what is happening instead of inventing a label.\nconst isUnresolved = computed(() => props.isResolving || !!props.error)\nconst hasExperiment = computed(() => !!props.experimentName || !!props.experimentCode)\n\nconst panelSubtitle = computed(() => {\n if (props.isResolving) return 'Loading experiment…'\n if (props.error) return 'Could not load this experiment'\n return hasExperiment.value ? 'Linked experiment context' : 'Link to an MINT experiment'\n})\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return props.experimentName || props.experimentCode || 'No experiment'\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, triggerRef, close, toggle } = useDropdownState({\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mint-experiment-popover\">\n <!-- Split trigger: experiment pill + inline save -->\n <div\n :class=\"[\n 'mint-experiment-popover__split',\n { 'mint-experiment-popover__split--with-save': showSave && experimentName },\n ]\"\n >\n <!-- Left: experiment trigger (opens popover). Prefer the human-readable\n name; the formal code, when present, stays in the detail panel. -->\n <button\n ref=\"triggerRef\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !isUnresolved && !hasExperiment },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"isUnresolved ? triggerLabel : experimentName || experimentCode || undefined\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isOpen\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, \"No experiment\" would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <span v-else-if=\"error\" class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n <!-- Name preferred, code as fallback, \"No experiment\" as empty state -->\n <span v-else-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Right: inline save button (direct action) -->\n <button\n v-if=\"showSave && experimentName\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save-trigger',\n { 'mint-experiment-popover__save-trigger--loading': saveLoading },\n { 'mint-experiment-popover__save-trigger--success': showSuccess },\n { 'mint-experiment-popover__save-trigger--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n :title=\"saveDisabled && saveDisabledMessage ? saveDisabledMessage : showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment'\"\n aria-label=\"Save to experiment\"\n @click.stop=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mint-experiment-popover__spinner--inline\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n </button>\n </div>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mint-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mint-experiment-popover__header\">\n <div class=\"mint-experiment-popover__title\">Experiment</div>\n <div class=\"mint-experiment-popover__subtitle\">\n {{ panelSubtitle }}\n </div>\n </div>\n\n <!-- Bound, still resolving — placeholders stand in for the record the\n platform has not returned yet. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\" aria-busy=\"true\">\n <div class=\"mint-experiment-popover__card-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--code\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--name\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--status\" />\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- Bound, resolution failed — Detach stays reachable so the user is not\n stuck on an experiment the platform cannot describe. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card mint-experiment-popover__card--error\" role=\"alert\">\n <div class=\"mint-experiment-popover__card-icon mint-experiment-popover__card-icon--error\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <div class=\"mint-experiment-popover__card-name\">Experiment unavailable</div>\n <div class=\"mint-experiment-popover__card-message\">{{ error }}</div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-else-if=\"!experimentName\" class=\"mint-experiment-popover__body\">\n <button type=\"button\" class=\"mint-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\">\n <div class=\"mint-experiment-popover__card-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__card-code\">{{ experimentCode }}</span>\n <div class=\"mint-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mint-experiment-popover__card-status\">\n {{ formatExperimentStatus(experimentStatus) }}\n </div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName}?`\"\n variant=\"info\"\n confirm-label=\"Save\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, computed, watch, onUnmounted } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { formatExperimentStatus } from '../composables/experiment-utils'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n /** An experiment is bound but the platform has not returned its record yet. */\n isResolving?: boolean\n /** Message explaining why the bound experiment could not be resolved. */\n error?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n isResolving: false,\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\n// An experiment is bound but has no name to show — either still resolving or\n// resolution failed. The id is never substituted: a name may only come from the\n// platform, so these states say what is happening instead of inventing a label.\nconst isUnresolved = computed(() => props.isResolving || !!props.error)\nconst hasExperiment = computed(() => !!props.experimentName || !!props.experimentCode)\n\nconst panelSubtitle = computed(() => {\n if (props.isResolving) return 'Loading experiment…'\n if (props.error) return 'Could not load this experiment'\n return hasExperiment.value ? 'Linked experiment context' : 'Link to an MINT experiment'\n})\n\nconst triggerLabel = computed(() => {\n if (props.isResolving) return 'Loading experiment'\n if (props.error) return 'Experiment unavailable'\n return props.experimentName || props.experimentCode || 'No experiment'\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, triggerRef, close, toggle } = useDropdownState({\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mint-experiment-popover\">\n <!-- Split trigger: experiment pill + inline save -->\n <div\n :class=\"[\n 'mint-experiment-popover__split',\n { 'mint-experiment-popover__split--with-save': showSave && experimentName },\n ]\"\n >\n <!-- Left: experiment trigger (opens popover). Prefer the human-readable\n name; the formal code, when present, stays in the detail panel. -->\n <button\n ref=\"triggerRef\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !isUnresolved && !hasExperiment },\n { 'mint-experiment-popover__trigger--loading': isResolving },\n { 'mint-experiment-popover__trigger--error': !isResolving && !!error },\n ]\"\n :title=\"isUnresolved ? triggerLabel : experimentName || experimentCode || undefined\"\n :aria-label=\"isUnresolved ? triggerLabel : undefined\"\n :aria-busy=\"isResolving || undefined\"\n :aria-expanded=\"isOpen\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <!-- Resolving and error come first: with an experiment bound but no name\n yet, \"No experiment\" would be a lie. -->\n <span v-if=\"isResolving\" class=\"mint-experiment-popover__trigger-skeleton\" aria-hidden=\"true\" />\n <span v-else-if=\"error\" class=\"mint-experiment-popover__trigger-text\">Experiment unavailable</span>\n <!-- Name preferred, code as fallback, \"No experiment\" as empty state -->\n <span v-else-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Right: inline save button (direct action) -->\n <button\n v-if=\"showSave && experimentName\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save-trigger',\n { 'mint-experiment-popover__save-trigger--loading': saveLoading },\n { 'mint-experiment-popover__save-trigger--success': showSuccess },\n { 'mint-experiment-popover__save-trigger--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n :title=\"saveDisabled && saveDisabledMessage ? saveDisabledMessage : showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment'\"\n aria-label=\"Save to experiment\"\n @click.stop=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mint-experiment-popover__spinner--inline\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mint-experiment-popover__save-trigger-icon\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n </button>\n </div>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mint-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mint-experiment-popover__header\">\n <div class=\"mint-experiment-popover__title\">Experiment</div>\n <div class=\"mint-experiment-popover__subtitle\">\n {{ panelSubtitle }}\n </div>\n </div>\n\n <!-- Bound, still resolving — placeholders stand in for the record the\n platform has not returned yet. -->\n <div v-if=\"isResolving\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\" aria-busy=\"true\">\n <div class=\"mint-experiment-popover__card-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--code\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--name\" />\n <span class=\"mint-experiment-popover__skeleton-line mint-experiment-popover__skeleton-line--status\" />\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- Bound, resolution failed — Detach stays reachable so the user is not\n stuck on an experiment the platform cannot describe. -->\n <div v-else-if=\"error\" class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card mint-experiment-popover__card--error\" role=\"alert\">\n <div class=\"mint-experiment-popover__card-icon mint-experiment-popover__card-icon--error\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <div class=\"mint-experiment-popover__card-name\">Experiment unavailable</div>\n <div class=\"mint-experiment-popover__card-message\">{{ error }}</div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-else-if=\"!experimentName\" class=\"mint-experiment-popover__body\">\n <button type=\"button\" class=\"mint-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\">\n <div class=\"mint-experiment-popover__card-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__card-code\">{{ experimentCode }}</span>\n <div class=\"mint-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mint-experiment-popover__card-status\">\n {{ formatExperimentStatus(experimentStatus) }}\n </div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName}?`\"\n variant=\"info\"\n confirm-label=\"Save\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n"],"mappings":"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"}
|