@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,805 @@
|
|
|
1
|
+
import { M as usePlatformContext, V as useApi } from "./useScheduleDrag-DAJueTbK.js";
|
|
2
|
+
import { r as useSettingsStore, t as useAuthStore } from "./auth-BYmxZdJl.js";
|
|
3
|
+
import { computed, getCurrentInstance, onMounted, onUnmounted, ref, watch } from "vue";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
//#region src/composables/useAuth.ts
|
|
6
|
+
var TOKEN_REFRESH_MARGIN_MS = 300 * 1e3;
|
|
7
|
+
var TOKEN_REFRESH_CHECK_INTERVAL_MS = 60 * 1e3;
|
|
8
|
+
var _refreshPromise = null;
|
|
9
|
+
var _refreshTimerId = null;
|
|
10
|
+
var _mountedConsumerCount = 0;
|
|
11
|
+
/** Manages authentication state with login/logout/register and automatic JWT token refresh. */
|
|
12
|
+
function useAuth() {
|
|
13
|
+
const authStore = useAuthStore();
|
|
14
|
+
const settingsStore = useSettingsStore();
|
|
15
|
+
const isRefreshing = ref(false);
|
|
16
|
+
function getApiBaseUrl() {
|
|
17
|
+
return settingsStore.getApiBaseUrl();
|
|
18
|
+
}
|
|
19
|
+
async function fetchAuthConfig() {
|
|
20
|
+
try {
|
|
21
|
+
const response = await axios.get(`${getApiBaseUrl()}/setup/config/public`);
|
|
22
|
+
const config = {
|
|
23
|
+
authRequired: response.data.auth_required,
|
|
24
|
+
passkeyEnabled: response.data.passkey_enabled,
|
|
25
|
+
passkeyRegistered: response.data.passkey_registered ?? false,
|
|
26
|
+
registrationEnabled: response.data.registration_enabled ?? false,
|
|
27
|
+
databaseMode: response.data.database_mode ?? "none"
|
|
28
|
+
};
|
|
29
|
+
authStore.setAuthConfig(config);
|
|
30
|
+
return config;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("Failed to fetch auth config:", error);
|
|
33
|
+
return {
|
|
34
|
+
authRequired: false,
|
|
35
|
+
passkeyEnabled: false,
|
|
36
|
+
passkeyRegistered: false,
|
|
37
|
+
registrationEnabled: false,
|
|
38
|
+
databaseMode: "none"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function login(username, password) {
|
|
43
|
+
authStore.setLoading(true);
|
|
44
|
+
authStore.setError(null);
|
|
45
|
+
try {
|
|
46
|
+
const response = await axios.post(`${getApiBaseUrl()}/auth/login`, {
|
|
47
|
+
username,
|
|
48
|
+
password
|
|
49
|
+
});
|
|
50
|
+
authStore.setToken(response.data.access_token, response.data.expires_in);
|
|
51
|
+
authStore.setUsername(username);
|
|
52
|
+
await getCurrentUser();
|
|
53
|
+
scheduleTokenRefresh();
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (axios.isAxiosError(error) && error.response) authStore.setError(error.response.data.detail || "Login failed");
|
|
57
|
+
else authStore.setError("Network error. Please try again.");
|
|
58
|
+
return false;
|
|
59
|
+
} finally {
|
|
60
|
+
authStore.setLoading(false);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function register(username, password, email) {
|
|
64
|
+
authStore.setLoading(true);
|
|
65
|
+
authStore.setError(null);
|
|
66
|
+
try {
|
|
67
|
+
await axios.post(`${getApiBaseUrl()}/users/register`, {
|
|
68
|
+
username,
|
|
69
|
+
password,
|
|
70
|
+
email
|
|
71
|
+
});
|
|
72
|
+
return await login(username, password);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (axios.isAxiosError(error) && error.response) authStore.setError(error.response.data.detail || "Registration failed");
|
|
75
|
+
else authStore.setError("Network error. Please try again.");
|
|
76
|
+
return false;
|
|
77
|
+
} finally {
|
|
78
|
+
authStore.setLoading(false);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function getCurrentUser() {
|
|
82
|
+
if (!authStore.token) return null;
|
|
83
|
+
try {
|
|
84
|
+
const response = await axios.get(`${getApiBaseUrl()}/users/me`, { headers: getAuthHeader() });
|
|
85
|
+
const userInfo = {
|
|
86
|
+
id: response.data.id,
|
|
87
|
+
username: response.data.username,
|
|
88
|
+
shortname: response.data.shortname,
|
|
89
|
+
email: response.data.email,
|
|
90
|
+
role: response.data.role,
|
|
91
|
+
isActive: response.data.is_active
|
|
92
|
+
};
|
|
93
|
+
authStore.setUserInfo(userInfo);
|
|
94
|
+
return userInfo;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function verifyToken() {
|
|
100
|
+
if (!authStore.token) return false;
|
|
101
|
+
try {
|
|
102
|
+
const response = await axios.get(`${getApiBaseUrl()}/auth/verify`, { headers: { Authorization: `Bearer ${authStore.token}` } });
|
|
103
|
+
if (response.data.valid && response.data.username) {
|
|
104
|
+
authStore.setUsername(response.data.username);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
authStore.clearToken();
|
|
108
|
+
return false;
|
|
109
|
+
} catch {
|
|
110
|
+
authStore.clearToken();
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Refresh the authentication token.
|
|
116
|
+
* Called automatically before token expiration.
|
|
117
|
+
* Uses promise caching to prevent concurrent refresh requests.
|
|
118
|
+
*/
|
|
119
|
+
async function refreshToken() {
|
|
120
|
+
if (!authStore.token) return false;
|
|
121
|
+
if (_refreshPromise) return _refreshPromise;
|
|
122
|
+
_refreshPromise = (async () => {
|
|
123
|
+
isRefreshing.value = true;
|
|
124
|
+
try {
|
|
125
|
+
const response = await axios.post(`${getApiBaseUrl()}/auth/refresh`, {}, { headers: getAuthHeader() });
|
|
126
|
+
authStore.setToken(response.data.access_token, response.data.expires_in);
|
|
127
|
+
scheduleTokenRefresh();
|
|
128
|
+
return true;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
|
131
|
+
console.warn("[Auth] Token refresh failed - session expired");
|
|
132
|
+
authStore.clearToken();
|
|
133
|
+
stopTokenRefresh();
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
} finally {
|
|
137
|
+
isRefreshing.value = false;
|
|
138
|
+
_refreshPromise = null;
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
return _refreshPromise;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Schedule automatic token refresh before expiration.
|
|
145
|
+
*/
|
|
146
|
+
function scheduleTokenRefresh() {
|
|
147
|
+
stopTokenRefresh();
|
|
148
|
+
if (!authStore.tokenExpires) return;
|
|
149
|
+
const refreshAt = authStore.tokenExpires.getTime() - TOKEN_REFRESH_MARGIN_MS;
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
if (refreshAt <= now) {
|
|
152
|
+
refreshToken();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const delay = refreshAt - now;
|
|
156
|
+
_refreshTimerId = window.setTimeout(() => {
|
|
157
|
+
refreshToken();
|
|
158
|
+
}, delay);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Stop automatic token refresh.
|
|
162
|
+
*/
|
|
163
|
+
function stopTokenRefresh() {
|
|
164
|
+
if (_refreshTimerId !== null) {
|
|
165
|
+
window.clearTimeout(_refreshTimerId);
|
|
166
|
+
_refreshTimerId = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check if token needs refresh and refresh if necessary.
|
|
171
|
+
* Called periodically as a safety net.
|
|
172
|
+
*/
|
|
173
|
+
function checkAndRefreshIfNeeded() {
|
|
174
|
+
if (!authStore.token || !authStore.tokenExpires) return;
|
|
175
|
+
const refreshAt = authStore.tokenExpires.getTime() - TOKEN_REFRESH_MARGIN_MS;
|
|
176
|
+
if (Date.now() >= refreshAt) refreshToken();
|
|
177
|
+
}
|
|
178
|
+
async function initializeAuth() {
|
|
179
|
+
authStore.initialize();
|
|
180
|
+
await fetchAuthConfig();
|
|
181
|
+
if (authStore.token) {
|
|
182
|
+
if (await verifyToken()) {
|
|
183
|
+
await getCurrentUser();
|
|
184
|
+
scheduleTokenRefresh();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function logout() {
|
|
189
|
+
stopTokenRefresh();
|
|
190
|
+
authStore.logout();
|
|
191
|
+
}
|
|
192
|
+
function getAuthHeader() {
|
|
193
|
+
if (authStore.token) return { Authorization: `Bearer ${authStore.token}` };
|
|
194
|
+
return {};
|
|
195
|
+
}
|
|
196
|
+
async function updateProfile(data) {
|
|
197
|
+
if (!authStore.token) return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: "Not authenticated"
|
|
200
|
+
};
|
|
201
|
+
try {
|
|
202
|
+
const requestData = {};
|
|
203
|
+
if (data.email !== void 0) requestData.email = data.email;
|
|
204
|
+
if (data.shortname !== void 0) requestData.shortname = data.shortname;
|
|
205
|
+
if (data.currentPassword) requestData.current_password = data.currentPassword;
|
|
206
|
+
if (data.newPassword) requestData.new_password = data.newPassword;
|
|
207
|
+
const response = await axios.put(`${getApiBaseUrl()}/users/me`, requestData, { headers: getAuthHeader() });
|
|
208
|
+
const userInfo = {
|
|
209
|
+
id: response.data.id,
|
|
210
|
+
username: response.data.username,
|
|
211
|
+
shortname: response.data.shortname,
|
|
212
|
+
email: response.data.email,
|
|
213
|
+
role: response.data.role,
|
|
214
|
+
isActive: response.data.is_active
|
|
215
|
+
};
|
|
216
|
+
authStore.setUserInfo(userInfo);
|
|
217
|
+
return { success: true };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (axios.isAxiosError(error) && error.response) return {
|
|
220
|
+
success: false,
|
|
221
|
+
error: error.response.data.detail || "Update failed"
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: "Network error. Please try again."
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
let checkInterval = null;
|
|
230
|
+
if (getCurrentInstance()) {
|
|
231
|
+
onMounted(() => {
|
|
232
|
+
_mountedConsumerCount += 1;
|
|
233
|
+
checkInterval = window.setInterval(checkAndRefreshIfNeeded, TOKEN_REFRESH_CHECK_INTERVAL_MS);
|
|
234
|
+
});
|
|
235
|
+
onUnmounted(() => {
|
|
236
|
+
if (checkInterval !== null) {
|
|
237
|
+
window.clearInterval(checkInterval);
|
|
238
|
+
checkInterval = null;
|
|
239
|
+
}
|
|
240
|
+
_mountedConsumerCount = Math.max(0, _mountedConsumerCount - 1);
|
|
241
|
+
if (_mountedConsumerCount === 0) stopTokenRefresh();
|
|
242
|
+
});
|
|
243
|
+
watch(() => authStore.tokenExpires, (newExpires) => {
|
|
244
|
+
if (newExpires) scheduleTokenRefresh();
|
|
245
|
+
else stopTokenRefresh();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
login,
|
|
250
|
+
logout,
|
|
251
|
+
register,
|
|
252
|
+
verifyToken,
|
|
253
|
+
fetchAuthConfig,
|
|
254
|
+
initializeAuth,
|
|
255
|
+
getCurrentUser,
|
|
256
|
+
getAuthHeader,
|
|
257
|
+
updateProfile,
|
|
258
|
+
refreshToken,
|
|
259
|
+
isRefreshing
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/composables/usePasskey.ts
|
|
264
|
+
async function loadWebAuthn() {
|
|
265
|
+
try {
|
|
266
|
+
return await import("@simplewebauthn/browser");
|
|
267
|
+
} catch {
|
|
268
|
+
throw new Error("@simplewebauthn/browser is required for passkey support. Install it: bun add @simplewebauthn/browser");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Registers and authenticates passkeys using WebAuthn, lazily loading the browser dependency. */
|
|
272
|
+
function usePasskey() {
|
|
273
|
+
const authStore = useAuthStore();
|
|
274
|
+
const settingsStore = useSettingsStore();
|
|
275
|
+
function getApiBaseUrl() {
|
|
276
|
+
return settingsStore.getApiBaseUrl();
|
|
277
|
+
}
|
|
278
|
+
async function isSupported() {
|
|
279
|
+
try {
|
|
280
|
+
const { browserSupportsWebAuthn } = await loadWebAuthn();
|
|
281
|
+
return browserSupportsWebAuthn();
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function registerPasskey(deviceName) {
|
|
287
|
+
const webauthn = await loadWebAuthn();
|
|
288
|
+
if (!webauthn.browserSupportsWebAuthn()) {
|
|
289
|
+
authStore.setError("WebAuthn is not supported in this browser");
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
authStore.setLoading(true);
|
|
293
|
+
authStore.setError(null);
|
|
294
|
+
try {
|
|
295
|
+
const optionsResponse = await axios.get(`${getApiBaseUrl()}/auth/passkey/register/options`, {
|
|
296
|
+
headers: { Authorization: `Bearer ${authStore.token}` },
|
|
297
|
+
withCredentials: true
|
|
298
|
+
});
|
|
299
|
+
const options = JSON.parse(optionsResponse.data.options);
|
|
300
|
+
const credential = await webauthn.startRegistration(options);
|
|
301
|
+
await axios.post(`${getApiBaseUrl()}/auth/passkey/register/verify`, {
|
|
302
|
+
credential: JSON.stringify(credential),
|
|
303
|
+
device_name: deviceName
|
|
304
|
+
}, {
|
|
305
|
+
headers: { Authorization: `Bearer ${authStore.token}` },
|
|
306
|
+
withCredentials: true
|
|
307
|
+
});
|
|
308
|
+
authStore.setAuthConfig({
|
|
309
|
+
...authStore.authConfig,
|
|
310
|
+
passkeyRegistered: true
|
|
311
|
+
});
|
|
312
|
+
return true;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (axios.isAxiosError(error) && error.response) authStore.setError(error.response.data.detail || "Passkey registration failed");
|
|
315
|
+
else if (error instanceof Error) if (error.name === "NotAllowedError") authStore.setError("Registration was cancelled or timed out");
|
|
316
|
+
else if (error.name === "InvalidStateError") authStore.setError("This authenticator is already registered");
|
|
317
|
+
else authStore.setError(error.message);
|
|
318
|
+
else authStore.setError("Passkey registration failed");
|
|
319
|
+
return false;
|
|
320
|
+
} finally {
|
|
321
|
+
authStore.setLoading(false);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function loginWithPasskey() {
|
|
325
|
+
const webauthn = await loadWebAuthn();
|
|
326
|
+
if (!webauthn.browserSupportsWebAuthn()) {
|
|
327
|
+
authStore.setError("WebAuthn is not supported in this browser");
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
authStore.setLoading(true);
|
|
331
|
+
authStore.setError(null);
|
|
332
|
+
try {
|
|
333
|
+
const optionsResponse = await axios.get(`${getApiBaseUrl()}/auth/passkey/login/options`, { withCredentials: true });
|
|
334
|
+
const options = JSON.parse(optionsResponse.data.options);
|
|
335
|
+
const credential = await webauthn.startAuthentication(options);
|
|
336
|
+
const response = await axios.post(`${getApiBaseUrl()}/auth/passkey/login/verify`, { credential: JSON.stringify(credential) }, { withCredentials: true });
|
|
337
|
+
authStore.setToken(response.data.access_token, response.data.expires_in);
|
|
338
|
+
return true;
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (axios.isAxiosError(error) && error.response) if (error.response.status === 404) authStore.setError("No passkeys registered. Please login with password first.");
|
|
341
|
+
else authStore.setError(error.response.data.detail || "Passkey login failed");
|
|
342
|
+
else if (error instanceof Error) if (error.name === "NotAllowedError") authStore.setError("Authentication was cancelled or timed out");
|
|
343
|
+
else authStore.setError(error.message);
|
|
344
|
+
else authStore.setError("Passkey login failed");
|
|
345
|
+
return false;
|
|
346
|
+
} finally {
|
|
347
|
+
authStore.setLoading(false);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function listCredentials() {
|
|
351
|
+
try {
|
|
352
|
+
return (await axios.get(`${getApiBaseUrl()}/auth/passkey/credentials`, { headers: { Authorization: `Bearer ${authStore.token}` } })).data.credentials;
|
|
353
|
+
} catch {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function deleteCredential(credentialId) {
|
|
358
|
+
try {
|
|
359
|
+
await axios.delete(`${getApiBaseUrl()}/auth/passkey/credentials/${encodeURIComponent(credentialId)}`, { headers: { Authorization: `Bearer ${authStore.token}` } });
|
|
360
|
+
if ((await listCredentials()).length === 0) authStore.setAuthConfig({
|
|
361
|
+
...authStore.authConfig,
|
|
362
|
+
passkeyRegistered: false
|
|
363
|
+
});
|
|
364
|
+
return true;
|
|
365
|
+
} catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function deleteAllCredentials() {
|
|
370
|
+
try {
|
|
371
|
+
await axios.delete(`${getApiBaseUrl()}/auth/passkey/credentials`, { headers: { Authorization: `Bearer ${authStore.token}` } });
|
|
372
|
+
authStore.setAuthConfig({
|
|
373
|
+
...authStore.authConfig,
|
|
374
|
+
passkeyRegistered: false
|
|
375
|
+
});
|
|
376
|
+
return true;
|
|
377
|
+
} catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
isSupported,
|
|
383
|
+
registerPasskey,
|
|
384
|
+
loginWithPasskey,
|
|
385
|
+
listCredentials,
|
|
386
|
+
deleteCredential,
|
|
387
|
+
deleteAllCredentials
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/composables/useAsync.ts
|
|
392
|
+
/**
|
|
393
|
+
* Default error transformer.
|
|
394
|
+
*/
|
|
395
|
+
function defaultTransformError(error) {
|
|
396
|
+
if (error instanceof Error) return {
|
|
397
|
+
message: error.message,
|
|
398
|
+
originalError: error
|
|
399
|
+
};
|
|
400
|
+
if (typeof error === "object" && error !== null) {
|
|
401
|
+
const errorObj = error;
|
|
402
|
+
if ("response" in errorObj && errorObj.response) {
|
|
403
|
+
const response = errorObj.response;
|
|
404
|
+
const data = response.data;
|
|
405
|
+
return {
|
|
406
|
+
message: data?.detail || data?.message || "Request failed",
|
|
407
|
+
code: String(response.status),
|
|
408
|
+
details: data,
|
|
409
|
+
originalError: error
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
message: errorObj.message || "Unknown error",
|
|
414
|
+
code: errorObj.code,
|
|
415
|
+
details: errorObj,
|
|
416
|
+
originalError: error
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
message: String(error),
|
|
421
|
+
originalError: error
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Composable for managing async operation state.
|
|
426
|
+
*
|
|
427
|
+
* Provides standardized loading, error, and success state management
|
|
428
|
+
* for any async function.
|
|
429
|
+
*
|
|
430
|
+
* @param asyncFn - The async function to wrap
|
|
431
|
+
* @param options - Configuration options
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```typescript
|
|
435
|
+
* // Basic usage
|
|
436
|
+
* const { data, isLoading, error, execute } = useAsync(
|
|
437
|
+
* async (id: string) => {
|
|
438
|
+
* const response = await api.get(`/users/${id}`)
|
|
439
|
+
* return response.data
|
|
440
|
+
* }
|
|
441
|
+
* )
|
|
442
|
+
*
|
|
443
|
+
* // Execute the function
|
|
444
|
+
* await execute('user-123')
|
|
445
|
+
*
|
|
446
|
+
* // In template
|
|
447
|
+
* <div v-if="isLoading">Loading...</div>
|
|
448
|
+
* <div v-else-if="error">{{ error.message }}</div>
|
|
449
|
+
* <div v-else-if="data">{{ data.name }}</div>
|
|
450
|
+
* ```
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```typescript
|
|
454
|
+
* // With options
|
|
455
|
+
* const { data, execute } = useAsync(
|
|
456
|
+
* fetchUser,
|
|
457
|
+
* {
|
|
458
|
+
* immediate: true,
|
|
459
|
+
* immediateArgs: ['default-user'],
|
|
460
|
+
* onSuccess: (user) => console.log('Fetched:', user.name),
|
|
461
|
+
* onError: (error) => toast.error(error.message),
|
|
462
|
+
* }
|
|
463
|
+
* )
|
|
464
|
+
* ```
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```typescript
|
|
468
|
+
* // Form submission
|
|
469
|
+
* const { isLoading, error, execute: submit } = useAsync(
|
|
470
|
+
* async (formData: FormData) => {
|
|
471
|
+
* await api.post('/submit', formData)
|
|
472
|
+
* },
|
|
473
|
+
* {
|
|
474
|
+
* onSuccess: () => {
|
|
475
|
+
* toast.success('Submitted!')
|
|
476
|
+
* router.push('/success')
|
|
477
|
+
* },
|
|
478
|
+
* }
|
|
479
|
+
* )
|
|
480
|
+
*
|
|
481
|
+
* const handleSubmit = () => submit(new FormData(formRef.value))
|
|
482
|
+
* ```
|
|
483
|
+
*/
|
|
484
|
+
/** Wraps an async function with reactive loading, error, and success state plus optional auto-execute. */
|
|
485
|
+
function useAsync(asyncFn, options = {}) {
|
|
486
|
+
const { initialData = null, immediate = false, immediateArgs = [], transformError = defaultTransformError, onSuccess, onError, resetOnExecute = false } = options;
|
|
487
|
+
const data = ref(initialData);
|
|
488
|
+
const error = ref(null);
|
|
489
|
+
const state = ref("idle");
|
|
490
|
+
const isIdle = computed(() => state.value === "idle");
|
|
491
|
+
const isLoading = computed(() => state.value === "loading");
|
|
492
|
+
const isSuccess = computed(() => state.value === "success");
|
|
493
|
+
const isError = computed(() => state.value === "error");
|
|
494
|
+
async function execute(...args) {
|
|
495
|
+
state.value = "loading";
|
|
496
|
+
error.value = null;
|
|
497
|
+
if (resetOnExecute) data.value = null;
|
|
498
|
+
try {
|
|
499
|
+
const result = await asyncFn(...args);
|
|
500
|
+
data.value = result;
|
|
501
|
+
state.value = "success";
|
|
502
|
+
onSuccess?.(result);
|
|
503
|
+
return result;
|
|
504
|
+
} catch (e) {
|
|
505
|
+
const asyncError = transformError(e);
|
|
506
|
+
error.value = asyncError;
|
|
507
|
+
state.value = "error";
|
|
508
|
+
onError?.(asyncError);
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function reset() {
|
|
513
|
+
data.value = initialData;
|
|
514
|
+
error.value = null;
|
|
515
|
+
state.value = "idle";
|
|
516
|
+
}
|
|
517
|
+
function setData(newData) {
|
|
518
|
+
data.value = newData;
|
|
519
|
+
if (newData !== null) {
|
|
520
|
+
state.value = "success";
|
|
521
|
+
error.value = null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function setError(newError) {
|
|
525
|
+
error.value = newError;
|
|
526
|
+
if (newError !== null) state.value = "error";
|
|
527
|
+
}
|
|
528
|
+
if (immediate) execute(...immediateArgs);
|
|
529
|
+
return {
|
|
530
|
+
data,
|
|
531
|
+
error,
|
|
532
|
+
state,
|
|
533
|
+
isIdle,
|
|
534
|
+
isLoading,
|
|
535
|
+
isSuccess,
|
|
536
|
+
isError,
|
|
537
|
+
execute,
|
|
538
|
+
reset,
|
|
539
|
+
setData,
|
|
540
|
+
setError
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Create a batch of async operations that can be executed in parallel.
|
|
545
|
+
*
|
|
546
|
+
* @example
|
|
547
|
+
* ```typescript
|
|
548
|
+
* const { results, isLoading, execute } = useAsyncBatch([
|
|
549
|
+
* () => fetchUser(userId),
|
|
550
|
+
* () => fetchPosts(userId),
|
|
551
|
+
* () => fetchComments(userId),
|
|
552
|
+
* ])
|
|
553
|
+
*
|
|
554
|
+
* await execute()
|
|
555
|
+
* // results.value = [user, posts, comments]
|
|
556
|
+
* ```
|
|
557
|
+
*/
|
|
558
|
+
function useAsyncBatch(asyncFns) {
|
|
559
|
+
const results = ref(asyncFns.map(() => null));
|
|
560
|
+
const errors = ref(asyncFns.map(() => null));
|
|
561
|
+
const isLoading = ref(false);
|
|
562
|
+
async function execute() {
|
|
563
|
+
isLoading.value = true;
|
|
564
|
+
errors.value = asyncFns.map(() => null);
|
|
565
|
+
const promises = asyncFns.map(async (fn, index) => {
|
|
566
|
+
try {
|
|
567
|
+
const result = await fn();
|
|
568
|
+
results.value[index] = result;
|
|
569
|
+
} catch (e) {
|
|
570
|
+
errors.value[index] = defaultTransformError(e);
|
|
571
|
+
results.value[index] = null;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
await Promise.all(promises);
|
|
575
|
+
isLoading.value = false;
|
|
576
|
+
}
|
|
577
|
+
function reset() {
|
|
578
|
+
results.value = asyncFns.map(() => null);
|
|
579
|
+
errors.value = asyncFns.map(() => null);
|
|
580
|
+
isLoading.value = false;
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
results,
|
|
584
|
+
errors,
|
|
585
|
+
isLoading,
|
|
586
|
+
execute,
|
|
587
|
+
reset
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/composables/usePluginConfig.ts
|
|
592
|
+
/** Loads, saves, and tracks dirty state for a plugin's persistent configuration via the platform API. */
|
|
593
|
+
function usePluginConfig(pluginName) {
|
|
594
|
+
const api = useApi();
|
|
595
|
+
const { plugin } = usePlatformContext();
|
|
596
|
+
const resolvedName = computed(() => pluginName ?? plugin.value?.name ?? "");
|
|
597
|
+
const config = ref({});
|
|
598
|
+
const savedConfig = ref({});
|
|
599
|
+
const isLoading = ref(false);
|
|
600
|
+
const isSaving = ref(false);
|
|
601
|
+
const error = ref(null);
|
|
602
|
+
const isDirty = computed(() => {
|
|
603
|
+
return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value);
|
|
604
|
+
});
|
|
605
|
+
async function load() {
|
|
606
|
+
const name = resolvedName.value;
|
|
607
|
+
if (!name) return;
|
|
608
|
+
isLoading.value = true;
|
|
609
|
+
error.value = null;
|
|
610
|
+
try {
|
|
611
|
+
const response = await api.get(`/plugins/${encodeURIComponent(name)}/config`);
|
|
612
|
+
config.value = { ...response.config };
|
|
613
|
+
savedConfig.value = { ...response.config };
|
|
614
|
+
} catch (e) {
|
|
615
|
+
error.value = e instanceof Error ? e.message : "Failed to load plugin config";
|
|
616
|
+
} finally {
|
|
617
|
+
isLoading.value = false;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async function save() {
|
|
621
|
+
const name = resolvedName.value;
|
|
622
|
+
if (!name) return false;
|
|
623
|
+
isSaving.value = true;
|
|
624
|
+
error.value = null;
|
|
625
|
+
try {
|
|
626
|
+
const response = await api.patch(`/plugins/${encodeURIComponent(name)}/config`, { config: config.value });
|
|
627
|
+
config.value = { ...response.config };
|
|
628
|
+
savedConfig.value = { ...response.config };
|
|
629
|
+
return true;
|
|
630
|
+
} catch (e) {
|
|
631
|
+
error.value = e instanceof Error ? e.message : "Failed to save plugin config";
|
|
632
|
+
return false;
|
|
633
|
+
} finally {
|
|
634
|
+
isSaving.value = false;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function reset() {
|
|
638
|
+
config.value = { ...savedConfig.value };
|
|
639
|
+
error.value = null;
|
|
640
|
+
}
|
|
641
|
+
onMounted(() => {
|
|
642
|
+
load();
|
|
643
|
+
});
|
|
644
|
+
return {
|
|
645
|
+
config,
|
|
646
|
+
isLoading,
|
|
647
|
+
isSaving,
|
|
648
|
+
error,
|
|
649
|
+
isDirty,
|
|
650
|
+
load,
|
|
651
|
+
save,
|
|
652
|
+
reset
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
//#endregion
|
|
656
|
+
//#region src/composables/useExperimentSave.ts
|
|
657
|
+
/** Persists and loads experiment design and analysis data via the plugin's API endpoint. */
|
|
658
|
+
function useExperimentSave(options = {}) {
|
|
659
|
+
const api = useApi({ baseUrl: options.apiBaseUrl });
|
|
660
|
+
const pluginId = options.pluginId;
|
|
661
|
+
const schemaVersion = options.schemaVersion ?? "1.0";
|
|
662
|
+
const isSaving = ref(false);
|
|
663
|
+
const error = ref(null);
|
|
664
|
+
const lastSavedAt = ref(null);
|
|
665
|
+
function setError(e) {
|
|
666
|
+
error.value = e instanceof Error ? e.message : "Unknown error";
|
|
667
|
+
}
|
|
668
|
+
async function saveDesign(experimentId, data) {
|
|
669
|
+
if (!pluginId) {
|
|
670
|
+
error.value = "pluginId is required for saveDesign";
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
isSaving.value = true;
|
|
674
|
+
error.value = null;
|
|
675
|
+
try {
|
|
676
|
+
await api.put(`/experiments/${experimentId}/data`, {
|
|
677
|
+
plugin_id: pluginId,
|
|
678
|
+
data,
|
|
679
|
+
schema_version: schemaVersion
|
|
680
|
+
});
|
|
681
|
+
lastSavedAt.value = /* @__PURE__ */ new Date();
|
|
682
|
+
return true;
|
|
683
|
+
} catch (e) {
|
|
684
|
+
setError(e);
|
|
685
|
+
return false;
|
|
686
|
+
} finally {
|
|
687
|
+
isSaving.value = false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
async function saveAnalysis(experimentId, result) {
|
|
691
|
+
if (!pluginId) {
|
|
692
|
+
error.value = "pluginId is required for saveAnalysis";
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
isSaving.value = true;
|
|
696
|
+
error.value = null;
|
|
697
|
+
try {
|
|
698
|
+
await api.put(`/experiments/${experimentId}/results/${pluginId}`, { result });
|
|
699
|
+
lastSavedAt.value = /* @__PURE__ */ new Date();
|
|
700
|
+
return true;
|
|
701
|
+
} catch (e) {
|
|
702
|
+
setError(e);
|
|
703
|
+
return false;
|
|
704
|
+
} finally {
|
|
705
|
+
isSaving.value = false;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function save(experimentId, opts) {
|
|
709
|
+
if ((opts.design || opts.analysis) && !pluginId) {
|
|
710
|
+
error.value = "pluginId is required for save";
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
isSaving.value = true;
|
|
714
|
+
error.value = null;
|
|
715
|
+
try {
|
|
716
|
+
if (opts.design) await api.put(`/experiments/${experimentId}/data`, {
|
|
717
|
+
plugin_id: pluginId,
|
|
718
|
+
data: opts.design,
|
|
719
|
+
schema_version: schemaVersion
|
|
720
|
+
});
|
|
721
|
+
if (opts.analysis) await api.put(`/experiments/${experimentId}/results/${pluginId}`, { result: opts.analysis });
|
|
722
|
+
lastSavedAt.value = /* @__PURE__ */ new Date();
|
|
723
|
+
return true;
|
|
724
|
+
} catch (e) {
|
|
725
|
+
setError(e);
|
|
726
|
+
return false;
|
|
727
|
+
} finally {
|
|
728
|
+
isSaving.value = false;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
async function loadDesign(experimentId) {
|
|
732
|
+
try {
|
|
733
|
+
return await api.get(`/experiments/${experimentId}/data`);
|
|
734
|
+
} catch {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async function loadAnalysis(experimentId) {
|
|
739
|
+
if (!pluginId) return null;
|
|
740
|
+
try {
|
|
741
|
+
return await api.get(`/experiments/${experimentId}/results/${pluginId}`);
|
|
742
|
+
} catch {
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async function deleteDesign(experimentId) {
|
|
747
|
+
try {
|
|
748
|
+
await api.delete(`/experiments/${experimentId}/data`);
|
|
749
|
+
return true;
|
|
750
|
+
} catch {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
async function deleteAnalysis(experimentId) {
|
|
755
|
+
if (!pluginId) return false;
|
|
756
|
+
try {
|
|
757
|
+
await api.delete(`/experiments/${experimentId}/results/${pluginId}`);
|
|
758
|
+
return true;
|
|
759
|
+
} catch {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
isSaving,
|
|
765
|
+
error,
|
|
766
|
+
lastSavedAt,
|
|
767
|
+
saveDesign,
|
|
768
|
+
saveAnalysis,
|
|
769
|
+
save,
|
|
770
|
+
loadDesign,
|
|
771
|
+
loadAnalysis,
|
|
772
|
+
deleteDesign,
|
|
773
|
+
deleteAnalysis
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
//#endregion
|
|
777
|
+
//#region src/composables/usePluginApi.ts
|
|
778
|
+
/**
|
|
779
|
+
* Pre-configured API client for plugins.
|
|
780
|
+
*
|
|
781
|
+
* Resolves the base URL in priority order:
|
|
782
|
+
* 1. `import.meta.env.VITE_API_PREFIX` (build-time env override)
|
|
783
|
+
* 2. `fallbackPrefix` option (plugin's route prefix)
|
|
784
|
+
* 3. `'/api'` (default)
|
|
785
|
+
*
|
|
786
|
+
* Eliminates the repeated pattern across plugins:
|
|
787
|
+
* ```ts
|
|
788
|
+
* const API_BASE = import.meta.env.VITE_API_PREFIX || '/api/my-plugin'
|
|
789
|
+
* const api = useApi({ baseUrl: API_BASE })
|
|
790
|
+
* ```
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* ```ts
|
|
794
|
+
* const api = usePluginApi({ fallbackPrefix: '/api/drp' })
|
|
795
|
+
* const data = await api.get('/sessions')
|
|
796
|
+
* ```
|
|
797
|
+
*/
|
|
798
|
+
/** Pre-configured API client resolving the plugin base URL from VITE_API_PREFIX or a fallback prefix. */
|
|
799
|
+
function usePluginApi(options = {}) {
|
|
800
|
+
return useApi({ baseUrl: options.fallbackPrefix || "/api" });
|
|
801
|
+
}
|
|
802
|
+
//#endregion
|
|
803
|
+
export { useAsyncBatch as a, useAsync as i, useExperimentSave as n, usePasskey as o, usePluginConfig as r, useAuth as s, usePluginApi as t };
|
|
804
|
+
|
|
805
|
+
//# sourceMappingURL=composables-D0QfFzq1.js.map
|