@morscherlab/mint-sdk 1.0.0-alpha.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/README.md +326 -0
- package/dist/__stories__/experiment-helpers.d.ts +25 -0
- package/dist/__tests__/components/AppLayout.test.d.ts +1 -0
- package/dist/__tests__/components/AppSidebar.test.d.ts +1 -0
- package/dist/__tests__/components/AppTopBar.test.d.ts +1 -0
- package/dist/__tests__/components/BaseInput.test.d.ts +1 -0
- package/dist/__tests__/components/BasePill.test.d.ts +1 -0
- package/dist/__tests__/components/Calendar.test.d.ts +1 -0
- package/dist/__tests__/components/CollapsibleCard.test.d.ts +1 -0
- package/dist/__tests__/components/DataFrame.test.d.ts +1 -0
- package/dist/__tests__/components/DropdownButton.test.d.ts +1 -0
- package/dist/__tests__/composables/formBuilderRegistry.test.d.ts +1 -0
- package/dist/__tests__/composables/useAppExperiment.test.d.ts +1 -0
- package/dist/__tests__/composables/useAuth.test.d.ts +1 -0
- package/dist/__tests__/composables/useAutoGroup.test.d.ts +1 -0
- package/dist/__tests__/composables/useExperimentData.test.d.ts +13 -0
- package/dist/__tests__/composables/useExperimentSave.test.d.ts +1 -0
- package/dist/__tests__/composables/useForm.test.d.ts +1 -0
- package/dist/__tests__/composables/useFormBuilder.test.d.ts +1 -0
- package/dist/__tests__/composables/usePlatformContext.test.d.ts +1 -0
- package/dist/__tests__/composables/usePluginApi.test.d.ts +13 -0
- package/dist/__tests__/composables/usePluginConfig.test.d.ts +14 -0
- package/dist/__tests__/utils/color.test.d.ts +1 -0
- package/dist/auth-BYmxZdJl.js +297 -0
- package/dist/auth-BYmxZdJl.js.map +1 -0
- package/dist/components/AlertBox.vue.d.ts +34 -0
- package/dist/components/AppAvatarMenu.vue.d.ts +58 -0
- package/dist/components/AppContainer.vue.d.ts +28 -0
- package/dist/components/AppLayout.vue.d.ts +31 -0
- package/dist/components/AppPageSelector.vue.d.ts +43 -0
- package/dist/components/AppPillNav.vue.d.ts +11 -0
- package/dist/components/AppPluginSwitcher.vue.d.ts +38 -0
- package/dist/components/AppSidebar.vue.d.ts +47 -0
- package/dist/components/AppTopBar.vue.d.ts +111 -0
- package/dist/components/AuditTrail.vue.d.ts +38 -0
- package/dist/components/AutoGroupModal.vue.d.ts +124 -0
- package/dist/components/Avatar.vue.d.ts +14 -0
- package/dist/components/BaseButton.vue.d.ts +37 -0
- package/dist/components/BaseCheckbox.vue.d.ts +17 -0
- package/dist/components/BaseInput.vue.d.ts +34 -0
- package/dist/components/BaseModal.vue.d.ts +46 -0
- package/dist/components/BasePill.vue.d.ts +57 -0
- package/dist/components/BaseRadioGroup.vue.d.ts +21 -0
- package/dist/components/BaseSelect.vue.d.ts +20 -0
- package/dist/components/BaseSlider.vue.d.ts +22 -0
- package/dist/components/BaseTabs.vue.d.ts +14 -0
- package/dist/components/BaseTextarea.vue.d.ts +30 -0
- package/dist/components/BaseToggle.vue.d.ts +19 -0
- package/dist/components/BatchProgressList.vue.d.ts +43 -0
- package/dist/components/Breadcrumb.vue.d.ts +33 -0
- package/dist/components/Calendar.vue.d.ts +107 -0
- package/dist/components/ChartContainer.vue.d.ts +31 -0
- package/dist/components/ChemicalFormula.vue.d.ts +8 -0
- package/dist/components/CollapsibleCard.vue.d.ts +41 -0
- package/dist/components/ColorSlider.vue.d.ts +34 -0
- package/dist/components/ConcentrationInput.vue.d.ts +25 -0
- package/dist/components/ConfirmDialog.vue.d.ts +42 -0
- package/dist/components/DataFrame.vue.d.ts +107 -0
- package/dist/components/DatePicker.vue.d.ts +25 -0
- package/dist/components/DateTimePicker.vue.d.ts +30 -0
- package/dist/components/Divider.vue.d.ts +14 -0
- package/dist/components/DoseCalculator.vue.d.ts +19 -0
- package/dist/components/DropdownButton.vue.d.ts +47 -0
- package/dist/components/EmptyState.vue.d.ts +36 -0
- package/dist/components/ExperimentCodeBadge.vue.d.ts +14 -0
- package/dist/components/ExperimentDataViewer.vue.d.ts +29 -0
- package/dist/components/ExperimentPopover.vue.d.ts +32 -0
- package/dist/components/ExperimentSelectorModal.vue.d.ts +28 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +44 -0
- package/dist/components/FileUploader.vue.d.ts +40 -0
- package/dist/components/FitPanel.vue.d.ts +46 -0
- package/dist/components/FormActions.vue.d.ts +33 -0
- package/dist/components/FormBuilder.vue.d.ts +287 -0
- package/dist/components/FormField.vue.d.ts +28 -0
- package/dist/components/FormFieldRenderer.vue.d.ts +31 -0
- package/dist/components/FormSection.vue.d.ts +43 -0
- package/dist/components/FormulaInput.vue.d.ts +25 -0
- package/dist/components/GroupAssigner.vue.d.ts +25 -0
- package/dist/components/GroupingModal.vue.d.ts +12 -0
- package/dist/components/IconButton.vue.d.ts +34 -0
- package/dist/components/LoadingSpinner.vue.d.ts +12 -0
- package/dist/components/MoleculeInput.vue.d.ts +27 -0
- package/dist/components/MultiSelect.vue.d.ts +19 -0
- package/dist/components/NumberInput.vue.d.ts +22 -0
- package/dist/components/PlateMapEditor.vue.d.ts +50 -0
- package/dist/components/ProgressBar.vue.d.ts +23 -0
- package/dist/components/ProtocolStepEditor.vue.d.ts +24 -0
- package/dist/components/RackEditor.vue.d.ts +40 -0
- package/dist/components/ReagentEditor.vue.d.ts +30 -0
- package/dist/components/ReagentList.vue.d.ts +32 -0
- package/dist/components/ResourceCard.vue.d.ts +50 -0
- package/dist/components/SampleHierarchyTree.vue.d.ts +26 -0
- package/dist/components/SampleLegend.vue.d.ts +32 -0
- package/dist/components/SampleSelector.vue.d.ts +29 -0
- package/dist/components/ScheduleCalendar.vue.d.ts +110 -0
- package/dist/components/ScientificNumber.vue.d.ts +14 -0
- package/dist/components/SegmentedControl.vue.d.ts +20 -0
- package/dist/components/SequenceInput.vue.d.ts +54 -0
- package/dist/components/SettingsButton.vue.d.ts +30 -0
- package/dist/components/SettingsModal.vue.d.ts +36 -0
- package/dist/components/Skeleton.vue.d.ts +11 -0
- package/dist/components/StatusIndicator.vue.d.ts +13 -0
- package/dist/components/StepWizard.vue.d.ts +65 -0
- package/dist/components/TagsInput.vue.d.ts +39 -0
- package/dist/components/ThemeToggle.vue.d.ts +7 -0
- package/dist/components/TimePicker.vue.d.ts +29 -0
- package/dist/components/TimeRangeInput.vue.d.ts +27 -0
- package/dist/components/ToastNotification.vue.d.ts +2 -0
- package/dist/components/Tooltip.vue.d.ts +35 -0
- package/dist/components/UnitInput.vue.d.ts +39 -0
- package/dist/components/WellEditPopup.vue.d.ts +25 -0
- package/dist/components/WellPlate.vue.d.ts +73 -0
- package/dist/components/index.d.ts +87 -0
- package/dist/components/index.js +3 -0
- package/dist/components-CKf-UpGi.js +15089 -0
- package/dist/components-CKf-UpGi.js.map +1 -0
- package/dist/composables/experiment-utils.d.ts +8 -0
- package/dist/composables/formBuilderRegistry.d.ts +13 -0
- package/dist/composables/index.d.ts +28 -0
- package/dist/composables/index.js +3 -0
- package/dist/composables/useApi.d.ts +20 -0
- package/dist/composables/useAppExperiment.d.ts +37 -0
- package/dist/composables/useAsync.d.ts +128 -0
- package/dist/composables/useAuth.d.ts +47 -0
- package/dist/composables/useAutoGroup.d.ts +106 -0
- package/dist/composables/useChemicalFormula.d.ts +21 -0
- package/dist/composables/useConcentrationUnits.d.ts +29 -0
- package/dist/composables/useDoseCalculator.d.ts +58 -0
- package/dist/composables/useExperimentData.d.ts +18 -0
- package/dist/composables/useExperimentSave.d.ts +36 -0
- package/dist/composables/useExperimentSelector.d.ts +30 -0
- package/dist/composables/useForm.d.ts +92 -0
- package/dist/composables/useFormBuilder.d.ts +24 -0
- package/dist/composables/usePasskey.d.ts +10 -0
- package/dist/composables/usePlatformContext.d.ts +131 -0
- package/dist/composables/usePluginApi.d.ts +29 -0
- package/dist/composables/usePluginConfig.d.ts +13 -0
- package/dist/composables/useProtocolTemplates.d.ts +44 -0
- package/dist/composables/useRackEditor.d.ts +31 -0
- package/dist/composables/useReagentSeries.d.ts +23 -0
- package/dist/composables/useScheduleDrag.d.ts +78 -0
- package/dist/composables/useSequenceUtils.d.ts +14 -0
- package/dist/composables/useTheme.d.ts +8 -0
- package/dist/composables/useTimeUtils.d.ts +29 -0
- package/dist/composables/useToast.d.ts +22 -0
- package/dist/composables/useWellPlateEditor.d.ts +33 -0
- package/dist/composables-D0QfFzq1.js +805 -0
- package/dist/composables-D0QfFzq1.js.map +1 -0
- package/dist/histoire.setup.d.ts +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/install.d.ts +16 -0
- package/dist/install.js +23 -0
- package/dist/install.js.map +1 -0
- package/dist/stores/auth.d.ts +146 -0
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/index.js +2 -0
- package/dist/stores/settings.d.ts +75 -0
- package/dist/styles.css +29728 -0
- package/dist/tailwind.preset.d.ts +58 -0
- package/dist/tailwind.preset.js +66 -0
- package/dist/tailwind.preset.js.map +1 -0
- package/dist/types/auth.d.ts +42 -0
- package/dist/types/auto-group.d.ts +34 -0
- package/dist/types/components.d.ts +528 -0
- package/dist/types/form-builder.d.ts +167 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +0 -0
- package/dist/types/platform.d.ts +75 -0
- package/dist/useScheduleDrag-DAJueTbK.js +7181 -0
- package/dist/useScheduleDrag-DAJueTbK.js.map +1 -0
- package/dist/utils/color.d.ts +24 -0
- package/package.json +114 -0
- package/src/__stories__/experiment-helpers.ts +83 -0
- package/src/__tests__/components/AppLayout.test.ts +163 -0
- package/src/__tests__/components/AppSidebar.test.ts +292 -0
- package/src/__tests__/components/AppTopBar.test.ts +683 -0
- package/src/__tests__/components/BaseInput.test.ts +99 -0
- package/src/__tests__/components/BasePill.test.ts +291 -0
- package/src/__tests__/components/Calendar.test.ts +566 -0
- package/src/__tests__/components/CollapsibleCard.test.ts +524 -0
- package/src/__tests__/components/DataFrame.test.ts +767 -0
- package/src/__tests__/components/DropdownButton.test.ts +471 -0
- package/src/__tests__/composables/formBuilderRegistry.test.ts +187 -0
- package/src/__tests__/composables/useAppExperiment.test.ts +560 -0
- package/src/__tests__/composables/useAuth.test.ts +188 -0
- package/src/__tests__/composables/useAutoGroup.test.ts +860 -0
- package/src/__tests__/composables/useExperimentData.test.ts +127 -0
- package/src/__tests__/composables/useExperimentSave.test.ts +347 -0
- package/src/__tests__/composables/useForm.test.ts +205 -0
- package/src/__tests__/composables/useFormBuilder.test.ts +917 -0
- package/src/__tests__/composables/usePlatformContext.test.ts +116 -0
- package/src/__tests__/composables/usePluginApi.test.ts +81 -0
- package/src/__tests__/composables/usePluginConfig.test.ts +176 -0
- package/src/__tests__/utils/color.test.ts +96 -0
- package/src/components/AlertBox.story.vue +204 -0
- package/src/components/AlertBox.vue +88 -0
- package/src/components/AppAvatarMenu.story.vue +155 -0
- package/src/components/AppAvatarMenu.vue +184 -0
- package/src/components/AppContainer.story.vue +104 -0
- package/src/components/AppContainer.vue +34 -0
- package/src/components/AppLayout.story.vue +292 -0
- package/src/components/AppLayout.vue +75 -0
- package/src/components/AppPageSelector.vue +159 -0
- package/src/components/AppPillNav.vue +66 -0
- package/src/components/AppPluginSwitcher.vue +241 -0
- package/src/components/AppSidebar.story.vue +309 -0
- package/src/components/AppSidebar.vue +119 -0
- package/src/components/AppTopBar.story.vue +304 -0
- package/src/components/AppTopBar.vue +661 -0
- package/src/components/AuditTrail.story.vue +163 -0
- package/src/components/AuditTrail.vue +151 -0
- package/src/components/AutoGroupModal.story.vue +273 -0
- package/src/components/AutoGroupModal.vue +566 -0
- package/src/components/Avatar.story.vue +115 -0
- package/src/components/Avatar.vue +79 -0
- package/src/components/BaseButton.story.vue +96 -0
- package/src/components/BaseButton.vue +73 -0
- package/src/components/BaseCheckbox.story.vue +73 -0
- package/src/components/BaseCheckbox.vue +69 -0
- package/src/components/BaseInput.story.vue +98 -0
- package/src/components/BaseInput.vue +74 -0
- package/src/components/BaseModal.story.vue +237 -0
- package/src/components/BaseModal.vue +182 -0
- package/src/components/BasePill.story.vue +142 -0
- package/src/components/BasePill.vue +89 -0
- package/src/components/BaseRadioGroup.story.vue +145 -0
- package/src/components/BaseRadioGroup.vue +124 -0
- package/src/components/BaseSelect.story.vue +120 -0
- package/src/components/BaseSelect.vue +71 -0
- package/src/components/BaseSlider.story.vue +122 -0
- package/src/components/BaseSlider.vue +126 -0
- package/src/components/BaseTabs.story.vue +127 -0
- package/src/components/BaseTabs.vue +59 -0
- package/src/components/BaseTextarea.story.vue +91 -0
- package/src/components/BaseTextarea.vue +62 -0
- package/src/components/BaseToggle.story.vue +81 -0
- package/src/components/BaseToggle.vue +76 -0
- package/src/components/BatchProgressList.story.vue +92 -0
- package/src/components/BatchProgressList.vue +184 -0
- package/src/components/Breadcrumb.story.vue +106 -0
- package/src/components/Breadcrumb.vue +75 -0
- package/src/components/Calendar.story.vue +106 -0
- package/src/components/Calendar.vue +363 -0
- package/src/components/ChartContainer.story.vue +113 -0
- package/src/components/ChartContainer.vue +64 -0
- package/src/components/ChemicalFormula.story.vue +102 -0
- package/src/components/ChemicalFormula.vue +39 -0
- package/src/components/CollapsibleCard.story.vue +135 -0
- package/src/components/CollapsibleCard.vue +167 -0
- package/src/components/ColorSlider.story.vue +120 -0
- package/src/components/ColorSlider.vue +164 -0
- package/src/components/ConcentrationInput.story.vue +77 -0
- package/src/components/ConcentrationInput.vue +185 -0
- package/src/components/ConfirmDialog.story.vue +248 -0
- package/src/components/ConfirmDialog.vue +93 -0
- package/src/components/DataFrame.story.vue +148 -0
- package/src/components/DataFrame.vue +419 -0
- package/src/components/DatePicker.story.vue +119 -0
- package/src/components/DatePicker.vue +330 -0
- package/src/components/DateTimePicker.story.vue +112 -0
- package/src/components/DateTimePicker.vue +392 -0
- package/src/components/Divider.story.vue +80 -0
- package/src/components/Divider.vue +49 -0
- package/src/components/DoseCalculator.story.vue +68 -0
- package/src/components/DoseCalculator.vue +476 -0
- package/src/components/DropdownButton.story.vue +102 -0
- package/src/components/DropdownButton.vue +181 -0
- package/src/components/EmptyState.story.vue +135 -0
- package/src/components/EmptyState.vue +69 -0
- package/src/components/ExperimentCodeBadge.story.vue +77 -0
- package/src/components/ExperimentCodeBadge.vue +64 -0
- package/src/components/ExperimentDataViewer.story.vue +174 -0
- package/src/components/ExperimentDataViewer.vue +288 -0
- package/src/components/ExperimentPopover.story.vue +384 -0
- package/src/components/ExperimentPopover.vue +241 -0
- package/src/components/ExperimentSelectorModal.story.vue +391 -0
- package/src/components/ExperimentSelectorModal.vue +387 -0
- package/src/components/ExperimentTimeline.story.vue +161 -0
- package/src/components/ExperimentTimeline.vue +382 -0
- package/src/components/FileUploader.story.vue +107 -0
- package/src/components/FileUploader.vue +386 -0
- package/src/components/FitPanel.story.vue +125 -0
- package/src/components/FitPanel.vue +120 -0
- package/src/components/FormActions.vue +92 -0
- package/src/components/FormBuilder.vue +214 -0
- package/src/components/FormField.story.vue +132 -0
- package/src/components/FormField.vue +59 -0
- package/src/components/FormFieldRenderer.vue +58 -0
- package/src/components/FormSection.vue +90 -0
- package/src/components/FormulaInput.story.vue +96 -0
- package/src/components/FormulaInput.vue +125 -0
- package/src/components/GroupAssigner.story.vue +83 -0
- package/src/components/GroupAssigner.vue +284 -0
- package/src/components/GroupingModal.story.vue +52 -0
- package/src/components/GroupingModal.vue +422 -0
- package/src/components/IconButton.story.vue +135 -0
- package/src/components/IconButton.vue +73 -0
- package/src/components/LoadingSpinner.story.vue +70 -0
- package/src/components/LoadingSpinner.vue +50 -0
- package/src/components/MoleculeInput.story.vue +66 -0
- package/src/components/MoleculeInput.vue +426 -0
- package/src/components/MultiSelect.story.vue +132 -0
- package/src/components/MultiSelect.vue +118 -0
- package/src/components/NumberInput.story.vue +122 -0
- package/src/components/NumberInput.vue +160 -0
- package/src/components/PlateMapEditor.story.vue +92 -0
- package/src/components/PlateMapEditor.vue +513 -0
- package/src/components/ProgressBar.story.vue +148 -0
- package/src/components/ProgressBar.vue +114 -0
- package/src/components/ProtocolStepEditor.story.vue +69 -0
- package/src/components/ProtocolStepEditor.vue +522 -0
- package/src/components/RackEditor.story.vue +100 -0
- package/src/components/RackEditor.vue +371 -0
- package/src/components/ReagentEditor.story.vue +153 -0
- package/src/components/ReagentEditor.vue +418 -0
- package/src/components/ReagentList.story.vue +137 -0
- package/src/components/ReagentList.vue +463 -0
- package/src/components/ResourceCard.story.vue +150 -0
- package/src/components/ResourceCard.vue +161 -0
- package/src/components/SampleHierarchyTree.story.vue +161 -0
- package/src/components/SampleHierarchyTree.vue +256 -0
- package/src/components/SampleLegend.story.vue +91 -0
- package/src/components/SampleLegend.vue +119 -0
- package/src/components/SampleSelector.story.vue +111 -0
- package/src/components/SampleSelector.vue +1033 -0
- package/src/components/ScheduleCalendar.story.vue +195 -0
- package/src/components/ScheduleCalendar.vue +569 -0
- package/src/components/ScientificNumber.story.vue +127 -0
- package/src/components/ScientificNumber.vue +197 -0
- package/src/components/SegmentedControl.story.vue +132 -0
- package/src/components/SegmentedControl.vue +79 -0
- package/src/components/SequenceInput.story.vue +119 -0
- package/src/components/SequenceInput.vue +209 -0
- package/src/components/SettingsButton.story.vue +58 -0
- package/src/components/SettingsButton.vue +76 -0
- package/src/components/SettingsModal.story.vue +145 -0
- package/src/components/SettingsModal.vue +146 -0
- package/src/components/Skeleton.story.vue +141 -0
- package/src/components/Skeleton.vue +74 -0
- package/src/components/StatusIndicator.story.vue +99 -0
- package/src/components/StatusIndicator.vue +40 -0
- package/src/components/StepWizard.story.vue +155 -0
- package/src/components/StepWizard.vue +223 -0
- package/src/components/TagsInput.story.vue +155 -0
- package/src/components/TagsInput.vue +265 -0
- package/src/components/ThemeToggle.story.vue +36 -0
- package/src/components/ThemeToggle.vue +54 -0
- package/src/components/TimePicker.story.vue +96 -0
- package/src/components/TimePicker.vue +273 -0
- package/src/components/TimeRangeInput.story.vue +104 -0
- package/src/components/TimeRangeInput.vue +122 -0
- package/src/components/ToastNotification.story.vue +157 -0
- package/src/components/ToastNotification.vue +62 -0
- package/src/components/Tooltip.story.vue +138 -0
- package/src/components/Tooltip.vue +119 -0
- package/src/components/UnitInput.story.vue +194 -0
- package/src/components/UnitInput.vue +213 -0
- package/src/components/WellEditPopup.vue +234 -0
- package/src/components/WellPlate.story.vue +282 -0
- package/src/components/WellPlate.vue +830 -0
- package/src/components/index.ts +118 -0
- package/src/composables/experiment-utils.ts +57 -0
- package/src/composables/formBuilderRegistry.ts +79 -0
- package/src/composables/index.ts +140 -0
- package/src/composables/useApi.ts +167 -0
- package/src/composables/useAppExperiment.ts +159 -0
- package/src/composables/useAsync.ts +323 -0
- package/src/composables/useAuth.ts +445 -0
- package/src/composables/useAutoGroup.ts +641 -0
- package/src/composables/useChemicalFormula.ts +275 -0
- package/src/composables/useConcentrationUnits.ts +246 -0
- package/src/composables/useDoseCalculator.ts +370 -0
- package/src/composables/useExperimentData.ts +86 -0
- package/src/composables/useExperimentSave.ts +192 -0
- package/src/composables/useExperimentSelector.ts +292 -0
- package/src/composables/useForm.ts +416 -0
- package/src/composables/useFormBuilder.ts +383 -0
- package/src/composables/usePasskey.ts +216 -0
- package/src/composables/usePlatformContext.ts +299 -0
- package/src/composables/usePluginApi.ts +39 -0
- package/src/composables/usePluginConfig.ts +93 -0
- package/src/composables/useProtocolTemplates.ts +518 -0
- package/src/composables/useRackEditor.ts +222 -0
- package/src/composables/useReagentSeries.ts +91 -0
- package/src/composables/useScheduleDrag.ts +245 -0
- package/src/composables/useSequenceUtils.ts +105 -0
- package/src/composables/useTheme.ts +58 -0
- package/src/composables/useTimeUtils.ts +131 -0
- package/src/composables/useToast.ts +40 -0
- package/src/composables/useWellPlateEditor.ts +421 -0
- package/src/histoire.setup.ts +17 -0
- package/src/index.ts +367 -0
- package/src/install.ts +32 -0
- package/src/stores/auth.ts +152 -0
- package/src/stores/index.ts +2 -0
- package/src/stores/settings.ts +218 -0
- package/src/styles/components/alert-box.css +150 -0
- package/src/styles/components/app-avatar-menu.css +155 -0
- package/src/styles/components/app-container.css +33 -0
- package/src/styles/components/app-layout.css +98 -0
- package/src/styles/components/app-page-selector.css +191 -0
- package/src/styles/components/app-pill-nav.css +57 -0
- package/src/styles/components/app-plugin-switcher.css +209 -0
- package/src/styles/components/app-sidebar.css +145 -0
- package/src/styles/components/app-top-bar.css +492 -0
- package/src/styles/components/audit-trail.css +143 -0
- package/src/styles/components/auto-group-modal.css +644 -0
- package/src/styles/components/avatar.css +73 -0
- package/src/styles/components/batch-progress-list.css +196 -0
- package/src/styles/components/breadcrumb.css +64 -0
- package/src/styles/components/button.css +188 -0
- package/src/styles/components/calendar.css +192 -0
- package/src/styles/components/chart-container.css +69 -0
- package/src/styles/components/checkbox.css +123 -0
- package/src/styles/components/chemical-formula.css +46 -0
- package/src/styles/components/collapsible-card.css +253 -0
- package/src/styles/components/color-slider.css +110 -0
- package/src/styles/components/concentration-input.css +156 -0
- package/src/styles/components/confirm-dialog.css +183 -0
- package/src/styles/components/dataframe.css +382 -0
- package/src/styles/components/date-picker.css +243 -0
- package/src/styles/components/datetime-picker.css +229 -0
- package/src/styles/components/divider.css +63 -0
- package/src/styles/components/dose-calculator.css +301 -0
- package/src/styles/components/dropdown-button.css +280 -0
- package/src/styles/components/empty-state.css +151 -0
- package/src/styles/components/experiment-code-badge.css +33 -0
- package/src/styles/components/experiment-data-viewer.css +138 -0
- package/src/styles/components/experiment-popover.css +562 -0
- package/src/styles/components/experiment-selector-modal.css +285 -0
- package/src/styles/components/experiment-timeline.css +529 -0
- package/src/styles/components/file-uploader.css +310 -0
- package/src/styles/components/fit-panel.css +67 -0
- package/src/styles/components/form-builder.css +69 -0
- package/src/styles/components/form-field.css +48 -0
- package/src/styles/components/formula-input.css +103 -0
- package/src/styles/components/group-assigner.css +200 -0
- package/src/styles/components/grouping-modal.css +323 -0
- package/src/styles/components/icon-button.css +192 -0
- package/src/styles/components/input.css +66 -0
- package/src/styles/components/loading-spinner.css +67 -0
- package/src/styles/components/modal.css +350 -0
- package/src/styles/components/molecule-input.css +186 -0
- package/src/styles/components/multi-select.css +131 -0
- package/src/styles/components/number-input.css +199 -0
- package/src/styles/components/pill.css +188 -0
- package/src/styles/components/plate-map-editor.css +464 -0
- package/src/styles/components/progress-bar.css +133 -0
- package/src/styles/components/protocol-step-editor.css +449 -0
- package/src/styles/components/rack-editor.css +265 -0
- package/src/styles/components/radio-group.css +240 -0
- package/src/styles/components/reagent-editor.css +510 -0
- package/src/styles/components/reagent-list.css +407 -0
- package/src/styles/components/resource-card.css +360 -0
- package/src/styles/components/sample-hierarchy-tree.css +314 -0
- package/src/styles/components/sample-legend.css +201 -0
- package/src/styles/components/sample-selector.css +751 -0
- package/src/styles/components/schedule-calendar.css +478 -0
- package/src/styles/components/scientific-number.css +63 -0
- package/src/styles/components/segmented-control.css +197 -0
- package/src/styles/components/select.css +77 -0
- package/src/styles/components/sequence-input.css +184 -0
- package/src/styles/components/settings-button.css +94 -0
- package/src/styles/components/settings-modal.css +95 -0
- package/src/styles/components/skeleton.css +49 -0
- package/src/styles/components/slider.css +74 -0
- package/src/styles/components/status-indicator.css +66 -0
- package/src/styles/components/step-wizard.css +192 -0
- package/src/styles/components/tabs.css +95 -0
- package/src/styles/components/tags-input.css +195 -0
- package/src/styles/components/textarea.css +82 -0
- package/src/styles/components/theme-toggle.css +69 -0
- package/src/styles/components/time-picker.css +171 -0
- package/src/styles/components/time-range-input.css +42 -0
- package/src/styles/components/toast.css +91 -0
- package/src/styles/components/toggle.css +146 -0
- package/src/styles/components/tooltip.css +91 -0
- package/src/styles/components/unit-input.css +123 -0
- package/src/styles/components/well-edit-popup.css +252 -0
- package/src/styles/components/well-plate.css +307 -0
- package/src/styles/index.css +87 -0
- package/src/styles/variables.css +1117 -0
- package/src/tailwind.preset.ts +61 -0
- package/src/types/auth.ts +55 -0
- package/src/types/auto-group.ts +40 -0
- package/src/types/components.ts +710 -0
- package/src/types/form-builder.ts +197 -0
- package/src/types/index.ts +207 -0
- package/src/types/platform.ts +116 -0
- package/src/utils/color.ts +96 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const unmountedCallbacks: Array<() => void> = []
|
|
4
|
+
|
|
5
|
+
type MessageHandler = (event: MessageEvent) => void
|
|
6
|
+
let messageHandler: MessageHandler | null = null
|
|
7
|
+
|
|
8
|
+
vi.mock('vue', async () => {
|
|
9
|
+
const actual = await vi.importActual<typeof import('vue')>('vue')
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
onMounted: vi.fn((cb: () => void) => cb()),
|
|
13
|
+
onUnmounted: vi.fn((cb: () => void) => {
|
|
14
|
+
unmountedCallbacks.push(cb)
|
|
15
|
+
}),
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
import { usePlatformContext } from '../../composables/usePlatformContext'
|
|
20
|
+
|
|
21
|
+
function sendThemeMessage(origin: string, theme: 'light' | 'dark' | 'system'): void {
|
|
22
|
+
if (!messageHandler) {
|
|
23
|
+
throw new Error('Message handler was not registered')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
messageHandler({
|
|
27
|
+
source: window.parent,
|
|
28
|
+
origin,
|
|
29
|
+
data: {
|
|
30
|
+
type: 'mld:theme-changed',
|
|
31
|
+
payload: theme,
|
|
32
|
+
},
|
|
33
|
+
} as MessageEvent)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('usePlatformContext', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
unmountedCallbacks.length = 0
|
|
39
|
+
messageHandler = null
|
|
40
|
+
|
|
41
|
+
vi.spyOn(window, 'addEventListener').mockImplementation((type, listener) => {
|
|
42
|
+
if (type === 'message') {
|
|
43
|
+
messageHandler = listener as MessageHandler
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
vi.spyOn(window, 'removeEventListener').mockImplementation(() => {})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
while (unmountedCallbacks.length > 0) {
|
|
51
|
+
const callback = unmountedCallbacks.pop()
|
|
52
|
+
callback?.()
|
|
53
|
+
}
|
|
54
|
+
vi.restoreAllMocks()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('clears allowAnyOrigin policy after last consumer unmounts', () => {
|
|
58
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
59
|
+
|
|
60
|
+
const first = usePlatformContext({ allowAnyOrigin: true })
|
|
61
|
+
sendThemeMessage('https://evil.example', 'dark')
|
|
62
|
+
expect(first.theme.value).toBe('dark')
|
|
63
|
+
|
|
64
|
+
expect(unmountedCallbacks).toHaveLength(1)
|
|
65
|
+
unmountedCallbacks[0]!()
|
|
66
|
+
unmountedCallbacks.length = 0
|
|
67
|
+
|
|
68
|
+
const second = usePlatformContext()
|
|
69
|
+
sendThemeMessage('https://evil.example', 'light')
|
|
70
|
+
expect(second.theme.value).toBe('system')
|
|
71
|
+
expect(
|
|
72
|
+
warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')),
|
|
73
|
+
).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('does not leak explicit allowed origins to new consumers', () => {
|
|
77
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
78
|
+
|
|
79
|
+
const first = usePlatformContext({ allowedOrigins: ['https://trusted.example'] })
|
|
80
|
+
sendThemeMessage('https://trusted.example', 'dark')
|
|
81
|
+
expect(first.theme.value).toBe('dark')
|
|
82
|
+
|
|
83
|
+
expect(unmountedCallbacks).toHaveLength(1)
|
|
84
|
+
unmountedCallbacks[0]!()
|
|
85
|
+
unmountedCallbacks.length = 0
|
|
86
|
+
|
|
87
|
+
const second = usePlatformContext()
|
|
88
|
+
sendThemeMessage('https://trusted.example', 'light')
|
|
89
|
+
expect(second.theme.value).toBe('system')
|
|
90
|
+
expect(
|
|
91
|
+
warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')),
|
|
92
|
+
).toBe(true)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('removes allowAnyOrigin scope when that consumer unmounts while others remain', () => {
|
|
96
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
97
|
+
|
|
98
|
+
usePlatformContext({ allowAnyOrigin: true })
|
|
99
|
+
const second = usePlatformContext()
|
|
100
|
+
|
|
101
|
+
sendThemeMessage('https://evil.example', 'dark')
|
|
102
|
+
expect(second.theme.value).toBe('dark')
|
|
103
|
+
|
|
104
|
+
expect(unmountedCallbacks).toHaveLength(2)
|
|
105
|
+
unmountedCallbacks[0]!()
|
|
106
|
+
|
|
107
|
+
sendThemeMessage('https://evil.example', 'light')
|
|
108
|
+
expect(second.theme.value).toBe('dark')
|
|
109
|
+
expect(
|
|
110
|
+
warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')),
|
|
111
|
+
).toBe(true)
|
|
112
|
+
|
|
113
|
+
unmountedCallbacks[1]!()
|
|
114
|
+
unmountedCallbacks.length = 0
|
|
115
|
+
})
|
|
116
|
+
})
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for usePluginApi.
|
|
3
|
+
*
|
|
4
|
+
* Exercises the baseUrl resolution priority that every plugin relies on:
|
|
5
|
+
* 1. VITE_API_PREFIX (build-time env override)
|
|
6
|
+
* 2. fallbackPrefix option (plugin's route prefix)
|
|
7
|
+
* 3. '/api' default
|
|
8
|
+
*
|
|
9
|
+
* The SDK-wide axios client is a singleton, so these tests stub axios
|
|
10
|
+
* with a MockAdapter-style approach via vi.spyOn to observe the
|
|
11
|
+
* baseURL actually used at request time.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
15
|
+
import { createPinia, setActivePinia } from 'pinia'
|
|
16
|
+
import axios, { type AxiosRequestConfig } from 'axios'
|
|
17
|
+
|
|
18
|
+
import { usePluginApi } from '../../composables/usePluginApi'
|
|
19
|
+
|
|
20
|
+
// Spy on axios create so each test starts with a fresh instance hook
|
|
21
|
+
// and we can observe request configs.
|
|
22
|
+
describe('usePluginApi', () => {
|
|
23
|
+
let requestConfigs: AxiosRequestConfig[] = []
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
setActivePinia(createPinia())
|
|
27
|
+
requestConfigs = []
|
|
28
|
+
// Intercept axios.get on the shared instance
|
|
29
|
+
vi.spyOn(axios.Axios.prototype, 'get').mockImplementation(async function (
|
|
30
|
+
this: unknown,
|
|
31
|
+
url: string,
|
|
32
|
+
config?: AxiosRequestConfig,
|
|
33
|
+
) {
|
|
34
|
+
requestConfigs.push({ url, ...config })
|
|
35
|
+
return { data: { called: url, baseURL: config?.baseURL } }
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
vi.restoreAllMocks()
|
|
41
|
+
vi.unstubAllEnvs()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('uses fallbackPrefix when no env override', async () => {
|
|
45
|
+
const api = usePluginApi({ fallbackPrefix: '/api/drp' })
|
|
46
|
+
await api.get('/sessions')
|
|
47
|
+
expect(requestConfigs).toHaveLength(1)
|
|
48
|
+
expect(requestConfigs[0]!.baseURL).toBe('/api/drp')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('falls back to /api when neither env nor fallback provided', async () => {
|
|
52
|
+
const api = usePluginApi()
|
|
53
|
+
await api.get('/health')
|
|
54
|
+
expect(requestConfigs[0]!.baseURL).toBe('/api')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('VITE_API_PREFIX env var overrides fallbackPrefix', async () => {
|
|
58
|
+
vi.stubEnv('VITE_API_PREFIX', '/api/override')
|
|
59
|
+
const api = usePluginApi({ fallbackPrefix: '/api/drp' })
|
|
60
|
+
await api.get('/check')
|
|
61
|
+
expect(requestConfigs[0]!.baseURL).toBe('/api/override')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('empty VITE_API_PREFIX env var falls through to fallback', async () => {
|
|
65
|
+
vi.stubEnv('VITE_API_PREFIX', '')
|
|
66
|
+
const api = usePluginApi({ fallbackPrefix: '/api/drp' })
|
|
67
|
+
await api.get('/check')
|
|
68
|
+
expect(requestConfigs[0]!.baseURL).toBe('/api/drp')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('returns a client with all CRUD methods', () => {
|
|
72
|
+
const api = usePluginApi({ fallbackPrefix: '/api/drp' })
|
|
73
|
+
expect(typeof api.get).toBe('function')
|
|
74
|
+
expect(typeof api.post).toBe('function')
|
|
75
|
+
expect(typeof api.put).toBe('function')
|
|
76
|
+
expect(typeof api.patch).toBe('function')
|
|
77
|
+
expect(typeof api.delete).toBe('function')
|
|
78
|
+
expect(typeof api.upload).toBe('function')
|
|
79
|
+
expect(typeof api.buildUrl).toBe('function')
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for usePluginConfig.
|
|
3
|
+
*
|
|
4
|
+
* Covers the plugin settings life-cycle used by every plugin's
|
|
5
|
+
* settings pane:
|
|
6
|
+
*
|
|
7
|
+
* - resolvedName prefers explicit pluginName, falls back to platform plugin
|
|
8
|
+
* - load() populates config + savedConfig from /plugins/{name}/config
|
|
9
|
+
* - save() PATCHes the endpoint, updates savedConfig, returns true
|
|
10
|
+
* - isDirty reflects user edits vs last saved state
|
|
11
|
+
* - reset() restores config to savedConfig and clears error
|
|
12
|
+
* - error messages bubble up from network failures
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
16
|
+
import { createPinia, setActivePinia } from 'pinia'
|
|
17
|
+
import axios, { type AxiosRequestConfig } from 'axios'
|
|
18
|
+
import { nextTick } from 'vue'
|
|
19
|
+
|
|
20
|
+
// usePluginConfig calls onMounted(load); happy-dom doesn't run Vue's
|
|
21
|
+
// lifecycle outside a component, so we stub onMounted to call the
|
|
22
|
+
// callback synchronously if and only if the test opts in.
|
|
23
|
+
let autoLoadOnMount = false
|
|
24
|
+
vi.mock('vue', async () => {
|
|
25
|
+
const actual = await vi.importActual<typeof import('vue')>('vue')
|
|
26
|
+
return {
|
|
27
|
+
...actual,
|
|
28
|
+
onMounted: vi.fn((cb: () => void) => {
|
|
29
|
+
if (autoLoadOnMount) cb()
|
|
30
|
+
}),
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
import { usePluginConfig } from '../../composables/usePluginConfig'
|
|
35
|
+
|
|
36
|
+
type FakeResponse = Record<string, unknown>
|
|
37
|
+
|
|
38
|
+
let getResponse: FakeResponse = { plugin_name: 'drp', config: {} }
|
|
39
|
+
let patchResponse: FakeResponse = { plugin_name: 'drp', config: {} }
|
|
40
|
+
let getError: Error | null = null
|
|
41
|
+
let patchError: Error | null = null
|
|
42
|
+
let recordedCalls: { method: string; url: string; data?: unknown }[] = []
|
|
43
|
+
|
|
44
|
+
function mockAxios() {
|
|
45
|
+
vi.spyOn(axios.Axios.prototype, 'get').mockImplementation(async function (
|
|
46
|
+
this: unknown,
|
|
47
|
+
url: string,
|
|
48
|
+
_config?: AxiosRequestConfig,
|
|
49
|
+
) {
|
|
50
|
+
recordedCalls.push({ method: 'GET', url })
|
|
51
|
+
if (getError) throw getError
|
|
52
|
+
return { data: getResponse }
|
|
53
|
+
})
|
|
54
|
+
vi.spyOn(axios.Axios.prototype, 'patch').mockImplementation(async function (
|
|
55
|
+
this: unknown,
|
|
56
|
+
url: string,
|
|
57
|
+
data?: unknown,
|
|
58
|
+
_config?: AxiosRequestConfig,
|
|
59
|
+
) {
|
|
60
|
+
recordedCalls.push({ method: 'PATCH', url, data })
|
|
61
|
+
if (patchError) throw patchError
|
|
62
|
+
return { data: patchResponse }
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe('usePluginConfig', () => {
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
setActivePinia(createPinia())
|
|
69
|
+
autoLoadOnMount = false
|
|
70
|
+
getResponse = { plugin_name: 'drp', config: {} }
|
|
71
|
+
patchResponse = { plugin_name: 'drp', config: {} }
|
|
72
|
+
getError = null
|
|
73
|
+
patchError = null
|
|
74
|
+
recordedCalls = []
|
|
75
|
+
mockAxios()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
vi.restoreAllMocks()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('load fetches config from correct endpoint and populates state', async () => {
|
|
83
|
+
getResponse = {
|
|
84
|
+
plugin_name: 'drp',
|
|
85
|
+
config: { raw_files_path: '/data/rfa', threshold: 0.5 },
|
|
86
|
+
}
|
|
87
|
+
const hook = usePluginConfig('drp')
|
|
88
|
+
await hook.load()
|
|
89
|
+
|
|
90
|
+
expect(recordedCalls).toEqual([
|
|
91
|
+
{ method: 'GET', url: '/plugins/drp/config' },
|
|
92
|
+
])
|
|
93
|
+
expect(hook.config.value).toEqual({
|
|
94
|
+
raw_files_path: '/data/rfa',
|
|
95
|
+
threshold: 0.5,
|
|
96
|
+
})
|
|
97
|
+
expect(hook.isDirty.value).toBe(false)
|
|
98
|
+
expect(hook.error.value).toBeNull()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('encodes special characters in plugin name', async () => {
|
|
102
|
+
const hook = usePluginConfig('my/plugin')
|
|
103
|
+
await hook.load()
|
|
104
|
+
expect(recordedCalls[0]!.url).toBe('/plugins/my%2Fplugin/config')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('save PATCHes the plugin config endpoint', async () => {
|
|
108
|
+
getResponse = { plugin_name: 'drp', config: { n: 1 } }
|
|
109
|
+
patchResponse = { plugin_name: 'drp', config: { n: 2 } }
|
|
110
|
+
const hook = usePluginConfig('drp')
|
|
111
|
+
await hook.load()
|
|
112
|
+
hook.config.value = { n: 2 }
|
|
113
|
+
await nextTick()
|
|
114
|
+
expect(hook.isDirty.value).toBe(true)
|
|
115
|
+
|
|
116
|
+
const ok = await hook.save()
|
|
117
|
+
expect(ok).toBe(true)
|
|
118
|
+
const patchCall = recordedCalls.find((c) => c.method === 'PATCH')!
|
|
119
|
+
expect(patchCall.url).toBe('/plugins/drp/config')
|
|
120
|
+
expect(patchCall.data).toEqual({ config: { n: 2 } })
|
|
121
|
+
expect(hook.config.value).toEqual({ n: 2 })
|
|
122
|
+
expect(hook.isDirty.value).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('isDirty reflects edits vs last saved snapshot', async () => {
|
|
126
|
+
getResponse = { plugin_name: 'drp', config: { k: 'v' } }
|
|
127
|
+
const hook = usePluginConfig('drp')
|
|
128
|
+
await hook.load()
|
|
129
|
+
expect(hook.isDirty.value).toBe(false)
|
|
130
|
+
|
|
131
|
+
hook.config.value = { k: 'changed' }
|
|
132
|
+
await nextTick()
|
|
133
|
+
expect(hook.isDirty.value).toBe(true)
|
|
134
|
+
|
|
135
|
+
hook.config.value = { k: 'v' }
|
|
136
|
+
await nextTick()
|
|
137
|
+
expect(hook.isDirty.value).toBe(false)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('reset restores saved config and clears error', async () => {
|
|
141
|
+
getResponse = { plugin_name: 'drp', config: { k: 'v' } }
|
|
142
|
+
const hook = usePluginConfig('drp')
|
|
143
|
+
await hook.load()
|
|
144
|
+
hook.config.value = { k: 'edited' }
|
|
145
|
+
hook.error.value = 'something'
|
|
146
|
+
|
|
147
|
+
hook.reset()
|
|
148
|
+
expect(hook.config.value).toEqual({ k: 'v' })
|
|
149
|
+
expect(hook.error.value).toBeNull()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('load surfaces network error message', async () => {
|
|
153
|
+
getError = new Error('network down')
|
|
154
|
+
const hook = usePluginConfig('drp')
|
|
155
|
+
await hook.load()
|
|
156
|
+
expect(hook.error.value).toBe('network down')
|
|
157
|
+
expect(hook.isLoading.value).toBe(false)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('save returns false and records error on failure', async () => {
|
|
161
|
+
patchError = new Error('403')
|
|
162
|
+
const hook = usePluginConfig('drp')
|
|
163
|
+
await hook.load()
|
|
164
|
+
hook.config.value = { x: 1 }
|
|
165
|
+
const ok = await hook.save()
|
|
166
|
+
expect(ok).toBe(false)
|
|
167
|
+
expect(hook.error.value).toBe('403')
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('empty plugin name produces a noop load', async () => {
|
|
171
|
+
const hook = usePluginConfig('')
|
|
172
|
+
await hook.load()
|
|
173
|
+
expect(recordedCalls).toEqual([])
|
|
174
|
+
expect(hook.error.value).toBeNull()
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { hexToHsl, hslToHex, deriveShade } from '../../utils/color'
|
|
3
|
+
|
|
4
|
+
describe('hexToHsl', () => {
|
|
5
|
+
it('parses 6-digit hex', () => {
|
|
6
|
+
const hsl = hexToHsl('#3B82F6')
|
|
7
|
+
expect(hsl.h).toBeGreaterThan(200)
|
|
8
|
+
expect(hsl.h).toBeLessThan(230)
|
|
9
|
+
expect(hsl.s).toBeGreaterThan(80)
|
|
10
|
+
expect(hsl.l).toBeGreaterThan(45)
|
|
11
|
+
expect(hsl.l).toBeLessThan(65)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('parses 3-digit hex', () => {
|
|
15
|
+
const a = hexToHsl('#39F')
|
|
16
|
+
const b = hexToHsl('#3399FF')
|
|
17
|
+
expect(Math.abs(a.h - b.h)).toBeLessThan(1)
|
|
18
|
+
expect(Math.abs(a.s - b.s)).toBeLessThan(1)
|
|
19
|
+
expect(Math.abs(a.l - b.l)).toBeLessThan(1)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('handles missing # prefix', () => {
|
|
23
|
+
const a = hexToHsl('3B82F6')
|
|
24
|
+
const b = hexToHsl('#3B82F6')
|
|
25
|
+
expect(a).toEqual(b)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns neutral gray for invalid input', () => {
|
|
29
|
+
const hsl = hexToHsl('not-a-hex')
|
|
30
|
+
expect(hsl.h).toBe(0)
|
|
31
|
+
expect(hsl.s).toBe(0)
|
|
32
|
+
expect(hsl.l).toBe(50)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('hslToHex round-trip', () => {
|
|
37
|
+
it('round-trips common colors within 1% lightness and 1° hue', () => {
|
|
38
|
+
const samples = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6']
|
|
39
|
+
for (const hex of samples) {
|
|
40
|
+
const hsl = hexToHsl(hex)
|
|
41
|
+
const back = hexToHsl(hslToHex(hsl.h, hsl.s, hsl.l))
|
|
42
|
+
expect(Math.abs(back.l - hsl.l)).toBeLessThan(1)
|
|
43
|
+
// Shortest angular distance between input and output hue
|
|
44
|
+
const dh = Math.abs(((back.h - hsl.h + 540) % 360) - 180)
|
|
45
|
+
const hueDelta = Math.min(dh, 360 - dh)
|
|
46
|
+
expect(hueDelta).toBeLessThan(1)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('deriveShade', () => {
|
|
52
|
+
it('returns the parent unchanged when total <= 1', () => {
|
|
53
|
+
expect(deriveShade('#3B82F6', 0, 1)).toBe('#3B82F6')
|
|
54
|
+
expect(deriveShade('#10B981', 0, 0)).toBe('#10B981')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('produces children that share the parent hue', () => {
|
|
58
|
+
const parent = '#3B82F6'
|
|
59
|
+
const parentH = hexToHsl(parent).h
|
|
60
|
+
|
|
61
|
+
const shades = [0, 1, 2, 3].map(i => deriveShade(parent, i, 4))
|
|
62
|
+
for (const child of shades) {
|
|
63
|
+
const ch = hexToHsl(child).h
|
|
64
|
+
expect(Math.abs(ch - parentH)).toBeLessThan(2) // hue locked
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('produces a monotonically darkening progression', () => {
|
|
69
|
+
const shades = [0, 1, 2, 3, 4].map(i => deriveShade('#3B82F6', i, 5))
|
|
70
|
+
const lightnesses = shades.map(s => hexToHsl(s).l)
|
|
71
|
+
for (let i = 1; i < lightnesses.length; i++) {
|
|
72
|
+
expect(lightnesses[i]).toBeLessThan(lightnesses[i - 1])
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('clamps lightness within readable [35, 75] band', () => {
|
|
77
|
+
const lightParent = '#FFFFFF'
|
|
78
|
+
const darkParent = '#000000'
|
|
79
|
+
for (const parent of [lightParent, darkParent]) {
|
|
80
|
+
for (let i = 0; i < 4; i++) {
|
|
81
|
+
const child = deriveShade(parent, i, 4)
|
|
82
|
+
const { l } = hexToHsl(child)
|
|
83
|
+
expect(l).toBeGreaterThanOrEqual(34)
|
|
84
|
+
expect(l).toBeLessThanOrEqual(76)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('keeps minimum saturation so colors do not turn grey', () => {
|
|
90
|
+
const shades = [0, 1, 2].map(i => deriveShade('#3B82F6', i, 3))
|
|
91
|
+
for (const c of shades) {
|
|
92
|
+
const { s } = hexToHsl(c)
|
|
93
|
+
expect(s).toBeGreaterThanOrEqual(35)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
})
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import AlertBox from './AlertBox.vue'
|
|
4
|
+
import type { AlertType } from '../types'
|
|
5
|
+
|
|
6
|
+
const alertTypes: AlertType[] = ['info', 'success', 'warning', 'error']
|
|
7
|
+
const dismissed = ref(false)
|
|
8
|
+
const actionFired = ref('')
|
|
9
|
+
|
|
10
|
+
function handleDismiss() {
|
|
11
|
+
dismissed.value = true
|
|
12
|
+
setTimeout(() => { dismissed.value = false }, 2000)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function handleAction(label: string) {
|
|
16
|
+
actionFired.value = label
|
|
17
|
+
setTimeout(() => { actionFired.value = '' }, 2000)
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<Story title="Feedback/AlertBox">
|
|
23
|
+
<Variant title="Playground">
|
|
24
|
+
<template #default="{ state }">
|
|
25
|
+
<div style="padding: 2rem; max-width: 600px; margin: 0 auto;">
|
|
26
|
+
<AlertBox
|
|
27
|
+
:type="state.type"
|
|
28
|
+
:title="state.showTitle ? state.title : undefined"
|
|
29
|
+
:dismissible="state.dismissible"
|
|
30
|
+
:action-label="state.showAction ? state.actionLabel : undefined"
|
|
31
|
+
@dismiss="() => console.log('dismissed')"
|
|
32
|
+
@action="() => handleAction(state.actionLabel)"
|
|
33
|
+
>
|
|
34
|
+
{{ state.message }}
|
|
35
|
+
</AlertBox>
|
|
36
|
+
<p
|
|
37
|
+
v-if="actionFired"
|
|
38
|
+
style="margin-top: 0.75rem; font-size: 0.8rem; color: var(--mint-success, #10B981); font-family: 'Fira Code', monospace;"
|
|
39
|
+
>
|
|
40
|
+
Action fired: "{{ actionFired }}"
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<template #controls="{ state }">
|
|
46
|
+
<HstSelect
|
|
47
|
+
v-model="state.type"
|
|
48
|
+
title="Type"
|
|
49
|
+
:options="alertTypes.map(t => ({ label: t, value: t }))"
|
|
50
|
+
/>
|
|
51
|
+
<HstCheckbox v-model="state.showTitle" title="Show Title" />
|
|
52
|
+
<HstText v-model="state.title" title="Title" />
|
|
53
|
+
<HstText v-model="state.message" title="Message" />
|
|
54
|
+
<HstCheckbox v-model="state.dismissible" title="Dismissible" />
|
|
55
|
+
<HstCheckbox v-model="state.showAction" title="Show Action" />
|
|
56
|
+
<HstText v-model="state.actionLabel" title="Action Label" />
|
|
57
|
+
</template>
|
|
58
|
+
</Variant>
|
|
59
|
+
|
|
60
|
+
<Variant title="All Types">
|
|
61
|
+
<div style="padding: 2rem; max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem;">
|
|
62
|
+
<AlertBox
|
|
63
|
+
v-for="type in alertTypes"
|
|
64
|
+
:key="type"
|
|
65
|
+
:type="type"
|
|
66
|
+
:title="`${type.charAt(0).toUpperCase() + type.slice(1)} Alert`"
|
|
67
|
+
>
|
|
68
|
+
This is a {{ type }} alert message with supporting details.
|
|
69
|
+
</AlertBox>
|
|
70
|
+
</div>
|
|
71
|
+
</Variant>
|
|
72
|
+
|
|
73
|
+
<Variant title="With Action Button">
|
|
74
|
+
<div style="padding: 2rem; max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem;">
|
|
75
|
+
<AlertBox
|
|
76
|
+
type="error"
|
|
77
|
+
title="Server connection lost"
|
|
78
|
+
action-label="Restart Server"
|
|
79
|
+
@action="handleAction('Restart Server')"
|
|
80
|
+
>
|
|
81
|
+
The backend server is not responding. Check your network connection or restart the server.
|
|
82
|
+
</AlertBox>
|
|
83
|
+
|
|
84
|
+
<AlertBox
|
|
85
|
+
type="warning"
|
|
86
|
+
title="Experiment incomplete"
|
|
87
|
+
action-label="Resume"
|
|
88
|
+
dismissible
|
|
89
|
+
@action="handleAction('Resume')"
|
|
90
|
+
>
|
|
91
|
+
3 wells have missing sample assignments. Resume to continue setup.
|
|
92
|
+
</AlertBox>
|
|
93
|
+
|
|
94
|
+
<AlertBox
|
|
95
|
+
type="info"
|
|
96
|
+
title="Update available"
|
|
97
|
+
action-label="Update Now"
|
|
98
|
+
@action="handleAction('Update Now')"
|
|
99
|
+
>
|
|
100
|
+
MINT Platform v1.0.0 is available with new analysis features.
|
|
101
|
+
</AlertBox>
|
|
102
|
+
|
|
103
|
+
<AlertBox
|
|
104
|
+
type="success"
|
|
105
|
+
title="Analysis complete"
|
|
106
|
+
action-label="View Results"
|
|
107
|
+
@action="handleAction('View Results')"
|
|
108
|
+
>
|
|
109
|
+
Successfully processed 96 wells across 3 plates.
|
|
110
|
+
</AlertBox>
|
|
111
|
+
|
|
112
|
+
<p
|
|
113
|
+
v-if="actionFired"
|
|
114
|
+
style="font-size: 0.8rem; color: var(--mint-success, #10B981); font-family: 'Fira Code', monospace;"
|
|
115
|
+
>
|
|
116
|
+
Action fired: "{{ actionFired }}"
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
</Variant>
|
|
120
|
+
|
|
121
|
+
<Variant title="Custom Actions Slot">
|
|
122
|
+
<div style="padding: 2rem; max-width: 600px; margin: 0 auto;">
|
|
123
|
+
<AlertBox type="error" title="Plugin conflict detected">
|
|
124
|
+
<template #default>
|
|
125
|
+
mld-plugin-drp requires numpy >=2.0 but the current environment has numpy 1.26.4.
|
|
126
|
+
</template>
|
|
127
|
+
<template #actions>
|
|
128
|
+
<button
|
|
129
|
+
style="padding: 0.3125rem 0.75rem; font-size: 0.8125rem; font-weight: 500; border-radius: 0.375rem; border: 1px solid var(--mint-error-border); color: var(--mint-error); background: transparent; cursor: pointer; white-space: nowrap;"
|
|
130
|
+
@click="handleAction('Force Install')"
|
|
131
|
+
>
|
|
132
|
+
Force Install
|
|
133
|
+
</button>
|
|
134
|
+
<button
|
|
135
|
+
style="padding: 0.3125rem 0.75rem; font-size: 0.8125rem; font-weight: 500; border-radius: 0.375rem; border: 1px solid var(--border-color); color: var(--text-secondary); background: transparent; cursor: pointer; white-space: nowrap;"
|
|
136
|
+
@click="handleAction('View Details')"
|
|
137
|
+
>
|
|
138
|
+
Details
|
|
139
|
+
</button>
|
|
140
|
+
</template>
|
|
141
|
+
</AlertBox>
|
|
142
|
+
</div>
|
|
143
|
+
</Variant>
|
|
144
|
+
|
|
145
|
+
<Variant title="Dismissible">
|
|
146
|
+
<div style="padding: 2rem; max-width: 600px; margin: 0 auto;">
|
|
147
|
+
<AlertBox
|
|
148
|
+
v-if="!dismissed"
|
|
149
|
+
type="warning"
|
|
150
|
+
title="Experiment incomplete"
|
|
151
|
+
dismissible
|
|
152
|
+
@dismiss="handleDismiss"
|
|
153
|
+
>
|
|
154
|
+
3 wells have missing sample assignments. Click dismiss to hide this alert (it will reappear after 2 seconds).
|
|
155
|
+
</AlertBox>
|
|
156
|
+
<p v-else style="color: var(--text-muted, #94a3b8); font-style: italic;">
|
|
157
|
+
Alert dismissed. Reappearing shortly...
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
</Variant>
|
|
161
|
+
|
|
162
|
+
<Variant title="Without Title">
|
|
163
|
+
<div style="padding: 2rem; max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem;">
|
|
164
|
+
<AlertBox type="info">
|
|
165
|
+
A simple informational message without a title.
|
|
166
|
+
</AlertBox>
|
|
167
|
+
<AlertBox type="error">
|
|
168
|
+
Something went wrong while processing the sample batch.
|
|
169
|
+
</AlertBox>
|
|
170
|
+
</div>
|
|
171
|
+
</Variant>
|
|
172
|
+
|
|
173
|
+
<Variant title="Rich Content">
|
|
174
|
+
<div style="padding: 2rem; max-width: 600px; margin: 0 auto;">
|
|
175
|
+
<AlertBox type="success" title="Analysis Complete" action-label="Download Report">
|
|
176
|
+
<p style="margin: 0 0 0.5rem;">
|
|
177
|
+
Successfully processed <strong>96 wells</strong> across 3 plates.
|
|
178
|
+
</p>
|
|
179
|
+
<ul style="margin: 0; padding-left: 1.25rem;">
|
|
180
|
+
<li>Peak detection: 1,247 peaks found</li>
|
|
181
|
+
<li>QC passed: 94/96 wells</li>
|
|
182
|
+
<li>Outliers flagged: 2 wells (B3, G11)</li>
|
|
183
|
+
</ul>
|
|
184
|
+
</AlertBox>
|
|
185
|
+
</div>
|
|
186
|
+
</Variant>
|
|
187
|
+
<Variant title="Centered alignment (default)">
|
|
188
|
+
<div style="padding: 2rem; max-width: 520px; display: flex; flex-direction: column; gap: 1rem;">
|
|
189
|
+
<AlertBox type="success" title="Analysis complete" action-label="View results">
|
|
190
|
+
Dose-response fit converged for all 96 wells.
|
|
191
|
+
</AlertBox>
|
|
192
|
+
<AlertBox type="error" title="Upload failed" dismissible>
|
|
193
|
+
The file exceeded the 50 MB limit.
|
|
194
|
+
</AlertBox>
|
|
195
|
+
<AlertBox type="warning" title="Low replicate count">
|
|
196
|
+
Wells C2, C3, C4 only have 2 replicates — consider additional runs.
|
|
197
|
+
</AlertBox>
|
|
198
|
+
<AlertBox type="info" title="New beta release">
|
|
199
|
+
Version 0.16.0-beta.3 ships a visual refresh for status components.
|
|
200
|
+
</AlertBox>
|
|
201
|
+
</div>
|
|
202
|
+
</Variant>
|
|
203
|
+
</Story>
|
|
204
|
+
</template>
|