@morscherlab/mld-sdk 0.7.7 → 0.7.8
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 +20 -0
- package/dist/components/ExperimentSelectorModal.vue.js +164 -0
- package/dist/components/ExperimentSelectorModal.vue.js.map +1 -0
- package/dist/components/ExperimentSelectorModal.vue3.js +6 -0
- package/dist/components/ExperimentSelectorModal.vue3.js.map +1 -0
- package/dist/components/FitPanel.vue.d.ts +46 -0
- package/dist/components/FitPanel.vue.js +118 -0
- package/dist/components/FitPanel.vue.js.map +1 -0
- package/dist/components/FitPanel.vue3.js +6 -0
- package/dist/components/FitPanel.vue3.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -1
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.js +2 -0
- package/dist/composables/index.js.map +1 -1
- package/dist/composables/useExperimentSelector.d.ts +20 -0
- package/dist/composables/useExperimentSelector.js +82 -0
- package/dist/composables/useExperimentSelector.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +294 -0
- package/dist/types/components.d.ts +30 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/ExperimentSelectorModal.vue +157 -0
- package/src/components/FitPanel.vue +119 -0
- package/src/components/index.ts +4 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useExperimentSelector.ts +113 -0
- package/src/index.ts +15 -0
- package/src/styles/components/experiment-selector-modal.css +101 -0
- package/src/styles/components/fit-panel.css +67 -0
- package/src/styles/index.css +2 -0
- package/src/types/components.ts +38 -0
- package/src/types/index.ts +8 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { ref, reactive, watch, type Ref } from 'vue'
|
|
2
|
+
import { useApi } from './useApi'
|
|
3
|
+
import type { ExperimentSummary, ExperimentListResponse, ExperimentFilters } from '../types'
|
|
4
|
+
|
|
5
|
+
export interface UseExperimentSelectorOptions {
|
|
6
|
+
experimentType?: string
|
|
7
|
+
apiBaseUrl?: string
|
|
8
|
+
limit?: number
|
|
9
|
+
immediate?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UseExperimentSelectorReturn {
|
|
13
|
+
experiments: Ref<ExperimentSummary[]>
|
|
14
|
+
total: Ref<number>
|
|
15
|
+
selectedExperiment: Ref<ExperimentSummary | null>
|
|
16
|
+
filters: ExperimentFilters
|
|
17
|
+
isLoading: Ref<boolean>
|
|
18
|
+
error: Ref<string | null>
|
|
19
|
+
fetch: () => Promise<void>
|
|
20
|
+
select: (experiment: ExperimentSummary) => void
|
|
21
|
+
clear: () => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useExperimentSelector(
|
|
25
|
+
options: UseExperimentSelectorOptions = {},
|
|
26
|
+
): UseExperimentSelectorReturn {
|
|
27
|
+
const { limit = 100, immediate = false, experimentType, apiBaseUrl } = options
|
|
28
|
+
const api = useApi({ baseUrl: apiBaseUrl })
|
|
29
|
+
|
|
30
|
+
const experiments = ref<ExperimentSummary[]>([])
|
|
31
|
+
const total = ref(0)
|
|
32
|
+
const selectedExperiment = ref<ExperimentSummary | null>(null)
|
|
33
|
+
const isLoading = ref(false)
|
|
34
|
+
const error = ref<string | null>(null)
|
|
35
|
+
|
|
36
|
+
const filters: ExperimentFilters = reactive({
|
|
37
|
+
search: undefined,
|
|
38
|
+
status: undefined,
|
|
39
|
+
project: undefined,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
async function fetchExperiments(): Promise<void> {
|
|
43
|
+
isLoading.value = true
|
|
44
|
+
error.value = null
|
|
45
|
+
try {
|
|
46
|
+
const params = new URLSearchParams()
|
|
47
|
+
if (experimentType) params.set('experiment_type', experimentType)
|
|
48
|
+
if (filters.status) params.set('status', filters.status)
|
|
49
|
+
if (filters.search) params.set('search', filters.search)
|
|
50
|
+
if (filters.project) params.set('project', filters.project)
|
|
51
|
+
params.set('limit', String(limit))
|
|
52
|
+
|
|
53
|
+
const query = params.toString()
|
|
54
|
+
const url = `/api/experiments${query ? `?${query}` : ''}`
|
|
55
|
+
const data = await api.get<ExperimentListResponse>(url)
|
|
56
|
+
experiments.value = data.experiments
|
|
57
|
+
total.value = data.total
|
|
58
|
+
} catch (e) {
|
|
59
|
+
error.value = e instanceof Error ? e.message : 'Failed to fetch experiments'
|
|
60
|
+
experiments.value = []
|
|
61
|
+
total.value = 0
|
|
62
|
+
} finally {
|
|
63
|
+
isLoading.value = false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function select(experiment: ExperimentSummary): void {
|
|
68
|
+
selectedExperiment.value = experiment
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function clear(): void {
|
|
72
|
+
selectedExperiment.value = null
|
|
73
|
+
filters.search = undefined
|
|
74
|
+
filters.status = undefined
|
|
75
|
+
filters.project = undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Debounced watch on search filter
|
|
79
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
80
|
+
watch(
|
|
81
|
+
() => filters.search,
|
|
82
|
+
() => {
|
|
83
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
84
|
+
debounceTimer = setTimeout(() => {
|
|
85
|
+
fetchExperiments()
|
|
86
|
+
}, 300)
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// Immediate watch on status/project filters (no debounce needed)
|
|
91
|
+
watch(
|
|
92
|
+
() => [filters.status, filters.project],
|
|
93
|
+
() => {
|
|
94
|
+
fetchExperiments()
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if (immediate) {
|
|
99
|
+
fetchExperiments()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
experiments,
|
|
104
|
+
total,
|
|
105
|
+
selectedExperiment,
|
|
106
|
+
filters,
|
|
107
|
+
isLoading,
|
|
108
|
+
error,
|
|
109
|
+
fetch: fetchExperiments,
|
|
110
|
+
select,
|
|
111
|
+
clear,
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -91,6 +91,9 @@ export {
|
|
|
91
91
|
TimeRangeInput,
|
|
92
92
|
ScheduleCalendar,
|
|
93
93
|
ResourceCard,
|
|
94
|
+
// Experiment / analysis components
|
|
95
|
+
ExperimentSelectorModal,
|
|
96
|
+
FitPanel,
|
|
94
97
|
} from './components'
|
|
95
98
|
|
|
96
99
|
// Composables
|
|
@@ -143,6 +146,10 @@ export {
|
|
|
143
146
|
// Auto-group
|
|
144
147
|
useAutoGroup,
|
|
145
148
|
DEFAULT_COLORS,
|
|
149
|
+
// Experiment selector
|
|
150
|
+
useExperimentSelector,
|
|
151
|
+
type UseExperimentSelectorOptions,
|
|
152
|
+
type UseExperimentSelectorReturn,
|
|
146
153
|
} from './composables'
|
|
147
154
|
|
|
148
155
|
// Stores
|
|
@@ -267,6 +274,14 @@ export type {
|
|
|
267
274
|
// Resource types
|
|
268
275
|
ResourceStatus,
|
|
269
276
|
ResourceSpec,
|
|
277
|
+
// Experiment selector types
|
|
278
|
+
ExperimentStatus,
|
|
279
|
+
ExperimentSummary,
|
|
280
|
+
ExperimentListResponse,
|
|
281
|
+
ExperimentFilters,
|
|
282
|
+
// FitPanel types
|
|
283
|
+
FitState,
|
|
284
|
+
FitResultSummary,
|
|
270
285
|
// UnitInput types
|
|
271
286
|
UnitOption,
|
|
272
287
|
// StepWizard types
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* ExperimentSelectorModal Component Styles */
|
|
2
|
+
|
|
3
|
+
.mld-experiment-selector {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 0.75rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* Filter bar */
|
|
10
|
+
.mld-experiment-selector__filters {
|
|
11
|
+
display: flex;
|
|
12
|
+
gap: 0.5rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.mld-experiment-selector__search {
|
|
16
|
+
flex: 1;
|
|
17
|
+
min-width: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.mld-experiment-selector__status-filter {
|
|
21
|
+
flex-shrink: 0;
|
|
22
|
+
width: 10rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Loading */
|
|
26
|
+
.mld-experiment-selector__loading {
|
|
27
|
+
display: flex;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
padding: 2rem 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Error */
|
|
33
|
+
.mld-experiment-selector__error {
|
|
34
|
+
padding: 0.75rem;
|
|
35
|
+
border-radius: var(--mld-radius-sm);
|
|
36
|
+
background-color: var(--mld-error-bg);
|
|
37
|
+
color: var(--mld-error);
|
|
38
|
+
font-size: 0.875rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Scrollable list */
|
|
42
|
+
.mld-experiment-selector__list {
|
|
43
|
+
max-height: 400px;
|
|
44
|
+
overflow-y: auto;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
gap: 1px;
|
|
48
|
+
border: 1px solid var(--border-color);
|
|
49
|
+
border-radius: var(--mld-radius);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Row */
|
|
53
|
+
.mld-experiment-selector__row {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 0.75rem;
|
|
57
|
+
padding: 0.75rem 1rem;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
border-left: 3px solid transparent;
|
|
60
|
+
background-color: var(--bg-primary);
|
|
61
|
+
transition:
|
|
62
|
+
background-color 0.15s ease,
|
|
63
|
+
border-color 0.15s ease;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.mld-experiment-selector__row:hover {
|
|
67
|
+
background-color: var(--bg-hover);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.mld-experiment-selector__row--active {
|
|
71
|
+
border-left-color: var(--color-primary);
|
|
72
|
+
background-color: var(--color-primary-soft);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.mld-experiment-selector__row--active:hover {
|
|
76
|
+
background-color: var(--color-primary-soft);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Row content */
|
|
80
|
+
.mld-experiment-selector__row-content {
|
|
81
|
+
flex: 1;
|
|
82
|
+
min-width: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.mld-experiment-selector__name {
|
|
86
|
+
font-size: 0.875rem;
|
|
87
|
+
font-weight: 500;
|
|
88
|
+
color: var(--text-primary);
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
text-overflow: ellipsis;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.mld-experiment-selector__meta {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: 0.5rem;
|
|
98
|
+
font-size: 0.75rem;
|
|
99
|
+
color: var(--text-muted);
|
|
100
|
+
margin-top: 0.125rem;
|
|
101
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* FitPanel Component Styles */
|
|
2
|
+
|
|
3
|
+
.mld-fit-panel {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 0.75rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* Action bar */
|
|
10
|
+
.mld-fit-panel__actions {
|
|
11
|
+
display: flex;
|
|
12
|
+
gap: 0.5rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Progress */
|
|
16
|
+
.mld-fit-panel__progress {
|
|
17
|
+
padding: 0.25rem 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Result list */
|
|
21
|
+
.mld-fit-panel__results {
|
|
22
|
+
border: 1px solid var(--border-color);
|
|
23
|
+
border-radius: var(--mld-radius-sm);
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.mld-fit-panel__result-list {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.mld-fit-panel__result-row {
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
align-items: center;
|
|
36
|
+
padding: 0.5rem 0.75rem;
|
|
37
|
+
border-bottom: 1px solid var(--border-color);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.mld-fit-panel__result-row:last-child {
|
|
41
|
+
border-bottom: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.mld-fit-panel__result-label {
|
|
45
|
+
font-size: 0.8125rem;
|
|
46
|
+
color: var(--text-secondary);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.mld-fit-panel__result-value {
|
|
50
|
+
font-size: 0.8125rem;
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
color: var(--text-primary);
|
|
53
|
+
font-variant-numeric: tabular-nums;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Result value variants */
|
|
57
|
+
.mld-fit-panel__result-value--success {
|
|
58
|
+
color: var(--mld-success);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.mld-fit-panel__result-value--warning {
|
|
62
|
+
color: var(--mld-warning);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.mld-fit-panel__result-value--error {
|
|
66
|
+
color: var(--mld-error);
|
|
67
|
+
}
|
package/src/styles/index.css
CHANGED
package/src/types/components.ts
CHANGED
|
@@ -601,3 +601,41 @@ export interface ResourceSpec {
|
|
|
601
601
|
label: string
|
|
602
602
|
value: string
|
|
603
603
|
}
|
|
604
|
+
|
|
605
|
+
// Experiment selector types
|
|
606
|
+
export type ExperimentStatus = 'planned' | 'ongoing' | 'completed'
|
|
607
|
+
|
|
608
|
+
export interface ExperimentSummary {
|
|
609
|
+
id: number
|
|
610
|
+
experiment_code?: string
|
|
611
|
+
name: string
|
|
612
|
+
status: ExperimentStatus
|
|
613
|
+
experiment_type: string
|
|
614
|
+
project?: string
|
|
615
|
+
project_id?: number
|
|
616
|
+
notes?: string
|
|
617
|
+
tags?: Record<string, unknown>
|
|
618
|
+
created_at: string
|
|
619
|
+
updated_at: string
|
|
620
|
+
has_design_data: boolean
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export interface ExperimentListResponse {
|
|
624
|
+
experiments: ExperimentSummary[]
|
|
625
|
+
total: number
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export interface ExperimentFilters {
|
|
629
|
+
search?: string
|
|
630
|
+
status?: ExperimentStatus | null
|
|
631
|
+
project?: string | null
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// FitPanel types
|
|
635
|
+
export type FitState = 'idle' | 'running' | 'completed' | 'error'
|
|
636
|
+
|
|
637
|
+
export interface FitResultSummary {
|
|
638
|
+
label: string
|
|
639
|
+
value: string | number
|
|
640
|
+
variant?: 'default' | 'success' | 'warning' | 'error'
|
|
641
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -126,6 +126,14 @@ export type {
|
|
|
126
126
|
ResourceSpec,
|
|
127
127
|
// MoleculeInput types
|
|
128
128
|
MoleculeData,
|
|
129
|
+
// Experiment selector types
|
|
130
|
+
ExperimentStatus,
|
|
131
|
+
ExperimentSummary,
|
|
132
|
+
ExperimentListResponse,
|
|
133
|
+
ExperimentFilters,
|
|
134
|
+
// FitPanel types
|
|
135
|
+
FitState,
|
|
136
|
+
FitResultSummary,
|
|
129
137
|
// ReagentList types
|
|
130
138
|
StorageCondition,
|
|
131
139
|
ReagentColumn,
|