@morscherlab/mld-sdk 0.9.0 → 0.9.2
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/components/ExperimentPopover.vue.d.ts +22 -0
- package/dist/components/ExperimentPopover.vue.js +212 -0
- package/dist/components/ExperimentPopover.vue.js.map +1 -0
- package/dist/components/ExperimentPopover.vue3.js +6 -0
- package/dist/components/ExperimentPopover.vue3.js.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +5 -2
- package/dist/components/index.js.map +1 -1
- package/dist/composables/useApi.js +3 -0
- package/dist/composables/useApi.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/styles.css +445 -0
- package/package.json +1 -1
- package/src/components/ExperimentPopover.vue +177 -0
- package/src/components/index.ts +1 -0
- package/src/composables/useApi.ts +5 -0
- package/src/index.ts +1 -0
- package/src/styles/components/experiment-popover.css +252 -0
- package/src/styles/index.css +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
experimentName?: string;
|
|
3
|
+
experimentStatus?: string;
|
|
4
|
+
showSave?: boolean;
|
|
5
|
+
saveDisabled?: boolean;
|
|
6
|
+
saveLoading?: boolean;
|
|
7
|
+
saveSuccessMessage?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
10
|
+
select: () => any;
|
|
11
|
+
save: () => any;
|
|
12
|
+
}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
|
|
13
|
+
onSelect?: (() => any) | undefined;
|
|
14
|
+
onSave?: (() => any) | undefined;
|
|
15
|
+
}>, {
|
|
16
|
+
showSave: boolean;
|
|
17
|
+
saveDisabled: boolean;
|
|
18
|
+
saveLoading: boolean;
|
|
19
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
|
|
20
|
+
popoverRef: HTMLDivElement;
|
|
21
|
+
}, HTMLDivElement>;
|
|
22
|
+
export default _default;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { defineComponent, ref, watch, onMounted, onUnmounted, openBlock, createElementBlock, createElementVNode, withModifiers, normalizeClass, toDisplayString, createTextVNode, createCommentVNode, Fragment } from "vue";
|
|
2
|
+
const _hoisted_1 = { class: "mld-experiment-popover__trigger-text" };
|
|
3
|
+
const _hoisted_2 = {
|
|
4
|
+
key: 0,
|
|
5
|
+
class: "mld-experiment-popover__panel"
|
|
6
|
+
};
|
|
7
|
+
const _hoisted_3 = {
|
|
8
|
+
key: 0,
|
|
9
|
+
class: "mld-experiment-popover__empty"
|
|
10
|
+
};
|
|
11
|
+
const _hoisted_4 = {
|
|
12
|
+
key: 1,
|
|
13
|
+
class: "mld-experiment-popover__card"
|
|
14
|
+
};
|
|
15
|
+
const _hoisted_5 = { class: "mld-experiment-popover__card-info" };
|
|
16
|
+
const _hoisted_6 = { class: "mld-experiment-popover__card-name" };
|
|
17
|
+
const _hoisted_7 = {
|
|
18
|
+
key: 0,
|
|
19
|
+
class: "mld-experiment-popover__card-status"
|
|
20
|
+
};
|
|
21
|
+
const _hoisted_8 = { class: "mld-experiment-popover__footer" };
|
|
22
|
+
const _hoisted_9 = ["disabled"];
|
|
23
|
+
const _hoisted_10 = {
|
|
24
|
+
key: 0,
|
|
25
|
+
class: "mld-experiment-popover__spinner"
|
|
26
|
+
};
|
|
27
|
+
const _hoisted_11 = {
|
|
28
|
+
key: 1,
|
|
29
|
+
class: "mld-experiment-popover__check-icon",
|
|
30
|
+
fill: "none",
|
|
31
|
+
stroke: "currentColor",
|
|
32
|
+
viewBox: "0 0 24 24"
|
|
33
|
+
};
|
|
34
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
35
|
+
__name: "ExperimentPopover",
|
|
36
|
+
props: {
|
|
37
|
+
experimentName: {},
|
|
38
|
+
experimentStatus: {},
|
|
39
|
+
showSave: { type: Boolean, default: false },
|
|
40
|
+
saveDisabled: { type: Boolean, default: false },
|
|
41
|
+
saveLoading: { type: Boolean, default: false },
|
|
42
|
+
saveSuccessMessage: {}
|
|
43
|
+
},
|
|
44
|
+
emits: ["select", "save"],
|
|
45
|
+
setup(__props, { emit: __emit }) {
|
|
46
|
+
const props = __props;
|
|
47
|
+
const emit = __emit;
|
|
48
|
+
const isOpen = ref(false);
|
|
49
|
+
const popoverRef = ref(null);
|
|
50
|
+
const showSuccess = ref(false);
|
|
51
|
+
function toggle() {
|
|
52
|
+
isOpen.value = !isOpen.value;
|
|
53
|
+
}
|
|
54
|
+
function close() {
|
|
55
|
+
isOpen.value = false;
|
|
56
|
+
}
|
|
57
|
+
function handleSelect() {
|
|
58
|
+
emit("select");
|
|
59
|
+
close();
|
|
60
|
+
}
|
|
61
|
+
function handleSave() {
|
|
62
|
+
if (props.saveDisabled || props.saveLoading) return;
|
|
63
|
+
emit("save");
|
|
64
|
+
}
|
|
65
|
+
function handleClickOutside(event) {
|
|
66
|
+
if (popoverRef.value && !popoverRef.value.contains(event.target)) {
|
|
67
|
+
close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
watch(() => props.saveSuccessMessage, (msg) => {
|
|
71
|
+
if (msg) {
|
|
72
|
+
showSuccess.value = true;
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
showSuccess.value = false;
|
|
75
|
+
}, 3e3);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
onMounted(() => {
|
|
79
|
+
document.addEventListener("click", handleClickOutside);
|
|
80
|
+
});
|
|
81
|
+
onUnmounted(() => {
|
|
82
|
+
document.removeEventListener("click", handleClickOutside);
|
|
83
|
+
});
|
|
84
|
+
function formatStatus(status) {
|
|
85
|
+
return status.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
|
|
86
|
+
}
|
|
87
|
+
return (_ctx, _cache) => {
|
|
88
|
+
return openBlock(), createElementBlock("div", {
|
|
89
|
+
ref_key: "popoverRef",
|
|
90
|
+
ref: popoverRef,
|
|
91
|
+
class: "mld-experiment-popover"
|
|
92
|
+
}, [
|
|
93
|
+
createElementVNode("button", {
|
|
94
|
+
type: "button",
|
|
95
|
+
class: normalizeClass([
|
|
96
|
+
"mld-experiment-popover__trigger",
|
|
97
|
+
{ "mld-experiment-popover__trigger--active": isOpen.value },
|
|
98
|
+
{ "mld-experiment-popover__trigger--empty": !__props.experimentName }
|
|
99
|
+
]),
|
|
100
|
+
onClick: withModifiers(toggle, ["stop"])
|
|
101
|
+
}, [
|
|
102
|
+
_cache[0] || (_cache[0] = createElementVNode("svg", {
|
|
103
|
+
class: "mld-experiment-popover__trigger-icon",
|
|
104
|
+
fill: "none",
|
|
105
|
+
stroke: "currentColor",
|
|
106
|
+
viewBox: "0 0 24 24"
|
|
107
|
+
}, [
|
|
108
|
+
createElementVNode("path", {
|
|
109
|
+
"stroke-linecap": "round",
|
|
110
|
+
"stroke-linejoin": "round",
|
|
111
|
+
"stroke-width": "1.75",
|
|
112
|
+
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"
|
|
113
|
+
})
|
|
114
|
+
], -1)),
|
|
115
|
+
createElementVNode("span", _hoisted_1, toDisplayString(__props.experimentName || "No experiment"), 1),
|
|
116
|
+
_cache[1] || (_cache[1] = createElementVNode("svg", {
|
|
117
|
+
class: "mld-experiment-popover__trigger-chevron",
|
|
118
|
+
viewBox: "0 0 24 24",
|
|
119
|
+
fill: "none",
|
|
120
|
+
stroke: "currentColor",
|
|
121
|
+
"stroke-width": "2",
|
|
122
|
+
"stroke-linecap": "round",
|
|
123
|
+
"stroke-linejoin": "round"
|
|
124
|
+
}, [
|
|
125
|
+
createElementVNode("path", { d: "m6 9 6 6 6-6" })
|
|
126
|
+
], -1))
|
|
127
|
+
], 2),
|
|
128
|
+
isOpen.value ? (openBlock(), createElementBlock("div", _hoisted_2, [
|
|
129
|
+
_cache[6] || (_cache[6] = createElementVNode("div", { class: "mld-experiment-popover__header" }, [
|
|
130
|
+
createElementVNode("div", { class: "mld-experiment-popover__title" }, "Experiment")
|
|
131
|
+
], -1)),
|
|
132
|
+
!__props.experimentName ? (openBlock(), createElementBlock("div", _hoisted_3, [
|
|
133
|
+
createElementVNode("button", {
|
|
134
|
+
type: "button",
|
|
135
|
+
class: "mld-experiment-popover__select-btn",
|
|
136
|
+
onClick: handleSelect
|
|
137
|
+
}, [..._cache[2] || (_cache[2] = [
|
|
138
|
+
createElementVNode("svg", {
|
|
139
|
+
width: "14",
|
|
140
|
+
height: "14",
|
|
141
|
+
fill: "none",
|
|
142
|
+
stroke: "currentColor",
|
|
143
|
+
viewBox: "0 0 24 24"
|
|
144
|
+
}, [
|
|
145
|
+
createElementVNode("path", {
|
|
146
|
+
"stroke-linecap": "round",
|
|
147
|
+
"stroke-linejoin": "round",
|
|
148
|
+
"stroke-width": "2",
|
|
149
|
+
d: "M12 4v16m8-8H4"
|
|
150
|
+
})
|
|
151
|
+
], -1),
|
|
152
|
+
createTextVNode(" Select Experiment ", -1)
|
|
153
|
+
])])
|
|
154
|
+
])) : (openBlock(), createElementBlock("div", _hoisted_4, [
|
|
155
|
+
_cache[3] || (_cache[3] = createElementVNode("div", { class: "mld-experiment-popover__card-icon" }, [
|
|
156
|
+
createElementVNode("svg", {
|
|
157
|
+
fill: "none",
|
|
158
|
+
stroke: "currentColor",
|
|
159
|
+
viewBox: "0 0 24 24"
|
|
160
|
+
}, [
|
|
161
|
+
createElementVNode("path", {
|
|
162
|
+
"stroke-linecap": "round",
|
|
163
|
+
"stroke-linejoin": "round",
|
|
164
|
+
"stroke-width": "1.75",
|
|
165
|
+
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"
|
|
166
|
+
})
|
|
167
|
+
])
|
|
168
|
+
], -1)),
|
|
169
|
+
createElementVNode("div", _hoisted_5, [
|
|
170
|
+
createElementVNode("div", _hoisted_6, toDisplayString(__props.experimentName), 1),
|
|
171
|
+
__props.experimentStatus ? (openBlock(), createElementBlock("div", _hoisted_7, toDisplayString(formatStatus(__props.experimentStatus)), 1)) : createCommentVNode("", true)
|
|
172
|
+
]),
|
|
173
|
+
createElementVNode("button", {
|
|
174
|
+
type: "button",
|
|
175
|
+
class: "mld-experiment-popover__change-btn",
|
|
176
|
+
onClick: handleSelect
|
|
177
|
+
}, " Change ")
|
|
178
|
+
])),
|
|
179
|
+
__props.showSave ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
|
|
180
|
+
_cache[5] || (_cache[5] = createElementVNode("div", { class: "mld-experiment-popover__divider" }, null, -1)),
|
|
181
|
+
createElementVNode("div", _hoisted_8, [
|
|
182
|
+
createElementVNode("button", {
|
|
183
|
+
type: "button",
|
|
184
|
+
class: normalizeClass([
|
|
185
|
+
"mld-experiment-popover__save-btn",
|
|
186
|
+
{ "mld-experiment-popover__save-btn--loading": __props.saveLoading },
|
|
187
|
+
{ "mld-experiment-popover__save-btn--success": showSuccess.value }
|
|
188
|
+
]),
|
|
189
|
+
disabled: __props.saveDisabled && !showSuccess.value,
|
|
190
|
+
onClick: handleSave
|
|
191
|
+
}, [
|
|
192
|
+
__props.saveLoading ? (openBlock(), createElementBlock("span", _hoisted_10)) : showSuccess.value ? (openBlock(), createElementBlock("svg", _hoisted_11, [..._cache[4] || (_cache[4] = [
|
|
193
|
+
createElementVNode("path", {
|
|
194
|
+
"stroke-linecap": "round",
|
|
195
|
+
"stroke-linejoin": "round",
|
|
196
|
+
"stroke-width": "2",
|
|
197
|
+
d: "M5 13l4 4L19 7"
|
|
198
|
+
}, null, -1)
|
|
199
|
+
])])) : createCommentVNode("", true),
|
|
200
|
+
createElementVNode("span", null, toDisplayString(showSuccess.value && __props.saveSuccessMessage ? __props.saveSuccessMessage : "Save to Experiment"), 1)
|
|
201
|
+
], 10, _hoisted_9)
|
|
202
|
+
])
|
|
203
|
+
], 64)) : createCommentVNode("", true)
|
|
204
|
+
])) : createCommentVNode("", true)
|
|
205
|
+
], 512);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
export {
|
|
210
|
+
_sfc_main as default
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=ExperimentPopover.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExperimentPopover.vue.js","sources":["../../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch, onMounted, onUnmounted } from 'vue'\n\ninterface Props {\n experimentName?: string\n experimentStatus?: string\n showSave?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSave: false,\n saveDisabled: false,\n saveLoading: false,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n}>()\n\nconst isOpen = ref(false)\nconst popoverRef = ref<HTMLElement | null>(null)\nconst showSuccess = ref(false)\n\nfunction toggle() {\n isOpen.value = !isOpen.value\n}\n\nfunction close() {\n isOpen.value = false\n}\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n emit('save')\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n if (popoverRef.value && !popoverRef.value.contains(event.target as Node)) {\n close()\n }\n}\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (msg) {\n showSuccess.value = true\n setTimeout(() => {\n showSuccess.value = false\n }, 3000)\n }\n})\n\nonMounted(() => {\n document.addEventListener('click', handleClickOutside)\n})\n\nonUnmounted(() => {\n document.removeEventListener('click', handleClickOutside)\n})\n\n// Format status for display (e.g., \"ready_to_extract\" -> \"Ready to extract\")\nfunction formatStatus(status: string): string {\n return status.replace(/_/g, ' ').replace(/^\\w/, c => c.toUpperCase())\n}\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mld-experiment-popover\">\n <!-- Trigger button -->\n <button\n type=\"button\"\n :class=\"[\n 'mld-experiment-popover__trigger',\n { 'mld-experiment-popover__trigger--active': isOpen },\n { 'mld-experiment-popover__trigger--empty': !experimentName },\n ]\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mld-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 <span class=\"mld-experiment-popover__trigger-text\">\n {{ experimentName || 'No experiment' }}\n </span>\n <!-- Chevron -->\n <svg class=\"mld-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 <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mld-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mld-experiment-popover__header\">\n <div class=\"mld-experiment-popover__title\">Experiment</div>\n </div>\n\n <!-- No experiment selected -->\n <div v-if=\"!experimentName\" class=\"mld-experiment-popover__empty\">\n <button type=\"button\" class=\"mld-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=\"mld-experiment-popover__card\">\n <div class=\"mld-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=\"mld-experiment-popover__card-info\">\n <div class=\"mld-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mld-experiment-popover__card-status\">\n {{ formatStatus(experimentStatus) }}\n </div>\n </div>\n <button type=\"button\" class=\"mld-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n </div>\n\n <!-- Save section -->\n <template v-if=\"showSave\">\n <div class=\"mld-experiment-popover__divider\" />\n <div class=\"mld-experiment-popover__footer\">\n <button\n type=\"button\"\n :class=\"[\n 'mld-experiment-popover__save-btn',\n { 'mld-experiment-popover__save-btn--loading': saveLoading },\n { 'mld-experiment-popover__save-btn--success': showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n @click=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mld-experiment-popover__spinner\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mld-experiment-popover__check-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Label -->\n <span>{{ showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment' }}</span>\n </button>\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_normalizeClass","_toDisplayString","_openBlock","_Fragment"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExperimentPopover.vue3.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
|
@@ -77,4 +77,5 @@ export { default as TimeRangeInput } from './TimeRangeInput.vue';
|
|
|
77
77
|
export { default as ScheduleCalendar } from './ScheduleCalendar.vue';
|
|
78
78
|
export { default as ResourceCard } from './ResourceCard.vue';
|
|
79
79
|
export { default as ExperimentSelectorModal } from './ExperimentSelectorModal.vue';
|
|
80
|
+
export { default as ExperimentPopover } from './ExperimentPopover.vue';
|
|
80
81
|
export { default as FitPanel } from './FitPanel.vue';
|
package/dist/components/index.js
CHANGED
|
@@ -154,7 +154,9 @@ import { default as default79 } from "./ResourceCard.vue.js";
|
|
|
154
154
|
/* empty css */
|
|
155
155
|
import { default as default80 } from "./ExperimentSelectorModal.vue.js";
|
|
156
156
|
/* empty css */
|
|
157
|
-
import { default as default81 } from "./
|
|
157
|
+
import { default as default81 } from "./ExperimentPopover.vue.js";
|
|
158
|
+
/* empty css */
|
|
159
|
+
import { default as default82 } from "./FitPanel.vue.js";
|
|
158
160
|
/* empty css */
|
|
159
161
|
export {
|
|
160
162
|
default25 as AlertBox,
|
|
@@ -194,10 +196,11 @@ export {
|
|
|
194
196
|
default41 as EmptyState,
|
|
195
197
|
default75 as ExperimentCodeBadge,
|
|
196
198
|
default74 as ExperimentDataViewer,
|
|
199
|
+
default81 as ExperimentPopover,
|
|
197
200
|
default80 as ExperimentSelectorModal,
|
|
198
201
|
default51 as ExperimentTimeline,
|
|
199
202
|
default24 as FileUploader,
|
|
200
|
-
|
|
203
|
+
default82 as FitPanel,
|
|
201
204
|
default72 as FormActions,
|
|
202
205
|
default70 as FormBuilder,
|
|
203
206
|
default19 as FormField,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -17,6 +17,9 @@ function useApi(options = {}) {
|
|
|
17
17
|
const settingsStore = useSettingsStore();
|
|
18
18
|
const authStore = useAuthStore();
|
|
19
19
|
const apiClient = getApiClient();
|
|
20
|
+
if (!authStore.isInitialized) {
|
|
21
|
+
authStore.initialize();
|
|
22
|
+
}
|
|
20
23
|
if (!interceptorAttached) {
|
|
21
24
|
apiClient.interceptors.request.use((config) => {
|
|
22
25
|
config.baseURL = options.baseUrl ?? settingsStore.getApiBaseUrl();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useApi.js","sources":["../../src/composables/useApi.ts"],"sourcesContent":["import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'\nimport { useSettingsStore } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\n\nlet apiClientInstance: AxiosInstance | null = null\nlet interceptorAttached = false\n\nfunction getApiClient(): AxiosInstance {\n if (!apiClientInstance) {\n apiClientInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n }\n return apiClientInstance\n}\n\nexport interface ApiClientOptions {\n baseUrl?: string\n timeout?: number\n withAuth?: boolean\n}\n\nexport interface UseApiReturn {\n client: AxiosInstance\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n upload: <T>(url: string, file: File, fieldName?: string, additionalData?: Record<string, unknown>) => Promise<T>\n download: (url: string, filename?: string) => Promise<string>\n buildUrl: (path: string) => string\n buildWsUrl: (path: string) => string\n}\n\nexport function useApi(options: ApiClientOptions = {}): UseApiReturn {\n const settingsStore = useSettingsStore()\n const authStore = useAuthStore()\n const apiClient = getApiClient()\n\n // Attach interceptor only once\n if (!interceptorAttached) {\n apiClient.interceptors.request.use((config) => {\n // Set base URL from settings or options\n config.baseURL = options.baseUrl ?? settingsStore.getApiBaseUrl()\n config.timeout = options.timeout ?? settingsStore.requestTimeout\n\n // Add Authorization header if token exists and auth is not explicitly disabled\n if (options.withAuth !== false && authStore.token) {\n config.headers.Authorization = `Bearer ${authStore.token}`\n }\n\n return config\n })\n interceptorAttached = true\n }\n\n // Generic request methods\n async function get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.get<T>(url, config)\n return response.data\n }\n\n async function post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.post<T>(url, data, config)\n return response.data\n }\n\n async function put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.put<T>(url, data, config)\n return response.data\n }\n\n async function patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.patch<T>(url, data, config)\n return response.data\n }\n\n async function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.delete<T>(url, config)\n return response.data\n }\n\n // File upload helper\n async function upload<T>(url: string, file: File, fieldName = 'file', additionalData?: Record<string, unknown>): Promise<T> {\n const formData = new FormData()\n formData.append(fieldName, file)\n\n if (additionalData) {\n Object.entries(additionalData).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value))\n }\n })\n }\n\n const response = await apiClient.post<T>(url, formData, {\n headers: { 'Content-Type': 'multipart/form-data' },\n })\n return response.data\n }\n\n // Download helper - returns blob URL\n async function download(url: string, filename?: string): Promise<string> {\n const response = await apiClient.get(url, { responseType: 'blob' })\n const blob = new Blob([response.data])\n const blobUrl = URL.createObjectURL(blob)\n\n // Optionally trigger download\n if (filename) {\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = filename\n link.click()\n }\n\n return blobUrl\n }\n\n // Build full URL for external use (e.g., <a href=\"...\">)\n function buildUrl(path: string): string {\n const baseUrl = options.baseUrl ?? settingsStore.getApiBaseUrl()\n return `${baseUrl}${path}`\n }\n\n // WebSocket URL builder\n function buildWsUrl(path: string): string {\n return `${settingsStore.getWsBaseUrl()}${path}`\n }\n\n return {\n client: apiClient,\n get,\n post,\n put,\n patch,\n delete: del,\n upload,\n download,\n buildUrl,\n buildWsUrl,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useApi.js","sources":["../../src/composables/useApi.ts"],"sourcesContent":["import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'\nimport { useSettingsStore } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\n\nlet apiClientInstance: AxiosInstance | null = null\nlet interceptorAttached = false\n\nfunction getApiClient(): AxiosInstance {\n if (!apiClientInstance) {\n apiClientInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n }\n return apiClientInstance\n}\n\nexport interface ApiClientOptions {\n baseUrl?: string\n timeout?: number\n withAuth?: boolean\n}\n\nexport interface UseApiReturn {\n client: AxiosInstance\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n upload: <T>(url: string, file: File, fieldName?: string, additionalData?: Record<string, unknown>) => Promise<T>\n download: (url: string, filename?: string) => Promise<string>\n buildUrl: (path: string) => string\n buildWsUrl: (path: string) => string\n}\n\nexport function useApi(options: ApiClientOptions = {}): UseApiReturn {\n const settingsStore = useSettingsStore()\n const authStore = useAuthStore()\n const apiClient = getApiClient()\n\n // Ensure auth store is initialized (reads token from localStorage)\n if (!authStore.isInitialized) {\n authStore.initialize()\n }\n\n // Attach interceptor only once\n if (!interceptorAttached) {\n apiClient.interceptors.request.use((config) => {\n // Set base URL from settings or options\n config.baseURL = options.baseUrl ?? settingsStore.getApiBaseUrl()\n config.timeout = options.timeout ?? settingsStore.requestTimeout\n\n // Add Authorization header if token exists and auth is not explicitly disabled\n if (options.withAuth !== false && authStore.token) {\n config.headers.Authorization = `Bearer ${authStore.token}`\n }\n\n return config\n })\n interceptorAttached = true\n }\n\n // Generic request methods\n async function get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.get<T>(url, config)\n return response.data\n }\n\n async function post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.post<T>(url, data, config)\n return response.data\n }\n\n async function put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.put<T>(url, data, config)\n return response.data\n }\n\n async function patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.patch<T>(url, data, config)\n return response.data\n }\n\n async function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.delete<T>(url, config)\n return response.data\n }\n\n // File upload helper\n async function upload<T>(url: string, file: File, fieldName = 'file', additionalData?: Record<string, unknown>): Promise<T> {\n const formData = new FormData()\n formData.append(fieldName, file)\n\n if (additionalData) {\n Object.entries(additionalData).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value))\n }\n })\n }\n\n const response = await apiClient.post<T>(url, formData, {\n headers: { 'Content-Type': 'multipart/form-data' },\n })\n return response.data\n }\n\n // Download helper - returns blob URL\n async function download(url: string, filename?: string): Promise<string> {\n const response = await apiClient.get(url, { responseType: 'blob' })\n const blob = new Blob([response.data])\n const blobUrl = URL.createObjectURL(blob)\n\n // Optionally trigger download\n if (filename) {\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = filename\n link.click()\n }\n\n return blobUrl\n }\n\n // Build full URL for external use (e.g., <a href=\"...\">)\n function buildUrl(path: string): string {\n const baseUrl = options.baseUrl ?? settingsStore.getApiBaseUrl()\n return `${baseUrl}${path}`\n }\n\n // WebSocket URL builder\n function buildWsUrl(path: string): string {\n return `${settingsStore.getWsBaseUrl()}${path}`\n }\n\n return {\n client: apiClient,\n get,\n post,\n put,\n patch,\n delete: del,\n upload,\n download,\n buildUrl,\n buildWsUrl,\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { MLDSdk, default } from './install';
|
|
2
|
-
export { BaseButton, BaseInput, BaseTextarea, BaseSelect, BaseCheckbox, BaseToggle, BaseRadioGroup, BaseSlider, ColorSlider, BaseTabs, BaseModal, FormField, DatePicker, TimePicker, TagsInput, NumberInput, FileUploader, AlertBox, ToastNotification, IconButton, ThemeToggle, SettingsButton, CollapsibleCard, AppTopBar, AppSidebar, AppLayout, AppContainer, Skeleton, WellPlate, RackEditor, SampleLegend, PlateMapEditor, ExperimentTimeline, SampleSelector, GroupingModal, AutoGroupModal, GroupAssigner, MoleculeInput, ConcentrationInput, DoseCalculator, ReagentList, SampleHierarchyTree, ProtocolStepEditor, SegmentedControl, MultiSelect, BasePill, DropdownButton, Calendar, DataFrame, LoadingSpinner, Divider, StatusIndicator, ProgressBar, Avatar, EmptyState, Breadcrumb, Tooltip, ConfirmDialog, ChartContainer, SettingsModal, ScientificNumber, ChemicalFormula, FormulaInput, SequenceInput, UnitInput, StepWizard, AuditTrail, BatchProgressList, ExperimentDataViewer, ExperimentCodeBadge, DateTimePicker, TimeRangeInput, ScheduleCalendar, ResourceCard, ExperimentSelectorModal, FitPanel, } from './components';
|
|
2
|
+
export { BaseButton, BaseInput, BaseTextarea, BaseSelect, BaseCheckbox, BaseToggle, BaseRadioGroup, BaseSlider, ColorSlider, BaseTabs, BaseModal, FormField, DatePicker, TimePicker, TagsInput, NumberInput, FileUploader, AlertBox, ToastNotification, IconButton, ThemeToggle, SettingsButton, CollapsibleCard, AppTopBar, AppSidebar, AppLayout, AppContainer, Skeleton, WellPlate, RackEditor, SampleLegend, PlateMapEditor, ExperimentTimeline, SampleSelector, GroupingModal, AutoGroupModal, GroupAssigner, MoleculeInput, ConcentrationInput, DoseCalculator, ReagentList, SampleHierarchyTree, ProtocolStepEditor, SegmentedControl, MultiSelect, BasePill, DropdownButton, Calendar, DataFrame, LoadingSpinner, Divider, StatusIndicator, ProgressBar, Avatar, EmptyState, Breadcrumb, Tooltip, ConfirmDialog, ChartContainer, SettingsModal, ScientificNumber, ChemicalFormula, FormulaInput, SequenceInput, UnitInput, StepWizard, AuditTrail, BatchProgressList, ExperimentDataViewer, ExperimentCodeBadge, DateTimePicker, TimeRangeInput, ScheduleCalendar, ResourceCard, ExperimentSelectorModal, ExperimentPopover, FitPanel, } from './components';
|
|
3
3
|
export { useApi, useAuth, usePasskey, useTheme, useToast, usePlatformContext, useWellPlateEditor, useConcentrationUnits, useDoseCalculator, useProtocolTemplates, useRackEditor, useChemicalFormula, ATOMIC_WEIGHTS, useSequenceUtils, type ApiClientOptions, type UseWellPlateEditorOptions, type UseWellPlateEditorReturn, type UseRackEditorOptions, type UseRackEditorReturn, type ConcentrationValue, type ConcentrationUnit, type VolumeValue, type VolumeUnit, type StepTemplate, type FormulaParseResult, type FormulaPart, type SequenceType, type SequenceStats, parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, useScheduleDrag, usePluginConfig, type UsePluginConfigReturn, useAutoGroup, DEFAULT_COLORS, useExperimentSelector, type UseExperimentSelectorOptions, type UseExperimentSelectorReturn, formatExperimentDate, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, EXPERIMENT_STATUS_LABELS, useExperimentData, type UseExperimentDataOptions, type UseExperimentDataReturn, } from './composables';
|
|
4
4
|
export { useAuthStore, useSettingsStore, colorPalettes, type SettingsState, } from './stores';
|
|
5
5
|
export type { ContainerDirection, ButtonVariant, ButtonSize, InputType, ModalSize, AlertType, Toast, TabItem, SelectOption, RadioOption, FormFieldProps, SidebarToolSection, CollapsibleState, TopBarVariant, TopBarPage, TopBarTab, TopBarTabOption, TopBarSettingsConfig, WellPlateFormat, WellState, WellPlateSelectionMode, Well, HeatmapColorScale, HeatmapConfig, SlotPosition, WellExtendedData, WellEditData, WellEditField, WellLegendItem, Rack, SampleType, PlateMap, PlateMapEditorState, ProtocolStepType, ProtocolStepStatus, ProtocolStep, SampleGroup, GroupItem, OutlierAction, InputMode, OutlierInfo, ColumnInfo, MetadataRow, AutoGroupResult, ParsedCsvData, FileUploaderMode, SegmentedOption, SegmentedControlVariant, SegmentedControlSize, MultiSelectOption, MultiSelectSize, PillVariant, PillSize, CalendarSelectionMode, CalendarMarker, CalendarDayContext, SortDirection, SortState, DataFrameColumn, PaginationState, SpinnerSize, SpinnerVariant, DividerSpacing, StatusType, ProgressVariant, ProgressSize, AvatarSize, EmptyStateColor, EmptyStateSize, BreadcrumbItem, TooltipPosition, ConfirmVariant, SettingsTab, NumberNotation, TimePickerFormat, TimeRange, ScheduleView, ScheduleEventStatus, ScheduleEvent, ScheduleBlockedSlot, ScheduleSlotContext, ScheduleEventCreateContext, ScheduleEventUpdateContext, ResourceStatus, ResourceSpec, ExperimentStatus, ExperimentSummary, ExperimentListResponse, ExperimentFilters, FitState, FitResultSummary, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, AuthConfig, UserInfo, LoginResponse, TokenVerifyResponse, RegisterRequest, UpdateProfileRequest, CredentialInfo, SummaryData, SummarySection, SummarySectionItem, TreeNode, TreeNodeType, PluginInfo, PluginNavItem, PluginSettings, PluginSettingField, PlatformContext, PlatformEventType, PlatformEvent, ThemeMode, ColorPalette, TableDensity, } from './types';
|
package/dist/index.js
CHANGED
|
@@ -149,7 +149,9 @@ import { default as default75 } from "./components/ResourceCard.vue.js";
|
|
|
149
149
|
/* empty css */
|
|
150
150
|
import { default as default76 } from "./components/ExperimentSelectorModal.vue.js";
|
|
151
151
|
/* empty css */
|
|
152
|
-
import { default as default77 } from "./components/
|
|
152
|
+
import { default as default77 } from "./components/ExperimentPopover.vue.js";
|
|
153
|
+
/* empty css */
|
|
154
|
+
import { default as default78 } from "./components/FitPanel.vue.js";
|
|
153
155
|
/* empty css */
|
|
154
156
|
import { useApi } from "./composables/useApi.js";
|
|
155
157
|
import { useAuth } from "./composables/useAuth.js";
|
|
@@ -216,10 +218,11 @@ export {
|
|
|
216
218
|
default41 as EmptyState,
|
|
217
219
|
default71 as ExperimentCodeBadge,
|
|
218
220
|
default70 as ExperimentDataViewer,
|
|
221
|
+
default77 as ExperimentPopover,
|
|
219
222
|
default76 as ExperimentSelectorModal,
|
|
220
223
|
default51 as ExperimentTimeline,
|
|
221
224
|
default24 as FileUploader,
|
|
222
|
-
|
|
225
|
+
default78 as FitPanel,
|
|
223
226
|
default19 as FormField,
|
|
224
227
|
default64 as FormulaInput,
|
|
225
228
|
default55 as GroupAssigner,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/styles.css
CHANGED
|
@@ -12765,6 +12765,223 @@ html.dark .mld-settings-modal__option-btn--active {
|
|
|
12765
12765
|
color: var(--text-muted);
|
|
12766
12766
|
margin-top: 0.125rem;
|
|
12767
12767
|
}
|
|
12768
|
+
/* ExperimentPopover - BEM-style naming */
|
|
12769
|
+
/* Trigger button */
|
|
12770
|
+
.mld-experiment-popover {
|
|
12771
|
+
position: relative;
|
|
12772
|
+
}
|
|
12773
|
+
.mld-experiment-popover__trigger {
|
|
12774
|
+
display: inline-flex;
|
|
12775
|
+
align-items: center;
|
|
12776
|
+
gap: 0.375rem;
|
|
12777
|
+
padding: 0.3125rem 0.625rem;
|
|
12778
|
+
border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
|
|
12779
|
+
background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
|
|
12780
|
+
border-radius: var(--mld-radius, 0.5rem);
|
|
12781
|
+
color: var(--text-primary, var(--mld-text-primary, #111827));
|
|
12782
|
+
font-size: 0.8125rem;
|
|
12783
|
+
font-weight: 500;
|
|
12784
|
+
cursor: pointer;
|
|
12785
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
12786
|
+
white-space: nowrap;
|
|
12787
|
+
max-width: 220px;
|
|
12788
|
+
}
|
|
12789
|
+
.mld-experiment-popover__trigger:hover {
|
|
12790
|
+
border-color: var(--color-primary, #6366F1);
|
|
12791
|
+
}
|
|
12792
|
+
.mld-experiment-popover__trigger--active {
|
|
12793
|
+
border-color: var(--color-primary, #6366F1);
|
|
12794
|
+
box-shadow: 0 0 0 1px var(--color-primary, #6366F1);
|
|
12795
|
+
}
|
|
12796
|
+
.mld-experiment-popover__trigger--empty {
|
|
12797
|
+
border-style: dashed;
|
|
12798
|
+
color: var(--text-secondary, var(--mld-text-secondary, #6b7280));
|
|
12799
|
+
}
|
|
12800
|
+
.mld-experiment-popover__trigger-icon {
|
|
12801
|
+
width: 1rem;
|
|
12802
|
+
height: 1rem;
|
|
12803
|
+
flex-shrink: 0;
|
|
12804
|
+
color: var(--color-primary, #6366F1);
|
|
12805
|
+
}
|
|
12806
|
+
.mld-experiment-popover__trigger--empty .mld-experiment-popover__trigger-icon {
|
|
12807
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
12808
|
+
}
|
|
12809
|
+
.mld-experiment-popover__trigger-text {
|
|
12810
|
+
overflow: hidden;
|
|
12811
|
+
text-overflow: ellipsis;
|
|
12812
|
+
white-space: nowrap;
|
|
12813
|
+
}
|
|
12814
|
+
.mld-experiment-popover__trigger-chevron {
|
|
12815
|
+
width: 0.875rem;
|
|
12816
|
+
height: 0.875rem;
|
|
12817
|
+
flex-shrink: 0;
|
|
12818
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
12819
|
+
transition: transform 0.15s ease;
|
|
12820
|
+
}
|
|
12821
|
+
.mld-experiment-popover__trigger--active .mld-experiment-popover__trigger-chevron {
|
|
12822
|
+
transform: rotate(180deg);
|
|
12823
|
+
}
|
|
12824
|
+
/* Popover panel */
|
|
12825
|
+
.mld-experiment-popover__panel {
|
|
12826
|
+
position: absolute;
|
|
12827
|
+
top: calc(100% + 0.5rem);
|
|
12828
|
+
right: 0;
|
|
12829
|
+
width: 280px;
|
|
12830
|
+
background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
|
|
12831
|
+
border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
|
|
12832
|
+
border-radius: var(--mld-radius, 0.5rem);
|
|
12833
|
+
box-shadow: var(--mld-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05));
|
|
12834
|
+
z-index: 50;
|
|
12835
|
+
overflow: hidden;
|
|
12836
|
+
}
|
|
12837
|
+
/* Header */
|
|
12838
|
+
.mld-experiment-popover__header {
|
|
12839
|
+
padding: 0.75rem 1rem 0.5rem;
|
|
12840
|
+
}
|
|
12841
|
+
.mld-experiment-popover__title {
|
|
12842
|
+
font-size: 0.75rem;
|
|
12843
|
+
font-weight: 600;
|
|
12844
|
+
text-transform: uppercase;
|
|
12845
|
+
letter-spacing: 0.05em;
|
|
12846
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
12847
|
+
}
|
|
12848
|
+
/* Empty state */
|
|
12849
|
+
.mld-experiment-popover__empty {
|
|
12850
|
+
padding: 0 1rem 0.75rem;
|
|
12851
|
+
}
|
|
12852
|
+
.mld-experiment-popover__select-btn {
|
|
12853
|
+
display: flex;
|
|
12854
|
+
align-items: center;
|
|
12855
|
+
justify-content: center;
|
|
12856
|
+
gap: 0.375rem;
|
|
12857
|
+
width: 100%;
|
|
12858
|
+
padding: 0.5rem;
|
|
12859
|
+
border: 1.5px dashed var(--color-primary, #6366F1);
|
|
12860
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.06));
|
|
12861
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
12862
|
+
color: var(--color-primary, #6366F1);
|
|
12863
|
+
font-size: 0.8125rem;
|
|
12864
|
+
font-weight: 500;
|
|
12865
|
+
cursor: pointer;
|
|
12866
|
+
transition: background-color 0.15s ease;
|
|
12867
|
+
}
|
|
12868
|
+
.mld-experiment-popover__select-btn:hover {
|
|
12869
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
|
|
12870
|
+
}
|
|
12871
|
+
/* Experiment card */
|
|
12872
|
+
.mld-experiment-popover__card {
|
|
12873
|
+
display: flex;
|
|
12874
|
+
align-items: center;
|
|
12875
|
+
gap: 0.625rem;
|
|
12876
|
+
padding: 0 1rem 0.75rem;
|
|
12877
|
+
}
|
|
12878
|
+
.mld-experiment-popover__card-icon {
|
|
12879
|
+
width: 2rem;
|
|
12880
|
+
height: 2rem;
|
|
12881
|
+
flex-shrink: 0;
|
|
12882
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
12883
|
+
background: rgba(139, 92, 246, 0.12);
|
|
12884
|
+
display: flex;
|
|
12885
|
+
align-items: center;
|
|
12886
|
+
justify-content: center;
|
|
12887
|
+
}
|
|
12888
|
+
.mld-experiment-popover__card-icon svg {
|
|
12889
|
+
width: 1.125rem;
|
|
12890
|
+
height: 1.125rem;
|
|
12891
|
+
color: #8B5CF6;
|
|
12892
|
+
}
|
|
12893
|
+
.mld-experiment-popover__card-info {
|
|
12894
|
+
flex: 1;
|
|
12895
|
+
min-width: 0;
|
|
12896
|
+
}
|
|
12897
|
+
.mld-experiment-popover__card-name {
|
|
12898
|
+
font-size: 0.8125rem;
|
|
12899
|
+
font-weight: 500;
|
|
12900
|
+
color: var(--text-primary, var(--mld-text-primary, #111827));
|
|
12901
|
+
overflow: hidden;
|
|
12902
|
+
text-overflow: ellipsis;
|
|
12903
|
+
white-space: nowrap;
|
|
12904
|
+
}
|
|
12905
|
+
.mld-experiment-popover__card-status {
|
|
12906
|
+
font-size: 0.6875rem;
|
|
12907
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
12908
|
+
}
|
|
12909
|
+
.mld-experiment-popover__change-btn {
|
|
12910
|
+
flex-shrink: 0;
|
|
12911
|
+
padding: 0.25rem 0.5rem;
|
|
12912
|
+
border: none;
|
|
12913
|
+
background: transparent;
|
|
12914
|
+
border-radius: var(--mld-radius-sm, 0.25rem);
|
|
12915
|
+
color: var(--color-primary, #6366F1);
|
|
12916
|
+
font-size: 0.75rem;
|
|
12917
|
+
font-weight: 500;
|
|
12918
|
+
cursor: pointer;
|
|
12919
|
+
transition: background-color 0.15s ease;
|
|
12920
|
+
}
|
|
12921
|
+
.mld-experiment-popover__change-btn:hover {
|
|
12922
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.08));
|
|
12923
|
+
}
|
|
12924
|
+
/* Divider */
|
|
12925
|
+
.mld-experiment-popover__divider {
|
|
12926
|
+
height: 1px;
|
|
12927
|
+
background: var(--border-color, var(--mld-border, #e5e7eb));
|
|
12928
|
+
margin: 0;
|
|
12929
|
+
}
|
|
12930
|
+
/* Footer (save section) */
|
|
12931
|
+
.mld-experiment-popover__footer {
|
|
12932
|
+
padding: 0.75rem 1rem;
|
|
12933
|
+
}
|
|
12934
|
+
.mld-experiment-popover__save-btn {
|
|
12935
|
+
display: flex;
|
|
12936
|
+
align-items: center;
|
|
12937
|
+
justify-content: center;
|
|
12938
|
+
gap: 0.375rem;
|
|
12939
|
+
width: 100%;
|
|
12940
|
+
padding: 0.4375rem 0.75rem;
|
|
12941
|
+
border: none;
|
|
12942
|
+
background: var(--color-primary, #6366F1);
|
|
12943
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
12944
|
+
color: white;
|
|
12945
|
+
font-size: 0.8125rem;
|
|
12946
|
+
font-weight: 500;
|
|
12947
|
+
cursor: pointer;
|
|
12948
|
+
transition: background-color 0.15s ease, opacity 0.15s ease;
|
|
12949
|
+
}
|
|
12950
|
+
.mld-experiment-popover__save-btn:hover:not(:disabled) {
|
|
12951
|
+
background: var(--color-primary-hover, #4F46E5);
|
|
12952
|
+
}
|
|
12953
|
+
.mld-experiment-popover__save-btn:disabled {
|
|
12954
|
+
opacity: 0.5;
|
|
12955
|
+
cursor: not-allowed;
|
|
12956
|
+
}
|
|
12957
|
+
.mld-experiment-popover__save-btn--loading {
|
|
12958
|
+
opacity: 0.8;
|
|
12959
|
+
pointer-events: none;
|
|
12960
|
+
}
|
|
12961
|
+
/* Save success state */
|
|
12962
|
+
.mld-experiment-popover__save-btn--success {
|
|
12963
|
+
background: var(--mld-success-bg, #059669);
|
|
12964
|
+
}
|
|
12965
|
+
.mld-experiment-popover__save-btn--success:hover {
|
|
12966
|
+
background: var(--mld-success-bg, #059669);
|
|
12967
|
+
}
|
|
12968
|
+
/* Spinner */
|
|
12969
|
+
.mld-experiment-popover__spinner {
|
|
12970
|
+
width: 0.875rem;
|
|
12971
|
+
height: 0.875rem;
|
|
12972
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
12973
|
+
border-top-color: white;
|
|
12974
|
+
border-radius: 50%;
|
|
12975
|
+
animation: mld-experiment-popover-spin 0.6s linear infinite;
|
|
12976
|
+
}
|
|
12977
|
+
@keyframes mld-experiment-popover-spin {
|
|
12978
|
+
to { transform: rotate(360deg); }
|
|
12979
|
+
}
|
|
12980
|
+
/* Check icon for success */
|
|
12981
|
+
.mld-experiment-popover__check-icon {
|
|
12982
|
+
width: 0.875rem;
|
|
12983
|
+
height: 0.875rem;
|
|
12984
|
+
}
|
|
12768
12985
|
/* FitPanel Component Styles */
|
|
12769
12986
|
.mld-fit-panel {
|
|
12770
12987
|
display: flex;
|
|
@@ -25176,6 +25393,234 @@ to { transform: rotate(360deg);
|
|
|
25176
25393
|
color: var(--text-muted);
|
|
25177
25394
|
margin-top: 0.125rem;
|
|
25178
25395
|
}
|
|
25396
|
+
/* ExperimentPopover - BEM-style naming */
|
|
25397
|
+
|
|
25398
|
+
/* Trigger button */
|
|
25399
|
+
.mld-experiment-popover {
|
|
25400
|
+
position: relative;
|
|
25401
|
+
}
|
|
25402
|
+
.mld-experiment-popover__trigger {
|
|
25403
|
+
display: inline-flex;
|
|
25404
|
+
align-items: center;
|
|
25405
|
+
gap: 0.375rem;
|
|
25406
|
+
padding: 0.3125rem 0.625rem;
|
|
25407
|
+
border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
|
|
25408
|
+
background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
|
|
25409
|
+
border-radius: var(--mld-radius, 0.5rem);
|
|
25410
|
+
color: var(--text-primary, var(--mld-text-primary, #111827));
|
|
25411
|
+
font-size: 0.8125rem;
|
|
25412
|
+
font-weight: 500;
|
|
25413
|
+
cursor: pointer;
|
|
25414
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
25415
|
+
white-space: nowrap;
|
|
25416
|
+
max-width: 220px;
|
|
25417
|
+
}
|
|
25418
|
+
.mld-experiment-popover__trigger:hover {
|
|
25419
|
+
border-color: var(--color-primary, #6366F1);
|
|
25420
|
+
}
|
|
25421
|
+
.mld-experiment-popover__trigger--active {
|
|
25422
|
+
border-color: var(--color-primary, #6366F1);
|
|
25423
|
+
box-shadow: 0 0 0 1px var(--color-primary, #6366F1);
|
|
25424
|
+
}
|
|
25425
|
+
.mld-experiment-popover__trigger--empty {
|
|
25426
|
+
border-style: dashed;
|
|
25427
|
+
color: var(--text-secondary, var(--mld-text-secondary, #6b7280));
|
|
25428
|
+
}
|
|
25429
|
+
.mld-experiment-popover__trigger-icon {
|
|
25430
|
+
width: 1rem;
|
|
25431
|
+
height: 1rem;
|
|
25432
|
+
flex-shrink: 0;
|
|
25433
|
+
color: var(--color-primary, #6366F1);
|
|
25434
|
+
}
|
|
25435
|
+
.mld-experiment-popover__trigger--empty .mld-experiment-popover__trigger-icon {
|
|
25436
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
25437
|
+
}
|
|
25438
|
+
.mld-experiment-popover__trigger-text {
|
|
25439
|
+
overflow: hidden;
|
|
25440
|
+
text-overflow: ellipsis;
|
|
25441
|
+
white-space: nowrap;
|
|
25442
|
+
}
|
|
25443
|
+
.mld-experiment-popover__trigger-chevron {
|
|
25444
|
+
width: 0.875rem;
|
|
25445
|
+
height: 0.875rem;
|
|
25446
|
+
flex-shrink: 0;
|
|
25447
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
25448
|
+
transition: transform 0.15s ease;
|
|
25449
|
+
}
|
|
25450
|
+
.mld-experiment-popover__trigger--active .mld-experiment-popover__trigger-chevron {
|
|
25451
|
+
transform: rotate(180deg);
|
|
25452
|
+
}
|
|
25453
|
+
|
|
25454
|
+
/* Popover panel */
|
|
25455
|
+
.mld-experiment-popover__panel {
|
|
25456
|
+
position: absolute;
|
|
25457
|
+
top: calc(100% + 0.5rem);
|
|
25458
|
+
right: 0;
|
|
25459
|
+
width: 280px;
|
|
25460
|
+
background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
|
|
25461
|
+
border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
|
|
25462
|
+
border-radius: var(--mld-radius, 0.5rem);
|
|
25463
|
+
box-shadow: var(--mld-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05));
|
|
25464
|
+
z-index: 50;
|
|
25465
|
+
overflow: hidden;
|
|
25466
|
+
}
|
|
25467
|
+
|
|
25468
|
+
/* Header */
|
|
25469
|
+
.mld-experiment-popover__header {
|
|
25470
|
+
padding: 0.75rem 1rem 0.5rem;
|
|
25471
|
+
}
|
|
25472
|
+
.mld-experiment-popover__title {
|
|
25473
|
+
font-size: 0.75rem;
|
|
25474
|
+
font-weight: 600;
|
|
25475
|
+
text-transform: uppercase;
|
|
25476
|
+
letter-spacing: 0.05em;
|
|
25477
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
25478
|
+
}
|
|
25479
|
+
|
|
25480
|
+
/* Empty state */
|
|
25481
|
+
.mld-experiment-popover__empty {
|
|
25482
|
+
padding: 0 1rem 0.75rem;
|
|
25483
|
+
}
|
|
25484
|
+
.mld-experiment-popover__select-btn {
|
|
25485
|
+
display: flex;
|
|
25486
|
+
align-items: center;
|
|
25487
|
+
justify-content: center;
|
|
25488
|
+
gap: 0.375rem;
|
|
25489
|
+
width: 100%;
|
|
25490
|
+
padding: 0.5rem;
|
|
25491
|
+
border: 1.5px dashed var(--color-primary, #6366F1);
|
|
25492
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.06));
|
|
25493
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
25494
|
+
color: var(--color-primary, #6366F1);
|
|
25495
|
+
font-size: 0.8125rem;
|
|
25496
|
+
font-weight: 500;
|
|
25497
|
+
cursor: pointer;
|
|
25498
|
+
transition: background-color 0.15s ease;
|
|
25499
|
+
}
|
|
25500
|
+
.mld-experiment-popover__select-btn:hover {
|
|
25501
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
|
|
25502
|
+
}
|
|
25503
|
+
|
|
25504
|
+
/* Experiment card */
|
|
25505
|
+
.mld-experiment-popover__card {
|
|
25506
|
+
display: flex;
|
|
25507
|
+
align-items: center;
|
|
25508
|
+
gap: 0.625rem;
|
|
25509
|
+
padding: 0 1rem 0.75rem;
|
|
25510
|
+
}
|
|
25511
|
+
.mld-experiment-popover__card-icon {
|
|
25512
|
+
width: 2rem;
|
|
25513
|
+
height: 2rem;
|
|
25514
|
+
flex-shrink: 0;
|
|
25515
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
25516
|
+
background: rgba(139, 92, 246, 0.12);
|
|
25517
|
+
display: flex;
|
|
25518
|
+
align-items: center;
|
|
25519
|
+
justify-content: center;
|
|
25520
|
+
}
|
|
25521
|
+
.mld-experiment-popover__card-icon svg {
|
|
25522
|
+
width: 1.125rem;
|
|
25523
|
+
height: 1.125rem;
|
|
25524
|
+
color: #8B5CF6;
|
|
25525
|
+
}
|
|
25526
|
+
.mld-experiment-popover__card-info {
|
|
25527
|
+
flex: 1;
|
|
25528
|
+
min-width: 0;
|
|
25529
|
+
}
|
|
25530
|
+
.mld-experiment-popover__card-name {
|
|
25531
|
+
font-size: 0.8125rem;
|
|
25532
|
+
font-weight: 500;
|
|
25533
|
+
color: var(--text-primary, var(--mld-text-primary, #111827));
|
|
25534
|
+
overflow: hidden;
|
|
25535
|
+
text-overflow: ellipsis;
|
|
25536
|
+
white-space: nowrap;
|
|
25537
|
+
}
|
|
25538
|
+
.mld-experiment-popover__card-status {
|
|
25539
|
+
font-size: 0.6875rem;
|
|
25540
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
25541
|
+
}
|
|
25542
|
+
.mld-experiment-popover__change-btn {
|
|
25543
|
+
flex-shrink: 0;
|
|
25544
|
+
padding: 0.25rem 0.5rem;
|
|
25545
|
+
border: none;
|
|
25546
|
+
background: transparent;
|
|
25547
|
+
border-radius: var(--mld-radius-sm, 0.25rem);
|
|
25548
|
+
color: var(--color-primary, #6366F1);
|
|
25549
|
+
font-size: 0.75rem;
|
|
25550
|
+
font-weight: 500;
|
|
25551
|
+
cursor: pointer;
|
|
25552
|
+
transition: background-color 0.15s ease;
|
|
25553
|
+
}
|
|
25554
|
+
.mld-experiment-popover__change-btn:hover {
|
|
25555
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.08));
|
|
25556
|
+
}
|
|
25557
|
+
|
|
25558
|
+
/* Divider */
|
|
25559
|
+
.mld-experiment-popover__divider {
|
|
25560
|
+
height: 1px;
|
|
25561
|
+
background: var(--border-color, var(--mld-border, #e5e7eb));
|
|
25562
|
+
margin: 0;
|
|
25563
|
+
}
|
|
25564
|
+
|
|
25565
|
+
/* Footer (save section) */
|
|
25566
|
+
.mld-experiment-popover__footer {
|
|
25567
|
+
padding: 0.75rem 1rem;
|
|
25568
|
+
}
|
|
25569
|
+
.mld-experiment-popover__save-btn {
|
|
25570
|
+
display: flex;
|
|
25571
|
+
align-items: center;
|
|
25572
|
+
justify-content: center;
|
|
25573
|
+
gap: 0.375rem;
|
|
25574
|
+
width: 100%;
|
|
25575
|
+
padding: 0.4375rem 0.75rem;
|
|
25576
|
+
border: none;
|
|
25577
|
+
background: var(--color-primary, #6366F1);
|
|
25578
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
25579
|
+
color: white;
|
|
25580
|
+
font-size: 0.8125rem;
|
|
25581
|
+
font-weight: 500;
|
|
25582
|
+
cursor: pointer;
|
|
25583
|
+
transition: background-color 0.15s ease, opacity 0.15s ease;
|
|
25584
|
+
}
|
|
25585
|
+
.mld-experiment-popover__save-btn:hover:not(:disabled) {
|
|
25586
|
+
background: var(--color-primary-hover, #4F46E5);
|
|
25587
|
+
}
|
|
25588
|
+
.mld-experiment-popover__save-btn:disabled {
|
|
25589
|
+
opacity: 0.5;
|
|
25590
|
+
cursor: not-allowed;
|
|
25591
|
+
}
|
|
25592
|
+
.mld-experiment-popover__save-btn--loading {
|
|
25593
|
+
opacity: 0.8;
|
|
25594
|
+
pointer-events: none;
|
|
25595
|
+
}
|
|
25596
|
+
|
|
25597
|
+
/* Save success state */
|
|
25598
|
+
.mld-experiment-popover__save-btn--success {
|
|
25599
|
+
background: var(--mld-success-bg, #059669);
|
|
25600
|
+
}
|
|
25601
|
+
.mld-experiment-popover__save-btn--success:hover {
|
|
25602
|
+
background: var(--mld-success-bg, #059669);
|
|
25603
|
+
}
|
|
25604
|
+
|
|
25605
|
+
/* Spinner */
|
|
25606
|
+
.mld-experiment-popover__spinner {
|
|
25607
|
+
width: 0.875rem;
|
|
25608
|
+
height: 0.875rem;
|
|
25609
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
25610
|
+
border-top-color: white;
|
|
25611
|
+
border-radius: 50%;
|
|
25612
|
+
animation: mld-experiment-popover-spin 0.6s linear infinite;
|
|
25613
|
+
}
|
|
25614
|
+
@keyframes mld-experiment-popover-spin {
|
|
25615
|
+
to { transform: rotate(360deg);
|
|
25616
|
+
}
|
|
25617
|
+
}
|
|
25618
|
+
|
|
25619
|
+
/* Check icon for success */
|
|
25620
|
+
.mld-experiment-popover__check-icon {
|
|
25621
|
+
width: 0.875rem;
|
|
25622
|
+
height: 0.875rem;
|
|
25623
|
+
}
|
|
25179
25624
|
/* FitPanel Component Styles */
|
|
25180
25625
|
.mld-fit-panel {
|
|
25181
25626
|
display: flex;
|
package/package.json
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
experimentName?: string
|
|
6
|
+
experimentStatus?: string
|
|
7
|
+
showSave?: boolean
|
|
8
|
+
saveDisabled?: boolean
|
|
9
|
+
saveLoading?: boolean
|
|
10
|
+
saveSuccessMessage?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
14
|
+
showSave: false,
|
|
15
|
+
saveDisabled: false,
|
|
16
|
+
saveLoading: false,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits<{
|
|
20
|
+
select: []
|
|
21
|
+
save: []
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
const isOpen = ref(false)
|
|
25
|
+
const popoverRef = ref<HTMLElement | null>(null)
|
|
26
|
+
const showSuccess = ref(false)
|
|
27
|
+
|
|
28
|
+
function toggle() {
|
|
29
|
+
isOpen.value = !isOpen.value
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function close() {
|
|
33
|
+
isOpen.value = false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function handleSelect() {
|
|
37
|
+
emit('select')
|
|
38
|
+
close()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleSave() {
|
|
42
|
+
if (props.saveDisabled || props.saveLoading) return
|
|
43
|
+
emit('save')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function handleClickOutside(event: MouseEvent) {
|
|
47
|
+
if (popoverRef.value && !popoverRef.value.contains(event.target as Node)) {
|
|
48
|
+
close()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Show success state when saveSuccessMessage changes from empty to a value
|
|
53
|
+
watch(() => props.saveSuccessMessage, (msg) => {
|
|
54
|
+
if (msg) {
|
|
55
|
+
showSuccess.value = true
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
showSuccess.value = false
|
|
58
|
+
}, 3000)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
onMounted(() => {
|
|
63
|
+
document.addEventListener('click', handleClickOutside)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
onUnmounted(() => {
|
|
67
|
+
document.removeEventListener('click', handleClickOutside)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// Format status for display (e.g., "ready_to_extract" -> "Ready to extract")
|
|
71
|
+
function formatStatus(status: string): string {
|
|
72
|
+
return status.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase())
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<template>
|
|
77
|
+
<div ref="popoverRef" class="mld-experiment-popover">
|
|
78
|
+
<!-- Trigger button -->
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
:class="[
|
|
82
|
+
'mld-experiment-popover__trigger',
|
|
83
|
+
{ 'mld-experiment-popover__trigger--active': isOpen },
|
|
84
|
+
{ 'mld-experiment-popover__trigger--empty': !experimentName },
|
|
85
|
+
]"
|
|
86
|
+
@click.stop="toggle"
|
|
87
|
+
>
|
|
88
|
+
<!-- Flask icon -->
|
|
89
|
+
<svg class="mld-experiment-popover__trigger-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
90
|
+
<path
|
|
91
|
+
stroke-linecap="round"
|
|
92
|
+
stroke-linejoin="round"
|
|
93
|
+
stroke-width="1.75"
|
|
94
|
+
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"
|
|
95
|
+
/>
|
|
96
|
+
</svg>
|
|
97
|
+
<span class="mld-experiment-popover__trigger-text">
|
|
98
|
+
{{ experimentName || 'No experiment' }}
|
|
99
|
+
</span>
|
|
100
|
+
<!-- Chevron -->
|
|
101
|
+
<svg class="mld-experiment-popover__trigger-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
102
|
+
<path d="m6 9 6 6 6-6" />
|
|
103
|
+
</svg>
|
|
104
|
+
</button>
|
|
105
|
+
|
|
106
|
+
<!-- Popover panel -->
|
|
107
|
+
<div v-if="isOpen" class="mld-experiment-popover__panel">
|
|
108
|
+
<!-- Header -->
|
|
109
|
+
<div class="mld-experiment-popover__header">
|
|
110
|
+
<div class="mld-experiment-popover__title">Experiment</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- No experiment selected -->
|
|
114
|
+
<div v-if="!experimentName" class="mld-experiment-popover__empty">
|
|
115
|
+
<button type="button" class="mld-experiment-popover__select-btn" @click="handleSelect">
|
|
116
|
+
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
117
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
118
|
+
</svg>
|
|
119
|
+
Select Experiment
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Experiment selected -->
|
|
124
|
+
<div v-else class="mld-experiment-popover__card">
|
|
125
|
+
<div class="mld-experiment-popover__card-icon">
|
|
126
|
+
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
127
|
+
<path
|
|
128
|
+
stroke-linecap="round"
|
|
129
|
+
stroke-linejoin="round"
|
|
130
|
+
stroke-width="1.75"
|
|
131
|
+
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"
|
|
132
|
+
/>
|
|
133
|
+
</svg>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="mld-experiment-popover__card-info">
|
|
136
|
+
<div class="mld-experiment-popover__card-name">{{ experimentName }}</div>
|
|
137
|
+
<div v-if="experimentStatus" class="mld-experiment-popover__card-status">
|
|
138
|
+
{{ formatStatus(experimentStatus) }}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
<button type="button" class="mld-experiment-popover__change-btn" @click="handleSelect">
|
|
142
|
+
Change
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<!-- Save section -->
|
|
147
|
+
<template v-if="showSave">
|
|
148
|
+
<div class="mld-experiment-popover__divider" />
|
|
149
|
+
<div class="mld-experiment-popover__footer">
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
:class="[
|
|
153
|
+
'mld-experiment-popover__save-btn',
|
|
154
|
+
{ 'mld-experiment-popover__save-btn--loading': saveLoading },
|
|
155
|
+
{ 'mld-experiment-popover__save-btn--success': showSuccess },
|
|
156
|
+
]"
|
|
157
|
+
:disabled="saveDisabled && !showSuccess"
|
|
158
|
+
@click="handleSave"
|
|
159
|
+
>
|
|
160
|
+
<!-- Loading spinner -->
|
|
161
|
+
<span v-if="saveLoading" class="mld-experiment-popover__spinner" />
|
|
162
|
+
<!-- Success check -->
|
|
163
|
+
<svg v-else-if="showSuccess" class="mld-experiment-popover__check-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
164
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
165
|
+
</svg>
|
|
166
|
+
<!-- Label -->
|
|
167
|
+
<span>{{ showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment' }}</span>
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
</template>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</template>
|
|
174
|
+
|
|
175
|
+
<style>
|
|
176
|
+
@import '../styles/components/experiment-popover.css';
|
|
177
|
+
</style>
|
package/src/components/index.ts
CHANGED
|
@@ -108,4 +108,5 @@ export { default as ResourceCard } from './ResourceCard.vue'
|
|
|
108
108
|
|
|
109
109
|
// Experiment / analysis components
|
|
110
110
|
export { default as ExperimentSelectorModal } from './ExperimentSelectorModal.vue'
|
|
111
|
+
export { default as ExperimentPopover } from './ExperimentPopover.vue'
|
|
111
112
|
export { default as FitPanel } from './FitPanel.vue'
|
|
@@ -40,6 +40,11 @@ export function useApi(options: ApiClientOptions = {}): UseApiReturn {
|
|
|
40
40
|
const authStore = useAuthStore()
|
|
41
41
|
const apiClient = getApiClient()
|
|
42
42
|
|
|
43
|
+
// Ensure auth store is initialized (reads token from localStorage)
|
|
44
|
+
if (!authStore.isInitialized) {
|
|
45
|
+
authStore.initialize()
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
// Attach interceptor only once
|
|
44
49
|
if (!interceptorAttached) {
|
|
45
50
|
apiClient.interceptors.request.use((config) => {
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/* ExperimentPopover - BEM-style naming */
|
|
2
|
+
|
|
3
|
+
/* Trigger button */
|
|
4
|
+
.mld-experiment-popover {
|
|
5
|
+
position: relative;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.mld-experiment-popover__trigger {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 0.375rem;
|
|
12
|
+
padding: 0.3125rem 0.625rem;
|
|
13
|
+
border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
|
|
14
|
+
background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
|
|
15
|
+
border-radius: var(--mld-radius, 0.5rem);
|
|
16
|
+
color: var(--text-primary, var(--mld-text-primary, #111827));
|
|
17
|
+
font-size: 0.8125rem;
|
|
18
|
+
font-weight: 500;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
21
|
+
white-space: nowrap;
|
|
22
|
+
max-width: 220px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.mld-experiment-popover__trigger:hover {
|
|
26
|
+
border-color: var(--color-primary, #6366F1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.mld-experiment-popover__trigger--active {
|
|
30
|
+
border-color: var(--color-primary, #6366F1);
|
|
31
|
+
box-shadow: 0 0 0 1px var(--color-primary, #6366F1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.mld-experiment-popover__trigger--empty {
|
|
35
|
+
border-style: dashed;
|
|
36
|
+
color: var(--text-secondary, var(--mld-text-secondary, #6b7280));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.mld-experiment-popover__trigger-icon {
|
|
40
|
+
width: 1rem;
|
|
41
|
+
height: 1rem;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
color: var(--color-primary, #6366F1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.mld-experiment-popover__trigger--empty .mld-experiment-popover__trigger-icon {
|
|
47
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.mld-experiment-popover__trigger-text {
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
text-overflow: ellipsis;
|
|
53
|
+
white-space: nowrap;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.mld-experiment-popover__trigger-chevron {
|
|
57
|
+
width: 0.875rem;
|
|
58
|
+
height: 0.875rem;
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
61
|
+
transition: transform 0.15s ease;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.mld-experiment-popover__trigger--active .mld-experiment-popover__trigger-chevron {
|
|
65
|
+
transform: rotate(180deg);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Popover panel */
|
|
69
|
+
.mld-experiment-popover__panel {
|
|
70
|
+
position: absolute;
|
|
71
|
+
top: calc(100% + 0.5rem);
|
|
72
|
+
right: 0;
|
|
73
|
+
width: 280px;
|
|
74
|
+
background: var(--bg-secondary, var(--mld-bg-card, #ffffff));
|
|
75
|
+
border: 1px solid var(--border-color, var(--mld-border, #e5e7eb));
|
|
76
|
+
border-radius: var(--mld-radius, 0.5rem);
|
|
77
|
+
box-shadow: var(--mld-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05));
|
|
78
|
+
z-index: 50;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Header */
|
|
83
|
+
.mld-experiment-popover__header {
|
|
84
|
+
padding: 0.75rem 1rem 0.5rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.mld-experiment-popover__title {
|
|
88
|
+
font-size: 0.75rem;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
text-transform: uppercase;
|
|
91
|
+
letter-spacing: 0.05em;
|
|
92
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Empty state */
|
|
96
|
+
.mld-experiment-popover__empty {
|
|
97
|
+
padding: 0 1rem 0.75rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.mld-experiment-popover__select-btn {
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: center;
|
|
104
|
+
gap: 0.375rem;
|
|
105
|
+
width: 100%;
|
|
106
|
+
padding: 0.5rem;
|
|
107
|
+
border: 1.5px dashed var(--color-primary, #6366F1);
|
|
108
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.06));
|
|
109
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
110
|
+
color: var(--color-primary, #6366F1);
|
|
111
|
+
font-size: 0.8125rem;
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
transition: background-color 0.15s ease;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.mld-experiment-popover__select-btn:hover {
|
|
118
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.12));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Experiment card */
|
|
122
|
+
.mld-experiment-popover__card {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 0.625rem;
|
|
126
|
+
padding: 0 1rem 0.75rem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.mld-experiment-popover__card-icon {
|
|
130
|
+
width: 2rem;
|
|
131
|
+
height: 2rem;
|
|
132
|
+
flex-shrink: 0;
|
|
133
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
134
|
+
background: rgba(139, 92, 246, 0.12);
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.mld-experiment-popover__card-icon svg {
|
|
141
|
+
width: 1.125rem;
|
|
142
|
+
height: 1.125rem;
|
|
143
|
+
color: #8B5CF6;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.mld-experiment-popover__card-info {
|
|
147
|
+
flex: 1;
|
|
148
|
+
min-width: 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.mld-experiment-popover__card-name {
|
|
152
|
+
font-size: 0.8125rem;
|
|
153
|
+
font-weight: 500;
|
|
154
|
+
color: var(--text-primary, var(--mld-text-primary, #111827));
|
|
155
|
+
overflow: hidden;
|
|
156
|
+
text-overflow: ellipsis;
|
|
157
|
+
white-space: nowrap;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.mld-experiment-popover__card-status {
|
|
161
|
+
font-size: 0.6875rem;
|
|
162
|
+
color: var(--text-muted, var(--mld-text-muted, #9ca3af));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.mld-experiment-popover__change-btn {
|
|
166
|
+
flex-shrink: 0;
|
|
167
|
+
padding: 0.25rem 0.5rem;
|
|
168
|
+
border: none;
|
|
169
|
+
background: transparent;
|
|
170
|
+
border-radius: var(--mld-radius-sm, 0.25rem);
|
|
171
|
+
color: var(--color-primary, #6366F1);
|
|
172
|
+
font-size: 0.75rem;
|
|
173
|
+
font-weight: 500;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
transition: background-color 0.15s ease;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.mld-experiment-popover__change-btn:hover {
|
|
179
|
+
background: var(--color-primary-soft, rgba(99, 102, 241, 0.08));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Divider */
|
|
183
|
+
.mld-experiment-popover__divider {
|
|
184
|
+
height: 1px;
|
|
185
|
+
background: var(--border-color, var(--mld-border, #e5e7eb));
|
|
186
|
+
margin: 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Footer (save section) */
|
|
190
|
+
.mld-experiment-popover__footer {
|
|
191
|
+
padding: 0.75rem 1rem;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.mld-experiment-popover__save-btn {
|
|
195
|
+
display: flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
justify-content: center;
|
|
198
|
+
gap: 0.375rem;
|
|
199
|
+
width: 100%;
|
|
200
|
+
padding: 0.4375rem 0.75rem;
|
|
201
|
+
border: none;
|
|
202
|
+
background: var(--color-primary, #6366F1);
|
|
203
|
+
border-radius: var(--mld-radius-sm, 0.375rem);
|
|
204
|
+
color: white;
|
|
205
|
+
font-size: 0.8125rem;
|
|
206
|
+
font-weight: 500;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
transition: background-color 0.15s ease, opacity 0.15s ease;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.mld-experiment-popover__save-btn:hover:not(:disabled) {
|
|
212
|
+
background: var(--color-primary-hover, #4F46E5);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.mld-experiment-popover__save-btn:disabled {
|
|
216
|
+
opacity: 0.5;
|
|
217
|
+
cursor: not-allowed;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.mld-experiment-popover__save-btn--loading {
|
|
221
|
+
opacity: 0.8;
|
|
222
|
+
pointer-events: none;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Save success state */
|
|
226
|
+
.mld-experiment-popover__save-btn--success {
|
|
227
|
+
background: var(--mld-success-bg, #059669);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.mld-experiment-popover__save-btn--success:hover {
|
|
231
|
+
background: var(--mld-success-bg, #059669);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* Spinner */
|
|
235
|
+
.mld-experiment-popover__spinner {
|
|
236
|
+
width: 0.875rem;
|
|
237
|
+
height: 0.875rem;
|
|
238
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
239
|
+
border-top-color: white;
|
|
240
|
+
border-radius: 50%;
|
|
241
|
+
animation: mld-experiment-popover-spin 0.6s linear infinite;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@keyframes mld-experiment-popover-spin {
|
|
245
|
+
to { transform: rotate(360deg); }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Check icon for success */
|
|
249
|
+
.mld-experiment-popover__check-icon {
|
|
250
|
+
width: 0.875rem;
|
|
251
|
+
height: 0.875rem;
|
|
252
|
+
}
|
package/src/styles/index.css
CHANGED