@morscherlab/mint-sdk 1.0.12 → 1.0.13
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/{ExperimentPopover-CCYB1oWp.js → ExperimentPopover-B29fIHQz.js} +6 -6
- package/dist/ExperimentPopover-B29fIHQz.js.map +1 -0
- package/dist/{ExperimentPopover-D0bg_fqM.js → ExperimentPopover-gdSA9ZCF.js} +1 -1
- package/dist/{ExperimentSelectorModal-DeBE0YKT.js → ExperimentSelectorModal-CHsU-LIh.js} +3 -3
- package/dist/{ExperimentSelectorModal-DeBE0YKT.js.map → ExperimentSelectorModal-CHsU-LIh.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-BXf-XnQw.js → ExperimentSelectorModal-DIFyL5ta.js} +1 -1
- package/dist/components/index.js +3 -3
- package/dist/{components-CqdBz8DI.js → components-CzFtQExv.js} +7 -7
- package/dist/{components-CqdBz8DI.js.map → components-CzFtQExv.js.map} +1 -1
- package/dist/composables/index.js +4 -4
- package/dist/{composables-BWh0MpcK.js → composables-BuG5yAb7.js} +3 -3
- package/dist/{composables-BWh0MpcK.js.map → composables-BuG5yAb7.js.map} +1 -1
- package/dist/{experiment-utils-hGXMHlAc.js → experiment-utils-D11yT3AR.js} +1 -2
- package/dist/{experiment-utils-hGXMHlAc.js.map → experiment-utils-D11yT3AR.js.map} +1 -1
- package/dist/index.js +7 -7
- package/dist/install.js +3 -3
- package/dist/styles.css +4 -8
- package/dist/{useExperimentSelector-B3hAGvL4.js → useExperimentSelector-BBaz0w51.js} +2 -2
- package/dist/{useExperimentSelector-B3hAGvL4.js.map → useExperimentSelector-BBaz0w51.js.map} +1 -1
- package/dist/{useProtocolTemplates-BJxS5F0_.js → useProtocolTemplates-DODHlhxr.js} +3 -3
- package/dist/{useProtocolTemplates-BJxS5F0_.js.map → useProtocolTemplates-DODHlhxr.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/ExperimentPopover.test.ts +23 -0
- package/src/__tests__/composables/experiment-utils.test.ts +2 -2
- package/src/__tests__/composables/useAppExperiment.test.ts +2 -1
- package/src/components/ExperimentPopover.story.vue +3 -4
- package/src/components/ExperimentPopover.vue +6 -6
- package/src/composables/experiment-utils.ts +1 -1
- package/src/styles/components/experiment-popover.css +2 -4
- package/dist/ExperimentPopover-CCYB1oWp.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,6 +21,29 @@ describe('ExperimentPopover', () => {
|
|
|
21
21
|
document.body.innerHTML = ''
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
+
it('prefers the experiment name in the trigger when both name and code are available', () => {
|
|
25
|
+
const wrapper = mountPopover({
|
|
26
|
+
experimentName: 'Dose response run',
|
|
27
|
+
experimentCode: 'EXP-001',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const trigger = wrapper.find('.mint-experiment-popover__trigger')
|
|
31
|
+
expect(trigger.text()).toContain('Dose response run')
|
|
32
|
+
expect(trigger.text()).not.toContain('EXP-001')
|
|
33
|
+
|
|
34
|
+
wrapper.unmount()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('falls back to the experiment code in the trigger when the name is unavailable', () => {
|
|
38
|
+
const wrapper = mountPopover({
|
|
39
|
+
experimentCode: 'EXP-001',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
expect(wrapper.find('.mint-experiment-popover__trigger').text()).toContain('EXP-001')
|
|
43
|
+
|
|
44
|
+
wrapper.unmount()
|
|
45
|
+
})
|
|
46
|
+
|
|
24
47
|
it('opens and closes the popover on trigger and outside click', async () => {
|
|
25
48
|
const wrapper = mountPopover({
|
|
26
49
|
experimentName: 'Dose response run',
|
|
@@ -22,9 +22,9 @@ describe('experiment-utils', () => {
|
|
|
22
22
|
expect(getExperimentStatusVariant(undefined)).toBe('default')
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
it('resolves formal experiment codes
|
|
25
|
+
it('resolves only formal experiment codes', () => {
|
|
26
26
|
expect(resolveExperimentCode({ id: 42, experiment_code: 'DR-042' })).toBe('DR-042')
|
|
27
|
-
expect(resolveExperimentCode({ id: 42 })).
|
|
27
|
+
expect(resolveExperimentCode({ id: 42 })).toBeUndefined()
|
|
28
28
|
expect(resolveExperimentCode({ id: null })).toBeUndefined()
|
|
29
29
|
})
|
|
30
30
|
})
|
|
@@ -121,12 +121,13 @@ describe('useAppExperiment', () => {
|
|
|
121
121
|
})
|
|
122
122
|
|
|
123
123
|
it('should update experimentName and experimentId on set', () => {
|
|
124
|
-
const { set, experimentName, experimentId } = useAppExperiment()
|
|
124
|
+
const { set, experimentName, experimentCode, experimentId } = useAppExperiment()
|
|
125
125
|
const experiment = makeExperiment({ id: 7, name: 'Alpha Run' })
|
|
126
126
|
|
|
127
127
|
set(experiment)
|
|
128
128
|
|
|
129
129
|
expect(experimentName.value).toBe('Alpha Run')
|
|
130
|
+
expect(experimentCode.value).toBeUndefined()
|
|
130
131
|
expect(experimentId.value).toBe(7)
|
|
131
132
|
})
|
|
132
133
|
|
|
@@ -48,8 +48,8 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
|
|
|
48
48
|
</template>
|
|
49
49
|
|
|
50
50
|
<template #controls="{ state }">
|
|
51
|
-
<HstText v-model="state.experimentCode" title="Experiment code (shown in
|
|
52
|
-
<HstText v-model="state.experimentName" title="Experiment name (shown in
|
|
51
|
+
<HstText v-model="state.experimentCode" title="Experiment code (shown in panel)" />
|
|
52
|
+
<HstText v-model="state.experimentName" title="Experiment name (shown in trigger)" />
|
|
53
53
|
<HstSelect
|
|
54
54
|
v-model="state.experimentStatus"
|
|
55
55
|
title="Status"
|
|
@@ -93,8 +93,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
|
|
|
93
93
|
/>
|
|
94
94
|
</div>
|
|
95
95
|
<div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
|
|
96
|
-
Trigger shows
|
|
97
|
-
live inside.
|
|
96
|
+
Trigger shows the name. Click to open the panel — formal code, status, and actions live inside.
|
|
98
97
|
</div>
|
|
99
98
|
</Variant>
|
|
100
99
|
|
|
@@ -92,8 +92,8 @@ onUnmounted(() => {
|
|
|
92
92
|
{ 'mint-experiment-popover__split--with-save': showSave && experimentName },
|
|
93
93
|
]"
|
|
94
94
|
>
|
|
95
|
-
<!-- Left: experiment trigger (opens popover)
|
|
96
|
-
|
|
95
|
+
<!-- Left: experiment trigger (opens popover). Prefer the human-readable
|
|
96
|
+
name; the formal code, when present, stays in the detail panel. -->
|
|
97
97
|
<button
|
|
98
98
|
type="button"
|
|
99
99
|
:class="[
|
|
@@ -101,7 +101,7 @@ onUnmounted(() => {
|
|
|
101
101
|
{ 'mint-experiment-popover__trigger--active': isOpen },
|
|
102
102
|
{ 'mint-experiment-popover__trigger--empty': !experimentCode && !experimentName },
|
|
103
103
|
]"
|
|
104
|
-
:title="experimentName || undefined"
|
|
104
|
+
:title="experimentName || experimentCode || undefined"
|
|
105
105
|
@click.stop="toggle"
|
|
106
106
|
>
|
|
107
107
|
<!-- Flask icon -->
|
|
@@ -113,9 +113,9 @@ onUnmounted(() => {
|
|
|
113
113
|
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"
|
|
114
114
|
/>
|
|
115
115
|
</svg>
|
|
116
|
-
<!--
|
|
117
|
-
<span v-if="
|
|
118
|
-
<span v-else-if="
|
|
116
|
+
<!-- Name preferred, code as fallback, "No experiment" as empty state -->
|
|
117
|
+
<span v-if="experimentName" class="mint-experiment-popover__trigger-text">{{ experimentName }}</span>
|
|
118
|
+
<span v-else-if="experimentCode" class="mint-experiment-popover__trigger-code">{{ experimentCode }}</span>
|
|
119
119
|
<span v-else class="mint-experiment-popover__trigger-text">No experiment</span>
|
|
120
120
|
<!-- Chevron -->
|
|
121
121
|
<svg class="mint-experiment-popover__trigger-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -64,7 +64,7 @@ export function getExperimentStatusVariant(status?: ExperimentStatus | string |
|
|
|
64
64
|
|
|
65
65
|
export function resolveExperimentCode(experiment: ExperimentCodeSource): string | undefined {
|
|
66
66
|
if (experiment.experiment_code) return experiment.experiment_code
|
|
67
|
-
return
|
|
67
|
+
return undefined
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export const DATE_PRESET_OPTIONS: SelectOption<string>[] = [
|
|
@@ -20,9 +20,7 @@
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/* ─── Trigger (left / main chip) ───
|
|
23
|
-
Shows
|
|
24
|
-
compactness. The trigger is now sized to its content — no fixed max-width
|
|
25
|
-
is needed once the long name is gone. */
|
|
23
|
+
Shows the experiment name, truncated to preserve topbar density. */
|
|
26
24
|
.mint-experiment-popover__trigger {
|
|
27
25
|
display: inline-flex;
|
|
28
26
|
align-items: center;
|
|
@@ -99,7 +97,7 @@
|
|
|
99
97
|
text-align: left;
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
/* Experiment code in the trigger — monospace, tabular
|
|
100
|
+
/* Experiment code fallback in the trigger — monospace, tabular */
|
|
103
101
|
.mint-experiment-popover__trigger-code {
|
|
104
102
|
font-family: var(--font-mono, 'Fira Code', monospace);
|
|
105
103
|
font-size: 0.75rem;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ExperimentPopover-CCYB1oWp.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 BaseModal from './BaseModal.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\nwithDefaults(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</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 <button\n type=\"button\"\n class=\"mint-confirm__btn-cancel\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </button>\n <button\n type=\"button\"\n :class=\"['mint-confirm__btn-confirm', `mint-confirm__btn-confirm--${variant}`]\"\n :disabled=\"loading\"\n @click=\"handleConfirm\"\n >\n <svg v-if=\"loading\" class=\"mint-confirm__btn-spinner\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle style=\"opacity: 0.25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\" />\n <path style=\"opacity: 0.75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\" />\n </svg>\n {{ confirmLabel }}\n </button>\n </div>\n </template>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/confirm-dialog.css';\n</style>\n","<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport BaseModal from './BaseModal.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\nwithDefaults(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</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 <button\n type=\"button\"\n class=\"mint-confirm__btn-cancel\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </button>\n <button\n type=\"button\"\n :class=\"['mint-confirm__btn-confirm', `mint-confirm__btn-confirm--${variant}`]\"\n :disabled=\"loading\"\n @click=\"handleConfirm\"\n >\n <svg v-if=\"loading\" class=\"mint-confirm__btn-spinner\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle style=\"opacity: 0.25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\" />\n <path style=\"opacity: 0.75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\" />\n </svg>\n {{ confirmLabel }}\n </button>\n </div>\n </template>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/confirm-dialog.css';\n</style>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, 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 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 showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, 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) — shows only the code for\n maximum topbar compactness; full name + status live in the panel. -->\n <button\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !experimentCode && !experimentName },\n ]\"\n :title=\"experimentName || undefined\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" 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 <!-- Code preferred, name as fallback, \"No experiment\" as empty state -->\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" 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 @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\" 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\" 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 {{ experimentName ? 'Linked experiment context' : 'Link to an MINT experiment' }}\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-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\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, 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 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 showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, 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) — shows only the code for\n maximum topbar compactness; full name + status live in the panel. -->\n <button\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !experimentCode && !experimentName },\n ]\"\n :title=\"experimentName || undefined\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" 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 <!-- Code preferred, name as fallback, \"No experiment\" as empty state -->\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" 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 @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\" 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\" 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 {{ experimentName ? 'Linked experiment context' : 'Link to an MINT experiment' }}\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-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\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n"],"mappings":"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"}
|