@morscherlab/mld-sdk 0.9.4 → 0.9.5
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/ExperimentSelectorModal.vue.d.ts +6 -0
- package/dist/components/ExperimentSelectorModal.vue.js +284 -35
- package/dist/components/ExperimentSelectorModal.vue.js.map +1 -1
- package/dist/components/ResourceCard.vue.d.ts +1 -1
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/composables/experiment-utils.d.ts +4 -1
- package/dist/composables/experiment-utils.js +22 -0
- package/dist/composables/experiment-utils.js.map +1 -1
- package/dist/composables/index.d.ts +1 -1
- package/dist/composables/index.js +4 -1
- package/dist/composables/useExperimentSelector.d.ts +6 -1
- package/dist/composables/useExperimentSelector.js +76 -7
- package/dist/composables/useExperimentSelector.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -1
- package/dist/stores/auth.js +21 -14
- package/dist/stores/auth.js.map +1 -1
- package/dist/styles.css +277 -6
- package/dist/types/components.d.ts +10 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/ExperimentSelectorModal.story.vue +170 -23
- package/src/components/ExperimentSelectorModal.vue +205 -14
- package/src/composables/experiment-utils.ts +23 -1
- package/src/composables/index.ts +3 -0
- package/src/composables/useExperimentSelector.ts +113 -9
- package/src/index.ts +3 -0
- package/src/stores/auth.ts +24 -16
- package/src/styles/components/experiment-selector-modal.css +152 -3
- package/src/types/components.ts +11 -0
- package/src/types/index.ts +3 -0
|
@@ -5,17 +5,23 @@ interface Props {
|
|
|
5
5
|
currentExperimentId?: number | null;
|
|
6
6
|
title?: string;
|
|
7
7
|
size?: ModalSize;
|
|
8
|
+
groupByProject?: boolean;
|
|
9
|
+
showFilters?: boolean;
|
|
8
10
|
}
|
|
9
11
|
declare const _default: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
10
12
|
select: (experiment: ExperimentSummary) => any;
|
|
11
13
|
"update:modelValue": (value: boolean) => any;
|
|
14
|
+
deselect: () => any;
|
|
12
15
|
}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
|
|
13
16
|
onSelect?: ((experiment: ExperimentSummary) => any) | undefined;
|
|
14
17
|
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
18
|
+
onDeselect?: (() => any) | undefined;
|
|
15
19
|
}>, {
|
|
16
20
|
size: ModalSize;
|
|
17
21
|
title: string;
|
|
22
|
+
showFilters: boolean;
|
|
18
23
|
currentExperimentId: number | null;
|
|
24
|
+
groupByProject: boolean;
|
|
19
25
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
|
|
20
26
|
listRef: HTMLDivElement;
|
|
21
27
|
}, any>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { defineComponent, ref, watch, openBlock, createBlock, withCtx, createElementVNode, createVNode, unref, createElementBlock,
|
|
1
|
+
import { defineComponent, ref, computed, reactive, watch, openBlock, createBlock, withCtx, createElementVNode, createVNode, unref, createElementBlock, createCommentVNode, normalizeClass, createTextVNode, withDirectives, vModelCheckbox, Fragment, renderList, toDisplayString, nextTick } from "vue";
|
|
2
2
|
import { useExperimentSelector } from "../composables/useExperimentSelector.js";
|
|
3
|
-
import { EXPERIMENT_STATUS_OPTIONS, formatExperimentDate, EXPERIMENT_STATUS_VARIANT_MAP } from "../composables/experiment-utils.js";
|
|
3
|
+
import { EXPERIMENT_STATUS_OPTIONS, DATE_PRESET_OPTIONS, SORT_OPTIONS, formatExperimentDate, EXPERIMENT_STATUS_VARIANT_MAP } from "../composables/experiment-utils.js";
|
|
4
4
|
import _sfc_main$1 from "./BaseModal.vue.js";
|
|
5
5
|
/* empty css */
|
|
6
6
|
import _sfc_main$2 from "./BaseInput.vue.js";
|
|
@@ -15,23 +15,53 @@ import _sfc_main$5 from "./EmptyState.vue.js";
|
|
|
15
15
|
/* empty css */
|
|
16
16
|
import _sfc_main$6 from "./ExperimentCodeBadge.vue.js";
|
|
17
17
|
/* empty css */
|
|
18
|
-
const _hoisted_1 = { class: "mld-experiment-selector__filters" };
|
|
18
|
+
const _hoisted_1 = { class: "mld-experiment-selector__filters-row" };
|
|
19
19
|
const _hoisted_2 = { class: "mld-experiment-selector__search" };
|
|
20
|
-
const _hoisted_3 = { class: "mld-experiment-
|
|
20
|
+
const _hoisted_3 = { class: "mld-experiment-selector__filter-select" };
|
|
21
21
|
const _hoisted_4 = {
|
|
22
22
|
key: 0,
|
|
23
|
-
class: "mld-experiment-
|
|
23
|
+
class: "mld-experiment-selector__filter-select"
|
|
24
|
+
};
|
|
25
|
+
const _hoisted_5 = {
|
|
26
|
+
key: 0,
|
|
27
|
+
class: "mld-experiment-selector__filters-dot"
|
|
24
28
|
};
|
|
25
|
-
const _hoisted_5 = { class: "mld-experiment-selector__skeleton-content" };
|
|
26
29
|
const _hoisted_6 = {
|
|
30
|
+
key: 0,
|
|
31
|
+
class: "mld-experiment-selector__filters-advanced"
|
|
32
|
+
};
|
|
33
|
+
const _hoisted_7 = {
|
|
34
|
+
key: 0,
|
|
35
|
+
class: "mld-experiment-selector__filter-select"
|
|
36
|
+
};
|
|
37
|
+
const _hoisted_8 = { class: "mld-experiment-selector__filter-select" };
|
|
38
|
+
const _hoisted_9 = { class: "mld-experiment-selector__filter-select" };
|
|
39
|
+
const _hoisted_10 = { class: "mld-experiment-selector__group-toggle" };
|
|
40
|
+
const _hoisted_11 = {
|
|
27
41
|
key: 1,
|
|
42
|
+
class: "mld-experiment-selector__skeleton"
|
|
43
|
+
};
|
|
44
|
+
const _hoisted_12 = { class: "mld-experiment-selector__skeleton-content" };
|
|
45
|
+
const _hoisted_13 = {
|
|
46
|
+
key: 2,
|
|
28
47
|
class: "mld-experiment-selector__error"
|
|
29
48
|
};
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
49
|
+
const _hoisted_14 = ["onClick"];
|
|
50
|
+
const _hoisted_15 = { class: "mld-experiment-selector__group-name" };
|
|
51
|
+
const _hoisted_16 = { class: "mld-experiment-selector__group-count" };
|
|
52
|
+
const _hoisted_17 = ["onClick", "onMouseenter"];
|
|
53
|
+
const _hoisted_18 = { class: "mld-experiment-selector__row-content" };
|
|
54
|
+
const _hoisted_19 = { class: "mld-experiment-selector__name" };
|
|
55
|
+
const _hoisted_20 = { class: "mld-experiment-selector__meta" };
|
|
56
|
+
const _hoisted_21 = ["onClick", "onMouseenter"];
|
|
57
|
+
const _hoisted_22 = { class: "mld-experiment-selector__row-content" };
|
|
58
|
+
const _hoisted_23 = { class: "mld-experiment-selector__name" };
|
|
59
|
+
const _hoisted_24 = { class: "mld-experiment-selector__meta" };
|
|
60
|
+
const _hoisted_25 = { key: 0 };
|
|
61
|
+
const _hoisted_26 = {
|
|
62
|
+
key: 6,
|
|
63
|
+
class: "mld-experiment-selector__footer"
|
|
64
|
+
};
|
|
35
65
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
36
66
|
__name: "ExperimentSelectorModal",
|
|
37
67
|
props: {
|
|
@@ -39,9 +69,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
39
69
|
experimentType: {},
|
|
40
70
|
currentExperimentId: { default: null },
|
|
41
71
|
title: { default: "Select Experiment" },
|
|
42
|
-
size: { default: "full" }
|
|
72
|
+
size: { default: "full" },
|
|
73
|
+
groupByProject: { type: Boolean, default: false },
|
|
74
|
+
showFilters: { type: Boolean, default: false }
|
|
43
75
|
},
|
|
44
|
-
emits: ["update:modelValue", "select"],
|
|
76
|
+
emits: ["update:modelValue", "select", "deselect"],
|
|
45
77
|
setup(__props, { emit: __emit }) {
|
|
46
78
|
const props = __props;
|
|
47
79
|
const emit = __emit;
|
|
@@ -50,25 +82,55 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
50
82
|
filters,
|
|
51
83
|
isLoading,
|
|
52
84
|
error,
|
|
53
|
-
|
|
85
|
+
sortKey,
|
|
86
|
+
experimentTypes,
|
|
87
|
+
projects,
|
|
88
|
+
groupedByProject,
|
|
89
|
+
fetch: fetchExperiments,
|
|
90
|
+
fetchFilterOptions
|
|
54
91
|
} = useExperimentSelector({
|
|
55
92
|
experimentType: props.experimentType
|
|
56
93
|
});
|
|
57
94
|
const activeIndex = ref(-1);
|
|
58
95
|
const listRef = ref(null);
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
const showAdvanced = ref(props.showFilters);
|
|
97
|
+
const groupToggle = ref(props.groupByProject);
|
|
98
|
+
const hasActiveAdvancedFilters = computed(
|
|
99
|
+
() => !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== "created_at:desc")
|
|
100
|
+
);
|
|
101
|
+
const typeFilterOptions = computed(() => [
|
|
102
|
+
{ value: "", label: "All types" },
|
|
103
|
+
...experimentTypes.value.map((t) => ({ value: t.value, label: t.label }))
|
|
104
|
+
]);
|
|
105
|
+
const projectFilterOptions = computed(() => [
|
|
106
|
+
{ value: "", label: "All projects" },
|
|
107
|
+
...projects.value
|
|
108
|
+
]);
|
|
109
|
+
const flatExperiments = computed(() => {
|
|
110
|
+
if (!groupToggle.value) return experiments.value;
|
|
111
|
+
return groupedByProject.value.flatMap(([, exps]) => exps);
|
|
112
|
+
});
|
|
113
|
+
function setFilter(key, value) {
|
|
114
|
+
filters[key] = String(value) || null;
|
|
115
|
+
}
|
|
116
|
+
function handleSortChange(value) {
|
|
117
|
+
sortKey.value = String(value) || "created_at:desc";
|
|
61
118
|
}
|
|
62
119
|
function handleSelect(experiment) {
|
|
63
120
|
emit("select", experiment);
|
|
64
121
|
emit("update:modelValue", false);
|
|
65
122
|
}
|
|
123
|
+
function handleDeselect() {
|
|
124
|
+
emit("deselect");
|
|
125
|
+
emit("update:modelValue", false);
|
|
126
|
+
}
|
|
66
127
|
function handleKeydown(event) {
|
|
67
|
-
|
|
128
|
+
const list = flatExperiments.value;
|
|
129
|
+
if (!list.length) return;
|
|
68
130
|
switch (event.key) {
|
|
69
131
|
case "ArrowDown":
|
|
70
132
|
event.preventDefault();
|
|
71
|
-
activeIndex.value = Math.min(activeIndex.value + 1,
|
|
133
|
+
activeIndex.value = Math.min(activeIndex.value + 1, list.length - 1);
|
|
72
134
|
scrollActiveIntoView();
|
|
73
135
|
break;
|
|
74
136
|
case "ArrowUp":
|
|
@@ -78,8 +140,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
78
140
|
break;
|
|
79
141
|
case "Enter":
|
|
80
142
|
event.preventDefault();
|
|
81
|
-
if (activeIndex.value >= 0 && activeIndex.value <
|
|
82
|
-
handleSelect(
|
|
143
|
+
if (activeIndex.value >= 0 && activeIndex.value < list.length) {
|
|
144
|
+
handleSelect(list[activeIndex.value]);
|
|
83
145
|
}
|
|
84
146
|
break;
|
|
85
147
|
}
|
|
@@ -91,6 +153,22 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
91
153
|
row == null ? void 0 : row.scrollIntoView({ block: "nearest" });
|
|
92
154
|
});
|
|
93
155
|
}
|
|
156
|
+
const flatIndexMap = computed(() => {
|
|
157
|
+
const map = /* @__PURE__ */ new Map();
|
|
158
|
+
flatExperiments.value.forEach((exp, i) => map.set(exp.id, i));
|
|
159
|
+
return map;
|
|
160
|
+
});
|
|
161
|
+
function getFlatIndex(experiment) {
|
|
162
|
+
return flatIndexMap.value.get(experiment.id) ?? -1;
|
|
163
|
+
}
|
|
164
|
+
const collapsedGroups = reactive(/* @__PURE__ */ new Set());
|
|
165
|
+
function toggleGroup(groupName) {
|
|
166
|
+
if (collapsedGroups.has(groupName)) {
|
|
167
|
+
collapsedGroups.delete(groupName);
|
|
168
|
+
} else {
|
|
169
|
+
collapsedGroups.add(groupName);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
94
172
|
watch(experiments, () => {
|
|
95
173
|
activeIndex.value = -1;
|
|
96
174
|
});
|
|
@@ -99,6 +177,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
99
177
|
(isOpen) => {
|
|
100
178
|
if (isOpen) {
|
|
101
179
|
activeIndex.value = -1;
|
|
180
|
+
collapsedGroups.clear();
|
|
181
|
+
fetchFilterOptions();
|
|
102
182
|
fetchExperiments();
|
|
103
183
|
}
|
|
104
184
|
}
|
|
@@ -108,7 +188,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
108
188
|
"model-value": __props.modelValue,
|
|
109
189
|
title: __props.title,
|
|
110
190
|
size: __props.size,
|
|
111
|
-
"onUpdate:modelValue": _cache[
|
|
191
|
+
"onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => emit("update:modelValue", $event))
|
|
112
192
|
}, {
|
|
113
193
|
default: withCtx(() => [
|
|
114
194
|
createElementVNode("div", {
|
|
@@ -130,17 +210,113 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
130
210
|
"model-value": unref(filters).status ?? "",
|
|
131
211
|
options: unref(EXPERIMENT_STATUS_OPTIONS),
|
|
132
212
|
size: "sm",
|
|
133
|
-
"onUpdate:modelValue":
|
|
213
|
+
"onUpdate:modelValue": _cache[1] || (_cache[1] = (v) => setFilter("status", v))
|
|
134
214
|
}, null, 8, ["model-value", "options"])
|
|
135
|
-
])
|
|
215
|
+
]),
|
|
216
|
+
typeFilterOptions.value.length > 1 ? (openBlock(), createElementBlock("div", _hoisted_4, [
|
|
217
|
+
createVNode(_sfc_main$3, {
|
|
218
|
+
"model-value": unref(filters).experimentType ?? "",
|
|
219
|
+
options: typeFilterOptions.value,
|
|
220
|
+
size: "sm",
|
|
221
|
+
"onUpdate:modelValue": _cache[2] || (_cache[2] = (v) => setFilter("experimentType", v))
|
|
222
|
+
}, null, 8, ["model-value", "options"])
|
|
223
|
+
])) : createCommentVNode("", true),
|
|
224
|
+
createElementVNode("button", {
|
|
225
|
+
class: normalizeClass(["mld-experiment-selector__filters-toggle", { "mld-experiment-selector__filters-toggle--active": hasActiveAdvancedFilters.value }]),
|
|
226
|
+
type: "button",
|
|
227
|
+
onClick: _cache[3] || (_cache[3] = ($event) => showAdvanced.value = !showAdvanced.value)
|
|
228
|
+
}, [
|
|
229
|
+
_cache[8] || (_cache[8] = createElementVNode("svg", {
|
|
230
|
+
width: "14",
|
|
231
|
+
height: "14",
|
|
232
|
+
viewBox: "0 0 24 24",
|
|
233
|
+
fill: "none",
|
|
234
|
+
stroke: "currentColor",
|
|
235
|
+
"stroke-width": "2",
|
|
236
|
+
"stroke-linecap": "round",
|
|
237
|
+
"stroke-linejoin": "round"
|
|
238
|
+
}, [
|
|
239
|
+
createElementVNode("line", {
|
|
240
|
+
x1: "4",
|
|
241
|
+
y1: "6",
|
|
242
|
+
x2: "20",
|
|
243
|
+
y2: "6"
|
|
244
|
+
}),
|
|
245
|
+
createElementVNode("line", {
|
|
246
|
+
x1: "8",
|
|
247
|
+
y1: "12",
|
|
248
|
+
x2: "20",
|
|
249
|
+
y2: "12"
|
|
250
|
+
}),
|
|
251
|
+
createElementVNode("line", {
|
|
252
|
+
x1: "12",
|
|
253
|
+
y1: "18",
|
|
254
|
+
x2: "20",
|
|
255
|
+
y2: "18"
|
|
256
|
+
}),
|
|
257
|
+
createElementVNode("circle", {
|
|
258
|
+
cx: "6",
|
|
259
|
+
cy: "12",
|
|
260
|
+
r: "2"
|
|
261
|
+
}),
|
|
262
|
+
createElementVNode("circle", {
|
|
263
|
+
cx: "10",
|
|
264
|
+
cy: "18",
|
|
265
|
+
r: "2"
|
|
266
|
+
}),
|
|
267
|
+
createElementVNode("circle", {
|
|
268
|
+
cx: "6",
|
|
269
|
+
cy: "6",
|
|
270
|
+
r: "2"
|
|
271
|
+
})
|
|
272
|
+
], -1)),
|
|
273
|
+
_cache[9] || (_cache[9] = createTextVNode(" Filters ", -1)),
|
|
274
|
+
hasActiveAdvancedFilters.value ? (openBlock(), createElementBlock("span", _hoisted_5)) : createCommentVNode("", true)
|
|
275
|
+
], 2)
|
|
136
276
|
]),
|
|
137
|
-
|
|
277
|
+
showAdvanced.value ? (openBlock(), createElementBlock("div", _hoisted_6, [
|
|
278
|
+
projectFilterOptions.value.length > 1 ? (openBlock(), createElementBlock("div", _hoisted_7, [
|
|
279
|
+
createVNode(_sfc_main$3, {
|
|
280
|
+
"model-value": unref(filters).project ?? "",
|
|
281
|
+
options: projectFilterOptions.value,
|
|
282
|
+
size: "sm",
|
|
283
|
+
"onUpdate:modelValue": _cache[4] || (_cache[4] = (v) => setFilter("project", v))
|
|
284
|
+
}, null, 8, ["model-value", "options"])
|
|
285
|
+
])) : createCommentVNode("", true),
|
|
286
|
+
createElementVNode("div", _hoisted_8, [
|
|
287
|
+
createVNode(_sfc_main$3, {
|
|
288
|
+
"model-value": unref(filters).datePreset ?? "",
|
|
289
|
+
options: unref(DATE_PRESET_OPTIONS),
|
|
290
|
+
size: "sm",
|
|
291
|
+
"onUpdate:modelValue": _cache[5] || (_cache[5] = (v) => setFilter("datePreset", v))
|
|
292
|
+
}, null, 8, ["model-value", "options"])
|
|
293
|
+
]),
|
|
294
|
+
createElementVNode("div", _hoisted_9, [
|
|
295
|
+
createVNode(_sfc_main$3, {
|
|
296
|
+
"model-value": unref(sortKey),
|
|
297
|
+
options: unref(SORT_OPTIONS),
|
|
298
|
+
size: "sm",
|
|
299
|
+
"onUpdate:modelValue": handleSortChange
|
|
300
|
+
}, null, 8, ["model-value", "options"])
|
|
301
|
+
]),
|
|
302
|
+
createElementVNode("label", _hoisted_10, [
|
|
303
|
+
withDirectives(createElementVNode("input", {
|
|
304
|
+
"onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => groupToggle.value = $event),
|
|
305
|
+
type: "checkbox",
|
|
306
|
+
class: "mld-experiment-selector__group-checkbox"
|
|
307
|
+
}, null, 512), [
|
|
308
|
+
[vModelCheckbox, groupToggle.value]
|
|
309
|
+
]),
|
|
310
|
+
_cache[10] || (_cache[10] = createTextVNode(" Group by project ", -1))
|
|
311
|
+
])
|
|
312
|
+
])) : createCommentVNode("", true),
|
|
313
|
+
unref(isLoading) ? (openBlock(), createElementBlock("div", _hoisted_11, [
|
|
138
314
|
(openBlock(), createElementBlock(Fragment, null, renderList(4, (n) => {
|
|
139
315
|
return createElementVNode("div", {
|
|
140
316
|
key: n,
|
|
141
317
|
class: "mld-experiment-selector__skeleton-row"
|
|
142
318
|
}, [
|
|
143
|
-
createElementVNode("div",
|
|
319
|
+
createElementVNode("div", _hoisted_12, [
|
|
144
320
|
createVNode(_sfc_main$4, {
|
|
145
321
|
width: 120 + n * 20,
|
|
146
322
|
height: "14px"
|
|
@@ -157,13 +333,79 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
157
333
|
})
|
|
158
334
|
]);
|
|
159
335
|
}), 64))
|
|
160
|
-
])) : unref(error) ? (openBlock(), createElementBlock("div",
|
|
161
|
-
key:
|
|
336
|
+
])) : unref(error) ? (openBlock(), createElementBlock("div", _hoisted_13, toDisplayString(unref(error)), 1)) : unref(experiments).length === 0 ? (openBlock(), createBlock(_sfc_main$5, {
|
|
337
|
+
key: 3,
|
|
162
338
|
title: "No experiments found",
|
|
163
339
|
description: "Try adjusting your search or filters.",
|
|
164
340
|
size: "sm"
|
|
165
|
-
})) : (openBlock(), createElementBlock("div", {
|
|
166
|
-
key:
|
|
341
|
+
})) : groupToggle.value ? (openBlock(), createElementBlock("div", {
|
|
342
|
+
key: 4,
|
|
343
|
+
ref_key: "listRef",
|
|
344
|
+
ref: listRef,
|
|
345
|
+
class: "mld-experiment-selector__list"
|
|
346
|
+
}, [
|
|
347
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(groupedByProject), ([groupName, groupExps]) => {
|
|
348
|
+
return openBlock(), createElementBlock(Fragment, { key: groupName }, [
|
|
349
|
+
createElementVNode("button", {
|
|
350
|
+
type: "button",
|
|
351
|
+
class: "mld-experiment-selector__group-header",
|
|
352
|
+
onClick: ($event) => toggleGroup(groupName)
|
|
353
|
+
}, [
|
|
354
|
+
(openBlock(), createElementBlock("svg", {
|
|
355
|
+
class: normalizeClass(["mld-experiment-selector__group-chevron", { "mld-experiment-selector__group-chevron--collapsed": collapsedGroups.has(groupName) }]),
|
|
356
|
+
width: "14",
|
|
357
|
+
height: "14",
|
|
358
|
+
viewBox: "0 0 24 24",
|
|
359
|
+
fill: "none",
|
|
360
|
+
stroke: "currentColor",
|
|
361
|
+
"stroke-width": "2",
|
|
362
|
+
"stroke-linecap": "round",
|
|
363
|
+
"stroke-linejoin": "round"
|
|
364
|
+
}, [..._cache[11] || (_cache[11] = [
|
|
365
|
+
createElementVNode("polyline", { points: "6 9 12 15 18 9" }, null, -1)
|
|
366
|
+
])], 2)),
|
|
367
|
+
createElementVNode("span", _hoisted_15, toDisplayString(groupName), 1),
|
|
368
|
+
createElementVNode("span", _hoisted_16, toDisplayString(groupExps.length), 1)
|
|
369
|
+
], 8, _hoisted_14),
|
|
370
|
+
!collapsedGroups.has(groupName) ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(groupExps, (exp) => {
|
|
371
|
+
return openBlock(), createElementBlock("div", {
|
|
372
|
+
key: exp.id,
|
|
373
|
+
class: normalizeClass(["mld-experiment-selector__row", {
|
|
374
|
+
"mld-experiment-selector__row--active": exp.id === __props.currentExperimentId,
|
|
375
|
+
"mld-experiment-selector__row--focused": getFlatIndex(exp) === activeIndex.value
|
|
376
|
+
}]),
|
|
377
|
+
onClick: ($event) => handleSelect(exp),
|
|
378
|
+
onMouseenter: ($event) => activeIndex.value = getFlatIndex(exp)
|
|
379
|
+
}, [
|
|
380
|
+
createElementVNode("div", _hoisted_18, [
|
|
381
|
+
createElementVNode("div", _hoisted_19, [
|
|
382
|
+
createTextVNode(toDisplayString(exp.name) + " ", 1),
|
|
383
|
+
exp.experiment_code ? (openBlock(), createBlock(_sfc_main$6, {
|
|
384
|
+
key: 0,
|
|
385
|
+
code: exp.experiment_code,
|
|
386
|
+
size: "sm",
|
|
387
|
+
copyable: false
|
|
388
|
+
}, null, 8, ["code"])) : createCommentVNode("", true)
|
|
389
|
+
]),
|
|
390
|
+
createElementVNode("div", _hoisted_20, [
|
|
391
|
+
createElementVNode("span", null, toDisplayString(unref(formatExperimentDate)(exp.created_at)), 1)
|
|
392
|
+
])
|
|
393
|
+
]),
|
|
394
|
+
createVNode(_sfc_main$7, {
|
|
395
|
+
variant: unref(EXPERIMENT_STATUS_VARIANT_MAP)[exp.status],
|
|
396
|
+
size: "sm"
|
|
397
|
+
}, {
|
|
398
|
+
default: withCtx(() => [
|
|
399
|
+
createTextVNode(toDisplayString(exp.status), 1)
|
|
400
|
+
]),
|
|
401
|
+
_: 2
|
|
402
|
+
}, 1032, ["variant"])
|
|
403
|
+
], 42, _hoisted_17);
|
|
404
|
+
}), 128)) : createCommentVNode("", true)
|
|
405
|
+
], 64);
|
|
406
|
+
}), 128))
|
|
407
|
+
], 512)) : (openBlock(), createElementBlock("div", {
|
|
408
|
+
key: 5,
|
|
167
409
|
ref_key: "listRef",
|
|
168
410
|
ref: listRef,
|
|
169
411
|
class: "mld-experiment-selector__list"
|
|
@@ -178,8 +420,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
178
420
|
onClick: ($event) => handleSelect(exp),
|
|
179
421
|
onMouseenter: ($event) => activeIndex.value = idx
|
|
180
422
|
}, [
|
|
181
|
-
createElementVNode("div",
|
|
182
|
-
createElementVNode("div",
|
|
423
|
+
createElementVNode("div", _hoisted_22, [
|
|
424
|
+
createElementVNode("div", _hoisted_23, [
|
|
183
425
|
createTextVNode(toDisplayString(exp.name) + " ", 1),
|
|
184
426
|
exp.experiment_code ? (openBlock(), createBlock(_sfc_main$6, {
|
|
185
427
|
key: 0,
|
|
@@ -188,8 +430,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
188
430
|
copyable: false
|
|
189
431
|
}, null, 8, ["code"])) : createCommentVNode("", true)
|
|
190
432
|
]),
|
|
191
|
-
createElementVNode("div",
|
|
192
|
-
exp.project ? (openBlock(), createElementBlock("span",
|
|
433
|
+
createElementVNode("div", _hoisted_24, [
|
|
434
|
+
exp.project_name || exp.project ? (openBlock(), createElementBlock("span", _hoisted_25, toDisplayString(exp.project_name || exp.project), 1)) : createCommentVNode("", true),
|
|
193
435
|
createElementVNode("span", null, toDisplayString(unref(formatExperimentDate)(exp.created_at)), 1)
|
|
194
436
|
])
|
|
195
437
|
]),
|
|
@@ -202,9 +444,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
202
444
|
]),
|
|
203
445
|
_: 2
|
|
204
446
|
}, 1032, ["variant"])
|
|
205
|
-
], 42,
|
|
447
|
+
], 42, _hoisted_21);
|
|
206
448
|
}), 128))
|
|
207
|
-
], 512))
|
|
449
|
+
], 512)),
|
|
450
|
+
__props.currentExperimentId != null ? (openBlock(), createElementBlock("div", _hoisted_26, [
|
|
451
|
+
createElementVNode("button", {
|
|
452
|
+
type: "button",
|
|
453
|
+
class: "mld-experiment-selector__clear-btn",
|
|
454
|
+
onClick: handleDeselect
|
|
455
|
+
}, " Clear selection ")
|
|
456
|
+
])) : createCommentVNode("", true)
|
|
208
457
|
], 32)
|
|
209
458
|
]),
|
|
210
459
|
_: 1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExperimentSelectorModal.vue.js","sources":["../../src/components/ExperimentSelectorModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch, nextTick } from 'vue'\nimport type { ModalSize, ExperimentSummary, ExperimentStatus } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport {\n formatExperimentDate,\n EXPERIMENT_STATUS_OPTIONS,\n EXPERIMENT_STATUS_VARIANT_MAP,\n} from '../composables/experiment-utils'\nimport BaseModal from './BaseModal.vue'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport BasePill from './BasePill.vue'\nimport Skeleton from './Skeleton.vue'\nimport EmptyState from './EmptyState.vue'\nimport ExperimentCodeBadge from './ExperimentCodeBadge.vue'\n\ninterface Props {\n modelValue: boolean\n experimentType?: string\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'full',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n select: [experiment: ExperimentSummary]\n}>()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n fetch: fetchExperiments,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n})\n\nconst activeIndex = ref(-1)\nconst listRef = ref<HTMLElement | null>(null)\n\nfunction handleStatusChange(value: string | number) {\n filters.status = (String(value) || null) as ExperimentStatus | null\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n emit('select', experiment)\n emit('update:modelValue', false)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (!experiments.value.length) return\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n activeIndex.value = Math.min(activeIndex.value + 1, experiments.value.length - 1)\n scrollActiveIntoView()\n break\n case 'ArrowUp':\n event.preventDefault()\n activeIndex.value = Math.max(activeIndex.value - 1, 0)\n scrollActiveIntoView()\n break\n case 'Enter':\n event.preventDefault()\n if (activeIndex.value >= 0 && activeIndex.value < experiments.value.length) {\n handleSelect(experiments.value[activeIndex.value])\n }\n break\n }\n}\n\nfunction scrollActiveIntoView() {\n nextTick(() => {\n const row = listRef.value?.querySelector('.mld-experiment-selector__row--focused')\n row?.scrollIntoView({ block: 'nearest' })\n })\n}\n\n// Reset active index when experiments change\nwatch(experiments, () => { activeIndex.value = -1 })\n\n// Fetch on open\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (isOpen) {\n activeIndex.value = -1\n fetchExperiments()\n }\n },\n)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mld-experiment-selector\" @keydown=\"handleKeydown\">\n <!-- Filter bar -->\n <div class=\"mld-experiment-selector__filters\">\n <div class=\"mld-experiment-selector__search\">\n <BaseInput\n v-model=\"filters.search\"\n placeholder=\"Search experiments...\"\n size=\"sm\"\n type=\"search\"\n />\n </div>\n <div class=\"mld-experiment-selector__status-filter\">\n <BaseSelect\n :model-value=\"filters.status ?? ''\"\n :options=\"EXPERIMENT_STATUS_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"handleStatusChange\"\n />\n </div>\n </div>\n\n <!-- Loading skeleton -->\n <div v-if=\"isLoading\" class=\"mld-experiment-selector__skeleton\">\n <div v-for=\"n in 4\" :key=\"n\" class=\"mld-experiment-selector__skeleton-row\">\n <div class=\"mld-experiment-selector__skeleton-content\">\n <Skeleton :width=\"120 + n * 20\" height=\"14px\" />\n <Skeleton width=\"80px\" height=\"10px\" />\n </div>\n <Skeleton width=\"60px\" height=\"20px\" variant=\"rounded\" />\n </div>\n </div>\n\n <!-- Error -->\n <div v-else-if=\"error\" class=\"mld-experiment-selector__error\">\n {{ error }}\n </div>\n\n <!-- Empty -->\n <EmptyState\n v-else-if=\"experiments.length === 0\"\n title=\"No experiments found\"\n description=\"Try adjusting your search or filters.\"\n size=\"sm\"\n />\n\n <!-- Experiment list -->\n <div v-else ref=\"listRef\" class=\"mld-experiment-selector__list\">\n <div\n v-for=\"(exp, idx) in experiments\"\n :key=\"exp.id\"\n class=\"mld-experiment-selector__row\"\n :class=\"{\n 'mld-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mld-experiment-selector__row--focused': idx === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <div class=\"mld-experiment-selector__row-content\">\n <div class=\"mld-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mld-experiment-selector__meta\">\n <span v-if=\"exp.project\">{{ exp.project }}</span>\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"EXPERIMENT_STATUS_VARIANT_MAP[exp.status]\" size=\"sm\">\n {{ exp.status }}\n </BasePill>\n </div>\n </div>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/experiment-selector-modal.css';\n</style>\n"],"names":["_createBlock","BaseModal","_createElementVNode","_createVNode","BaseInput","_unref","BaseSelect","_openBlock","_createElementBlock","_Fragment","_renderList","Skeleton","_toDisplayString","EmptyState","ExperimentCodeBadge","BasePill","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExperimentSelectorModal.vue.js","sources":["../../src/components/ExperimentSelectorModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, reactive, computed, watch, nextTick } from 'vue'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport {\n formatExperimentDate,\n EXPERIMENT_STATUS_OPTIONS,\n EXPERIMENT_STATUS_VARIANT_MAP,\n DATE_PRESET_OPTIONS,\n SORT_OPTIONS,\n} from '../composables/experiment-utils'\nimport BaseModal from './BaseModal.vue'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport BasePill from './BasePill.vue'\nimport Skeleton from './Skeleton.vue'\nimport EmptyState from './EmptyState.vue'\nimport ExperimentCodeBadge from './ExperimentCodeBadge.vue'\n\ninterface Props {\n modelValue: boolean\n experimentType?: string\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n groupByProject?: boolean\n showFilters?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'full',\n groupByProject: false,\n showFilters: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n select: [experiment: ExperimentSummary]\n deselect: []\n}>()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n fetchFilterOptions,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n})\n\nconst activeIndex = ref(-1)\nconst listRef = ref<HTMLElement | null>(null)\nconst showAdvanced = ref(props.showFilters)\nconst groupToggle = ref(props.groupByProject)\n\n// Track whether any advanced filter is active (for badge dot)\nconst hasActiveAdvancedFilters = computed(() =>\n !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== 'created_at:desc'),\n)\n\n// Build type filter options from fetched experiment types\nconst typeFilterOptions = computed(() => [\n { value: '', label: 'All types' },\n ...experimentTypes.value.map(t => ({ value: t.value, label: t.label })),\n])\n\n// Build project filter options from fetched projects\nconst projectFilterOptions = computed(() => [\n { value: '', label: 'All projects' },\n ...projects.value,\n])\n\n// Flat list of experiments for keyboard navigation (works across groups too)\nconst flatExperiments = computed(() => {\n if (!groupToggle.value) return experiments.value\n return groupedByProject.value.flatMap(([, exps]) => exps)\n})\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || null\n}\n\nfunction handleSortChange(value: string | number) {\n sortKey.value = String(value) || 'created_at:desc'\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n emit('select', experiment)\n emit('update:modelValue', false)\n}\n\nfunction handleDeselect() {\n emit('deselect')\n emit('update:modelValue', false)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const list = flatExperiments.value\n if (!list.length) return\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n activeIndex.value = Math.min(activeIndex.value + 1, list.length - 1)\n scrollActiveIntoView()\n break\n case 'ArrowUp':\n event.preventDefault()\n activeIndex.value = Math.max(activeIndex.value - 1, 0)\n scrollActiveIntoView()\n break\n case 'Enter':\n event.preventDefault()\n if (activeIndex.value >= 0 && activeIndex.value < list.length) {\n handleSelect(list[activeIndex.value])\n }\n break\n }\n}\n\nfunction scrollActiveIntoView() {\n nextTick(() => {\n const row = listRef.value?.querySelector('.mld-experiment-selector__row--focused')\n row?.scrollIntoView({ block: 'nearest' })\n })\n}\n\n// Precomputed id → flat index for O(1) lookup in grouped mode\nconst flatIndexMap = computed(() => {\n const map = new Map<number, number>()\n flatExperiments.value.forEach((exp, i) => map.set(exp.id, i))\n return map\n})\n\nfunction getFlatIndex(experiment: ExperimentSummary): number {\n return flatIndexMap.value.get(experiment.id) ?? -1\n}\n\n// Track collapsed groups (reactive Set tracks .add/.delete/.has automatically)\nconst collapsedGroups = reactive(new Set<string>())\n\nfunction toggleGroup(groupName: string) {\n if (collapsedGroups.has(groupName)) {\n collapsedGroups.delete(groupName)\n } else {\n collapsedGroups.add(groupName)\n }\n}\n\n// Reset active index when experiments change\nwatch(experiments, () => { activeIndex.value = -1 })\n\n// Fetch on open\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (isOpen) {\n activeIndex.value = -1\n collapsedGroups.clear()\n fetchFilterOptions()\n fetchExperiments()\n }\n },\n)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mld-experiment-selector\" @keydown=\"handleKeydown\">\n <!-- Filter bar row 1 -->\n <div class=\"mld-experiment-selector__filters-row\">\n <div class=\"mld-experiment-selector__search\">\n <BaseInput\n v-model=\"filters.search\"\n placeholder=\"Search experiments...\"\n size=\"sm\"\n type=\"search\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.status ?? ''\"\n :options=\"EXPERIMENT_STATUS_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('status', v)\"\n />\n </div>\n <div v-if=\"typeFilterOptions.length > 1\" class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.experimentType ?? ''\"\n :options=\"typeFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('experimentType', v)\"\n />\n </div>\n <button\n class=\"mld-experiment-selector__filters-toggle\"\n :class=\"{ 'mld-experiment-selector__filters-toggle--active': hasActiveAdvancedFilters }\"\n type=\"button\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" /><line x1=\"8\" y1=\"12\" x2=\"20\" y2=\"12\" /><line x1=\"12\" y1=\"18\" x2=\"20\" y2=\"18\" />\n <circle cx=\"6\" cy=\"12\" r=\"2\" /><circle cx=\"10\" cy=\"18\" r=\"2\" /><circle cx=\"6\" cy=\"6\" r=\"2\" />\n </svg>\n Filters\n <span v-if=\"hasActiveAdvancedFilters\" class=\"mld-experiment-selector__filters-dot\" />\n </button>\n </div>\n\n <!-- Filter bar row 2 (advanced, collapsible) -->\n <div v-if=\"showAdvanced\" class=\"mld-experiment-selector__filters-advanced\">\n <div v-if=\"projectFilterOptions.length > 1\" class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.project ?? ''\"\n :options=\"projectFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('project', v)\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.datePreset ?? ''\"\n :options=\"DATE_PRESET_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('datePreset', v)\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"sortKey\"\n :options=\"SORT_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"handleSortChange\"\n />\n </div>\n <label class=\"mld-experiment-selector__group-toggle\">\n <input\n v-model=\"groupToggle\"\n type=\"checkbox\"\n class=\"mld-experiment-selector__group-checkbox\"\n />\n Group by project\n </label>\n </div>\n\n <!-- Loading skeleton -->\n <div v-if=\"isLoading\" class=\"mld-experiment-selector__skeleton\">\n <div v-for=\"n in 4\" :key=\"n\" class=\"mld-experiment-selector__skeleton-row\">\n <div class=\"mld-experiment-selector__skeleton-content\">\n <Skeleton :width=\"120 + n * 20\" height=\"14px\" />\n <Skeleton width=\"80px\" height=\"10px\" />\n </div>\n <Skeleton width=\"60px\" height=\"20px\" variant=\"rounded\" />\n </div>\n </div>\n\n <!-- Error -->\n <div v-else-if=\"error\" class=\"mld-experiment-selector__error\">\n {{ error }}\n </div>\n\n <!-- Empty -->\n <EmptyState\n v-else-if=\"experiments.length === 0\"\n title=\"No experiments found\"\n description=\"Try adjusting your search or filters.\"\n size=\"sm\"\n />\n\n <!-- Experiment list: grouped mode -->\n <div v-else-if=\"groupToggle\" ref=\"listRef\" class=\"mld-experiment-selector__list\">\n <template v-for=\"([groupName, groupExps]) in groupedByProject\" :key=\"groupName\">\n <button\n type=\"button\"\n class=\"mld-experiment-selector__group-header\"\n @click=\"toggleGroup(groupName)\"\n >\n <svg\n class=\"mld-experiment-selector__group-chevron\"\n :class=\"{ 'mld-experiment-selector__group-chevron--collapsed': collapsedGroups.has(groupName) }\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n <span class=\"mld-experiment-selector__group-name\">{{ groupName }}</span>\n <span class=\"mld-experiment-selector__group-count\">{{ groupExps.length }}</span>\n </button>\n <template v-if=\"!collapsedGroups.has(groupName)\">\n <div\n v-for=\"exp in groupExps\"\n :key=\"exp.id\"\n class=\"mld-experiment-selector__row\"\n :class=\"{\n 'mld-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mld-experiment-selector__row--focused': getFlatIndex(exp) === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = getFlatIndex(exp)\"\n >\n <div class=\"mld-experiment-selector__row-content\">\n <div class=\"mld-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mld-experiment-selector__meta\">\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"EXPERIMENT_STATUS_VARIANT_MAP[exp.status]\" size=\"sm\">\n {{ exp.status }}\n </BasePill>\n </div>\n </template>\n </template>\n </div>\n\n <!-- Experiment list: flat mode -->\n <div v-else ref=\"listRef\" class=\"mld-experiment-selector__list\">\n <div\n v-for=\"(exp, idx) in experiments\"\n :key=\"exp.id\"\n class=\"mld-experiment-selector__row\"\n :class=\"{\n 'mld-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mld-experiment-selector__row--focused': idx === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <div class=\"mld-experiment-selector__row-content\">\n <div class=\"mld-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mld-experiment-selector__meta\">\n <span v-if=\"exp.project_name || exp.project\">{{ exp.project_name || exp.project }}</span>\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"EXPERIMENT_STATUS_VARIANT_MAP[exp.status]\" size=\"sm\">\n {{ exp.status }}\n </BasePill>\n </div>\n </div>\n\n <!-- Footer: clear selection -->\n <div v-if=\"currentExperimentId != null\" class=\"mld-experiment-selector__footer\">\n <button\n type=\"button\"\n class=\"mld-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/experiment-selector-modal.css';\n</style>\n"],"names":["_createBlock","BaseModal","_createElementVNode","_createVNode","BaseInput","_unref","BaseSelect","_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","Skeleton","_toDisplayString","EmptyState","ExperimentCodeBadge","BasePill","_createTextVNode"],"mappings":"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"}
|
|
@@ -35,9 +35,9 @@ declare const __VLS_component: import('vue').DefineComponent<Props, {}, {}, {},
|
|
|
35
35
|
onBook?: (() => any) | undefined;
|
|
36
36
|
}>, {
|
|
37
37
|
compact: boolean;
|
|
38
|
+
status: ResourceStatus;
|
|
38
39
|
size: "sm" | "md" | "lg";
|
|
39
40
|
tags: string[];
|
|
40
|
-
status: ResourceStatus;
|
|
41
41
|
specs: ResourceSpec[];
|
|
42
42
|
showBookAction: boolean;
|
|
43
43
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
@@ -6,8 +6,8 @@ interface Props {
|
|
|
6
6
|
}
|
|
7
7
|
declare const _default: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{}>, {
|
|
8
8
|
label: string;
|
|
9
|
+
status: "success" | "warning" | "error" | "info" | "muted";
|
|
9
10
|
color: string;
|
|
10
11
|
pulse: boolean;
|
|
11
|
-
status: "success" | "warning" | "error" | "info" | "muted";
|
|
12
12
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLSpanElement>;
|
|
13
13
|
export default _default;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { ExperimentStatus, SelectOption, PillVariant } from '../types';
|
|
1
|
+
import { DatePreset, ExperimentStatus, SelectOption, PillVariant } from '../types';
|
|
2
2
|
export declare function formatExperimentDate(dateStr: string): string;
|
|
3
|
+
export declare function datePresetToISO(preset: DatePreset): string;
|
|
3
4
|
export declare const EXPERIMENT_STATUS_OPTIONS: SelectOption<string>[];
|
|
4
5
|
export declare const EXPERIMENT_STATUS_VARIANT_MAP: Record<ExperimentStatus, PillVariant>;
|
|
5
6
|
export declare const EXPERIMENT_STATUS_LABELS: Record<ExperimentStatus, string>;
|
|
7
|
+
export declare const DATE_PRESET_OPTIONS: SelectOption<string>[];
|
|
8
|
+
export declare const SORT_OPTIONS: SelectOption<string>[];
|
|
@@ -9,6 +9,12 @@ function formatExperimentDate(dateStr) {
|
|
|
9
9
|
return dateStr;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
function datePresetToISO(preset) {
|
|
13
|
+
const now = /* @__PURE__ */ new Date();
|
|
14
|
+
const days = preset === "last_7_days" ? 7 : preset === "last_30_days" ? 30 : 90;
|
|
15
|
+
const d = new Date(now.getTime() - days * 864e5);
|
|
16
|
+
return d.toISOString();
|
|
17
|
+
}
|
|
12
18
|
const EXPERIMENT_STATUS_OPTIONS = [
|
|
13
19
|
{ value: "", label: "All statuses" },
|
|
14
20
|
{ value: "planned", label: "Planned" },
|
|
@@ -25,10 +31,26 @@ const EXPERIMENT_STATUS_LABELS = {
|
|
|
25
31
|
ongoing: "Ongoing",
|
|
26
32
|
completed: "Completed"
|
|
27
33
|
};
|
|
34
|
+
const DATE_PRESET_OPTIONS = [
|
|
35
|
+
{ value: "", label: "Any time" },
|
|
36
|
+
{ value: "last_7_days", label: "Last 7 days" },
|
|
37
|
+
{ value: "last_30_days", label: "Last 30 days" },
|
|
38
|
+
{ value: "last_90_days", label: "Last 90 days" }
|
|
39
|
+
];
|
|
40
|
+
const SORT_OPTIONS = [
|
|
41
|
+
{ value: "created_at:desc", label: "Newest first" },
|
|
42
|
+
{ value: "created_at:asc", label: "Oldest first" },
|
|
43
|
+
{ value: "updated_at:desc", label: "Recently updated" },
|
|
44
|
+
{ value: "name:asc", label: "Name A–Z" },
|
|
45
|
+
{ value: "name:desc", label: "Name Z–A" }
|
|
46
|
+
];
|
|
28
47
|
export {
|
|
48
|
+
DATE_PRESET_OPTIONS,
|
|
29
49
|
EXPERIMENT_STATUS_LABELS,
|
|
30
50
|
EXPERIMENT_STATUS_OPTIONS,
|
|
31
51
|
EXPERIMENT_STATUS_VARIANT_MAP,
|
|
52
|
+
SORT_OPTIONS,
|
|
53
|
+
datePresetToISO,
|
|
32
54
|
formatExperimentDate
|
|
33
55
|
};
|
|
34
56
|
//# sourceMappingURL=experiment-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"experiment-utils.js","sources":["../../src/composables/experiment-utils.ts"],"sourcesContent":["import type { ExperimentStatus, SelectOption, PillVariant } from '../types'\n\nexport function formatExperimentDate(dateStr: string): string {\n try {\n return new Date(dateStr).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n } catch {\n return dateStr\n }\n}\n\nexport const EXPERIMENT_STATUS_OPTIONS: SelectOption<string>[] = [\n { value: '', label: 'All statuses' },\n { value: 'planned', label: 'Planned' },\n { value: 'ongoing', label: 'Ongoing' },\n { value: 'completed', label: 'Completed' },\n]\n\nexport const EXPERIMENT_STATUS_VARIANT_MAP: Record<ExperimentStatus, PillVariant> = {\n planned: 'default',\n ongoing: 'primary',\n completed: 'success',\n}\n\nexport const EXPERIMENT_STATUS_LABELS: Record<ExperimentStatus, string> = {\n planned: 'Planned',\n ongoing: 'Ongoing',\n completed: 'Completed',\n}\n"],"names":[],"mappings":"AAEO,SAAS,qBAAqB,SAAyB;AAC5D,MAAI;AACF,WAAO,IAAI,KAAK,OAAO,EAAE,mBAAmB,QAAW;AAAA,MACrD,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,IAAA,CACN;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,MAAM,4BAAoD;AAAA,EAC/D,EAAE,OAAO,IAAI,OAAO,eAAA;AAAA,EACpB,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,EAC3B,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,EAC3B,EAAE,OAAO,aAAa,OAAO,YAAA;AAC/B;AAEO,MAAM,gCAAuE;AAAA,EAClF,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AACb;AAEO,MAAM,2BAA6D;AAAA,EACxE,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AACb;"}
|
|
1
|
+
{"version":3,"file":"experiment-utils.js","sources":["../../src/composables/experiment-utils.ts"],"sourcesContent":["import type { DatePreset, ExperimentStatus, SelectOption, PillVariant } from '../types'\n\nexport function formatExperimentDate(dateStr: string): string {\n try {\n return new Date(dateStr).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n } catch {\n return dateStr\n }\n}\n\nexport function datePresetToISO(preset: DatePreset): string {\n const now = new Date()\n const days = preset === 'last_7_days' ? 7 : preset === 'last_30_days' ? 30 : 90\n const d = new Date(now.getTime() - days * 86_400_000)\n return d.toISOString()\n}\n\nexport const EXPERIMENT_STATUS_OPTIONS: SelectOption<string>[] = [\n { value: '', label: 'All statuses' },\n { value: 'planned', label: 'Planned' },\n { value: 'ongoing', label: 'Ongoing' },\n { value: 'completed', label: 'Completed' },\n]\n\nexport const EXPERIMENT_STATUS_VARIANT_MAP: Record<ExperimentStatus, PillVariant> = {\n planned: 'default',\n ongoing: 'primary',\n completed: 'success',\n}\n\nexport const EXPERIMENT_STATUS_LABELS: Record<ExperimentStatus, string> = {\n planned: 'Planned',\n ongoing: 'Ongoing',\n completed: 'Completed',\n}\n\nexport const DATE_PRESET_OPTIONS: SelectOption<string>[] = [\n { value: '', label: 'Any time' },\n { value: 'last_7_days', label: 'Last 7 days' },\n { value: 'last_30_days', label: 'Last 30 days' },\n { value: 'last_90_days', label: 'Last 90 days' },\n]\n\nexport const SORT_OPTIONS: SelectOption<string>[] = [\n { value: 'created_at:desc', label: 'Newest first' },\n { value: 'created_at:asc', label: 'Oldest first' },\n { value: 'updated_at:desc', label: 'Recently updated' },\n { value: 'name:asc', label: 'Name A\\u2013Z' },\n { value: 'name:desc', label: 'Name Z\\u2013A' },\n]\n"],"names":[],"mappings":"AAEO,SAAS,qBAAqB,SAAyB;AAC5D,MAAI;AACF,WAAO,IAAI,KAAK,OAAO,EAAE,mBAAmB,QAAW;AAAA,MACrD,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,IAAA,CACN;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,gBAAgB,QAA4B;AAC1D,QAAM,0BAAU,KAAA;AAChB,QAAM,OAAO,WAAW,gBAAgB,IAAI,WAAW,iBAAiB,KAAK;AAC7E,QAAM,IAAI,IAAI,KAAK,IAAI,QAAA,IAAY,OAAO,KAAU;AACpD,SAAO,EAAE,YAAA;AACX;AAEO,MAAM,4BAAoD;AAAA,EAC/D,EAAE,OAAO,IAAI,OAAO,eAAA;AAAA,EACpB,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,EAC3B,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,EAC3B,EAAE,OAAO,aAAa,OAAO,YAAA;AAC/B;AAEO,MAAM,gCAAuE;AAAA,EAClF,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AACb;AAEO,MAAM,2BAA6D;AAAA,EACxE,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AACb;AAEO,MAAM,sBAA8C;AAAA,EACzD,EAAE,OAAO,IAAI,OAAO,WAAA;AAAA,EACpB,EAAE,OAAO,eAAe,OAAO,cAAA;AAAA,EAC/B,EAAE,OAAO,gBAAgB,OAAO,eAAA;AAAA,EAChC,EAAE,OAAO,gBAAgB,OAAO,eAAA;AAClC;AAEO,MAAM,eAAuC;AAAA,EAClD,EAAE,OAAO,mBAAmB,OAAO,eAAA;AAAA,EACnC,EAAE,OAAO,kBAAkB,OAAO,eAAA;AAAA,EAClC,EAAE,OAAO,mBAAmB,OAAO,mBAAA;AAAA,EACnC,EAAE,OAAO,YAAY,OAAO,WAAA;AAAA,EAC5B,EAAE,OAAO,aAAa,OAAO,WAAA;AAC/B;"}
|
|
@@ -19,6 +19,6 @@ export { useFormBuilder, evaluateCondition } from './useFormBuilder';
|
|
|
19
19
|
export { useAutoGroup, DEFAULT_COLORS } from './useAutoGroup';
|
|
20
20
|
export { usePluginConfig, type UsePluginConfigReturn } from './usePluginConfig';
|
|
21
21
|
export { useExperimentSelector, type UseExperimentSelectorOptions, type UseExperimentSelectorReturn, } from './useExperimentSelector';
|
|
22
|
-
export { formatExperimentDate, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, EXPERIMENT_STATUS_LABELS, } from './experiment-utils';
|
|
22
|
+
export { formatExperimentDate, datePresetToISO, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, EXPERIMENT_STATUS_LABELS, DATE_PRESET_OPTIONS, SORT_OPTIONS, } from './experiment-utils';
|
|
23
23
|
export { useExperimentData, type UseExperimentDataOptions, type UseExperimentDataReturn, } from './useExperimentData';
|
|
24
24
|
export { getFieldRegistryEntry, getTypeDefault, type RegistryEntry, } from './formBuilderRegistry';
|