@morscherlab/mint-sdk 1.4.10 → 1.4.12
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/dist/{AlertBox-CIhqsWbN.js → AlertBox-CexONPhA.js} +2 -2
- package/dist/{AlertBox-CIhqsWbN.js.map → AlertBox-CexONPhA.js.map} +1 -1
- package/dist/{AppSidebar-siLhxLf0.js → AppSidebar-C5zxZifu.js} +6 -6
- package/dist/{AppSidebar-siLhxLf0.js.map → AppSidebar-C5zxZifu.js.map} +1 -1
- package/dist/{ExperimentPopover-BM92LlrQ.js → ExperimentPopover-CzV1--OY.js} +2 -2
- package/dist/{ExperimentPopover-BM92LlrQ.js.map → ExperimentPopover-CzV1--OY.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-LsB1ABmh.js → ExperimentSelectorModal-CUkKmgfr.js} +35 -6
- package/dist/ExperimentSelectorModal-CUkKmgfr.js.map +1 -0
- package/dist/{SegmentedControl-Dz41vAFM.js → SegmentedControl-C-sckoJZ.js} +2 -2
- package/dist/{SegmentedControl-Dz41vAFM.js.map → SegmentedControl-C-sckoJZ.js.map} +1 -1
- package/dist/{SettingsModal-CBFcS_Fi.js → SettingsModal-BCuw7uli.js} +6 -6
- package/dist/{SettingsModal-CBFcS_Fi.js.map → SettingsModal-BCuw7uli.js.map} +1 -1
- package/dist/composables/useExperimentSelector.d.ts +2 -0
- package/dist/{controlSchemaModel-C20f-1_T.js → controlSchemaModel-C_XqpkgE.js} +2 -2
- package/dist/{controlSchemaModel-C20f-1_T.js.map → controlSchemaModel-C_XqpkgE.js.map} +1 -1
- package/dist/{data-LzdkEZJ-.js → data-8G-U6Pb0.js} +9 -9
- package/dist/{data-LzdkEZJ-.js.map → data-8G-U6Pb0.js.map} +1 -1
- package/dist/data.js +7 -7
- package/dist/index.js +16 -16
- package/dist/{install-Bqqsp2aZ.js → install-DnBvfEEq.js} +14 -38
- package/dist/install-DnBvfEEq.js.map +1 -0
- package/dist/install.js +1 -1
- package/dist/{lab-B5sKmlWF.js → lab-CPJ7n7e5.js} +37 -166
- package/dist/lab-CPJ7n7e5.js.map +1 -0
- package/dist/lab.js +4 -4
- package/dist/{records-CWRztE5M.js → records-BAymyHAn.js} +10 -110
- package/dist/records-BAymyHAn.js.map +1 -0
- package/dist/{runtime-CC-r7-xv.js → runtime-C8OanR7O.js} +82 -45
- package/dist/runtime-C8OanR7O.js.map +1 -0
- package/dist/runtime.js +6 -6
- package/dist/{shell-rEgoYaoa.js → shell-B9jc0pVb.js} +4 -4
- package/dist/{shell-rEgoYaoa.js.map → shell-B9jc0pVb.js.map} +1 -1
- package/dist/shell.js +4 -4
- package/dist/styles.css +59 -104
- package/dist/{useExperimentSamples-Cz2v3V_q.js → useExperimentSamples-DEDAKa2E.js} +3 -3
- package/dist/{useExperimentSamples-Cz2v3V_q.js.map → useExperimentSamples-DEDAKa2E.js.map} +1 -1
- package/dist/{useListReorder-C--K06el.js → useListReorder-W1bzK_bo.js} +2 -2
- package/dist/{useListReorder-C--K06el.js.map → useListReorder-W1bzK_bo.js.map} +1 -1
- package/dist/{useRackEditor-Cba_0OfI.js → useRackEditor-BWOnob3i.js} +154 -25
- package/dist/useRackEditor-BWOnob3i.js.map +1 -0
- package/dist/{useRealtime-CNQbrS01.js → useRealtime-B0u_FvEH.js} +2 -2
- package/dist/{useRealtime-CNQbrS01.js.map → useRealtime-B0u_FvEH.js.map} +1 -1
- package/dist/{useRequest-Bfsh7gIm.js → useRequest-D9-3BS-3.js} +2 -2
- package/dist/{useRequest-Bfsh7gIm.js.map → useRequest-D9-3BS-3.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +16 -0
- package/src/components/ExperimentSelectorModal.vue +35 -0
- package/src/components/FileUploader.vue +3 -35
- package/src/components/GeneratedPluginView.vue +35 -81
- package/src/components/GeneratedResultRenderer.vue +2 -1
- package/src/components/PlotlyChart.vue +1 -1
- package/src/composables/useExperimentSelector.ts +4 -0
- package/src/generated-ui/main.ts +3 -1
- package/src/styles/components/alert-box.css +1 -1
- package/src/styles/components/app-sidebar.css +5 -5
- package/src/styles/components/button.css +3 -3
- package/src/styles/components/checkbox.css +1 -1
- package/src/styles/components/collapsible-card.css +1 -1
- package/src/styles/components/confirm-dialog.css +2 -2
- package/src/styles/components/file-uploader.css +3 -3
- package/src/styles/components/icon-button.css +3 -3
- package/src/styles/components/loading-spinner.css +1 -1
- package/src/styles/components/pill.css +1 -1
- package/src/styles/components/plugin-icon.css +1 -1
- package/src/styles/components/presence-avatars.css +4 -4
- package/src/styles/components/progress-bar.css +1 -1
- package/src/styles/components/sequence-input.css +1 -1
- package/src/styles/components/slider.css +1 -1
- package/src/styles/components/toggle.css +1 -1
- package/src/styles/variables.css +4 -0
- package/dist/ExperimentSelectorModal-LsB1ABmh.js.map +0 -1
- package/dist/install-Bqqsp2aZ.js.map +0 -1
- package/dist/lab-B5sKmlWF.js.map +0 -1
- package/dist/records-CWRztE5M.js.map +0 -1
- package/dist/runtime-CC-r7-xv.js.map +0 -1
- package/dist/useRackEditor-Cba_0OfI.js.map +0 -1
|
@@ -22,10 +22,12 @@ import {
|
|
|
22
22
|
type GeneratedAnalysisTransport,
|
|
23
23
|
} from '../composables/useJobsEndpoint'
|
|
24
24
|
import AlertBox from './AlertBox.vue'
|
|
25
|
+
import AppContainer from './AppContainer.vue'
|
|
25
26
|
import EmptyState from './EmptyState.vue'
|
|
26
27
|
import GeneratedResultRenderer from './GeneratedResultRenderer.vue'
|
|
27
28
|
import LoadingSpinner from './LoadingSpinner.vue'
|
|
28
29
|
import PluginWorkspaceView from './PluginWorkspaceView.vue'
|
|
30
|
+
import SectionCard from './SectionCard.vue'
|
|
29
31
|
import { GENERATED_JOBS_API_KEY } from './internal/generatedJobsApi'
|
|
30
32
|
import { definePluginWorkspaceView } from '../composables/componentSpecs'
|
|
31
33
|
import { providePluginIdentity } from '../composables/pluginIdentity'
|
|
@@ -48,6 +50,7 @@ const generated = useJobsEndpoint({
|
|
|
48
50
|
provide(GENERATED_JOBS_API_KEY, { baseUrl: props.baseUrl, transport: props.transport })
|
|
49
51
|
const activeDefinitionId = ref('')
|
|
50
52
|
const selectedJobId = ref('')
|
|
53
|
+
const sidebarCollapsed = ref(false)
|
|
51
54
|
const valuesByDefinition = reactive<Record<string, Record<string, unknown>>>({})
|
|
52
55
|
const definitions = computed(() => generated.manifest.value?.jobs ?? [])
|
|
53
56
|
const activeDefinition = computed(() => (
|
|
@@ -77,12 +80,22 @@ const workspace = computed(() => definePluginWorkspaceView({
|
|
|
77
80
|
subtitle: generated.manifest.value?.ui.subtitle || generated.manifest.value?.ui.description,
|
|
78
81
|
// The platform shell renders the toasts around this view.
|
|
79
82
|
toasts: false,
|
|
83
|
+
floating: true,
|
|
80
84
|
topBar: {
|
|
85
|
+
variant: 'card',
|
|
81
86
|
pills: pillNav.value,
|
|
87
|
+
themeToggle: true,
|
|
82
88
|
settings: false,
|
|
83
89
|
standaloneLabel: props.showStandaloneLabel ? 'Standalone' : false,
|
|
84
90
|
},
|
|
85
|
-
sidebar: {
|
|
91
|
+
sidebar: {
|
|
92
|
+
title: 'Parameters',
|
|
93
|
+
width: '300px',
|
|
94
|
+
collapsible: true,
|
|
95
|
+
collapsedWidth: '3rem',
|
|
96
|
+
panels: panels.value,
|
|
97
|
+
forms: forms.value,
|
|
98
|
+
},
|
|
86
99
|
jobs: generated,
|
|
87
100
|
}))
|
|
88
101
|
|
|
@@ -160,6 +173,7 @@ onMounted(() => {
|
|
|
160
173
|
<template>
|
|
161
174
|
<PluginWorkspaceView
|
|
162
175
|
v-model:view="activeDefinitionId"
|
|
176
|
+
v-model:sidebar-collapsed="sidebarCollapsed"
|
|
163
177
|
:spec="workspace"
|
|
164
178
|
:values="currentValues"
|
|
165
179
|
:form-loading="generated.isSubmitting.value"
|
|
@@ -168,7 +182,7 @@ onMounted(() => {
|
|
|
168
182
|
@form-submit="submitJob"
|
|
169
183
|
@job-select="selectJob"
|
|
170
184
|
>
|
|
171
|
-
<
|
|
185
|
+
<AppContainer direction="column" gap="var(--mint-gutter)" class="mint-generated-workspace">
|
|
172
186
|
<div v-if="generated.isLoading.value" class="mint-generated-workspace__loading">
|
|
173
187
|
<LoadingSpinner size="lg" />
|
|
174
188
|
<span>Loading analysis workspace</span>
|
|
@@ -177,18 +191,15 @@ onMounted(() => {
|
|
|
177
191
|
<template v-else>
|
|
178
192
|
<AlertBox v-if="generated.error.value" tone="error">{{ generated.error.value }}</AlertBox>
|
|
179
193
|
|
|
180
|
-
<
|
|
181
|
-
<
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
</
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
</
|
|
189
|
-
</header>
|
|
190
|
-
|
|
191
|
-
<section class="mint-generated-workspace__result">
|
|
194
|
+
<SectionCard :title="activeDefinition?.title ?? 'Result'">
|
|
195
|
+
<template v-if="activeDefinition" #actions>
|
|
196
|
+
<span class="mint-generated-workspace__resources" aria-label="Job resource requirements">
|
|
197
|
+
{{ resourceSummary(activeDefinition) }}
|
|
198
|
+
</span>
|
|
199
|
+
</template>
|
|
200
|
+
<p v-if="activeDefinition?.description" class="mint-generated-workspace__description">
|
|
201
|
+
{{ activeDefinition.description }}
|
|
202
|
+
</p>
|
|
192
203
|
<GeneratedResultRenderer
|
|
193
204
|
v-if="currentResult"
|
|
194
205
|
:result="currentResult"
|
|
@@ -199,93 +210,36 @@ onMounted(() => {
|
|
|
199
210
|
description="Set the parameters and run the analysis. Jobs continue independently if you switch tabs."
|
|
200
211
|
size="md"
|
|
201
212
|
/>
|
|
202
|
-
</
|
|
213
|
+
</SectionCard>
|
|
203
214
|
</template>
|
|
204
|
-
</
|
|
215
|
+
</AppContainer>
|
|
205
216
|
</PluginWorkspaceView>
|
|
206
217
|
</template>
|
|
207
218
|
|
|
208
219
|
<style scoped>
|
|
209
220
|
.mint-generated-workspace {
|
|
210
|
-
position: relative;
|
|
211
221
|
min-width: 0;
|
|
212
|
-
min-height:
|
|
213
|
-
|
|
214
|
-
background: var(--bg-primary);
|
|
222
|
+
min-height: 0;
|
|
223
|
+
overflow-y: auto;
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
.mint-generated-workspace__loading {
|
|
218
227
|
display: flex;
|
|
219
|
-
|
|
228
|
+
flex: 1;
|
|
220
229
|
align-items: center;
|
|
221
230
|
justify-content: center;
|
|
222
231
|
gap: 0.75rem;
|
|
223
232
|
color: var(--text-secondary);
|
|
224
233
|
}
|
|
225
234
|
|
|
226
|
-
.mint-generated-workspace__header {
|
|
227
|
-
display: flex;
|
|
228
|
-
align-items: flex-start;
|
|
229
|
-
justify-content: space-between;
|
|
230
|
-
gap: 1.5rem;
|
|
231
|
-
max-width: 76rem;
|
|
232
|
-
margin: 0 auto 1.25rem;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.mint-generated-workspace__header h1 {
|
|
236
|
-
margin: 0;
|
|
237
|
-
color: var(--text-primary);
|
|
238
|
-
font-size: clamp(1.35rem, 2vw, 1.8rem);
|
|
239
|
-
line-height: 1.2;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.mint-generated-workspace__header p:not(.mint-generated-workspace__eyebrow) {
|
|
243
|
-
margin: 0.4rem 0 0;
|
|
244
|
-
color: var(--text-secondary);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.mint-generated-workspace__eyebrow {
|
|
248
|
-
margin: 0 0 0.35rem;
|
|
249
|
-
color: var(--text-secondary);
|
|
250
|
-
font-size: 0.72rem;
|
|
251
|
-
font-weight: 700;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
235
|
.mint-generated-workspace__resources {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
justify-content: flex-end;
|
|
258
|
-
gap: 0.4rem;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
.mint-generated-workspace__resources span {
|
|
262
|
-
padding: 0.3rem 0.55rem;
|
|
263
|
-
border: 1px solid var(--border-color);
|
|
264
|
-
border-radius: 999px;
|
|
265
|
-
background: var(--bg-secondary);
|
|
266
|
-
color: var(--text-secondary);
|
|
236
|
+
color: var(--text-muted);
|
|
237
|
+
font-family: var(--font-mono);
|
|
267
238
|
font-size: 0.75rem;
|
|
268
|
-
font-weight: 600;
|
|
269
239
|
}
|
|
270
240
|
|
|
271
|
-
.mint-generated-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
margin: 0 auto;
|
|
275
|
-
padding: clamp(1rem, 2vw, 1.75rem);
|
|
276
|
-
border: 1px solid var(--border-color);
|
|
277
|
-
border-radius: 1rem;
|
|
278
|
-
background: var(--bg-secondary);
|
|
279
|
-
box-shadow: var(--shadow-sm);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
@media (max-width: 640px) {
|
|
283
|
-
.mint-generated-workspace__header {
|
|
284
|
-
flex-direction: column;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.mint-generated-workspace__resources {
|
|
288
|
-
justify-content: flex-start;
|
|
289
|
-
}
|
|
241
|
+
.mint-generated-workspace__description {
|
|
242
|
+
margin: 0 0 var(--mint-gutter);
|
|
243
|
+
color: var(--text-secondary);
|
|
290
244
|
}
|
|
291
245
|
</style>
|
|
@@ -133,6 +133,7 @@ function humanize(value: string): string {
|
|
|
133
133
|
padding: 0.75rem 1rem;
|
|
134
134
|
border-radius: var(--radius-md);
|
|
135
135
|
background: var(--bg-tertiary);
|
|
136
|
+
font-family: var(--font-mono);
|
|
136
137
|
font-size: 0.8125rem;
|
|
137
138
|
line-height: 1.55;
|
|
138
139
|
white-space: pre-wrap;
|
|
@@ -152,7 +153,7 @@ function humanize(value: string): string {
|
|
|
152
153
|
gap: 1rem;
|
|
153
154
|
padding: 1rem;
|
|
154
155
|
border: 1px solid var(--border-color);
|
|
155
|
-
border-radius:
|
|
156
|
+
border-radius: var(--radius-lg);
|
|
156
157
|
color: var(--color-primary);
|
|
157
158
|
text-decoration: none;
|
|
158
159
|
}
|
|
@@ -30,6 +30,8 @@ export interface UseExperimentSelectorOptions {
|
|
|
30
30
|
immediate?: boolean
|
|
31
31
|
/** Keep `statusCounts` in step with the non-status filters (one stats request per change). */
|
|
32
32
|
withStatusCounts?: boolean
|
|
33
|
+
/** Filter values to start from, for example the ones a caller saved at the last visit. */
|
|
34
|
+
initialFilters?: Partial<ExperimentFilters>
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export interface UseExperimentSelectorReturn {
|
|
@@ -89,6 +91,7 @@ export function useExperimentSelector(
|
|
|
89
91
|
allowedExperimentTypes: optionAllowedExperimentTypes,
|
|
90
92
|
apiBaseUrl,
|
|
91
93
|
withStatusCounts = false,
|
|
94
|
+
initialFilters,
|
|
92
95
|
} = options
|
|
93
96
|
const platformBase = apiBaseUrl ?? getPlatformApiUrl()
|
|
94
97
|
const api = useApi()
|
|
@@ -124,6 +127,7 @@ export function useExperimentSelector(
|
|
|
124
127
|
experimentType: undefined,
|
|
125
128
|
datePreset: undefined,
|
|
126
129
|
mine: undefined,
|
|
130
|
+
...initialFilters,
|
|
127
131
|
})
|
|
128
132
|
|
|
129
133
|
function normalizeExperimentTypes(values: string[] | null | undefined): string[] | null {
|
package/src/generated-ui/main.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createPinia } from 'pinia'
|
|
|
2
2
|
import { createApp, h } from 'vue'
|
|
3
3
|
|
|
4
4
|
import GeneratedPluginView from '../components/GeneratedPluginView.vue'
|
|
5
|
+
import { useSettingsStore } from '../stores/settings'
|
|
5
6
|
import '../styles/index.css'
|
|
6
7
|
|
|
7
8
|
function generatedApiBase(location: Location): string {
|
|
@@ -13,9 +14,10 @@ const app = createApp({
|
|
|
13
14
|
name: 'MintGeneratedUiApp',
|
|
14
15
|
render: () => h(GeneratedPluginView, {
|
|
15
16
|
baseUrl: generatedApiBase(window.location),
|
|
16
|
-
showStandaloneLabel: true,
|
|
17
17
|
}),
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
app.use(createPinia())
|
|
21
|
+
// Theme, palette and density apply before the first paint, as in the platform.
|
|
22
|
+
useSettingsStore().initialize()
|
|
21
23
|
app.mount('#app')
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
.mint-alert__icon--info { background-color: var(--mint-info-bg); color: var(--mint-info); }
|
|
32
32
|
.mint-alert__icon--neutral { background-color: var(--bg-tertiary); color: var(--text-secondary); }
|
|
33
33
|
.mint-alert__icon--primary { background-color: var(--color-primary-soft); color: var(--color-primary); }
|
|
34
|
-
.mint-alert__icon--cta { background-color: var(--color-primary-soft); color: var(--color-
|
|
34
|
+
.mint-alert__icon--cta { background-color: var(--color-primary-soft); color: var(--color-primary-hover); }
|
|
35
35
|
|
|
36
36
|
.mint-alert__content {
|
|
37
37
|
flex: 1;
|
|
@@ -97,8 +97,8 @@
|
|
|
97
97
|
min-width: 1.375rem;
|
|
98
98
|
padding: 0.125rem 0.375rem;
|
|
99
99
|
border-radius: var(--radius);
|
|
100
|
-
background: color-mix(in srgb, var(--color-
|
|
101
|
-
color: var(--color-
|
|
100
|
+
background: color-mix(in srgb, var(--color-primary) 14%, transparent);
|
|
101
|
+
color: var(--color-primary);
|
|
102
102
|
font-size: 0.6875rem;
|
|
103
103
|
font-weight: 700;
|
|
104
104
|
line-height: 1.1;
|
|
@@ -369,7 +369,7 @@
|
|
|
369
369
|
width: 11px;
|
|
370
370
|
height: 11px;
|
|
371
371
|
border-radius: 9999px;
|
|
372
|
-
background:
|
|
372
|
+
background: var(--control-knob);
|
|
373
373
|
box-shadow: var(--shadow-sm);
|
|
374
374
|
transition: left 180ms var(--mint-ease-out-quart);
|
|
375
375
|
}
|
|
@@ -469,7 +469,7 @@
|
|
|
469
469
|
padding: 0 0.25rem;
|
|
470
470
|
border-radius: var(--radius);
|
|
471
471
|
background: var(--mint-sidebar-tone);
|
|
472
|
-
color:
|
|
472
|
+
color: var(--text-on-accent);
|
|
473
473
|
font-size: 0.6875rem;
|
|
474
474
|
font-weight: 700;
|
|
475
475
|
line-height: 1rem;
|
|
@@ -492,7 +492,7 @@
|
|
|
492
492
|
.mint-sidebar__rail-badge--cta,
|
|
493
493
|
.mint-sidebar__tag--cta,
|
|
494
494
|
.mint-sidebar__group-action--cta {
|
|
495
|
-
--mint-sidebar-tone: var(--color-
|
|
495
|
+
--mint-sidebar-tone: var(--color-primary);
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
.mint-sidebar__rail-badge--success,
|
|
@@ -68,17 +68,17 @@
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
.mint-button--cta {
|
|
71
|
-
background-color: var(--color-
|
|
71
|
+
background-color: var(--color-primary);
|
|
72
72
|
color: var(--text-on-accent);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
.mint-button--cta:hover:not(.mint-button--disabled) {
|
|
76
|
-
background-color: var(--color-
|
|
76
|
+
background-color: var(--color-primary-hover);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
.mint-button--danger {
|
|
80
80
|
background-color: var(--mint-error);
|
|
81
|
-
color:
|
|
81
|
+
color: var(--text-on-danger);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
.mint-button--danger:hover:not(.mint-button--disabled) {
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
.mint-checkbox__native:disabled + .mint-checkbox__box.mint-checkbox__box--indeterminate {
|
|
134
134
|
background-color: var(--text-muted);
|
|
135
135
|
border-color: var(--text-muted);
|
|
136
|
-
color:
|
|
136
|
+
color: var(--control-knob);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
.mint-checkbox__box--sm {
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
.mint-confirm__icon--cta {
|
|
51
|
-
background-color: color-mix(in srgb, var(--color-
|
|
52
|
-
color: var(--color-
|
|
51
|
+
background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
52
|
+
color: var(--color-primary);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
.mint-confirm__icon--neutral {
|
|
@@ -160,9 +160,9 @@
|
|
|
160
160
|
font-size: 0.6875rem;
|
|
161
161
|
font-weight: 700;
|
|
162
162
|
font-family: var(--font-mono);
|
|
163
|
-
color: var(--
|
|
164
|
-
background-color:
|
|
165
|
-
border: 1px solid
|
|
163
|
+
color: var(--text-secondary);
|
|
164
|
+
background-color: var(--bg-tertiary);
|
|
165
|
+
border: 1px solid var(--border-color);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
.mint-file-uploader__file-meta {
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
.mint-icon-button--cta {
|
|
60
|
-
background-color: var(--color-
|
|
60
|
+
background-color: var(--color-primary);
|
|
61
61
|
color: var(--text-on-accent);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
.mint-icon-button--cta:hover:not(.mint-icon-button--disabled) {
|
|
65
|
-
background-color: var(--color-
|
|
65
|
+
background-color: var(--color-primary-hover);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
.mint-icon-button--danger {
|
|
69
69
|
background-color: var(--mint-error);
|
|
70
|
-
color:
|
|
70
|
+
color: var(--text-on-danger);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
.mint-icon-button--danger:hover:not(.mint-icon-button--disabled) {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
/* Tones */
|
|
35
35
|
.mint-spinner--primary { color: var(--color-primary); }
|
|
36
|
-
.mint-spinner--cta { color: var(--color-
|
|
36
|
+
.mint-spinner--cta { color: var(--color-primary); }
|
|
37
37
|
.mint-spinner--success { color: var(--mint-success); }
|
|
38
38
|
.mint-spinner--warning { color: var(--mint-warning); }
|
|
39
39
|
.mint-spinner--error { color: var(--mint-error); }
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
(soft-tint background, dot, outline border); `--mint-pill-ink` the text. A
|
|
34
34
|
`color` prop sets `--mint-pill-tone` inline. */
|
|
35
35
|
.mint-pill--primary { --mint-pill-tone: var(--color-primary); --mint-pill-ink: var(--color-primary-hover); }
|
|
36
|
-
.mint-pill--cta { --mint-pill-tone: var(--color-
|
|
36
|
+
.mint-pill--cta { --mint-pill-tone: var(--color-primary); --mint-pill-ink: var(--color-primary-hover); }
|
|
37
37
|
.mint-pill--success { --mint-pill-tone: var(--mint-success); --mint-pill-ink: var(--mint-success-hover); }
|
|
38
38
|
.mint-pill--warning { --mint-pill-tone: var(--mint-warning); --mint-pill-ink: var(--mint-warning-text); }
|
|
39
39
|
.mint-pill--error { --mint-pill-tone: var(--mint-error); --mint-pill-ink: var(--mint-error-hover); }
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
/* Tones (deprecated with the tone/color props, removed in 1.5): the v2 tile no longer reads the variable. */
|
|
20
20
|
.mint-plugin-icon--neutral { --mint-plugin-icon-tone: var(--text-secondary); }
|
|
21
21
|
.mint-plugin-icon--primary { --mint-plugin-icon-tone: var(--color-primary); }
|
|
22
|
-
.mint-plugin-icon--cta { --mint-plugin-icon-tone: var(--color-
|
|
22
|
+
.mint-plugin-icon--cta { --mint-plugin-icon-tone: var(--color-primary); }
|
|
23
23
|
.mint-plugin-icon--success { --mint-plugin-icon-tone: var(--mint-success); }
|
|
24
24
|
.mint-plugin-icon--warning { --mint-plugin-icon-tone: var(--mint-warning); }
|
|
25
25
|
.mint-plugin-icon--error { --mint-plugin-icon-tone: var(--mint-error); }
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/* PresenceAvatars: an avatar stack that opens a list of who is here.
|
|
2
2
|
* The editing cue is a ring plus a pencil badge, so it does not depend on
|
|
3
|
-
* colour only. Light mode uses --color-
|
|
4
|
-
* mode --color-
|
|
3
|
+
* colour only. Light mode uses --color-primary-hover (3:1 on the card), dark
|
|
4
|
+
* mode --color-primary. --mint-presence-surface is the colour the avatars are
|
|
5
5
|
* cut out of; set it when the stack sits on another surface. */
|
|
6
6
|
|
|
7
7
|
.mint-presence {
|
|
8
|
-
--mint-presence-editing: var(--color-
|
|
8
|
+
--mint-presence-editing: var(--color-primary-hover);
|
|
9
9
|
--mint-presence-surface: var(--bg-secondary);
|
|
10
10
|
position: relative;
|
|
11
11
|
display: inline-flex;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
:where(html.dark) .mint-presence {
|
|
15
|
-
--mint-presence-editing: var(--color-
|
|
15
|
+
--mint-presence-editing: var(--color-primary);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.mint-presence__trigger {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
.mint-progress__bar--success { background-color: var(--mint-success); }
|
|
47
47
|
.mint-progress__bar--warning { background-color: var(--mint-warning); }
|
|
48
48
|
.mint-progress__bar--error { background-color: var(--mint-error); }
|
|
49
|
-
.mint-progress__bar--cta { background-color: var(--color-
|
|
49
|
+
.mint-progress__bar--cta { background-color: var(--color-primary); }
|
|
50
50
|
.mint-progress__bar--neutral { background-color: var(--text-muted); }
|
|
51
51
|
.mint-progress__bar--info { background-color: var(--color-primary); } /* 6b: progress fill is always primary; --mint-info is for the running dot */
|
|
52
52
|
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
.mint-sequence-input__problem--error .mint-sequence-input__problem-shape { fill: var(--mint-error); }
|
|
227
227
|
|
|
228
228
|
.mint-sequence-input__problem-mark {
|
|
229
|
-
stroke:
|
|
229
|
+
stroke: var(--text-on-danger);
|
|
230
230
|
stroke-width: 1.6;
|
|
231
231
|
stroke-linecap: round;
|
|
232
232
|
}
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
box-sizing: border-box;
|
|
57
57
|
transform: translateX(-50%) translateY(-50%);
|
|
58
58
|
border-radius: 9999px;
|
|
59
|
-
background-color:
|
|
59
|
+
background-color: var(--control-knob);
|
|
60
60
|
border: 1px solid color-mix(in srgb, var(--text-secondary) 40%, transparent);
|
|
61
61
|
box-shadow: var(--shadow-sm);
|
|
62
62
|
pointer-events: none;
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
top: 50%;
|
|
152
152
|
left: 2px;
|
|
153
153
|
border-radius: 9999px;
|
|
154
|
-
background-color:
|
|
154
|
+
background-color: var(--control-knob);
|
|
155
155
|
box-shadow: var(--shadow-sm);
|
|
156
156
|
transition: transform 180ms cubic-bezier(0.25, 1, 0.5, 1);
|
|
157
157
|
pointer-events: none;
|
package/src/styles/variables.css
CHANGED
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
--mint-primary: var(--color-primary);
|
|
60
60
|
/* Text and icons on an accent fill (filled buttons, Save). */
|
|
61
61
|
--text-on-accent: #FFFFFF;
|
|
62
|
+
/* Text on a --mint-error fill (danger buttons); white in both themes, 3.76:1 on #EF4444. */
|
|
63
|
+
--text-on-danger: #FFFFFF;
|
|
64
|
+
/* Knob of a toggle or slider, and the mark of a disabled checkbox; white in both themes. */
|
|
65
|
+
--control-knob: #FFFFFF;
|
|
62
66
|
/* Retired hues, aliased to the accent until 1.5 (mint doctor warns on use). */
|
|
63
67
|
--color-cta: var(--color-primary);
|
|
64
68
|
--color-cta-hover: var(--color-primary-hover);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ExperimentSelectorModal-LsB1ABmh.js","names":["$slots"],"sources":["../src/composables/useExperimentSelector.ts","../src/components/ExperimentSelectorModal.vue","../src/components/ExperimentSelectorModal.vue"],"sourcesContent":["import { ref, reactive, computed, watch, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport { useDebouncedWatch } from './useDebouncedWatch'\nimport { useRequest } from './useRequest'\nimport { datePresetToISO } from './experiment-utils'\nimport type {\n ExperimentSummary,\n ExperimentListResponse,\n ExperimentFilters,\n ExperimentTypeOption,\n ExperimentSortField,\n PlatformContext,\n SelectOption,\n} from '../types'\n\nfunction getPlatformContext(): PlatformContext | undefined {\n if (typeof window === 'undefined') return undefined\n return (window as unknown as { __MINT_PLATFORM__?: PlatformContext }).__MINT_PLATFORM__\n}\n\nfunction getPlatformApiUrl(): string | undefined {\n return getPlatformContext()?.platformApiUrl\n}\n\nexport interface UseExperimentSelectorOptions {\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n apiBaseUrl?: string\n limit?: number\n immediate?: boolean\n /** Keep `statusCounts` in step with the non-status filters (one stats request per change). */\n withStatusCounts?: boolean\n}\n\nexport interface UseExperimentSelectorReturn {\n /** Current page of fetched experiment rows. */\n experiments: Ref<ExperimentSummary[]>\n /** Total matching experiment count reported by the platform. */\n total: Ref<number>\n /** Currently selected experiment, or null. */\n selectedExperiment: Ref<ExperimentSummary | null>\n /** Mutable filters used for the next fetch. */\n filters: ExperimentFilters\n /** Whether experiment rows are currently loading. */\n isLoading: Ref<boolean>\n /** Error message from the last failed fetch, or null. */\n error: Ref<string | null>\n /** Timestamp of the last successful experiment fetch, or null. */\n lastLoadedAt: Ref<Date | null>\n /** Current zero-based page index. */\n page: Ref<number>\n /** Whether another page can be loaded. */\n hasMore: ComputedRef<boolean>\n // Sort\n /** Combined sort key in the form `<field>:<asc|desc>`. */\n sortKey: Ref<string>\n // Filter options\n /** Available experiment type options for filter UI. */\n experimentTypes: Ref<ExperimentTypeOption[]>\n /** Available project options for filter UI. */\n projects: Ref<SelectOption<string>[]>\n /** Experiment count per status under every filter except status itself. */\n statusCounts: Ref<Record<string, number>>\n // Methods\n /** Fetch the current page with the current filters. */\n fetch: () => Promise<void>\n /** Fetch and append the next page when available. */\n loadMore: () => Promise<void>\n /** Clear fetched rows and refetch page zero. */\n reset: () => void\n /** Select an experiment. */\n select: (experiment: ExperimentSummary) => void\n /** Clear selection and filters. */\n clear: () => void\n /** Fetch experiment type and project filter options. */\n fetchFilterOptions: () => Promise<void>\n /** Fetch `statusCounts` from `GET /experiments/stats`. */\n fetchStatusCounts: () => Promise<void>\n}\n\n/** Fetches a paginated, filtered experiment list from the platform API for picker and selector UIs. */\nexport function useExperimentSelector(\n options: UseExperimentSelectorOptions = {},\n): UseExperimentSelectorReturn {\n const {\n limit = 100,\n immediate = false,\n experimentType,\n allowedExperimentTypes: optionAllowedExperimentTypes,\n apiBaseUrl,\n withStatusCounts = false,\n } = options\n const platformBase = apiBaseUrl ?? getPlatformApiUrl()\n const api = useApi()\n\n const experiments = ref<ExperimentSummary[]>([])\n const total = ref(0)\n const selectedExperiment = ref<ExperimentSummary | null>(null)\n const request = useRequest('Failed to fetch experiments')\n const isLoading = request.isLoading\n const error = request.error\n const lastLoadedAt = ref<Date | null>(null)\n const page = ref(0)\n let fetchGeneration = 0\n\n // Sort: combined key like \"created_at:desc\"\n const sortKey = ref<string>('created_at:desc')\n\n // Filter option data (fetched once, cached)\n const experimentTypes = ref<ExperimentTypeOption[]>([])\n const projects = ref<SelectOption<string>[]>([])\n let filterOptionsFetched = false\n\n const statusCounts = ref<Record<string, number>>({})\n let statsGeneration = 0\n let countedScope: string | null = null\n\n const hasMore = computed(() => experiments.value.length < total.value)\n\n const filters: ExperimentFilters = reactive({\n search: undefined,\n status: undefined,\n project: undefined,\n experimentType: undefined,\n datePreset: undefined,\n mine: undefined,\n })\n\n function normalizeExperimentTypes(values: string[] | null | undefined): string[] | null {\n if (values == null) return null\n return [...new Set(values.filter((value): value is string => typeof value === 'string' && value.length > 0))]\n }\n\n function currentAllowedExperimentTypes(): string[] | null {\n if (optionAllowedExperimentTypes !== undefined) {\n return normalizeExperimentTypes(optionAllowedExperimentTypes)\n }\n return normalizeExperimentTypes(getPlatformContext()?.allowedExperimentTypes)\n }\n\n function parseSortKey(): { sortBy: ExperimentSortField; sortOrder: 'asc' | 'desc' } {\n const [field, order] = sortKey.value.split(':')\n return {\n sortBy: (field || 'created_at') as ExperimentSortField,\n sortOrder: (order || 'desc') as 'asc' | 'desc',\n }\n }\n\n type ExperimentTypeScope =\n | { kind: 'all' }\n | { kind: 'blocked' }\n | { kind: 'single'; experimentType: string }\n | { kind: 'multi'; experimentTypes: string[] }\n\n function resolveExperimentTypeScope(): ExperimentTypeScope {\n const allowedTypes = currentAllowedExperimentTypes()\n const selectedType = experimentType ?? filters.experimentType ?? undefined\n\n if (selectedType) {\n if (allowedTypes && !allowedTypes.includes(selectedType)) {\n return { kind: 'blocked' }\n }\n return { kind: 'single', experimentType: selectedType }\n }\n\n if (allowedTypes) {\n if (allowedTypes.length === 0) return { kind: 'blocked' }\n if (allowedTypes.length === 1) return { kind: 'single', experimentType: allowedTypes[0] }\n return { kind: 'multi', experimentTypes: allowedTypes }\n }\n\n return { kind: 'all' }\n }\n\n /** Query params shared by the list and the stats route; status, sort and paging are list-only. */\n function scopeParams(typeFilter: string | undefined): URLSearchParams {\n const params = new URLSearchParams()\n if (typeFilter) params.set('experiment_type', typeFilter)\n if (filters.search) params.set('search', filters.search)\n if (filters.project) params.set('project', filters.project)\n if (filters.mine) params.set('mine', 'true')\n if (filters.datePreset) {\n params.set('created_after', datePresetToISO(filters.datePreset))\n }\n return params\n }\n\n function buildExperimentListUrl(typeFilter: string | undefined, skip: number, pageLimit: number): string {\n const params = scopeParams(typeFilter)\n if (filters.status) params.set('status', filters.status)\n\n const { sortBy, sortOrder } = parseSortKey()\n params.set('sort_by', sortBy)\n params.set('sort_order', sortOrder)\n\n params.set('limit', String(pageLimit))\n params.set('skip', String(skip))\n\n const query = params.toString()\n const base = platformBase ?? ''\n return `${base}/experiments${query ? `?${query}` : ''}`\n }\n\n function sortExperiments(items: ExperimentSummary[]): ExperimentSummary[] {\n const { sortBy, sortOrder } = parseSortKey()\n const direction = sortOrder === 'desc' ? -1 : 1\n return [...items].sort((a, b) => {\n const aValue = a[sortBy] ?? ''\n const bValue = b[sortBy] ?? ''\n if (aValue < bValue) return -1 * direction\n if (aValue > bValue) return 1 * direction\n return 0\n })\n }\n\n async function fetchExperimentList(typeFilter: string | undefined, skip: number, pageLimit: number) {\n return await api.get<ExperimentListResponse>(buildExperimentListUrl(typeFilter, skip, pageLimit))\n }\n\n async function fetchScopedExperimentPage(\n requestedPage: number,\n ): Promise<ExperimentListResponse> {\n const scope = resolveExperimentTypeScope()\n const pageSkip = requestedPage * limit\n\n if (scope.kind === 'blocked') {\n return { experiments: [], total: 0 }\n }\n\n if (scope.kind === 'multi') {\n const perTypeLimit = pageSkip + limit\n const responses = await Promise.all(\n scope.experimentTypes.map(type => fetchExperimentList(type, 0, perTypeLimit)),\n )\n const merged = sortExperiments(responses.flatMap(response => response.experiments))\n return {\n experiments: merged.slice(pageSkip, pageSkip + limit),\n total: responses.reduce((sum, response) => sum + response.total, 0),\n }\n }\n\n return await fetchExperimentList(\n scope.kind === 'single' ? scope.experimentType : undefined,\n pageSkip,\n limit,\n )\n }\n\n async function fetchExperiments(): Promise<void> {\n const generation = ++fetchGeneration\n const requestedPage = page.value\n isLoading.value = true\n request.clearError()\n try {\n const data = await fetchScopedExperimentPage(requestedPage)\n if (generation !== fetchGeneration) return\n if (requestedPage === 0) {\n experiments.value = data.experiments\n } else {\n experiments.value = [...experiments.value, ...data.experiments]\n }\n total.value = data.total\n lastLoadedAt.value = new Date()\n } catch (err) {\n if (generation !== fetchGeneration) return\n request.setError(err, 'Failed to fetch experiments')\n if (requestedPage === 0) {\n experiments.value = []\n total.value = 0\n }\n } finally {\n if (generation === fetchGeneration) {\n isLoading.value = false\n }\n }\n }\n\n function invalidateFetch(): void {\n fetchGeneration += 1\n isLoading.value = false\n }\n\n async function fetchFilterOptions(): Promise<void> {\n if (filterOptionsFetched) return\n filterOptionsFetched = true\n\n const base = platformBase ?? ''\n const [typesRes, projectsRes] = await Promise.allSettled([\n api.get<Array<{ value: string; label: string; color?: string }>>(`${base}/experiments/experiment-types`),\n api.get<{ projects: Array<{ id: number; name: string }>; total: number }>(`${base}/projects`),\n ])\n\n if (typesRes.status === 'fulfilled' && Array.isArray(typesRes.value)) {\n const allowedTypes = currentAllowedExperimentTypes()\n const allowedSet = allowedTypes ? new Set(allowedTypes) : null\n experimentTypes.value = typesRes.value\n .filter(t => !allowedSet || allowedSet.has(t.value))\n .map(t => ({\n value: t.value,\n label: t.label,\n color: t.color,\n }))\n if (filters.experimentType && allowedSet && !allowedSet.has(filters.experimentType)) {\n filters.experimentType = undefined\n }\n }\n\n if (projectsRes.status === 'fulfilled' && projectsRes.value?.projects && Array.isArray(projectsRes.value.projects)) {\n projects.value = projectsRes.value.projects.map(p => ({\n value: p.name,\n label: p.name,\n }))\n }\n }\n\n /** The filters the stats route reads; status, sort and paging do not change the counts. */\n function countScope(): string {\n return JSON.stringify([filters.search, filters.project, filters.experimentType, filters.datePreset, filters.mine])\n }\n\n function refreshStatusCountsOnScopeChange(): void {\n if (withStatusCounts && countScope() !== countedScope) fetchStatusCounts()\n }\n\n async function fetchStatusCounts(): Promise<void> {\n const generation = ++statsGeneration\n countedScope = countScope()\n const scope = resolveExperimentTypeScope()\n if (scope.kind === 'blocked') {\n statusCounts.value = {}\n return\n }\n const types = scope.kind === 'multi'\n ? scope.experimentTypes\n : [scope.kind === 'single' ? scope.experimentType : undefined]\n const base = platformBase ?? ''\n try {\n const responses = await Promise.all(\n types.map(type => api.get<{ total: number; by_status: Record<string, number> }>(\n `${base}/experiments/stats?${scopeParams(type)}`,\n )),\n )\n if (generation !== statsGeneration) return\n const counts: Record<string, number> = {}\n for (const response of responses) {\n for (const [status, count] of Object.entries(response.by_status)) {\n counts[status] = (counts[status] ?? 0) + count\n }\n }\n statusCounts.value = counts\n } catch {\n // Counts only decorate the status menu; without them it shows bare labels.\n // Forget the scope so the next status or sort change retries the request.\n if (generation === statsGeneration) {\n statusCounts.value = {}\n countedScope = null\n }\n }\n }\n\n async function loadMore(): Promise<void> {\n if (!hasMore.value || isLoading.value) return\n page.value++\n await fetchExperiments()\n }\n\n function reset(): void {\n page.value = 0\n experiments.value = []\n total.value = 0\n fetchExperiments()\n }\n\n function select(experiment: ExperimentSummary): void {\n selectedExperiment.value = experiment\n }\n\n function clear(): void {\n invalidateFetch()\n selectedExperiment.value = null\n filters.search = undefined\n filters.status = undefined\n filters.project = undefined\n filters.experimentType = undefined\n filters.datePreset = undefined\n filters.mine = undefined\n sortKey.value = 'created_at:desc'\n page.value = 0\n request.clearError()\n }\n\n watch(\n () => [\n filters.search,\n filters.status,\n filters.project,\n filters.experimentType,\n filters.datePreset,\n filters.mine,\n sortKey.value,\n ],\n () => invalidateFetch(),\n { flush: 'sync' },\n )\n\n const searchWatch = useDebouncedWatch(\n () => filters.search,\n () => {\n page.value = 0\n fetchExperiments()\n refreshStatusCountsOnScopeChange()\n },\n { delay: 300 },\n )\n\n // Immediate watch on discrete filters and sort (cancel any pending search debounce)\n watch(\n () => [filters.status, filters.project, filters.experimentType, filters.datePreset, filters.mine, sortKey.value],\n () => {\n searchWatch.cancel()\n page.value = 0\n fetchExperiments()\n // Also checked on a status or sort change: the cancelled debounce may hold a new search.\n refreshStatusCountsOnScopeChange()\n },\n )\n\n if (immediate) {\n fetchExperiments()\n }\n\n return {\n experiments,\n total,\n selectedExperiment,\n filters,\n isLoading,\n error,\n lastLoadedAt,\n page,\n hasMore,\n sortKey,\n experimentTypes,\n projects,\n statusCounts,\n fetch: fetchExperiments,\n loadMore,\n reset,\n select,\n clear,\n fetchFilterOptions,\n fetchStatusCounts,\n }\n}\n","<script setup lang=\"ts\">\n/**\n * Modal for searching and selecting an experiment from the platform list with filters and keyboard nav.\n *\n * @example Recommended\n * ```vue\n * <ExperimentSelectorModal v-model:open=\"open\" :allowed-experiment-types=\"['mass-spectrometry']\" />\n * ```\n */\nimport { ref, computed, watch, type Component } from 'vue'\nimport { ChevronDown, CircleDot, Folder, Shapes, User } from 'lucide-vue-next'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters, ActionMenuItem, Tone } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport { useExperimentStore } from '../stores/experiment'\nimport { formatExperimentDate } from '../composables/experiment-utils'\nimport * as experimentStatus from '../utils/experimentStatus'\nimport BaseModal from './BaseModal.vue'\nimport ActionMenu from './ActionMenu.vue'\nimport ResourcePickerListInternal from './internal/ResourcePickerListInternal.vue'\n\ninterface Props {\n open: boolean\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'lg',\n})\n\n// Selection is committed to the shared experiment store rather than emitted.\n// A single owner of \"the current experiment\" is what stops callers holding —\n// or inventing — a record of their own.\nconst emit = defineEmits<{\n 'update:open': [value: boolean]\n}>()\n\ndefineSlots<{\n /** Text above the experiment list: why the caller asks for an experiment. */\n description?: () => unknown\n /** Extra footer content, before Clear selection. `close()` closes the modal without a selection. */\n footer?: (props: { close: () => void }) => unknown\n}>()\n\nfunction close() {\n emit('update:open', false)\n}\n\nconst experimentStore = useExperimentStore()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n experimentTypes,\n projects,\n statusCounts,\n fetch: fetchExperiments,\n fetchFilterOptions,\n fetchStatusCounts,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n allowedExperimentTypes: props.allowedExperimentTypes,\n withStatusCounts: true,\n})\n\n// Re-mounting the list on open clears its keyboard highlight.\nconst openCount = ref(0)\n\nconst STATUS_ORDER = ['ongoing', 'planned', 'completed', 'cancelled'] as const\n\nconst canChooseExperimentType = computed(() =>\n !props.experimentType && experimentTypes.value.length > 1,\n)\n\ntype FilterKey = 'status' | 'project' | 'experimentType'\n\ninterface FilterMenu {\n key: FilterKey\n label: string\n icon: Component\n items: ActionMenuItem[]\n /** Trigger text while the filter is set. */\n activeLabel?: string\n dotTone?: Tone\n}\n\n/** Every item carries `checked`, so ActionMenu renders a single-choice menu. */\nfunction choiceItems(key: FilterKey, anyLabel: string, options: { value: string; label: string }[]): ActionMenuItem[] {\n const current = filters[key] ?? ''\n return [{ value: '', label: anyLabel }, ...options].map(option => ({\n id: option.value,\n label: option.label,\n checked: option.value === current,\n }))\n}\n\nconst filterMenus = computed<FilterMenu[]>(() => {\n const menus: FilterMenu[] = [{\n key: 'status',\n label: 'Status',\n icon: CircleDot,\n items: choiceItems('status', 'Any status', STATUS_ORDER.map(status => {\n const count = statusCounts.value[status]\n const label = experimentStatus.format(status)\n return { value: status, label: count === undefined ? label : `${label} · ${count}` }\n })),\n activeLabel: filters.status ? experimentStatus.format(filters.status) : undefined,\n dotTone: filters.status ? experimentStatus.tone(filters.status) : undefined,\n }]\n if (projects.value.length) {\n menus.push({\n key: 'project',\n label: 'Project',\n icon: Folder,\n items: choiceItems('project', 'Any project', projects.value),\n activeLabel: filters.project ?? undefined,\n })\n }\n if (canChooseExperimentType.value) {\n menus.push({\n key: 'experimentType',\n label: 'Type',\n icon: Shapes,\n items: choiceItems('experimentType', 'Any type', experimentTypes.value),\n activeLabel: experimentTypes.value.find(t => t.value === filters.experimentType)?.label\n ?? filters.experimentType ?? undefined,\n })\n }\n return menus\n})\n\nconst activeFilterCount = computed(() =>\n [filters.status, filters.project, filters.experimentType, filters.mine].filter(Boolean).length,\n)\n\nfunction clearFilters() {\n filters.status = undefined\n filters.project = undefined\n filters.experimentType = undefined\n filters.mine = undefined\n}\n\nfunction describe(experiment: ExperimentSummary) {\n const owner = experiment.created_by_user\n return {\n code: experiment.experiment_code,\n title: experiment.name,\n meta: [\n experiment.project_name || experiment.project || '',\n owner ? [owner.first_name, owner.last_name].filter(Boolean).join(' ') || owner.username : '',\n ].filter(Boolean),\n status: experiment.status\n ? { label: experimentStatus.format(experiment.status), tone: experimentStatus.tone(experiment.status) }\n : undefined,\n date: formatExperimentDate(experiment.created_at),\n }\n}\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n experimentStore.select(experiment)\n emit('update:open', false)\n}\n\nfunction handleDeselect() {\n experimentStore.clear()\n emit('update:open', false)\n}\n\n// Fetch on open\nwatch(\n () => props.open,\n (isOpen) => {\n if (isOpen) {\n openCount.value++\n fetchFilterOptions()\n fetchExperiments()\n fetchStatusCounts()\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n :open=\"open\"\n :title=\"title\"\n :size=\"size\"\n @update:open=\"emit('update:open', $event)\"\n >\n <div v-if=\"$slots.description\" class=\"mint-experiment-selector__description\">\n <slot name=\"description\" />\n </div>\n\n <ResourcePickerListInternal\n :key=\"openCount\"\n :search=\"filters.search ?? ''\"\n class=\"mint-experiment-selector\"\n autofocus-search\n label=\"Experiments\"\n placeholder=\"Search experiments…\"\n :items=\"experiments\"\n :item-key=\"(experiment) => experiment.id\"\n :describe=\"describe\"\n :selected-key=\"currentExperimentId\"\n :loading=\"isLoading\"\n :error=\"error\"\n empty-title=\"No experiments found\"\n @update:search=\"filters.search = $event\"\n @select=\"handleSelect\"\n >\n <template #filters>\n <ActionMenu\n v-for=\"menu in filterMenus\"\n :key=\"menu.key\"\n :label=\"menu.label\"\n :items=\"menu.items\"\n @select=\"item => setFilter(menu.key, item.id)\"\n >\n <template #trigger=\"{ open: menuOpen, toggle, keydown }\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__toggle\"\n :class=\"{ 'mint-experiment-selector__toggle--active': menu.activeLabel }\"\n :aria-label=\"`${menu.label}: ${menu.activeLabel ?? menu.items[0].label}`\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"menuOpen\"\n @click=\"toggle\"\n @keydown=\"keydown\"\n >\n <span\n v-if=\"menu.dotTone\"\n class=\"mint-experiment-selector__dot\"\n :class=\"`mint-experiment-selector__dot--${menu.dotTone}`\"\n aria-hidden=\"true\"\n />\n <component :is=\"menu.icon\" v-else :size=\"13\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-selector__toggle-label\">{{ menu.activeLabel ?? menu.label }}</span>\n <ChevronDown :size=\"12\" aria-hidden=\"true\" />\n </button>\n </template>\n </ActionMenu>\n <button\n type=\"button\"\n class=\"mint-experiment-selector__toggle\"\n :class=\"{ 'mint-experiment-selector__toggle--active': filters.mine }\"\n :aria-pressed=\"!!filters.mine\"\n @click=\"filters.mine = filters.mine ? undefined : true\"\n >\n <User :size=\"13\" aria-hidden=\"true\" />\n Mine\n </button>\n <button\n v-if=\"activeFilterCount\"\n type=\"button\"\n class=\"mint-experiment-selector__clear-filters\"\n @click=\"clearFilters\"\n >\n Clear {{ activeFilterCount }}\n </button>\n </template>\n </ResourcePickerListInternal>\n\n <div v-if=\"$slots.footer || currentExperimentId != null\" class=\"mint-experiment-selector__footer\">\n <div v-if=\"$slots.footer\" class=\"mint-experiment-selector__footer-slot\">\n <slot name=\"footer\" :close=\"close\" />\n </div>\n <button\n v-if=\"currentExperimentId != null\"\n type=\"button\"\n class=\"mint-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </BaseModal>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Modal for searching and selecting an experiment from the platform list with filters and keyboard nav.\n *\n * @example Recommended\n * ```vue\n * <ExperimentSelectorModal v-model:open=\"open\" :allowed-experiment-types=\"['mass-spectrometry']\" />\n * ```\n */\nimport { ref, computed, watch, type Component } from 'vue'\nimport { ChevronDown, CircleDot, Folder, Shapes, User } from 'lucide-vue-next'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters, ActionMenuItem, Tone } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport { useExperimentStore } from '../stores/experiment'\nimport { formatExperimentDate } from '../composables/experiment-utils'\nimport * as experimentStatus from '../utils/experimentStatus'\nimport BaseModal from './BaseModal.vue'\nimport ActionMenu from './ActionMenu.vue'\nimport ResourcePickerListInternal from './internal/ResourcePickerListInternal.vue'\n\ninterface Props {\n open: boolean\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'lg',\n})\n\n// Selection is committed to the shared experiment store rather than emitted.\n// A single owner of \"the current experiment\" is what stops callers holding —\n// or inventing — a record of their own.\nconst emit = defineEmits<{\n 'update:open': [value: boolean]\n}>()\n\ndefineSlots<{\n /** Text above the experiment list: why the caller asks for an experiment. */\n description?: () => unknown\n /** Extra footer content, before Clear selection. `close()` closes the modal without a selection. */\n footer?: (props: { close: () => void }) => unknown\n}>()\n\nfunction close() {\n emit('update:open', false)\n}\n\nconst experimentStore = useExperimentStore()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n experimentTypes,\n projects,\n statusCounts,\n fetch: fetchExperiments,\n fetchFilterOptions,\n fetchStatusCounts,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n allowedExperimentTypes: props.allowedExperimentTypes,\n withStatusCounts: true,\n})\n\n// Re-mounting the list on open clears its keyboard highlight.\nconst openCount = ref(0)\n\nconst STATUS_ORDER = ['ongoing', 'planned', 'completed', 'cancelled'] as const\n\nconst canChooseExperimentType = computed(() =>\n !props.experimentType && experimentTypes.value.length > 1,\n)\n\ntype FilterKey = 'status' | 'project' | 'experimentType'\n\ninterface FilterMenu {\n key: FilterKey\n label: string\n icon: Component\n items: ActionMenuItem[]\n /** Trigger text while the filter is set. */\n activeLabel?: string\n dotTone?: Tone\n}\n\n/** Every item carries `checked`, so ActionMenu renders a single-choice menu. */\nfunction choiceItems(key: FilterKey, anyLabel: string, options: { value: string; label: string }[]): ActionMenuItem[] {\n const current = filters[key] ?? ''\n return [{ value: '', label: anyLabel }, ...options].map(option => ({\n id: option.value,\n label: option.label,\n checked: option.value === current,\n }))\n}\n\nconst filterMenus = computed<FilterMenu[]>(() => {\n const menus: FilterMenu[] = [{\n key: 'status',\n label: 'Status',\n icon: CircleDot,\n items: choiceItems('status', 'Any status', STATUS_ORDER.map(status => {\n const count = statusCounts.value[status]\n const label = experimentStatus.format(status)\n return { value: status, label: count === undefined ? label : `${label} · ${count}` }\n })),\n activeLabel: filters.status ? experimentStatus.format(filters.status) : undefined,\n dotTone: filters.status ? experimentStatus.tone(filters.status) : undefined,\n }]\n if (projects.value.length) {\n menus.push({\n key: 'project',\n label: 'Project',\n icon: Folder,\n items: choiceItems('project', 'Any project', projects.value),\n activeLabel: filters.project ?? undefined,\n })\n }\n if (canChooseExperimentType.value) {\n menus.push({\n key: 'experimentType',\n label: 'Type',\n icon: Shapes,\n items: choiceItems('experimentType', 'Any type', experimentTypes.value),\n activeLabel: experimentTypes.value.find(t => t.value === filters.experimentType)?.label\n ?? filters.experimentType ?? undefined,\n })\n }\n return menus\n})\n\nconst activeFilterCount = computed(() =>\n [filters.status, filters.project, filters.experimentType, filters.mine].filter(Boolean).length,\n)\n\nfunction clearFilters() {\n filters.status = undefined\n filters.project = undefined\n filters.experimentType = undefined\n filters.mine = undefined\n}\n\nfunction describe(experiment: ExperimentSummary) {\n const owner = experiment.created_by_user\n return {\n code: experiment.experiment_code,\n title: experiment.name,\n meta: [\n experiment.project_name || experiment.project || '',\n owner ? [owner.first_name, owner.last_name].filter(Boolean).join(' ') || owner.username : '',\n ].filter(Boolean),\n status: experiment.status\n ? { label: experimentStatus.format(experiment.status), tone: experimentStatus.tone(experiment.status) }\n : undefined,\n date: formatExperimentDate(experiment.created_at),\n }\n}\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n experimentStore.select(experiment)\n emit('update:open', false)\n}\n\nfunction handleDeselect() {\n experimentStore.clear()\n emit('update:open', false)\n}\n\n// Fetch on open\nwatch(\n () => props.open,\n (isOpen) => {\n if (isOpen) {\n openCount.value++\n fetchFilterOptions()\n fetchExperiments()\n fetchStatusCounts()\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n :open=\"open\"\n :title=\"title\"\n :size=\"size\"\n @update:open=\"emit('update:open', $event)\"\n >\n <div v-if=\"$slots.description\" class=\"mint-experiment-selector__description\">\n <slot name=\"description\" />\n </div>\n\n <ResourcePickerListInternal\n :key=\"openCount\"\n :search=\"filters.search ?? ''\"\n class=\"mint-experiment-selector\"\n autofocus-search\n label=\"Experiments\"\n placeholder=\"Search experiments…\"\n :items=\"experiments\"\n :item-key=\"(experiment) => experiment.id\"\n :describe=\"describe\"\n :selected-key=\"currentExperimentId\"\n :loading=\"isLoading\"\n :error=\"error\"\n empty-title=\"No experiments found\"\n @update:search=\"filters.search = $event\"\n @select=\"handleSelect\"\n >\n <template #filters>\n <ActionMenu\n v-for=\"menu in filterMenus\"\n :key=\"menu.key\"\n :label=\"menu.label\"\n :items=\"menu.items\"\n @select=\"item => setFilter(menu.key, item.id)\"\n >\n <template #trigger=\"{ open: menuOpen, toggle, keydown }\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__toggle\"\n :class=\"{ 'mint-experiment-selector__toggle--active': menu.activeLabel }\"\n :aria-label=\"`${menu.label}: ${menu.activeLabel ?? menu.items[0].label}`\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"menuOpen\"\n @click=\"toggle\"\n @keydown=\"keydown\"\n >\n <span\n v-if=\"menu.dotTone\"\n class=\"mint-experiment-selector__dot\"\n :class=\"`mint-experiment-selector__dot--${menu.dotTone}`\"\n aria-hidden=\"true\"\n />\n <component :is=\"menu.icon\" v-else :size=\"13\" aria-hidden=\"true\" />\n <span class=\"mint-experiment-selector__toggle-label\">{{ menu.activeLabel ?? menu.label }}</span>\n <ChevronDown :size=\"12\" aria-hidden=\"true\" />\n </button>\n </template>\n </ActionMenu>\n <button\n type=\"button\"\n class=\"mint-experiment-selector__toggle\"\n :class=\"{ 'mint-experiment-selector__toggle--active': filters.mine }\"\n :aria-pressed=\"!!filters.mine\"\n @click=\"filters.mine = filters.mine ? undefined : true\"\n >\n <User :size=\"13\" aria-hidden=\"true\" />\n Mine\n </button>\n <button\n v-if=\"activeFilterCount\"\n type=\"button\"\n class=\"mint-experiment-selector__clear-filters\"\n @click=\"clearFilters\"\n >\n Clear {{ activeFilterCount }}\n </button>\n </template>\n </ResourcePickerListInternal>\n\n <div v-if=\"$slots.footer || currentExperimentId != null\" class=\"mint-experiment-selector__footer\">\n <div v-if=\"$slots.footer\" class=\"mint-experiment-selector__footer-slot\">\n <slot name=\"footer\" :close=\"close\" />\n </div>\n <button\n v-if=\"currentExperimentId != null\"\n type=\"button\"\n class=\"mint-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </BaseModal>\n</template>\n"],"mappings":";;;;;;;;;AAeA,SAAS,qBAAkD;CACzD,IAAI,OAAO,WAAW,aAAa,OAAO,KAAA;CAC1C,OAAQ,OAA8D;AACxE;AAEA,SAAS,oBAAwC;CAC/C,OAAO,mBAAmB,CAAC,EAAE;AAC/B;;AA2DA,SAAgB,sBACd,UAAwC,CAAC,GACZ;CAC7B,MAAM,EACJ,QAAQ,KACR,YAAY,OACZ,gBACA,wBAAwB,8BACxB,YACA,mBAAmB,UACjB;CACJ,MAAM,eAAe,cAAc,kBAAkB;CACrD,MAAM,MAAM,OAAO;CAEnB,MAAM,cAAc,IAAyB,CAAC,CAAC;CAC/C,MAAM,QAAQ,IAAI,CAAC;CACnB,MAAM,qBAAqB,IAA8B,IAAI;CAC7D,MAAM,UAAU,WAAW,6BAA6B;CACxD,MAAM,YAAY,QAAQ;CAC1B,MAAM,QAAQ,QAAQ;CACtB,MAAM,eAAe,IAAiB,IAAI;CAC1C,MAAM,OAAO,IAAI,CAAC;CAClB,IAAI,kBAAkB;CAGtB,MAAM,UAAU,IAAY,iBAAiB;CAG7C,MAAM,kBAAkB,IAA4B,CAAC,CAAC;CACtD,MAAM,WAAW,IAA4B,CAAC,CAAC;CAC/C,IAAI,uBAAuB;CAE3B,MAAM,eAAe,IAA4B,CAAC,CAAC;CACnD,IAAI,kBAAkB;CACtB,IAAI,eAA8B;CAElC,MAAM,UAAU,eAAe,YAAY,MAAM,SAAS,MAAM,KAAK;CAErE,MAAM,UAA6B,SAAS;EAC1C,QAAQ,KAAA;EACR,QAAQ,KAAA;EACR,SAAS,KAAA;EACT,gBAAgB,KAAA;EAChB,YAAY,KAAA;EACZ,MAAM,KAAA;CACR,CAAC;CAED,SAAS,yBAAyB,QAAsD;EACtF,IAAI,UAAU,MAAM,OAAO;EAC3B,OAAO,CAAC,GAAG,IAAI,IAAI,OAAO,QAAQ,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC,CAAC,CAAC;CAC9G;CAEA,SAAS,gCAAiD;EACxD,IAAI,iCAAiC,KAAA,GACnC,OAAO,yBAAyB,4BAA4B;EAE9D,OAAO,yBAAyB,mBAAmB,CAAC,EAAE,sBAAsB;CAC9E;CAEA,SAAS,eAA2E;EAClF,MAAM,CAAC,OAAO,SAAS,QAAQ,MAAM,MAAM,GAAG;EAC9C,OAAO;GACL,QAAS,SAAS;GAClB,WAAY,SAAS;EACvB;CACF;CAQA,SAAS,6BAAkD;EACzD,MAAM,eAAe,8BAA8B;EACnD,MAAM,eAAe,kBAAkB,QAAQ,kBAAkB,KAAA;EAEjE,IAAI,cAAc;GAChB,IAAI,gBAAgB,CAAC,aAAa,SAAS,YAAY,GACrD,OAAO,EAAE,MAAM,UAAU;GAE3B,OAAO;IAAE,MAAM;IAAU,gBAAgB;GAAa;EACxD;EAEA,IAAI,cAAc;GAChB,IAAI,aAAa,WAAW,GAAG,OAAO,EAAE,MAAM,UAAU;GACxD,IAAI,aAAa,WAAW,GAAG,OAAO;IAAE,MAAM;IAAU,gBAAgB,aAAa;GAAG;GACxF,OAAO;IAAE,MAAM;IAAS,iBAAiB;GAAa;EACxD;EAEA,OAAO,EAAE,MAAM,MAAM;CACvB;;CAGA,SAAS,YAAY,YAAiD;EACpE,MAAM,SAAS,IAAI,gBAAgB;EACnC,IAAI,YAAY,OAAO,IAAI,mBAAmB,UAAU;EACxD,IAAI,QAAQ,QAAQ,OAAO,IAAI,UAAU,QAAQ,MAAM;EACvD,IAAI,QAAQ,SAAS,OAAO,IAAI,WAAW,QAAQ,OAAO;EAC1D,IAAI,QAAQ,MAAM,OAAO,IAAI,QAAQ,MAAM;EAC3C,IAAI,QAAQ,YACV,OAAO,IAAI,iBAAiB,gBAAgB,QAAQ,UAAU,CAAC;EAEjE,OAAO;CACT;CAEA,SAAS,uBAAuB,YAAgC,MAAc,WAA2B;EACvG,MAAM,SAAS,YAAY,UAAU;EACrC,IAAI,QAAQ,QAAQ,OAAO,IAAI,UAAU,QAAQ,MAAM;EAEvD,MAAM,EAAE,QAAQ,cAAc,aAAa;EAC3C,OAAO,IAAI,WAAW,MAAM;EAC5B,OAAO,IAAI,cAAc,SAAS;EAElC,OAAO,IAAI,SAAS,OAAO,SAAS,CAAC;EACrC,OAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;EAE/B,MAAM,QAAQ,OAAO,SAAS;EAE9B,OAAO,GADM,gBAAgB,GACd,cAAc,QAAQ,IAAI,UAAU;CACrD;CAEA,SAAS,gBAAgB,OAAiD;EACxE,MAAM,EAAE,QAAQ,cAAc,aAAa;EAC3C,MAAM,YAAY,cAAc,SAAS,KAAK;EAC9C,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM;GAC/B,MAAM,SAAS,EAAE,WAAW;GAC5B,MAAM,SAAS,EAAE,WAAW;GAC5B,IAAI,SAAS,QAAQ,OAAO,KAAK;GACjC,IAAI,SAAS,QAAQ,OAAO,IAAI;GAChC,OAAO;EACT,CAAC;CACH;CAEA,eAAe,oBAAoB,YAAgC,MAAc,WAAmB;EAClG,OAAO,MAAM,IAAI,IAA4B,uBAAuB,YAAY,MAAM,SAAS,CAAC;CAClG;CAEA,eAAe,0BACb,eACiC;EACjC,MAAM,QAAQ,2BAA2B;EACzC,MAAM,WAAW,gBAAgB;EAEjC,IAAI,MAAM,SAAS,WACjB,OAAO;GAAE,aAAa,CAAC;GAAG,OAAO;EAAE;EAGrC,IAAI,MAAM,SAAS,SAAS;GAC1B,MAAM,eAAe,WAAW;GAChC,MAAM,YAAY,MAAM,QAAQ,IAC9B,MAAM,gBAAgB,KAAI,SAAQ,oBAAoB,MAAM,GAAG,YAAY,CAAC,CAC9E;GAEA,OAAO;IACL,aAFa,gBAAgB,UAAU,SAAQ,aAAY,SAAS,WAAW,CAElE,CAAA,CAAO,MAAM,UAAU,WAAW,KAAK;IACpD,OAAO,UAAU,QAAQ,KAAK,aAAa,MAAM,SAAS,OAAO,CAAC;GACpE;EACF;EAEA,OAAO,MAAM,oBACX,MAAM,SAAS,WAAW,MAAM,iBAAiB,KAAA,GACjD,UACA,KACF;CACF;CAEA,eAAe,mBAAkC;EAC/C,MAAM,aAAa,EAAE;EACrB,MAAM,gBAAgB,KAAK;EAC3B,UAAU,QAAQ;EAClB,QAAQ,WAAW;EACnB,IAAI;GACF,MAAM,OAAO,MAAM,0BAA0B,aAAa;GAC1D,IAAI,eAAe,iBAAiB;GACpC,IAAI,kBAAkB,GACpB,YAAY,QAAQ,KAAK;QAEzB,YAAY,QAAQ,CAAC,GAAG,YAAY,OAAO,GAAG,KAAK,WAAW;GAEhE,MAAM,QAAQ,KAAK;GACnB,aAAa,wBAAQ,IAAI,KAAK;EAChC,SAAS,KAAK;GACZ,IAAI,eAAe,iBAAiB;GACpC,QAAQ,SAAS,KAAK,6BAA6B;GACnD,IAAI,kBAAkB,GAAG;IACvB,YAAY,QAAQ,CAAC;IACrB,MAAM,QAAQ;GAChB;EACF,UAAU;GACR,IAAI,eAAe,iBACjB,UAAU,QAAQ;EAEtB;CACF;CAEA,SAAS,kBAAwB;EAC/B,mBAAmB;EACnB,UAAU,QAAQ;CACpB;CAEA,eAAe,qBAAoC;EACjD,IAAI,sBAAsB;EAC1B,uBAAuB;EAEvB,MAAM,OAAO,gBAAgB;EAC7B,MAAM,CAAC,UAAU,eAAe,MAAM,QAAQ,WAAW,CACvD,IAAI,IAA6D,GAAG,KAAK,8BAA8B,GACvG,IAAI,IAAsE,GAAG,KAAK,UAAU,CAC9F,CAAC;EAED,IAAI,SAAS,WAAW,eAAe,MAAM,QAAQ,SAAS,KAAK,GAAG;GACpE,MAAM,eAAe,8BAA8B;GACnD,MAAM,aAAa,eAAe,IAAI,IAAI,YAAY,IAAI;GAC1D,gBAAgB,QAAQ,SAAS,MAC9B,QAAO,MAAK,CAAC,cAAc,WAAW,IAAI,EAAE,KAAK,CAAC,CAAC,CACnD,KAAI,OAAM;IACT,OAAO,EAAE;IACT,OAAO,EAAE;IACT,OAAO,EAAE;GACX,EAAE;GACJ,IAAI,QAAQ,kBAAkB,cAAc,CAAC,WAAW,IAAI,QAAQ,cAAc,GAChF,QAAQ,iBAAiB,KAAA;EAE7B;EAEA,IAAI,YAAY,WAAW,eAAe,YAAY,OAAO,YAAY,MAAM,QAAQ,YAAY,MAAM,QAAQ,GAC/G,SAAS,QAAQ,YAAY,MAAM,SAAS,KAAI,OAAM;GACpD,OAAO,EAAE;GACT,OAAO,EAAE;EACX,EAAE;CAEN;;CAGA,SAAS,aAAqB;EAC5B,OAAO,KAAK,UAAU;GAAC,QAAQ;GAAQ,QAAQ;GAAS,QAAQ;GAAgB,QAAQ;GAAY,QAAQ;EAAI,CAAC;CACnH;CAEA,SAAS,mCAAyC;EAChD,IAAI,oBAAoB,WAAW,MAAM,cAAc,kBAAkB;CAC3E;CAEA,eAAe,oBAAmC;EAChD,MAAM,aAAa,EAAE;EACrB,eAAe,WAAW;EAC1B,MAAM,QAAQ,2BAA2B;EACzC,IAAI,MAAM,SAAS,WAAW;GAC5B,aAAa,QAAQ,CAAC;GACtB;EACF;EACA,MAAM,QAAQ,MAAM,SAAS,UACzB,MAAM,kBACN,CAAC,MAAM,SAAS,WAAW,MAAM,iBAAiB,KAAA,CAAS;EAC/D,MAAM,OAAO,gBAAgB;EAC7B,IAAI;GACF,MAAM,YAAY,MAAM,QAAQ,IAC9B,MAAM,KAAI,SAAQ,IAAI,IACpB,GAAG,KAAK,qBAAqB,YAAY,IAAI,GAC/C,CAAC,CACH;GACA,IAAI,eAAe,iBAAiB;GACpC,MAAM,SAAiC,CAAC;GACxC,KAAK,MAAM,YAAY,WACrB,KAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,SAAS,SAAS,GAC7D,OAAO,WAAW,OAAO,WAAW,KAAK;GAG7C,aAAa,QAAQ;EACvB,QAAQ;GAGN,IAAI,eAAe,iBAAiB;IAClC,aAAa,QAAQ,CAAC;IACtB,eAAe;GACjB;EACF;CACF;CAEA,eAAe,WAA0B;EACvC,IAAI,CAAC,QAAQ,SAAS,UAAU,OAAO;EACvC,KAAK;EACL,MAAM,iBAAiB;CACzB;CAEA,SAAS,QAAc;EACrB,KAAK,QAAQ;EACb,YAAY,QAAQ,CAAC;EACrB,MAAM,QAAQ;EACd,iBAAiB;CACnB;CAEA,SAAS,OAAO,YAAqC;EACnD,mBAAmB,QAAQ;CAC7B;CAEA,SAAS,QAAc;EACrB,gBAAgB;EAChB,mBAAmB,QAAQ;EAC3B,QAAQ,SAAS,KAAA;EACjB,QAAQ,SAAS,KAAA;EACjB,QAAQ,UAAU,KAAA;EAClB,QAAQ,iBAAiB,KAAA;EACzB,QAAQ,aAAa,KAAA;EACrB,QAAQ,OAAO,KAAA;EACf,QAAQ,QAAQ;EAChB,KAAK,QAAQ;EACb,QAAQ,WAAW;CACrB;CAEA,YACQ;EACJ,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;CACV,SACM,gBAAgB,GACtB,EAAE,OAAO,OAAO,CAClB;CAEA,MAAM,cAAc,wBACZ,QAAQ,cACR;EACJ,KAAK,QAAQ;EACb,iBAAiB;EACjB,iCAAiC;CACnC,GACA,EAAE,OAAO,IAAI,CACf;CAGA,YACQ;EAAC,QAAQ;EAAQ,QAAQ;EAAS,QAAQ;EAAgB,QAAQ;EAAY,QAAQ;EAAM,QAAQ;CAAK,SACzG;EACJ,YAAY,OAAO;EACnB,KAAK,QAAQ;EACb,iBAAiB;EAEjB,iCAAiC;CACnC,CACF;CAEA,IAAI,WACF,iBAAiB;CAGnB,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECxaA,MAAM,QAAQ;EASd,MAAM,OAAO;EAWb,SAAS,QAAQ;GACf,KAAK,eAAe,KAAK;EAC3B;EAEA,MAAM,kBAAkB,mBAAmB;EAE3C,MAAM,EACJ,aACA,SACA,WACA,OACA,iBACA,UACA,cACA,OAAO,kBACP,oBACA,sBACE,sBAAsB;GACxB,gBAAgB,MAAM;GACtB,wBAAwB,MAAM;GAC9B,kBAAkB;EACpB,CAAC;EAGD,MAAM,YAAY,IAAI,CAAC;EAEvB,MAAM,eAAe;GAAC;GAAW;GAAW;GAAa;EAAW;EAEpE,MAAM,0BAA0B,eAC9B,CAAC,MAAM,kBAAkB,gBAAgB,MAAM,SAAS,CAC1D;EAeA,SAAS,YAAY,KAAgB,UAAkB,SAA+D;GACpH,MAAM,UAAU,QAAQ,QAAQ;GAChC,OAAO,CAAC;IAAE,OAAO;IAAI,OAAO;GAAS,GAAG,GAAG,OAAO,CAAC,CAAC,KAAI,YAAW;IACjE,IAAI,OAAO;IACX,OAAO,OAAO;IACd,SAAS,OAAO,UAAU;GAC5B,EAAE;EACJ;EAEA,MAAM,cAAc,eAA6B;GAC/C,MAAM,QAAsB,CAAC;IAC3B,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO,YAAY,UAAU,cAAc,aAAa,KAAI,WAAU;KACpE,MAAM,QAAQ,aAAa,MAAM;KACjC,MAAM,QAAQ,OAAwB,MAAM;KAC5C,OAAO;MAAE,OAAO;MAAQ,OAAO,UAAU,KAAA,IAAY,QAAQ,GAAG,MAAM,KAAK;KAAQ;IACrF,CAAC,CAAC;IACF,aAAa,QAAQ,SAAS,OAAwB,QAAQ,MAAM,IAAI,KAAA;IACxE,SAAS,QAAQ,SAAS,KAAsB,QAAQ,MAAM,IAAI,KAAA;GACpE,CAAC;GACD,IAAI,SAAS,MAAM,QACjB,MAAM,KAAK;IACT,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO,YAAY,WAAW,eAAe,SAAS,KAAK;IAC3D,aAAa,QAAQ,WAAW,KAAA;GAClC,CAAC;GAEH,IAAI,wBAAwB,OAC1B,MAAM,KAAK;IACT,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO,YAAY,kBAAkB,YAAY,gBAAgB,KAAK;IACtE,aAAa,gBAAgB,MAAM,MAAK,MAAK,EAAE,UAAU,QAAQ,cAAc,CAAC,EAAE,SAC7E,QAAQ,kBAAkB,KAAA;GACjC,CAAC;GAEH,OAAO;EACT,CAAC;EAED,MAAM,oBAAoB,eACxB;GAAC,QAAQ;GAAQ,QAAQ;GAAS,QAAQ;GAAgB,QAAQ;EAAI,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,MAC1F;EAEA,SAAS,eAAe;GACtB,QAAQ,SAAS,KAAA;GACjB,QAAQ,UAAU,KAAA;GAClB,QAAQ,iBAAiB,KAAA;GACzB,QAAQ,OAAO,KAAA;EACjB;EAEA,SAAS,SAAS,YAA+B;GAC/C,MAAM,QAAQ,WAAW;GACzB,OAAO;IACL,MAAM,WAAW;IACjB,OAAO,WAAW;IAClB,MAAM,CACJ,WAAW,gBAAgB,WAAW,WAAW,IACjD,QAAQ,CAAC,MAAM,YAAY,MAAM,SAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,MAAM,WAAW,EAC5F,CAAC,CAAC,OAAO,OAAO;IAChB,QAAQ,WAAW,SACf;KAAE,OAAO,OAAwB,WAAW,MAAM;KAAG,MAAM,KAAsB,WAAW,MAAM;IAAE,IACpG,KAAA;IACJ,MAAM,qBAAqB,WAAW,UAAU;GAClD;EACF;EAEA,SAAS,UAA6C,KAAQ,OAAwB;GACnF,QAAqC,OAAO,OAAO,KAAK,KAAK,KAAA;EAChE;EAEA,SAAS,aAAa,YAA+B;GACnD,gBAAgB,OAAO,UAAU;GACjC,KAAK,eAAe,KAAK;EAC3B;EAEA,SAAS,iBAAiB;GACxB,gBAAgB,MAAM;GACtB,KAAK,eAAe,KAAK;EAC3B;EAGA,YACQ,MAAM,OACX,WAAW;GACV,IAAI,QAAQ;IACV,UAAU;IACV,mBAAmB;IACnB,iBAAiB;IACjB,kBAAkB;GACpB;EACF,GACA,EAAE,WAAW,KAAK,CACpB;;GAIE,OAAA,UAAA,GAAA,YA4FY,mBAAA;IA3FT,MAAM,QAAA;IACN,OAAO,QAAA;IACP,MAAM,QAAA;IACN,iBAAW,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,eAAgB,MAAM;;IAExC,SAAA,cAEM;KAFKA,KAAAA,OAAO,eAAlB,UAAA,GAAA,mBAEM,OAFN,YAEM,CADJ,WAA2B,KAAA,QAAA,aAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;MAG7B,UAAA,GAAA,YAmE6B,oCAAA;MAlE1B,KAAK,UAAA;MACL,QAAQ,MAAA,OAAA,CAAO,CAAC,UAAM;MACvB,OAAM;MACN,oBAAA;MACA,OAAM;MACN,aAAY;MACX,OAAO,MAAA,WAAA;MACP,aAAW,eAAe,WAAW;MAC3B;MACV,gBAAc,QAAA;MACd,SAAS,MAAA,SAAA;MACT,OAAO,MAAA,KAAA;MACR,eAAY;MACX,mBAAa,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,OAAA,CAAO,CAAC,SAAS;MAChC,UAAQ;;MAEE,SAAO,cAEa;QAD7B,UAAA,IAAA,GAAA,mBA6Ba,UAAA,MAAA,WA5BI,YAAA,QAAR,SAAI;QADb,OAAA,UAAA,GAAA,YA6Ba,oBAAA;SA3BV,KAAK,KAAK;SACV,OAAO,KAAK;SACZ,OAAO,KAAK;SACZ,WAAQ,SAAQ,UAAU,KAAK,KAAK,KAAK,EAAE;;SAEjC,SAAO,SAoBP,EAAA,MApBiB,UAAU,QAAQ,cAAO,CACnD,mBAmBS,UAAA;UAlBP,MAAK;UACL,OAAK,eAAA,CAAC,oCAAkC,EAAA,4CACc,KAAK,YAAW,CAAA,CAAA;UACrE,cAAU,GAAK,KAAK,MAAK,IAAK,KAAK,eAAe,KAAK,MAAK,EAAA,CAAI;UACjE,iBAAc;UACb,iBAAe;UACf,SAAO;UACP,WAAS;;UAGF,KAAK,WADb,UAAA,GAAA,mBAKE,QAAA;;WAHA,OAAK,eAAA,CAAC,iCAA+B,kCACK,KAAK,SAAO,CAAA;WACtD,eAAY;UAEd,GAAA,MAAA,CAAA,MAAA,UAAA,GAAA,YAAkE,wBAAlD,KAAK,IAAI,GAAA;;WAAU,MAAM;WAAI,eAAY;;UACzD,mBAAgG,QAAhG,YAAgG,gBAAxC,KAAK,eAAe,KAAK,KAAK,GAAA,CAAA;UACtF,YAA6C,MAAA,WAAA,GAAA;WAA/B,MAAM;WAAI,eAAY;;;;;;;;;;OAI1C,mBASS,UAAA;QARP,MAAK;QACL,OAAK,eAAA,CAAC,oCAAkC,EAAA,4CACc,MAAA,OAAA,CAAO,CAAC,KAAI,CAAA,CAAA;QACjE,gBAAY,CAAA,CAAI,MAAA,OAAA,CAAO,CAAC;QACxB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,MAAA,OAAA,CAAO,CAAC,OAAO,MAAA,OAAA,CAAO,CAAC,OAAO,KAAA,IAAS;OAE/C,GAAA,CAAA,YAAsC,MAAA,IAAA,GAAA;QAA/B,MAAM;QAAI,eAAY;OAAS,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,gBAAA,UAExC,EAAA,EAAA,GAAA,IAAA,UAAA;OAEQ,kBAAA,SADR,UAAA,GAAA,mBAOS,UAAA;;QALP,MAAK;QACL,OAAM;QACL,SAAO;OACT,GAAA,YACO,gBAAG,kBAAA,KAAiB,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;;;;;;;;;;KAKrBA,KAAAA,OAAO,UAAU,QAAA,uBAAmB,QAA/C,UAAA,GAAA,mBAYM,OAZN,YAYM,CAXOA,KAAAA,OAAO,UAAlB,UAAA,GAAA,mBAEM,OAFN,YAEM,CADJ,WAAqC,KAAA,QAAA,UAAA,EAAT,MAAK,CAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAG3B,QAAA,uBAAmB,QAD3B,UAAA,GAAA,mBAOS,UAAA;;MALP,MAAK;MACL,OAAM;MACL,SAAO;KACT,GAAA,mBAED,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA"}
|