@morscherlab/mld-sdk 0.7.8 → 0.8.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/ExperimentCodeBadge.vue.d.ts +7 -1
- package/dist/components/ExperimentCodeBadge.vue.js +41 -5
- package/dist/components/ExperimentCodeBadge.vue.js.map +1 -1
- package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
- package/dist/components/ExperimentDataViewer.vue.js +98 -44
- package/dist/components/ExperimentDataViewer.vue.js.map +1 -1
- package/dist/components/ExperimentSelectorModal.vue.d.ts +3 -1
- package/dist/components/ExperimentSelectorModal.vue.js +117 -63
- package/dist/components/ExperimentSelectorModal.vue.js.map +1 -1
- package/dist/composables/experiment-utils.d.ts +5 -0
- package/dist/composables/experiment-utils.js +34 -0
- package/dist/composables/experiment-utils.js.map +1 -0
- package/dist/composables/index.d.ts +2 -0
- package/dist/composables/index.js +7 -0
- package/dist/composables/index.js.map +1 -1
- package/dist/composables/useExperimentData.d.ts +17 -0
- package/dist/composables/useExperimentData.js +62 -0
- package/dist/composables/useExperimentData.js.map +1 -0
- package/dist/composables/useExperimentSelector.d.ts +5 -1
- package/dist/composables/useExperimentSelector.js +39 -9
- package/dist/composables/useExperimentSelector.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +121 -10
- package/package.json +1 -1
- package/src/components/ExperimentCodeBadge.story.vue +77 -0
- package/src/components/ExperimentCodeBadge.vue +46 -3
- package/src/components/ExperimentDataViewer.story.vue +174 -0
- package/src/components/ExperimentDataViewer.vue +49 -12
- package/src/components/ExperimentSelectorModal.story.vue +244 -0
- package/src/components/ExperimentSelectorModal.vue +75 -37
- package/src/components/FitPanel.story.vue +125 -0
- package/src/composables/experiment-utils.ts +32 -0
- package/src/composables/index.ts +11 -0
- package/src/composables/useExperimentData.ts +85 -0
- package/src/composables/useExperimentSelector.ts +48 -9
- package/src/index.ts +9 -0
- package/src/styles/components/experiment-code-badge.css +20 -0
- package/src/styles/components/experiment-data-viewer.css +8 -1
- package/src/styles/components/experiment-selector-modal.css +39 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExperimentSelector.js","sources":["../../src/composables/useExperimentSelector.ts"],"sourcesContent":["import { ref, reactive, watch, type Ref } from 'vue'\nimport { useApi } from './useApi'\nimport type { ExperimentSummary, ExperimentListResponse, ExperimentFilters } from '../types'\n\nexport interface UseExperimentSelectorOptions {\n experimentType?: string\n apiBaseUrl?: string\n limit?: number\n immediate?: boolean\n}\n\nexport interface UseExperimentSelectorReturn {\n experiments: Ref<ExperimentSummary[]>\n total: Ref<number>\n selectedExperiment: Ref<ExperimentSummary | null>\n filters: ExperimentFilters\n isLoading: Ref<boolean>\n error: Ref<string | null>\n fetch: () => Promise<void>\n select: (experiment: ExperimentSummary) => void\n clear: () => void\n}\n\nexport function useExperimentSelector(\n options: UseExperimentSelectorOptions = {},\n): UseExperimentSelectorReturn {\n const { limit = 100, immediate = false, experimentType, apiBaseUrl } = options\n const api = useApi({ baseUrl: apiBaseUrl })\n\n const experiments = ref<ExperimentSummary[]>([])\n const total = ref(0)\n const selectedExperiment = ref<ExperimentSummary | null>(null)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n
|
|
1
|
+
{"version":3,"file":"useExperimentSelector.js","sources":["../../src/composables/useExperimentSelector.ts"],"sourcesContent":["import { ref, reactive, computed, watch, onScopeDispose, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport type { ExperimentSummary, ExperimentListResponse, ExperimentFilters } from '../types'\n\nexport interface UseExperimentSelectorOptions {\n experimentType?: string\n apiBaseUrl?: string\n limit?: number\n immediate?: boolean\n}\n\nexport interface UseExperimentSelectorReturn {\n experiments: Ref<ExperimentSummary[]>\n total: Ref<number>\n selectedExperiment: Ref<ExperimentSummary | null>\n filters: ExperimentFilters\n isLoading: Ref<boolean>\n error: Ref<string | null>\n page: Ref<number>\n hasMore: ComputedRef<boolean>\n fetch: () => Promise<void>\n loadMore: () => Promise<void>\n reset: () => void\n select: (experiment: ExperimentSummary) => void\n clear: () => void\n}\n\nexport function useExperimentSelector(\n options: UseExperimentSelectorOptions = {},\n): UseExperimentSelectorReturn {\n const { limit = 100, immediate = false, experimentType, apiBaseUrl } = options\n const api = useApi({ baseUrl: apiBaseUrl })\n\n const experiments = ref<ExperimentSummary[]>([])\n const total = ref(0)\n const selectedExperiment = ref<ExperimentSummary | null>(null)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n const page = ref(0)\n\n const hasMore = computed(() => experiments.value.length < total.value)\n\n async function fetchExperiments(): Promise<void> {\n isLoading.value = true\n error.value = null\n try {\n const params = new URLSearchParams()\n if (experimentType) params.set('experiment_type', experimentType)\n if (filters.status) params.set('status', filters.status)\n if (filters.search) params.set('search', filters.search)\n if (filters.project) params.set('project', filters.project)\n params.set('limit', String(limit))\n params.set('skip', String(page.value * limit))\n\n const query = params.toString()\n const url = `/api/experiments${query ? `?${query}` : ''}`\n const data = await api.get<ExperimentListResponse>(url)\n\n if (page.value === 0) {\n experiments.value = data.experiments\n } else {\n experiments.value = [...experiments.value, ...data.experiments]\n }\n total.value = data.total\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to fetch experiments'\n if (page.value === 0) {\n experiments.value = []\n total.value = 0\n }\n } finally {\n isLoading.value = false\n }\n }\n\n async function loadMore(): Promise<void> {\n if (!hasMore.value || isLoading.value) return\n page.value++\n await fetchExperiments()\n }\n\n function reset(): void {\n page.value = 0\n experiments.value = []\n total.value = 0\n fetchExperiments()\n }\n\n function select(experiment: ExperimentSummary): void {\n selectedExperiment.value = experiment\n }\n\n function clear(): void {\n selectedExperiment.value = null\n filters.search = undefined\n filters.status = undefined\n filters.project = undefined\n page.value = 0\n }\n\n const filters: ExperimentFilters = reactive({\n search: undefined,\n status: undefined,\n project: undefined,\n })\n\n // Debounced watch on search filter\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n watch(\n () => filters.search,\n () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n page.value = 0\n fetchExperiments()\n }, 300)\n },\n )\n\n // Immediate watch on status/project filters (no debounce needed)\n watch(\n () => [filters.status, filters.project],\n () => {\n page.value = 0\n fetchExperiments()\n },\n )\n\n onScopeDispose(() => {\n if (debounceTimer) clearTimeout(debounceTimer)\n })\n\n if (immediate) {\n fetchExperiments()\n }\n\n return {\n experiments,\n total,\n selectedExperiment,\n filters,\n isLoading,\n error,\n page,\n hasMore,\n fetch: fetchExperiments,\n loadMore,\n reset,\n select,\n clear,\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
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';
|
|
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, } from './composables';
|
|
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
|
@@ -169,6 +169,8 @@ import { useScheduleDrag } from "./composables/useScheduleDrag.js";
|
|
|
169
169
|
import { DEFAULT_COLORS, useAutoGroup } from "./composables/useAutoGroup.js";
|
|
170
170
|
import { usePluginConfig } from "./composables/usePluginConfig.js";
|
|
171
171
|
import { useExperimentSelector } from "./composables/useExperimentSelector.js";
|
|
172
|
+
import { EXPERIMENT_STATUS_LABELS, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, formatExperimentDate } from "./composables/experiment-utils.js";
|
|
173
|
+
import { useExperimentData } from "./composables/useExperimentData.js";
|
|
172
174
|
import { useAuthStore } from "./stores/auth.js";
|
|
173
175
|
import { colorPalettes, useSettingsStore } from "./stores/settings.js";
|
|
174
176
|
export {
|
|
@@ -208,6 +210,9 @@ export {
|
|
|
208
210
|
default37 as Divider,
|
|
209
211
|
default58 as DoseCalculator,
|
|
210
212
|
default16 as DropdownButton,
|
|
213
|
+
EXPERIMENT_STATUS_LABELS,
|
|
214
|
+
EXPERIMENT_STATUS_OPTIONS,
|
|
215
|
+
EXPERIMENT_STATUS_VARIANT_MAP,
|
|
211
216
|
default41 as EmptyState,
|
|
212
217
|
default71 as ExperimentCodeBadge,
|
|
213
218
|
default70 as ExperimentDataViewer,
|
|
@@ -258,6 +263,7 @@ export {
|
|
|
258
263
|
durationMinutes,
|
|
259
264
|
findAvailableSlots,
|
|
260
265
|
formatDuration,
|
|
266
|
+
formatExperimentDate,
|
|
261
267
|
formatTime,
|
|
262
268
|
generateTimeSlots,
|
|
263
269
|
isTimeInRange,
|
|
@@ -271,6 +277,7 @@ export {
|
|
|
271
277
|
useChemicalFormula,
|
|
272
278
|
useConcentrationUnits,
|
|
273
279
|
useDoseCalculator,
|
|
280
|
+
useExperimentData,
|
|
274
281
|
useExperimentSelector,
|
|
275
282
|
usePasskey,
|
|
276
283
|
usePlatformContext,
|
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
|
@@ -12515,7 +12515,14 @@ html.dark .mld-settings-modal__option-btn--active {
|
|
|
12515
12515
|
max-height: 600px;
|
|
12516
12516
|
overflow-y: auto;
|
|
12517
12517
|
}
|
|
12518
|
-
.mld-data-viewer__loading
|
|
12518
|
+
.mld-data-viewer__loading {
|
|
12519
|
+
padding: 24px 12px;
|
|
12520
|
+
}
|
|
12521
|
+
.mld-data-viewer__skeleton {
|
|
12522
|
+
display: flex;
|
|
12523
|
+
flex-direction: column;
|
|
12524
|
+
gap: 12px;
|
|
12525
|
+
}
|
|
12519
12526
|
.mld-data-viewer__empty {
|
|
12520
12527
|
text-align: center;
|
|
12521
12528
|
padding: 32px;
|
|
@@ -12619,10 +12626,28 @@ html.dark .mld-settings-modal__option-btn--active {
|
|
|
12619
12626
|
color: var(--mld-color-text-secondary, #666);
|
|
12620
12627
|
border-radius: var(--mld-radius-sm, 4px);
|
|
12621
12628
|
letter-spacing: 0.02em;
|
|
12629
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
12622
12630
|
}
|
|
12623
12631
|
.mld-exp-code--sm { padding: 1px 6px; font-size: 11px; }
|
|
12624
12632
|
.mld-exp-code--md { padding: 2px 8px; font-size: 12px; }
|
|
12625
12633
|
.mld-exp-code--lg { padding: 3px 10px; font-size: 14px; }
|
|
12634
|
+
/* Copyable state */
|
|
12635
|
+
.mld-exp-code--copyable {
|
|
12636
|
+
cursor: pointer;
|
|
12637
|
+
user-select: none;
|
|
12638
|
+
}
|
|
12639
|
+
.mld-exp-code--copyable:hover {
|
|
12640
|
+
background: var(--mld-color-surface-3, #e4e4e4);
|
|
12641
|
+
}
|
|
12642
|
+
.mld-exp-code--copyable:focus-visible {
|
|
12643
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
12644
|
+
outline-offset: 1px;
|
|
12645
|
+
}
|
|
12646
|
+
/* Copied feedback */
|
|
12647
|
+
.mld-exp-code--copied {
|
|
12648
|
+
background: var(--mld-color-success-soft, #dcfce7);
|
|
12649
|
+
color: var(--mld-color-success, #16a34a);
|
|
12650
|
+
}
|
|
12626
12651
|
/* ExperimentSelectorModal Component Styles */
|
|
12627
12652
|
.mld-experiment-selector {
|
|
12628
12653
|
display: flex;
|
|
@@ -12642,11 +12667,26 @@ html.dark .mld-settings-modal__option-btn--active {
|
|
|
12642
12667
|
flex-shrink: 0;
|
|
12643
12668
|
width: 10rem;
|
|
12644
12669
|
}
|
|
12645
|
-
/* Loading */
|
|
12646
|
-
.mld-experiment-
|
|
12670
|
+
/* Loading skeleton */
|
|
12671
|
+
.mld-experiment-selector__skeleton {
|
|
12647
12672
|
display: flex;
|
|
12648
|
-
|
|
12649
|
-
|
|
12673
|
+
flex-direction: column;
|
|
12674
|
+
gap: 1px;
|
|
12675
|
+
border: 1px solid var(--border-color);
|
|
12676
|
+
border-radius: var(--mld-radius);
|
|
12677
|
+
}
|
|
12678
|
+
.mld-experiment-selector__skeleton-row {
|
|
12679
|
+
display: flex;
|
|
12680
|
+
align-items: center;
|
|
12681
|
+
gap: 0.75rem;
|
|
12682
|
+
padding: 0.75rem 1rem;
|
|
12683
|
+
background-color: var(--bg-primary);
|
|
12684
|
+
}
|
|
12685
|
+
.mld-experiment-selector__skeleton-content {
|
|
12686
|
+
flex: 1;
|
|
12687
|
+
display: flex;
|
|
12688
|
+
flex-direction: column;
|
|
12689
|
+
gap: 6px;
|
|
12650
12690
|
}
|
|
12651
12691
|
/* Error */
|
|
12652
12692
|
.mld-experiment-selector__error {
|
|
@@ -12689,15 +12729,30 @@ html.dark .mld-settings-modal__option-btn--active {
|
|
|
12689
12729
|
.mld-experiment-selector__row--active:hover {
|
|
12690
12730
|
background-color: var(--color-primary-soft);
|
|
12691
12731
|
}
|
|
12732
|
+
/* Keyboard focused row */
|
|
12733
|
+
.mld-experiment-selector__row--focused {
|
|
12734
|
+
background-color: var(--bg-hover);
|
|
12735
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
12736
|
+
outline-offset: -2px;
|
|
12737
|
+
border-radius: 2px;
|
|
12738
|
+
}
|
|
12739
|
+
.mld-experiment-selector__row--active.mld-experiment-selector__row--focused {
|
|
12740
|
+
background-color: var(--color-primary-soft);
|
|
12741
|
+
}
|
|
12692
12742
|
/* Row content */
|
|
12693
12743
|
.mld-experiment-selector__row-content {
|
|
12694
12744
|
flex: 1;
|
|
12695
12745
|
min-width: 0;
|
|
12696
12746
|
}
|
|
12697
12747
|
.mld-experiment-selector__name {
|
|
12748
|
+
display: flex;
|
|
12749
|
+
align-items: center;
|
|
12750
|
+
gap: 0.5rem;
|
|
12698
12751
|
font-size: 0.875rem;
|
|
12699
12752
|
font-weight: 500;
|
|
12700
12753
|
color: var(--text-primary);
|
|
12754
|
+
}
|
|
12755
|
+
.mld-experiment-selector__name > span:first-child {
|
|
12701
12756
|
white-space: nowrap;
|
|
12702
12757
|
overflow: hidden;
|
|
12703
12758
|
text-overflow: ellipsis;
|
|
@@ -24080,7 +24135,14 @@ to { transform: rotate(360deg);
|
|
|
24080
24135
|
max-height: 600px;
|
|
24081
24136
|
overflow-y: auto;
|
|
24082
24137
|
}
|
|
24083
|
-
.mld-data-viewer__loading
|
|
24138
|
+
.mld-data-viewer__loading {
|
|
24139
|
+
padding: 24px 12px;
|
|
24140
|
+
}
|
|
24141
|
+
.mld-data-viewer__skeleton {
|
|
24142
|
+
display: flex;
|
|
24143
|
+
flex-direction: column;
|
|
24144
|
+
gap: 12px;
|
|
24145
|
+
}
|
|
24084
24146
|
.mld-data-viewer__empty {
|
|
24085
24147
|
text-align: center;
|
|
24086
24148
|
padding: 32px;
|
|
@@ -24184,6 +24246,7 @@ to { transform: rotate(360deg);
|
|
|
24184
24246
|
color: var(--mld-color-text-secondary, #666);
|
|
24185
24247
|
border-radius: var(--mld-radius-sm, 4px);
|
|
24186
24248
|
letter-spacing: 0.02em;
|
|
24249
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
24187
24250
|
}
|
|
24188
24251
|
.mld-exp-code--sm { padding: 1px 6px; font-size: 11px;
|
|
24189
24252
|
}
|
|
@@ -24191,6 +24254,23 @@ to { transform: rotate(360deg);
|
|
|
24191
24254
|
}
|
|
24192
24255
|
.mld-exp-code--lg { padding: 3px 10px; font-size: 14px;
|
|
24193
24256
|
}
|
|
24257
|
+
/* Copyable state */
|
|
24258
|
+
.mld-exp-code--copyable {
|
|
24259
|
+
cursor: pointer;
|
|
24260
|
+
user-select: none;
|
|
24261
|
+
}
|
|
24262
|
+
.mld-exp-code--copyable:hover {
|
|
24263
|
+
background: var(--mld-color-surface-3, #e4e4e4);
|
|
24264
|
+
}
|
|
24265
|
+
.mld-exp-code--copyable:focus-visible {
|
|
24266
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
24267
|
+
outline-offset: 1px;
|
|
24268
|
+
}
|
|
24269
|
+
/* Copied feedback */
|
|
24270
|
+
.mld-exp-code--copied {
|
|
24271
|
+
background: var(--mld-color-success-soft, #dcfce7);
|
|
24272
|
+
color: var(--mld-color-success, #16a34a);
|
|
24273
|
+
}
|
|
24194
24274
|
/* TimeRangeInput Component Styles */
|
|
24195
24275
|
.mld-time-range {
|
|
24196
24276
|
display: flex;
|
|
@@ -24993,11 +25073,26 @@ to { transform: rotate(360deg);
|
|
|
24993
25073
|
width: 10rem;
|
|
24994
25074
|
}
|
|
24995
25075
|
|
|
24996
|
-
/* Loading */
|
|
24997
|
-
.mld-experiment-
|
|
25076
|
+
/* Loading skeleton */
|
|
25077
|
+
.mld-experiment-selector__skeleton {
|
|
24998
25078
|
display: flex;
|
|
24999
|
-
|
|
25000
|
-
|
|
25079
|
+
flex-direction: column;
|
|
25080
|
+
gap: 1px;
|
|
25081
|
+
border: 1px solid var(--border-color);
|
|
25082
|
+
border-radius: var(--mld-radius);
|
|
25083
|
+
}
|
|
25084
|
+
.mld-experiment-selector__skeleton-row {
|
|
25085
|
+
display: flex;
|
|
25086
|
+
align-items: center;
|
|
25087
|
+
gap: 0.75rem;
|
|
25088
|
+
padding: 0.75rem 1rem;
|
|
25089
|
+
background-color: var(--bg-primary);
|
|
25090
|
+
}
|
|
25091
|
+
.mld-experiment-selector__skeleton-content {
|
|
25092
|
+
flex: 1;
|
|
25093
|
+
display: flex;
|
|
25094
|
+
flex-direction: column;
|
|
25095
|
+
gap: 6px;
|
|
25001
25096
|
}
|
|
25002
25097
|
|
|
25003
25098
|
/* Error */
|
|
@@ -25044,15 +25139,31 @@ to { transform: rotate(360deg);
|
|
|
25044
25139
|
background-color: var(--color-primary-soft);
|
|
25045
25140
|
}
|
|
25046
25141
|
|
|
25142
|
+
/* Keyboard focused row */
|
|
25143
|
+
.mld-experiment-selector__row--focused {
|
|
25144
|
+
background-color: var(--bg-hover);
|
|
25145
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
25146
|
+
outline-offset: -2px;
|
|
25147
|
+
border-radius: 2px;
|
|
25148
|
+
}
|
|
25149
|
+
.mld-experiment-selector__row--active.mld-experiment-selector__row--focused {
|
|
25150
|
+
background-color: var(--color-primary-soft);
|
|
25151
|
+
}
|
|
25152
|
+
|
|
25047
25153
|
/* Row content */
|
|
25048
25154
|
.mld-experiment-selector__row-content {
|
|
25049
25155
|
flex: 1;
|
|
25050
25156
|
min-width: 0;
|
|
25051
25157
|
}
|
|
25052
25158
|
.mld-experiment-selector__name {
|
|
25159
|
+
display: flex;
|
|
25160
|
+
align-items: center;
|
|
25161
|
+
gap: 0.5rem;
|
|
25053
25162
|
font-size: 0.875rem;
|
|
25054
25163
|
font-weight: 500;
|
|
25055
25164
|
color: var(--text-primary);
|
|
25165
|
+
}
|
|
25166
|
+
.mld-experiment-selector__name > span:first-child {
|
|
25056
25167
|
white-space: nowrap;
|
|
25057
25168
|
overflow: hidden;
|
|
25058
25169
|
text-overflow: ellipsis;
|
package/package.json
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import ExperimentCodeBadge from './ExperimentCodeBadge.vue'
|
|
3
|
+
|
|
4
|
+
const sizes = ['sm', 'md', 'lg'] as const
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<Story title="Experiment/ExperimentCodeBadge">
|
|
9
|
+
<Variant title="Playground">
|
|
10
|
+
<template #default="{ state }">
|
|
11
|
+
<div style="padding: 2rem; display: flex; align-items: center; justify-content: center;">
|
|
12
|
+
<ExperimentCodeBadge
|
|
13
|
+
:code="state.code"
|
|
14
|
+
:size="state.size"
|
|
15
|
+
:copyable="state.copyable"
|
|
16
|
+
@copy="(code) => console.log('Copied:', code)"
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<template #controls="{ state }">
|
|
22
|
+
<HstText v-model="state.code" title="Code" />
|
|
23
|
+
<HstSelect
|
|
24
|
+
v-model="state.size"
|
|
25
|
+
title="Size"
|
|
26
|
+
:options="sizes.map(s => ({ label: s, value: s }))"
|
|
27
|
+
/>
|
|
28
|
+
<HstCheckbox v-model="state.copyable" title="Copyable" />
|
|
29
|
+
</template>
|
|
30
|
+
</Variant>
|
|
31
|
+
|
|
32
|
+
<Variant title="All Sizes">
|
|
33
|
+
<div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem; align-items: flex-start;">
|
|
34
|
+
<div v-for="size in sizes" :key="size" style="display: flex; align-items: center; gap: 1rem;">
|
|
35
|
+
<span style="font-size: 0.75rem; color: var(--text-muted, #94a3b8); text-transform: uppercase; width: 2rem;">
|
|
36
|
+
{{ size }}
|
|
37
|
+
</span>
|
|
38
|
+
<ExperimentCodeBadge :code="'EXP-2024-001'" :size="size" :copyable="false" />
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</Variant>
|
|
42
|
+
|
|
43
|
+
<Variant title="Long Code">
|
|
44
|
+
<div style="padding: 2rem; display: flex; flex-direction: column; gap: 0.75rem; align-items: flex-start;">
|
|
45
|
+
<ExperimentCodeBadge code="EXP-2024-001" :copyable="false" />
|
|
46
|
+
<ExperimentCodeBadge code="DR-CV-HEK293T-2024-12-001" :copyable="false" />
|
|
47
|
+
<ExperimentCodeBadge code="IC50-PANC1-GEM-SERIES-A" :copyable="false" />
|
|
48
|
+
</div>
|
|
49
|
+
</Variant>
|
|
50
|
+
|
|
51
|
+
<Variant title="Copyable Demo">
|
|
52
|
+
<div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem; align-items: flex-start;">
|
|
53
|
+
<p style="font-size: 0.8125rem; color: var(--text-muted, #94a3b8); margin: 0;">
|
|
54
|
+
Click any badge to copy its code to clipboard
|
|
55
|
+
</p>
|
|
56
|
+
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
|
57
|
+
<ExperimentCodeBadge
|
|
58
|
+
code="EXP-2024-001"
|
|
59
|
+
@copy="(code) => console.log('Copied:', code)"
|
|
60
|
+
/>
|
|
61
|
+
<ExperimentCodeBadge
|
|
62
|
+
code="DR-HeLa-DOX-003"
|
|
63
|
+
@copy="(code) => console.log('Copied:', code)"
|
|
64
|
+
/>
|
|
65
|
+
<ExperimentCodeBadge
|
|
66
|
+
code="CV-MCF7-24H"
|
|
67
|
+
@copy="(code) => console.log('Copied:', code)"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
<p style="font-size: 0.8125rem; color: var(--text-muted, #94a3b8); margin: 0;">
|
|
71
|
+
Non-copyable badge (display only)
|
|
72
|
+
</p>
|
|
73
|
+
<ExperimentCodeBadge code="EXP-2024-001" :copyable="false" />
|
|
74
|
+
</div>
|
|
75
|
+
</Variant>
|
|
76
|
+
</Story>
|
|
77
|
+
</template>
|
|
@@ -1,17 +1,60 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
code: string
|
|
4
6
|
size?: 'sm' | 'md' | 'lg'
|
|
7
|
+
copyable?: boolean
|
|
5
8
|
}
|
|
6
9
|
|
|
7
|
-
withDefaults(defineProps<Props>(), {
|
|
10
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
8
11
|
size: 'md',
|
|
12
|
+
copyable: true,
|
|
9
13
|
})
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
copy: [code: string]
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
const copied = ref(false)
|
|
20
|
+
let copyTimeout: ReturnType<typeof setTimeout> | null = null
|
|
21
|
+
|
|
22
|
+
async function handleCopy() {
|
|
23
|
+
if (!props.copyable) return
|
|
24
|
+
try {
|
|
25
|
+
await navigator.clipboard.writeText(props.code)
|
|
26
|
+
copied.value = true
|
|
27
|
+
emit('copy', props.code)
|
|
28
|
+
if (copyTimeout) clearTimeout(copyTimeout)
|
|
29
|
+
copyTimeout = setTimeout(() => { copied.value = false }, 1500)
|
|
30
|
+
} catch {
|
|
31
|
+
// Clipboard API not available (e.g. insecure context)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
36
|
+
if (!props.copyable) return
|
|
37
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
38
|
+
event.preventDefault()
|
|
39
|
+
handleCopy()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
10
42
|
</script>
|
|
11
43
|
|
|
12
44
|
<template>
|
|
13
|
-
<span
|
|
14
|
-
|
|
45
|
+
<span
|
|
46
|
+
:class="[
|
|
47
|
+
'mld-exp-code',
|
|
48
|
+
`mld-exp-code--${size}`,
|
|
49
|
+
{ 'mld-exp-code--copyable': copyable, 'mld-exp-code--copied': copied },
|
|
50
|
+
]"
|
|
51
|
+
:role="copyable ? 'button' : undefined"
|
|
52
|
+
:tabindex="copyable ? 0 : undefined"
|
|
53
|
+
:title="copyable ? (copied ? 'Copied!' : 'Click to copy') : undefined"
|
|
54
|
+
@click="handleCopy"
|
|
55
|
+
@keydown="handleKeydown"
|
|
56
|
+
>
|
|
57
|
+
{{ copied ? 'Copied!' : code }}
|
|
15
58
|
</span>
|
|
16
59
|
</template>
|
|
17
60
|
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import ExperimentDataViewer from './ExperimentDataViewer.vue'
|
|
3
|
+
import type { TreeNode, SummaryData } from '../types'
|
|
4
|
+
|
|
5
|
+
const treeData: TreeNode[] = [
|
|
6
|
+
{
|
|
7
|
+
id: 'plate-1',
|
|
8
|
+
label: 'Plate PLT-001',
|
|
9
|
+
type: 'plate',
|
|
10
|
+
badge: 96,
|
|
11
|
+
badgeVariant: 'default',
|
|
12
|
+
children: [
|
|
13
|
+
{
|
|
14
|
+
id: 'group-ctrl',
|
|
15
|
+
label: 'Vehicle Control',
|
|
16
|
+
type: 'treatment',
|
|
17
|
+
badge: 12,
|
|
18
|
+
children: [
|
|
19
|
+
{ id: 's-1', label: 'Well A01', type: 'sample', metadata: { viability: 98.5 } },
|
|
20
|
+
{ id: 's-2', label: 'Well A02', type: 'sample', metadata: { viability: 97.2 } },
|
|
21
|
+
{ id: 's-3', label: 'Well A03', type: 'sample', metadata: { viability: 99.1 } },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'group-dox',
|
|
26
|
+
label: 'Doxorubicin 1uM',
|
|
27
|
+
type: 'treatment',
|
|
28
|
+
badge: 12,
|
|
29
|
+
children: [
|
|
30
|
+
{ id: 's-4', label: 'Well B01', type: 'sample', metadata: { viability: 45.2 } },
|
|
31
|
+
{ id: 's-5', label: 'Well B02', type: 'sample', metadata: { viability: 42.8 } },
|
|
32
|
+
{ id: 's-6', label: 'Well B03', type: 'sample', metadata: { viability: 48.1 } },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const tableData = [
|
|
40
|
+
{ well: 'A01', group: 'Vehicle Control', concentration: 0, viability: 98.5, normalized: 100 },
|
|
41
|
+
{ well: 'A02', group: 'Vehicle Control', concentration: 0, viability: 97.2, normalized: 98.7 },
|
|
42
|
+
{ well: 'A03', group: 'Vehicle Control', concentration: 0, viability: 99.1, normalized: 100.6 },
|
|
43
|
+
{ well: 'B01', group: 'Doxorubicin', concentration: 1.0, viability: 45.2, normalized: 45.9 },
|
|
44
|
+
{ well: 'B02', group: 'Doxorubicin', concentration: 1.0, viability: 42.8, normalized: 43.5 },
|
|
45
|
+
{ well: 'B03', group: 'Doxorubicin', concentration: 1.0, viability: 48.1, normalized: 48.8 },
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const summaryData: SummaryData = {
|
|
49
|
+
metadata: {
|
|
50
|
+
cell_line: 'HeLa',
|
|
51
|
+
passage: 'P12',
|
|
52
|
+
plate_format: '96-well',
|
|
53
|
+
seeding_density: '5000 cells/well',
|
|
54
|
+
},
|
|
55
|
+
sections: [
|
|
56
|
+
{
|
|
57
|
+
key: 'treatments',
|
|
58
|
+
label: 'Treatments',
|
|
59
|
+
type: 'group',
|
|
60
|
+
items: [
|
|
61
|
+
{
|
|
62
|
+
label: 'Vehicle Control',
|
|
63
|
+
metadata: { type: 'negative control' },
|
|
64
|
+
item_count: 12,
|
|
65
|
+
item_key: 'wells',
|
|
66
|
+
columns: ['well', 'viability', 'normalized'],
|
|
67
|
+
rows: [
|
|
68
|
+
{ well: 'A01', viability: 98.5, normalized: 100 },
|
|
69
|
+
{ well: 'A02', viability: 97.2, normalized: 98.7 },
|
|
70
|
+
{ well: 'A03', viability: 99.1, normalized: 100.6 },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Doxorubicin 1uM',
|
|
75
|
+
metadata: { type: 'treatment', compound: 'Doxorubicin' },
|
|
76
|
+
item_count: 12,
|
|
77
|
+
item_key: 'wells',
|
|
78
|
+
columns: ['well', 'viability', 'normalized'],
|
|
79
|
+
rows: [
|
|
80
|
+
{ well: 'B01', viability: 45.2, normalized: 45.9 },
|
|
81
|
+
{ well: 'B02', viability: 42.8, normalized: 43.5 },
|
|
82
|
+
{ well: 'B03', viability: 48.1, normalized: 48.8 },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const views = ['summary', 'tree', 'table'] as const
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<template>
|
|
94
|
+
<Story title="Experiment/ExperimentDataViewer">
|
|
95
|
+
<Variant title="Playground">
|
|
96
|
+
<template #default="{ state }">
|
|
97
|
+
<div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
|
|
98
|
+
<ExperimentDataViewer
|
|
99
|
+
:tree-data="treeData"
|
|
100
|
+
:table-data="tableData"
|
|
101
|
+
:summary-data="state.showSummary ? summaryData : null"
|
|
102
|
+
:default-view="state.defaultView"
|
|
103
|
+
:loading="state.loading"
|
|
104
|
+
title="Cell Viability Data"
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
<template #controls="{ state }">
|
|
110
|
+
<HstSelect
|
|
111
|
+
v-model="state.defaultView"
|
|
112
|
+
title="Default View"
|
|
113
|
+
:options="views.map(v => ({ label: v, value: v }))"
|
|
114
|
+
/>
|
|
115
|
+
<HstCheckbox v-model="state.loading" title="Loading" />
|
|
116
|
+
<HstCheckbox v-model="state.showSummary" title="Show Summary" />
|
|
117
|
+
</template>
|
|
118
|
+
</Variant>
|
|
119
|
+
|
|
120
|
+
<Variant title="Summary View">
|
|
121
|
+
<div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
|
|
122
|
+
<ExperimentDataViewer
|
|
123
|
+
:tree-data="treeData"
|
|
124
|
+
:table-data="tableData"
|
|
125
|
+
:summary-data="summaryData"
|
|
126
|
+
default-view="summary"
|
|
127
|
+
title="Dose Response - HeLa"
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
</Variant>
|
|
131
|
+
|
|
132
|
+
<Variant title="Tree View">
|
|
133
|
+
<div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
|
|
134
|
+
<ExperimentDataViewer
|
|
135
|
+
:tree-data="treeData"
|
|
136
|
+
:table-data="tableData"
|
|
137
|
+
default-view="tree"
|
|
138
|
+
title="Sample Hierarchy"
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
</Variant>
|
|
142
|
+
|
|
143
|
+
<Variant title="Table View">
|
|
144
|
+
<div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
|
|
145
|
+
<ExperimentDataViewer
|
|
146
|
+
:tree-data="treeData"
|
|
147
|
+
:table-data="tableData"
|
|
148
|
+
default-view="table"
|
|
149
|
+
title="Raw Measurements"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
</Variant>
|
|
153
|
+
|
|
154
|
+
<Variant title="Loading">
|
|
155
|
+
<div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
|
|
156
|
+
<ExperimentDataViewer
|
|
157
|
+
:tree-data="[]"
|
|
158
|
+
:loading="true"
|
|
159
|
+
title="Loading Data..."
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
</Variant>
|
|
163
|
+
|
|
164
|
+
<Variant title="Empty">
|
|
165
|
+
<div style="padding: 1rem; max-width: 800px; margin: 0 auto;">
|
|
166
|
+
<ExperimentDataViewer
|
|
167
|
+
:tree-data="[]"
|
|
168
|
+
default-view="tree"
|
|
169
|
+
title="Empty Experiment"
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
</Variant>
|
|
173
|
+
</Story>
|
|
174
|
+
</template>
|