@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.
Files changed (37) hide show
  1. package/dist/components/ExperimentSelectorModal.vue.d.ts +20 -0
  2. package/dist/components/ExperimentSelectorModal.vue.js +164 -0
  3. package/dist/components/ExperimentSelectorModal.vue.js.map +1 -0
  4. package/dist/components/ExperimentSelectorModal.vue3.js +6 -0
  5. package/dist/components/ExperimentSelectorModal.vue3.js.map +1 -0
  6. package/dist/components/FitPanel.vue.d.ts +46 -0
  7. package/dist/components/FitPanel.vue.js +118 -0
  8. package/dist/components/FitPanel.vue.js.map +1 -0
  9. package/dist/components/FitPanel.vue3.js +6 -0
  10. package/dist/components/FitPanel.vue3.js.map +1 -0
  11. package/dist/components/index.d.ts +2 -0
  12. package/dist/components/index.js +6 -0
  13. package/dist/components/index.js.map +1 -1
  14. package/dist/composables/index.d.ts +1 -0
  15. package/dist/composables/index.js +2 -0
  16. package/dist/composables/index.js.map +1 -1
  17. package/dist/composables/useExperimentSelector.d.ts +20 -0
  18. package/dist/composables/useExperimentSelector.js +82 -0
  19. package/dist/composables/useExperimentSelector.js.map +1 -0
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.js +8 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/styles.css +294 -0
  24. package/dist/types/components.d.ts +30 -0
  25. package/dist/types/index.d.ts +1 -1
  26. package/package.json +1 -1
  27. package/src/components/ExperimentSelectorModal.vue +157 -0
  28. package/src/components/FitPanel.vue +119 -0
  29. package/src/components/index.ts +4 -0
  30. package/src/composables/index.ts +5 -0
  31. package/src/composables/useExperimentSelector.ts +113 -0
  32. package/src/index.ts +15 -0
  33. package/src/styles/components/experiment-selector-modal.css +101 -0
  34. package/src/styles/components/fit-panel.css +67 -0
  35. package/src/styles/index.css +2 -0
  36. package/src/types/components.ts +38 -0
  37. 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
+ }
@@ -82,3 +82,5 @@
82
82
  @import './components/form-builder.css';
83
83
  @import './components/experiment-data-viewer.css';
84
84
  @import './components/experiment-code-badge.css';
85
+ @import './components/experiment-selector-modal.css';
86
+ @import './components/fit-panel.css';
@@ -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
+ }
@@ -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,