@morscherlab/mint-sdk 1.2.10 → 1.3.0-beta.10
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 +45 -137
- package/dist/ResourcePickerListInternal-CFAw4vTZ.js +605 -0
- package/dist/ResourcePickerListInternal-CFAw4vTZ.js.map +1 -0
- package/dist/__stories__/experiment-helpers.d.ts +12 -1
- package/dist/__tests__/components/ScientificNumber.test.d.ts +1 -0
- package/dist/__tests__/components/SequenceInput.test.d.ts +1 -0
- package/dist/__tests__/components/Skeleton.test.d.ts +1 -0
- package/dist/__tests__/components/SmartGroupFieldRecipe.test.d.ts +1 -0
- package/dist/__tests__/components/SmartGroupModal.test.d.ts +1 -0
- package/dist/__tests__/components/StepWizard.test.d.ts +1 -0
- package/dist/__tests__/components/TimeRangeInput.test.d.ts +1 -0
- package/dist/__tests__/components/UnitInput.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.keyboard.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.sampleTypeIndicator.test.d.ts +1 -0
- package/dist/__tests__/composables/useChemicalFormula.test.d.ts +1 -0
- package/dist/__tests__/composables/useRackEditor.test.d.ts +1 -0
- package/dist/__tests__/composables/useSequenceUtils.test.d.ts +1 -0
- package/dist/__tests__/styles/baseControlsRules.test.d.ts +1 -0
- package/dist/__tests__/styles/controlRefreshRules.test.d.ts +1 -0
- package/dist/__tests__/styles/darkModeRules.test.d.ts +1 -0
- package/dist/__tests__/styles/menusShellRefresh.test.d.ts +1 -0
- package/dist/__tests__/templates/templatesDeprecation.test.d.ts +1 -0
- package/dist/__tests__/utils/plotlyTemplate.test.d.ts +1 -0
- package/dist/__tests__/utils/sampleTypeMarker.test.d.ts +1 -0
- package/dist/{analysisArtifactTypes-DQ0MjwHZ.js → analysisArtifactTypes-Cy3D0Ipg.js} +1 -1
- package/dist/{analysisArtifactTypes-DQ0MjwHZ.js.map → analysisArtifactTypes-Cy3D0Ipg.js.map} +1 -1
- package/dist/components/ActionMenu.vue.d.ts +17 -5
- package/dist/components/AdductText.vue.d.ts +3 -2
- package/dist/components/AlertBox.vue.d.ts +11 -14
- package/dist/components/AppAvatarMenu.vue.d.ts +18 -28
- package/dist/components/AppContainer.vue.d.ts +7 -11
- package/dist/components/AppLayout.vue.d.ts +11 -13
- package/dist/components/AppSidebar.vue.d.ts +30 -28
- package/dist/components/AppToastContainer.vue.d.ts +2 -1
- package/dist/components/AppTopBar.vue.d.ts +45 -44
- package/dist/components/ArtifactSaveDialog.vue.d.ts +3 -2
- package/dist/components/ArtifactSelectorModal.vue.d.ts +5 -6
- package/dist/components/AuditTrail.vue.d.ts +17 -18
- package/dist/components/AutoGroupModal.vue.d.ts +4 -3
- package/dist/components/Avatar.vue.d.ts +2 -1
- package/dist/components/BaseButton.vue.d.ts +11 -13
- package/dist/components/BaseCheckbox.vue.d.ts +7 -1
- package/dist/components/BaseInput.vue.d.ts +15 -2
- package/dist/components/BaseModal.vue.d.ts +20 -23
- package/dist/components/BasePill.vue.d.ts +10 -13
- package/dist/components/BaseRadioGroup.vue.d.ts +2 -1
- package/dist/components/BaseSelect.vue.d.ts +3 -2
- package/dist/components/BaseSlider.vue.d.ts +18 -1
- package/dist/components/BaseTabs.vue.d.ts +2 -1
- package/dist/components/BaseTextarea.vue.d.ts +2 -1
- package/dist/components/BaseToggle.vue.d.ts +2 -1
- package/dist/components/BatchProgressList.vue.d.ts +22 -26
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +7 -14
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +6 -13
- package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +7 -14
- package/dist/components/BioTemplateRenderer.vue.d.ts +2 -1
- package/dist/components/Breadcrumb.vue.d.ts +13 -16
- package/dist/components/Calendar.vue.d.ts +21 -23
- package/dist/components/ChartContainer.vue.d.ts +19 -25
- package/dist/components/ChemicalFormula.vue.d.ts +7 -11
- package/dist/components/CollapsibleCard.vue.d.ts +7 -11
- package/dist/components/ComponentBindingRenderer.vue.d.ts +2 -1
- package/dist/components/ConcentrationInput.vue.d.ts +2 -1
- package/dist/components/ConfirmDialog.vue.d.ts +10 -13
- package/dist/components/ControlWorkspaceView.vue.d.ts +9 -18
- package/dist/components/DataFrame.vue.d.ts +27 -24
- package/dist/components/DatePicker.vue.d.ts +3 -6
- package/dist/components/DateTimePicker.vue.d.ts +3 -6
- package/dist/components/Divider.vue.d.ts +3 -2
- package/dist/components/DoseCalculator.vue.d.ts +2 -1
- package/dist/components/DoseCalculatorVolumeField.vue.d.ts +2 -1
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +9 -18
- package/dist/components/EmptyState.vue.d.ts +9 -12
- package/dist/components/ExperimentCodeBadge.vue.d.ts +2 -1
- package/dist/components/ExperimentDataViewer.vue.d.ts +13 -15
- package/dist/components/ExperimentPopover.vue.d.ts +3 -5
- package/dist/components/ExperimentSelectorModal.vue.d.ts +3 -4
- package/dist/components/ExperimentTimeline.vue.d.ts +13 -15
- package/dist/components/FilePicker.vue.d.ts +10 -5
- package/dist/components/FileUploader.vue.d.ts +8 -16
- package/dist/components/FitPanel.vue.d.ts +26 -16
- package/dist/components/FormActions.vue.d.ts +2 -1
- package/dist/components/FormBuilder.vue.d.ts +54 -250
- package/dist/components/FormField.vue.d.ts +14 -16
- package/dist/components/FormulaInput.vue.d.ts +2 -1
- package/dist/components/GeneratedPathInput.vue.d.ts +6 -5
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +2 -1
- package/dist/components/GeneratedPluginView.vue.d.ts +2 -1
- package/dist/components/GeneratedResultRenderer.vue.d.ts +2 -1
- package/dist/components/GroupAssigner.vue.d.ts +2 -1
- package/dist/components/IconButton.vue.d.ts +7 -11
- package/dist/components/InstrumentStateBadge.vue.d.ts +4 -3
- package/dist/components/JobsStatusTray.vue.d.ts +13 -29
- package/dist/components/LayoutResizeHandle.vue.d.ts +2 -1
- package/dist/components/LoadingSpinner.vue.d.ts +2 -1
- package/dist/components/MobileSupportGate.vue.d.ts +5 -13
- package/dist/components/MoleculeInput.vue.d.ts +3 -4
- package/dist/components/MultiSelect.vue.d.ts +2 -1
- package/dist/components/NumberInput.vue.d.ts +2 -1
- package/dist/components/PlateEditor.vue.d.ts +147 -0
- package/dist/components/PlotlyChart.vue.d.ts +21 -31
- package/dist/components/PluginIcon.vue.d.ts +2 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +2 -6
- package/dist/components/PluginWorkspaceView.vue.d.ts +23 -51
- package/dist/components/ProgressBar.vue.d.ts +4 -9
- package/dist/components/ProtocolStepEditor.vue.d.ts +7 -5
- package/dist/components/ProtocolStepParameterField.vue.d.ts +2 -1
- package/dist/components/ReagentEditor.vue.d.ts +2 -1
- package/dist/components/ReagentList.presentation.d.ts +2 -1
- package/dist/components/ReagentList.vue.d.ts +15 -17
- package/dist/components/ResourceCard.vue.d.ts +18 -17
- package/dist/components/RuntimeUpdateNotice.vue.d.ts +3 -2
- package/dist/components/SampleHierarchyTree.vue.d.ts +21 -15
- package/dist/components/SampleLegend.vue.d.ts +2 -1
- package/dist/components/SampleSelector.vue.d.ts +15 -21
- package/dist/components/SampleSelectorGroupNode.vue.d.ts +2 -1
- package/dist/components/SampleSelectorSampleRow.vue.d.ts +2 -5
- package/dist/components/ScheduleCalendar.vue.d.ts +34 -36
- package/dist/components/ScientificNumber.vue.d.ts +3 -2
- package/dist/components/SearchableSelect.vue.d.ts +14 -32
- package/dist/components/SecretInput.vue.d.ts +2 -1
- package/dist/components/SectionCard.vue.d.ts +13 -14
- package/dist/components/SegmentedControl.vue.d.ts +2 -1
- package/dist/components/SequenceInput.vue.d.ts +10 -21
- package/dist/components/SequenceProgressBar.vue.d.ts +2 -1
- package/dist/components/SettingsModal.schema.d.ts +1 -1
- package/dist/components/SettingsModal.vue.d.ts +28 -25
- package/dist/components/Skeleton.vue.d.ts +2 -1
- package/dist/components/SmartGroup.types.d.ts +2 -2
- package/dist/components/SmartGroupFieldRecipe.groups.d.ts +2 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +4 -7
- package/dist/components/SmartGroupManual.vue.d.ts +3 -2
- package/dist/components/SmartGroupModal.vue.d.ts +6 -4
- package/dist/components/StatusIndicator.vue.d.ts +2 -1
- package/dist/components/StepWizard.vue.d.ts +26 -27
- package/dist/components/TagsInput.vue.d.ts +3 -5
- package/dist/components/ThemeToggle.vue.d.ts +2 -1
- package/dist/components/TimePicker.vue.d.ts +6 -6
- package/dist/components/TimeRangeInput.vue.d.ts +9 -2
- package/dist/components/Tooltip.vue.d.ts +8 -18
- package/dist/components/UnitInput.vue.d.ts +2 -1
- package/dist/components/WellPlate.colors.d.ts +2 -4
- package/dist/components/WellPlate.rendering.d.ts +7 -1
- package/dist/components/WellPlate.sampleDrop.d.ts +2 -0
- package/dist/components/WellPlate.vue.d.ts +22 -39
- package/dist/components/index.d.ts +34 -31
- package/dist/components/index.js +5 -8
- package/dist/components/internal/ActionItemInternal.vue.d.ts +11 -13
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +14 -23
- package/dist/components/internal/AppTopBarPillNavInternal.vue.d.ts +2 -1
- package/dist/components/internal/CalendarGridPanelInternal.vue.d.ts +2 -1
- package/dist/components/internal/DataStateInternal.vue.d.ts +7 -11
- package/dist/components/internal/{FilePickerTreeInternal.vue.d.ts → FilePickerListInternal.vue.d.ts} +9 -6
- package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +4 -3
- package/dist/components/internal/FormFieldRendererInternal.vue.d.ts +16 -18
- package/dist/components/internal/FormSectionRenderer.vue.d.ts +36 -32
- package/dist/components/internal/PlateEditorBindingInternal.vue.d.ts +8 -0
- package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +65 -0
- package/dist/components/internal/SettingsPaneHeadInternal.vue.d.ts +15 -0
- package/dist/components/internal/WellEditPopupInternal.vue.d.ts +12 -4
- package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +6 -13
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +19 -42
- package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +9 -19
- package/dist/components/internal/filePickerPresentation.d.ts +8 -0
- package/dist/components/internal/generatedJobsApi.d.ts +9 -0
- package/dist/{components-Bbg_gtzF.js → components-GDDvof8D.js} +7088 -5955
- package/dist/components-GDDvof8D.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +4 -43
- package/dist/composables/createFilePickerAdapter.d.ts +5 -1
- package/dist/composables/filePicker/usePickerNavigation.d.ts +7 -2
- package/dist/composables/index.d.ts +2 -13
- package/dist/composables/index.js +7 -11
- package/dist/composables/pluginWorkspaceModel.d.ts +1 -3
- package/dist/composables/useAutoGroup.d.ts +10 -0
- package/dist/composables/useBioTemplateComponents.d.ts +15 -3
- package/dist/composables/useBioTemplateControls.d.ts +5 -1
- package/dist/composables/useBioTemplatePackWorkspace.d.ts +2 -0
- package/dist/composables/useBioTemplatePresetWorkspace.d.ts +5 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +5 -1
- package/dist/composables/useChemicalFormula.d.ts +2 -1
- package/dist/composables/useDropdownState.d.ts +5 -0
- package/dist/composables/useExperimentSave.d.ts +5 -1
- package/dist/composables/useFileBrowser.d.ts +3 -2
- package/dist/composables/useFilePicker.d.ts +25 -13
- package/dist/composables/useFocusTrap.d.ts +2 -1
- package/dist/composables/useGroupAssignment.d.ts +5 -1
- package/dist/composables/useJobsStatusTray.d.ts +5 -5
- package/dist/composables/useMenuKeyboard.d.ts +3 -3
- package/dist/composables/usePlatformContext.d.ts +3 -0
- package/dist/composables/usePluginClient.d.ts +1 -1
- package/dist/composables/useRackEditor.d.ts +36 -1
- package/dist/composables/useSequenceUtils.d.ts +8 -0
- package/dist/composables/useTemplateCollection.d.ts +5 -1
- package/dist/composables/workspaceRecipes.d.ts +1 -0
- package/dist/composables-D6uqp0UF.js +591 -0
- package/dist/composables-D6uqp0UF.js.map +1 -0
- package/dist/{errors-BLuqMee5.js → experiment-DgllIc3I.js} +89 -333
- package/dist/experiment-DgllIc3I.js.map +1 -0
- package/dist/{experiment-utils-CV2Wza3z.js → experiment-utils-DTANyVKV.js} +68 -37
- package/dist/experiment-utils-DTANyVKV.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +122 -17
- package/dist/index.js.map +1 -1
- package/dist/install.js +1 -2
- package/dist/install.js.map +1 -1
- package/dist/instrument.d.ts +6 -0
- package/dist/lcms.d.ts +11 -0
- package/dist/permissions.d.ts +2 -47
- package/dist/permissions.js +2 -26
- package/dist/permissions.js.map +1 -1
- package/dist/settings-BLr8lzw6.js +341 -0
- package/dist/settings-BLr8lzw6.js.map +1 -0
- package/dist/stores/auth.d.ts +2 -150
- package/dist/stores/experiment.d.ts +2 -106
- package/dist/stores/index.js +2 -2
- package/dist/stores/settings.d.ts +2 -44
- package/dist/styles.css +8608 -6950
- package/dist/tailwind.preset.js.map +1 -1
- package/dist/templates/adapters.d.ts +3 -0
- package/dist/templates/assayMatrixAdapters.d.ts +8 -1
- package/dist/templates/assayMatrixBuilder.d.ts +1 -0
- package/dist/templates/builderPresetControls.d.ts +1 -0
- package/dist/templates/calibrationCurveAdapters.d.ts +3 -0
- package/dist/templates/calibrationCurveBuilder.d.ts +1 -0
- package/dist/templates/catalog.d.ts +6 -2
- package/dist/templates/componentBindings.d.ts +31 -5
- package/dist/templates/componentPlateHelpers.d.ts +7 -1
- package/dist/templates/controlSchemas.d.ts +9 -0
- package/dist/templates/defaultBioTemplateBuilder.d.ts +1 -0
- package/dist/templates/doseResponseAdapters.d.ts +2 -0
- package/dist/templates/doseResponseBuilder.d.ts +1 -0
- package/dist/templates/elisaAssayCollectionBuilder.d.ts +1 -0
- package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +1 -0
- package/dist/templates/flowCytometryPanelBuilder.d.ts +1 -0
- package/dist/templates/flowPanelAdapters.d.ts +3 -0
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.js +2 -3
- package/dist/templates/instrumentRunAdapters.d.ts +13 -2
- package/dist/templates/instrumentRunBuilder.d.ts +1 -0
- package/dist/templates/lcmsBatchCollectionBuilder.d.ts +1 -0
- package/dist/templates/packs.d.ts +4 -0
- package/dist/templates/plateMapAdapters.d.ts +2 -0
- package/dist/templates/plateMapBuilder.d.ts +1 -0
- package/dist/templates/presets.d.ts +6 -2
- package/dist/templates/protocolAdapters.d.ts +4 -0
- package/dist/templates/protocolStepsBuilder.d.ts +1 -0
- package/dist/templates/qpcrAdapters.d.ts +4 -0
- package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +1 -0
- package/dist/templates/qpcrPlateBuilder.d.ts +1 -0
- package/dist/templates/reagentAdapters.d.ts +4 -0
- package/dist/templates/reagentListBuilder.d.ts +1 -0
- package/dist/templates/samplePrepAdapters.d.ts +3 -0
- package/dist/templates/samplePrepBuilder.d.ts +1 -0
- package/dist/templates/sampleSheetAdapters.d.ts +4 -0
- package/dist/templates/sampleSheetBuilder.d.ts +1 -0
- package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +1 -0
- package/dist/templates/templateEnvelopes.d.ts +8 -0
- package/dist/templates/templatePackCollectionBuilder.d.ts +1 -0
- package/dist/templates/templatePresetCollectionBuilder.d.ts +2 -0
- package/dist/templates/templateValidators.d.ts +12 -0
- package/dist/templates/timeCourseAdapters.d.ts +4 -0
- package/dist/templates/timeCourseBuilder.d.ts +1 -0
- package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +1 -0
- package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +1 -0
- package/dist/templates-BU1pet28.js +12304 -0
- package/dist/templates-BU1pet28.js.map +1 -0
- package/dist/types/auth.d.ts +0 -27
- package/dist/types/componentLabTypes.d.ts +14 -7
- package/dist/types/componentWorkflowTypes.d.ts +26 -0
- package/dist/types/components.d.ts +40 -17
- package/dist/types/fileBrowserTypes.d.ts +13 -0
- package/dist/types/filePicker.d.ts +19 -1
- package/dist/types/generated-ui.d.ts +13 -0
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.js +1 -1
- package/dist/types/instrument.d.ts +43 -15
- package/dist/types/platform.d.ts +2 -0
- package/dist/utils/color.d.ts +6 -1
- package/dist/utils/formModelSync.d.ts +1 -0
- package/dist/utils/plotlyTemplate.d.ts +3 -0
- package/dist/utils/rack.d.ts +16 -0
- package/dist/utils/sampleTypeMarker.d.ts +23 -0
- package/dist/{validation-ChMyixAf.js → validation-Dmp9Q5W0.js} +3032 -838
- package/dist/validation-Dmp9Q5W0.js.map +1 -0
- package/package.json +17 -23
- package/src/__stories__/experiment-helpers.ts +8 -4
- package/src/__tests__/components/ActionMenu.test.ts +66 -0
- package/src/__tests__/components/AlertBox.test.ts +21 -0
- package/src/__tests__/components/AppAvatarMenu.test.ts +25 -0
- package/src/__tests__/components/AppTopBar.test.ts +79 -0
- package/src/__tests__/components/AppTopBarPageSelector.test.ts +20 -0
- package/src/__tests__/components/AppTopBarPillNav.test.ts +74 -0
- package/src/__tests__/components/ArtifactSelectorModal.test.ts +20 -6
- package/src/__tests__/components/AuditTrail.test.ts +38 -0
- package/src/__tests__/components/BaseCheckbox.test.ts +43 -0
- package/src/__tests__/components/BaseInput.test.ts +43 -0
- package/src/__tests__/components/BaseModal.test.ts +90 -0
- package/src/__tests__/components/BaseRadioGroup.test.ts +8 -0
- package/src/__tests__/components/BaseSlider.test.ts +116 -0
- package/src/__tests__/components/BaseToggle.test.ts +7 -0
- package/src/__tests__/components/BatchProgressList.test.ts +22 -14
- package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +4 -4
- package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +1 -1
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +8 -8
- package/src/__tests__/components/BioTemplateRenderer.test.ts +2 -2
- package/src/__tests__/components/Breadcrumb.test.ts +45 -0
- package/src/__tests__/components/CalendarGridPanel.test.ts +14 -0
- package/src/__tests__/components/ChartContainer.test.ts +23 -0
- package/src/__tests__/components/ChemicalFormula.test.ts +15 -0
- package/src/__tests__/components/CollapsibleCard.test.ts +12 -9
- package/src/__tests__/components/ComponentBindingRenderer.test.ts +45 -17
- package/src/__tests__/components/ConcentrationInput.test.ts +17 -2
- package/src/__tests__/components/DataFrame.test.ts +107 -4
- package/src/__tests__/components/DataStates.test.ts +0 -16
- package/src/__tests__/components/DatePicker.test.ts +10 -0
- package/src/__tests__/components/DoseCalculator.test.ts +47 -0
- package/src/__tests__/components/DoseCalculatorVolumeField.test.ts +37 -34
- package/src/__tests__/components/EmptyState.test.ts +16 -1
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +31 -3
- package/src/__tests__/components/ExperimentTimeline.test.ts +45 -0
- package/src/__tests__/components/FilePicker.state.test.ts +2 -2
- package/src/__tests__/components/FilePicker.test.ts +360 -13
- package/src/__tests__/components/FileUploader.test.ts +15 -0
- package/src/__tests__/components/FitPanel.test.ts +96 -0
- package/src/__tests__/components/FormulaInput.test.ts +97 -0
- package/src/__tests__/components/GeneratedPathInput.test.ts +168 -41
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +1 -1
- package/src/__tests__/components/GeneratedResultRenderer.test.ts +47 -2
- package/src/__tests__/components/InstrumentStateBadge.test.ts +50 -0
- package/src/__tests__/components/JobsStatusTray.test.ts +27 -0
- package/src/__tests__/components/MoleculeInput.test.ts +78 -0
- package/src/__tests__/components/MultiSelect.test.ts +6 -0
- package/src/__tests__/components/PlateEditor.test.ts +1134 -0
- package/src/__tests__/components/PlateEditorBindingInternal.test.ts +64 -0
- package/src/__tests__/components/PlotlyChart.test.ts +147 -1
- package/src/__tests__/components/ProgressBar.test.ts +18 -0
- package/src/__tests__/components/ProtocolStepEditor.test.ts +106 -18
- package/src/__tests__/components/ProtocolStepParameterField.test.ts +74 -29
- package/src/__tests__/components/ReagentList.presentation.test.ts +10 -5
- package/src/__tests__/components/ReagentList.test.ts +23 -0
- package/src/__tests__/components/ResourcePickerListInternal.test.ts +141 -0
- package/src/__tests__/components/RetiredStyles.test.ts +0 -6
- package/src/__tests__/components/SampleHierarchyTree.test.ts +102 -0
- package/src/__tests__/components/SampleLegend.test.ts +18 -0
- package/src/__tests__/components/SampleSelector.test.ts +92 -1
- package/src/__tests__/components/SampleSelectorSampleRow.test.ts +5 -2
- package/src/__tests__/components/ScheduleCalendar.test.ts +32 -0
- package/src/__tests__/components/ScientificNumber.test.ts +113 -0
- package/src/__tests__/components/SearchableSelect.test.ts +21 -0
- package/src/__tests__/components/SecretInput.test.ts +6 -0
- package/src/__tests__/components/SegmentedControl.test.ts +14 -0
- package/src/__tests__/components/SequenceInput.test.ts +81 -0
- package/src/__tests__/components/SettingsModal.schema.test.ts +5 -5
- package/src/__tests__/components/SettingsModal.test.ts +371 -23
- package/src/__tests__/components/Skeleton.test.ts +23 -0
- package/src/__tests__/components/SmartGroupFieldRecipe.test.ts +12 -0
- package/src/__tests__/components/SmartGroupModal.test.ts +69 -0
- package/src/__tests__/components/StepWizard.test.ts +75 -0
- package/src/__tests__/components/TagsInput.test.ts +16 -0
- package/src/__tests__/components/TimePicker.test.ts +66 -0
- package/src/__tests__/components/TimeRangeInput.test.ts +83 -0
- package/src/__tests__/components/UnitInput.test.ts +147 -0
- package/src/__tests__/components/WellEditPopupInternal.test.ts +434 -2
- package/src/__tests__/components/WellPlate.decoration.test.ts +18 -0
- package/src/__tests__/components/WellPlate.geometry.test.ts +13 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +213 -0
- package/src/__tests__/components/WellPlate.keyboard.test.ts +24 -0
- package/src/__tests__/components/WellPlate.rendering.test.ts +38 -19
- package/src/__tests__/components/WellPlate.sampleDrop.test.ts +19 -0
- package/src/__tests__/components/WellPlate.sampleTypeIndicator.test.ts +58 -0
- package/src/__tests__/composables/createFilePickerAdapter.test.ts +114 -1
- package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +1 -1
- package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +1 -1
- package/src/__tests__/composables/useChemicalFormula.test.ts +60 -0
- package/src/__tests__/composables/useControlSchema.test.ts +15 -5
- package/src/__tests__/composables/useDropdownState.test.ts +20 -1
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +52 -0
- package/src/__tests__/composables/usePickerNavigation.test.ts +61 -1
- package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +18 -6
- package/src/__tests__/composables/useRackEditor.test.ts +270 -0
- package/src/__tests__/composables/useSequenceUtils.test.ts +28 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +44 -9
- package/src/__tests__/styles/baseControlsRules.test.ts +115 -0
- package/src/__tests__/styles/controlRefreshRules.test.ts +218 -0
- package/src/__tests__/styles/cssTokens.test.ts +7 -5
- package/src/__tests__/styles/darkModeRules.test.ts +47 -0
- package/src/__tests__/styles/menusShellRefresh.test.ts +75 -0
- package/src/__tests__/templates/templates.test.ts +65 -15
- package/src/__tests__/templates/templatesDeprecation.test.ts +78 -0
- package/src/__tests__/utils/permissions.test.ts +0 -68
- package/src/__tests__/utils/plotlyTemplate.test.ts +69 -0
- package/src/__tests__/utils/sampleTypeMarker.test.ts +54 -0
- package/src/components/ActionMenu.vue +44 -23
- package/src/components/AdductText.story.vue +18 -0
- package/src/components/AdductText.vue +7 -1
- package/src/components/AlertBox.vue +3 -7
- package/src/components/AppAvatarMenu.vue +12 -10
- package/src/components/AppSidebar.vue +1 -1
- package/src/components/AppTopBar.story.vue +47 -4
- package/src/components/AppTopBar.vue +16 -22
- package/src/components/ArtifactSelectorModal.story.vue +1 -1
- package/src/components/ArtifactSelectorModal.vue +48 -160
- package/src/components/AuditTrail.vue +26 -12
- package/src/components/AutoGroupModal.vue +1 -1
- package/src/components/BaseCheckbox.story.vue +58 -2
- package/src/components/BaseCheckbox.vue +21 -3
- package/src/components/BaseInput.story.vue +51 -0
- package/src/components/BaseInput.vue +62 -1
- package/src/components/BaseModal.story.vue +55 -0
- package/src/components/BaseModal.vue +83 -50
- package/src/components/BaseRadioGroup.vue +1 -1
- package/src/components/BaseSlider.story.vue +36 -0
- package/src/components/BaseSlider.vue +56 -8
- package/src/components/BaseToggle.vue +1 -7
- package/src/components/BatchProgressList.vue +50 -52
- package/src/components/BioTemplatePresetWorkspaceView.vue +1 -1
- package/src/components/Breadcrumb.story.vue +1 -1
- package/src/components/Breadcrumb.vue +50 -15
- package/src/components/ChartContainer.story.vue +42 -0
- package/src/components/ChartContainer.vue +22 -1
- package/src/components/ChemicalFormula.story.vue +11 -11
- package/src/components/ChemicalFormula.vue +1 -0
- package/src/components/CollapsibleCard.vue +12 -32
- package/src/components/ComponentBindingRenderer.vue +6 -8
- package/src/components/ConcentrationInput.story.vue +1 -1
- package/src/components/ConcentrationInput.vue +8 -14
- package/src/components/ControlWorkspaceView.vue +1 -1
- package/src/components/DataFrame.story.vue +45 -2
- package/src/components/DataFrame.vue +28 -13
- package/src/components/DatePicker.story.vue +11 -0
- package/src/components/DatePicker.vue +5 -5
- package/src/components/DateTimePicker.story.vue +1 -1
- package/src/components/DateTimePicker.vue +8 -5
- package/src/components/Divider.story.vue +1 -1
- package/src/components/DoseCalculator.vue +44 -51
- package/src/components/DoseCalculatorVolumeField.vue +22 -35
- package/src/components/DoseDesignWorkspaceView.vue +1 -1
- package/src/components/EmptyState.story.vue +4 -4
- package/src/components/EmptyState.vue +4 -2
- package/src/components/ExperimentPopover.story.vue +2 -2
- package/src/components/ExperimentPopover.vue +6 -4
- package/src/components/ExperimentSelectorModal.story.vue +2 -1
- package/src/components/ExperimentSelectorModal.vue +91 -261
- package/src/components/ExperimentTimeline.story.vue +29 -0
- package/src/components/ExperimentTimeline.vue +20 -0
- package/src/components/FilePicker.story.vue +65 -10
- package/src/components/FilePicker.vue +203 -121
- package/src/components/FitPanel.story.vue +72 -6
- package/src/components/FitPanel.vue +70 -18
- package/src/components/FormField.vue +2 -0
- package/src/components/FormulaInput.story.vue +6 -0
- package/src/components/FormulaInput.vue +49 -70
- package/src/components/GeneratedPathInput.story.vue +121 -8
- package/src/components/GeneratedPathInput.vue +155 -86
- package/src/components/GeneratedPlotlyResult.vue +1 -1
- package/src/components/GeneratedPluginView.vue +4 -4
- package/src/components/GeneratedResultRenderer.vue +19 -0
- package/src/components/InstrumentStateBadge.story.vue +41 -0
- package/src/components/InstrumentStateBadge.vue +24 -14
- package/src/components/JobsStatusTray.vue +30 -8
- package/src/components/MoleculeInput.vue +12 -7
- package/src/components/MultiSelect.vue +3 -3
- package/src/components/PlateEditor.story.vue +333 -0
- package/src/components/PlateEditor.vue +793 -0
- package/src/components/PlotlyChart.story.vue +37 -1
- package/src/components/PlotlyChart.vue +86 -16
- package/src/components/PluginWorkspaceView.props.ts +1 -7
- package/src/components/PluginWorkspaceView.vue +0 -4
- package/src/components/ProgressBar.story.vue +0 -30
- package/src/components/ProgressBar.vue +3 -53
- package/src/components/ProtocolStepEditor.story.vue +8 -0
- package/src/components/ProtocolStepEditor.vue +55 -145
- package/src/components/ProtocolStepParameterField.vue +55 -93
- package/src/components/ReagentList.presentation.ts +7 -6
- package/src/components/ReagentList.vue +18 -17
- package/src/components/SampleHierarchyTree.story.vue +19 -0
- package/src/components/SampleHierarchyTree.vue +47 -14
- package/src/components/SampleLegend.vue +12 -4
- package/src/components/SampleSelector.story.vue +5 -1
- package/src/components/SampleSelector.vue +25 -15
- package/src/components/SampleSelectorGroupNode.vue +10 -20
- package/src/components/SampleSelectorSampleRow.vue +7 -18
- package/src/components/ScheduleCalendar.story.vue +14 -0
- package/src/components/ScheduleCalendar.vue +13 -1
- package/src/components/ScientificNumber.story.vue +15 -2
- package/src/components/ScientificNumber.vue +22 -26
- package/src/components/SearchableSelect.vue +14 -9
- package/src/components/SecretInput.vue +1 -1
- package/src/components/SegmentedControl.story.vue +23 -0
- package/src/components/SegmentedControl.vue +6 -1
- package/src/components/SequenceInput.story.vue +27 -5
- package/src/components/SequenceInput.vue +74 -29
- package/src/components/SequenceProgressBar.vue +0 -1
- package/src/components/SettingsModal.schema.ts +4 -2
- package/src/components/SettingsModal.story.vue +83 -22
- package/src/components/SettingsModal.vue +338 -79
- package/src/components/Skeleton.vue +6 -35
- package/src/components/SmartGroup.types.ts +2 -2
- package/src/components/SmartGroupFieldRecipe.groups.ts +4 -1
- package/src/components/SmartGroupFieldRecipe.vue +14 -8
- package/src/components/SmartGroupManual.vue +14 -11
- package/src/components/StepWizard.vue +16 -19
- package/src/components/TagsInput.story.vue +16 -11
- package/src/components/TagsInput.vue +20 -3
- package/src/components/TimePicker.story.vue +38 -3
- package/src/components/TimePicker.vue +26 -11
- package/src/components/TimeRangeInput.story.vue +50 -1
- package/src/components/TimeRangeInput.vue +36 -17
- package/src/components/UnitInput.story.vue +1 -1
- package/src/components/UnitInput.vue +173 -107
- package/src/components/WellPlate.colors.ts +7 -6
- package/src/components/WellPlate.geometry.ts +3 -0
- package/src/components/WellPlate.interaction.ts +39 -5
- package/src/components/WellPlate.rendering.ts +21 -27
- package/src/components/WellPlate.sampleDrop.ts +20 -12
- package/src/components/WellPlate.story.vue +18 -7
- package/src/components/WellPlate.vue +28 -22
- package/src/components/index.ts +34 -31
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +13 -7
- package/src/components/internal/AppTopBarPillNavInternal.vue +12 -7
- package/src/components/internal/CalendarGridPanelInternal.vue +1 -1
- package/src/components/internal/FilePickerListInternal.vue +318 -0
- package/src/components/internal/FilePickerPreviewInternal.vue +54 -52
- package/src/components/internal/PlateEditorBindingInternal.vue +37 -0
- package/src/components/internal/ResourcePickerListInternal.vue +237 -0
- package/src/components/internal/SettingsPaneHeadInternal.vue +41 -0
- package/src/components/internal/WellEditPopupInternal.vue +219 -146
- package/src/components/internal/WorkspaceShellInternal.vue +0 -6
- package/src/components/internal/filePickerPresentation.ts +30 -0
- package/src/components/internal/generatedJobsApi.ts +12 -0
- package/src/composables/autoGroup/index.ts +3 -0
- package/src/composables/controlSchemaAdapters.ts +0 -4
- package/src/composables/controlSchemaFormFields.ts +0 -4
- package/src/composables/controlSchemaTypes.ts +4 -43
- package/src/composables/createFilePickerAdapter.ts +90 -22
- package/src/composables/filePicker/localFiles.ts +1 -0
- package/src/composables/filePicker/usePickerNavigation.ts +29 -2
- package/src/composables/index.ts +2 -71
- package/src/composables/pluginWorkspaceModel.ts +0 -3
- package/src/composables/useAutoGroup.ts +10 -0
- package/src/composables/useBioTemplateComponents.ts +15 -3
- package/src/composables/useBioTemplateControls.ts +5 -1
- package/src/composables/useBioTemplatePackWorkspace.ts +2 -0
- package/src/composables/useBioTemplatePresetWorkspace.ts +5 -1
- package/src/composables/useBioTemplateWorkspace.ts +5 -1
- package/src/composables/useChemicalFormula.ts +39 -13
- package/src/composables/useDropdownState.ts +6 -1
- package/src/composables/useExperimentSave.ts +5 -1
- package/src/composables/useFileBrowser.ts +3 -2
- package/src/composables/useFilePicker.ts +98 -13
- package/src/composables/useFocusTrap.ts +9 -6
- package/src/composables/useGeneratedAnalysis.ts +38 -0
- package/src/composables/useGroupAssignment.ts +5 -1
- package/src/composables/useJobsStatusTray.ts +5 -5
- package/src/composables/useMenuKeyboard.ts +5 -3
- package/src/composables/usePlatformFilePickerAdapter.ts +26 -4
- package/src/composables/usePluginClient.ts +1 -1
- package/src/composables/usePluginWorkspace.ts +0 -1
- package/src/composables/useRackEditor.ts +202 -12
- package/src/composables/useSequenceUtils.ts +20 -2
- package/src/composables/useTemplateCollection.ts +5 -1
- package/src/composables/workspaceRecipes.ts +1 -0
- package/src/index.ts +9 -3
- package/src/instrument.ts +6 -0
- package/src/lcms.ts +11 -0
- package/src/permissions.ts +2 -59
- package/src/styles/components/action-menu.css +9 -38
- package/src/styles/components/adduct-text.css +15 -3
- package/src/styles/components/alert-box.css +19 -70
- package/src/styles/components/app-avatar-menu.css +22 -57
- package/src/styles/components/app-container.css +4 -4
- package/src/styles/components/app-layout.css +18 -18
- package/src/styles/components/app-page-selector.css +50 -105
- package/src/styles/components/app-pill-nav.css +24 -42
- package/src/styles/components/app-sidebar.css +2 -2
- package/src/styles/components/app-top-bar.css +78 -55
- package/src/styles/components/artifact-selector-modal.css +1 -211
- package/src/styles/components/audit-trail.css +45 -38
- package/src/styles/components/avatar.css +1 -1
- package/src/styles/components/batch-progress-list.css +179 -95
- package/src/styles/components/breadcrumb.css +34 -15
- package/src/styles/components/calendar.css +2 -8
- package/src/styles/components/chart-container.css +81 -38
- package/src/styles/components/checkbox.css +8 -6
- package/src/styles/components/chemical-formula.css +8 -6
- package/src/styles/components/collapsible-card.css +6 -46
- package/src/styles/components/concentration-input.css +1 -10
- package/src/styles/components/dataframe.css +48 -67
- package/src/styles/components/date-picker.css +124 -62
- package/src/styles/components/datetime-picker.css +81 -47
- package/src/styles/components/divider.css +2 -4
- package/src/styles/components/dose-calculator.css +9 -135
- package/src/styles/components/empty-state.css +26 -35
- package/src/styles/components/experiment-code-badge.css +1 -1
- package/src/styles/components/experiment-popover.css +16 -83
- package/src/styles/components/experiment-selector-modal.css +9 -226
- package/src/styles/components/experiment-timeline.css +120 -50
- package/src/styles/components/file-picker.css +323 -299
- package/src/styles/components/file-uploader.css +4 -6
- package/src/styles/components/fit-panel.css +108 -22
- package/src/styles/components/form-field.css +6 -4
- package/src/styles/components/formula-input.css +121 -48
- package/src/styles/components/group-assigner.css +1 -3
- package/src/styles/components/icon-button.css +15 -37
- package/src/styles/components/input.css +91 -2
- package/src/styles/components/instrument-monitor.css +37 -447
- package/src/styles/components/jobs-status-tray.css +82 -61
- package/src/styles/components/loading-spinner.css +18 -0
- package/src/styles/components/mobile-support-gate.css +15 -17
- package/src/styles/components/modal.css +44 -33
- package/src/styles/components/molecule-input.css +31 -16
- package/src/styles/components/multi-select.css +33 -29
- package/src/styles/components/number-input.css +1 -1
- package/src/styles/components/plate-editor.css +497 -0
- package/src/styles/components/popover.css +162 -0
- package/src/styles/components/progress-bar.css +1 -70
- package/src/styles/components/protocol-step-editor.css +21 -333
- package/src/styles/components/radio-group.css +9 -18
- package/src/styles/components/reagent-editor.css +3 -7
- package/src/styles/components/reagent-list.css +63 -69
- package/src/styles/components/resource-card.css +4 -6
- package/src/styles/components/resource-picker-list.css +212 -0
- package/src/styles/components/sample-hierarchy-tree.css +83 -65
- package/src/styles/components/sample-legend.css +18 -7
- package/src/styles/components/sample-selector.css +41 -68
- package/src/styles/components/schedule-calendar.css +59 -54
- package/src/styles/components/scientific-number.css +72 -18
- package/src/styles/components/searchable-select.css +17 -26
- package/src/styles/components/secret-input.css +20 -23
- package/src/styles/components/section-card.css +3 -5
- package/src/styles/components/segmented-control.css +14 -1
- package/src/styles/components/select.css +19 -8
- package/src/styles/components/sequence-input.css +85 -37
- package/src/styles/components/sequence-progress-bar.css +18 -37
- package/src/styles/components/settings-modal.css +683 -34
- package/src/styles/components/skeleton.css +24 -23
- package/src/styles/components/slider.css +74 -8
- package/src/styles/components/smart-group.css +27 -39
- package/src/styles/components/status-indicator.css +1 -1
- package/src/styles/components/step-wizard.css +62 -99
- package/src/styles/components/tabs.css +2 -6
- package/src/styles/components/tags-input.css +64 -54
- package/src/styles/components/textarea.css +2 -2
- package/src/styles/components/time-picker.css +67 -38
- package/src/styles/components/time-range-input.css +16 -18
- package/src/styles/components/toast.css +1 -1
- package/src/styles/components/toggle.css +21 -31
- package/src/styles/components/tooltip.css +13 -15
- package/src/styles/components/unit-input.css +86 -36
- package/src/styles/components/well-edit-popup.css +111 -189
- package/src/styles/components/well-plate.css +7 -7
- package/src/styles/index.css +5 -7
- package/src/styles/variables.css +35 -7
- package/src/templates/adapters.ts +4 -0
- package/src/templates/assayMatrixAdapters.ts +8 -1
- package/src/templates/assayMatrixBuilder.ts +1 -0
- package/src/templates/builderPresetControls.ts +1 -0
- package/src/templates/calibrationCurveAdapters.ts +3 -0
- package/src/templates/calibrationCurveBuilder.ts +1 -0
- package/src/templates/catalog.ts +6 -2
- package/src/templates/componentBindingCatalog.ts +1 -1
- package/src/templates/componentBindings.ts +31 -5
- package/src/templates/componentDoseResponseProps.ts +0 -1
- package/src/templates/componentPlateHelpers.ts +22 -1
- package/src/templates/componentPlateMapProps.ts +7 -5
- package/src/templates/componentQpcrPlateProps.ts +0 -1
- package/src/templates/controlSchemas.ts +9 -0
- package/src/templates/defaultBioTemplateBuilder.ts +1 -0
- package/src/templates/doseResponseAdapters.ts +2 -0
- package/src/templates/doseResponseBuilder.ts +1 -0
- package/src/templates/elisaAssayCollectionBuilder.ts +1 -0
- package/src/templates/flowCytometryAssayCollectionBuilder.ts +1 -0
- package/src/templates/flowCytometryPanelBuilder.ts +1 -0
- package/src/templates/flowPanelAdapters.ts +3 -0
- package/src/templates/index.ts +4 -0
- package/src/templates/instrumentRunAdapters.ts +13 -2
- package/src/templates/instrumentRunBuilder.ts +1 -0
- package/src/templates/lcmsBatchCollectionBuilder.ts +1 -0
- package/src/templates/packs.ts +4 -0
- package/src/templates/plateMapAdapters.ts +2 -0
- package/src/templates/plateMapBuilder.ts +1 -0
- package/src/templates/presets.ts +6 -2
- package/src/templates/protocolAdapters.ts +4 -0
- package/src/templates/protocolStepsBuilder.ts +1 -0
- package/src/templates/qpcrAdapters.ts +4 -0
- package/src/templates/qpcrExpressionCollectionBuilder.ts +1 -0
- package/src/templates/qpcrPlateBuilder.ts +1 -0
- package/src/templates/reagentAdapters.ts +4 -0
- package/src/templates/reagentListBuilder.ts +1 -0
- package/src/templates/samplePrepAdapters.ts +3 -0
- package/src/templates/samplePrepBuilder.ts +1 -0
- package/src/templates/sampleSheetAdapters.ts +4 -0
- package/src/templates/sampleSheetBuilder.ts +1 -0
- package/src/templates/targetedMetabolomicsCollectionBuilder.ts +1 -0
- package/src/templates/templateEnvelopes.ts +8 -0
- package/src/templates/templatePackCollectionBuilder.ts +1 -0
- package/src/templates/templatePresetCollectionBuilder.ts +2 -0
- package/src/templates/templateValidators.ts +12 -0
- package/src/templates/timeCourseAdapters.ts +4 -0
- package/src/templates/timeCourseBuilder.ts +1 -0
- package/src/templates/wellPlateScreenCollectionBuilder.ts +1 -0
- package/src/templates/westernBlotAssayCollectionBuilder.ts +1 -0
- package/src/types/auth.ts +0 -37
- package/src/types/componentLabTypes.ts +16 -13
- package/src/types/componentWorkflowTypes.ts +29 -0
- package/src/types/components.ts +43 -20
- package/src/types/fileBrowserTypes.ts +15 -0
- package/src/types/filePicker.ts +19 -1
- package/src/types/generated-ui.ts +15 -0
- package/src/types/index.ts +12 -11
- package/src/types/instrument.ts +47 -15
- package/src/types/platform.ts +2 -0
- package/src/utils/color.ts +16 -1
- package/src/utils/formModelSync.ts +1 -1
- package/src/utils/plotlyTemplate.ts +39 -0
- package/src/utils/rack.ts +16 -0
- package/src/utils/sampleTypeMarker.ts +44 -0
- package/dist/ExperimentPopover-CJdNlCZb.js +0 -451
- package/dist/ExperimentPopover-CJdNlCZb.js.map +0 -1
- package/dist/ExperimentSelectorModal-DHE9k8TP.js +0 -507
- package/dist/ExperimentSelectorModal-DHE9k8TP.js.map +0 -1
- package/dist/SettingsModal-DELMkUFR.js +0 -492
- package/dist/SettingsModal-DELMkUFR.js.map +0 -1
- package/dist/Skeleton-jhF9wUkU.js +0 -485
- package/dist/Skeleton-jhF9wUkU.js.map +0 -1
- package/dist/__tests__/composables/usePluginConfig.test.d.ts +0 -15
- package/dist/chunk-BoAXSpZd.js +0 -33
- package/dist/components/AppPluginSwitcher.vue.d.ts +0 -40
- package/dist/components/ColorSlider.vue.d.ts +0 -34
- package/dist/components/DropdownButton.vue.d.ts +0 -49
- package/dist/components/FileBrowserModal.vue.d.ts +0 -99
- package/dist/components/InstrumentAlertLog.vue.d.ts +0 -22
- package/dist/components/InstrumentStatusCard.vue.d.ts +0 -13
- package/dist/components/LcmsSequenceTable.vue.d.ts +0 -54
- package/dist/components/PlateMapEditor.vue.d.ts +0 -50
- package/dist/components/RackEditor.vue.d.ts +0 -78
- package/dist/components/fileBrowserPresentation.d.ts +0 -10
- package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +0 -34
- package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +0 -25
- package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +0 -26
- package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +0 -28
- package/dist/components-Bbg_gtzF.js.map +0 -1
- package/dist/composables/useAsync.d.ts +0 -132
- package/dist/composables/useAuth.d.ts +0 -47
- package/dist/composables/useCommandHistory.d.ts +0 -33
- package/dist/composables/useOptimisticMutation.d.ts +0 -32
- package/dist/composables/usePasskey.d.ts +0 -10
- package/dist/composables/usePluginConfig.d.ts +0 -27
- package/dist/composables/useResourceCrud.d.ts +0 -44
- package/dist/composables/useWellPainting.d.ts +0 -39
- package/dist/composables/useWellPlateAdapter.d.ts +0 -40
- package/dist/composables/useWellPlateEditor.d.ts +0 -33
- package/dist/composables/useWellPlateValidation.d.ts +0 -31
- package/dist/composables-BCbYb2lA.js +0 -2356
- package/dist/composables-BCbYb2lA.js.map +0 -1
- package/dist/errors-BLuqMee5.js.map +0 -1
- package/dist/experiment-CVDRAnqp.js +0 -90
- package/dist/experiment-CVDRAnqp.js.map +0 -1
- package/dist/experiment-utils-CV2Wza3z.js.map +0 -1
- package/dist/plotly.min-c2Sez60c.js +0 -180634
- package/dist/plotly.min-c2Sez60c.js.map +0 -1
- package/dist/templates-NuaNHohp.js +0 -5260
- package/dist/templates-NuaNHohp.js.map +0 -1
- package/dist/useControlSchema-vLiRPUmj.js +0 -7301
- package/dist/useControlSchema-vLiRPUmj.js.map +0 -1
- package/dist/useDropdownState-C7hRiVQf.js +0 -63
- package/dist/useDropdownState-C7hRiVQf.js.map +0 -1
- package/dist/useExperimentSelector-G_CAOFTX.js +0 -328
- package/dist/useExperimentSelector-G_CAOFTX.js.map +0 -1
- package/dist/useFormBuilder-D37ajgS8.js +0 -729
- package/dist/useFormBuilder-D37ajgS8.js.map +0 -1
- package/dist/validation-ChMyixAf.js.map +0 -1
- package/src/__tests__/components/AppPluginSwitcher.test.ts +0 -44
- package/src/__tests__/components/DropdownButton.test.ts +0 -628
- package/src/__tests__/components/FileBrowserModal.test.ts +0 -460
- package/src/__tests__/components/InstrumentAlertLog.test.ts +0 -59
- package/src/__tests__/components/LcmsSequenceTable.test.ts +0 -79
- package/src/__tests__/components/PlateMapEditorToolbarInternal.test.ts +0 -54
- package/src/__tests__/components/RackEditor.test.ts +0 -169
- package/src/__tests__/composables/useAuth.test.ts +0 -233
- package/src/__tests__/composables/useCommandHistory.test.ts +0 -43
- package/src/__tests__/composables/useOptimisticMutation.test.ts +0 -40
- package/src/__tests__/composables/usePluginConfig.test.ts +0 -497
- package/src/__tests__/composables/useResourceCrud.test.ts +0 -56
- package/src/__tests__/composables/useWellPainting.test.ts +0 -52
- package/src/__tests__/composables/useWellPlateAdapter.test.ts +0 -50
- package/src/__tests__/composables/useWellPlateValidation.test.ts +0 -42
- package/src/components/AppPluginSwitcher.story.vue +0 -87
- package/src/components/AppPluginSwitcher.vue +0 -134
- package/src/components/ColorSlider.story.vue +0 -120
- package/src/components/ColorSlider.vue +0 -160
- package/src/components/DropdownButton.story.vue +0 -102
- package/src/components/DropdownButton.vue +0 -181
- package/src/components/FileBrowserModal.story.vue +0 -203
- package/src/components/FileBrowserModal.vue +0 -384
- package/src/components/InstrumentAlertLog.vue +0 -187
- package/src/components/InstrumentStatusCard.vue +0 -184
- package/src/components/LcmsSequenceTable.vue +0 -222
- package/src/components/PlateMapEditor.story.vue +0 -92
- package/src/components/PlateMapEditor.vue +0 -423
- package/src/components/RackEditor.story.vue +0 -100
- package/src/components/RackEditor.vue +0 -429
- package/src/components/fileBrowserPresentation.ts +0 -39
- package/src/components/internal/FileBrowserDialogInternal.vue +0 -75
- package/src/components/internal/FileBrowserListingInternal.vue +0 -197
- package/src/components/internal/FileBrowserToolbarInternal.vue +0 -136
- package/src/components/internal/FilePickerTreeInternal.vue +0 -238
- package/src/components/internal/PlateMapEditorToolbarInternal.vue +0 -128
- package/src/composables/useAsync.ts +0 -327
- package/src/composables/useAuth.ts +0 -458
- package/src/composables/useCommandHistory.ts +0 -117
- package/src/composables/useOptimisticMutation.ts +0 -97
- package/src/composables/usePasskey.ts +0 -216
- package/src/composables/usePluginConfig.ts +0 -252
- package/src/composables/useResourceCrud.ts +0 -235
- package/src/composables/useWellPainting.ts +0 -167
- package/src/composables/useWellPlateAdapter.ts +0 -151
- package/src/composables/useWellPlateEditor.ts +0 -415
- package/src/composables/useWellPlateValidation.ts +0 -89
- package/src/plotly-dist.d.ts +0 -3
- package/src/styles/components/app-plugin-switcher.css +0 -196
- package/src/styles/components/color-slider.css +0 -110
- package/src/styles/components/dropdown-button.css +0 -280
- package/src/styles/components/file-browser-modal.css +0 -489
- package/src/styles/components/lcms-sequence-table.css +0 -204
- package/src/styles/components/plate-map-editor.css +0 -464
- package/src/styles/components/rack-editor.css +0 -287
- /package/dist/__tests__/components/{AppPluginSwitcher.test.d.ts → AlertBox.test.d.ts} +0 -0
- /package/dist/__tests__/components/{DropdownButton.test.d.ts → AuditTrail.test.d.ts} +0 -0
- /package/dist/__tests__/components/{FileBrowserModal.test.d.ts → BaseCheckbox.test.d.ts} +0 -0
- /package/dist/__tests__/components/{InstrumentAlertLog.test.d.ts → BaseSlider.test.d.ts} +0 -0
- /package/dist/__tests__/components/{LcmsSequenceTable.test.d.ts → DoseCalculator.test.d.ts} +0 -0
- /package/dist/__tests__/components/{PlateMapEditorToolbarInternal.test.d.ts → FileUploader.test.d.ts} +0 -0
- /package/dist/__tests__/components/{RackEditor.test.d.ts → FitPanel.test.d.ts} +0 -0
- /package/dist/__tests__/{composables/useAuth.test.d.ts → components/FormulaInput.test.d.ts} +0 -0
- /package/dist/__tests__/{composables/useCommandHistory.test.d.ts → components/InstrumentStateBadge.test.d.ts} +0 -0
- /package/dist/__tests__/{composables/useOptimisticMutation.test.d.ts → components/MoleculeInput.test.d.ts} +0 -0
- /package/dist/__tests__/{composables/useResourceCrud.test.d.ts → components/PlateEditor.test.d.ts} +0 -0
- /package/dist/__tests__/{composables/useWellPainting.test.d.ts → components/PlateEditorBindingInternal.test.d.ts} +0 -0
- /package/dist/__tests__/{composables/useWellPlateAdapter.test.d.ts → components/ResourcePickerListInternal.test.d.ts} +0 -0
- /package/dist/__tests__/{composables/useWellPlateValidation.test.d.ts → components/SampleLegend.test.d.ts} +0 -0
|
@@ -1,2356 +0,0 @@
|
|
|
1
|
-
import { a as useAuthStore, d as useSettingsStore, i as useApi, n as AuthenticationRequiredError, r as authenticationRequiredError, t as readErrorMessage } from "./errors-BLuqMee5.js";
|
|
2
|
-
import { n as useRequestSyncState } from "./Skeleton-jhF9wUkU.js";
|
|
3
|
-
import { B as currentExperimentFromContext, U as resolveCurrentExperimentId, V as getInjectedPlatformContext, et as useEventStream, lt as PLUGIN_SECRET_FORMAT_KEY } from "./useControlSchema-vLiRPUmj.js";
|
|
4
|
-
import { n as pickerFormat } from "./validation-ChMyixAf.js";
|
|
5
|
-
import { i as usePlatformContext } from "./useFormBuilder-D37ajgS8.js";
|
|
6
|
-
import { bn as rowLabel, pn as PLATE_CONFIGS, yn as rowIndex } from "./templates-NuaNHohp.js";
|
|
7
|
-
import { computed, getCurrentInstance, getCurrentScope, onBeforeUnmount, onMounted, onScopeDispose, onUnmounted, ref, shallowRef, toValue, watch } from "vue";
|
|
8
|
-
import axios from "axios";
|
|
9
|
-
//#region src/composables/useAuth.ts
|
|
10
|
-
var TOKEN_REFRESH_MARGIN_MS = 300 * 1e3;
|
|
11
|
-
var TOKEN_REFRESH_CHECK_INTERVAL_MS = 60 * 1e3;
|
|
12
|
-
var _refreshPromise = null;
|
|
13
|
-
var _refreshTimerId = null;
|
|
14
|
-
var _mountedConsumerCount = 0;
|
|
15
|
-
/** Manages authentication state with login/logout/register and automatic JWT token refresh. */
|
|
16
|
-
function useAuth() {
|
|
17
|
-
const authStore = useAuthStore();
|
|
18
|
-
const settingsStore = useSettingsStore();
|
|
19
|
-
const isRefreshing = ref(false);
|
|
20
|
-
function getApiBaseUrl() {
|
|
21
|
-
return settingsStore.getApiBaseUrl();
|
|
22
|
-
}
|
|
23
|
-
async function fetchAuthConfig() {
|
|
24
|
-
try {
|
|
25
|
-
const response = await axios.get(`${getApiBaseUrl()}/setup/config/public`);
|
|
26
|
-
const config = {
|
|
27
|
-
authRequired: response.data.auth_required,
|
|
28
|
-
passkeyEnabled: response.data.passkey_enabled,
|
|
29
|
-
passkeyRegistered: response.data.passkey_registered ?? false,
|
|
30
|
-
registrationEnabled: response.data.registration_enabled ?? false,
|
|
31
|
-
databaseMode: response.data.database_mode ?? "none",
|
|
32
|
-
experimentVisibilityMode: response.data.access?.experiment_visibility_mode ?? response.data.access?.experimentVisibilityMode ?? "open"
|
|
33
|
-
};
|
|
34
|
-
authStore.setAuthConfig(config);
|
|
35
|
-
return config;
|
|
36
|
-
} catch (error) {
|
|
37
|
-
console.error("Failed to fetch auth config:", error);
|
|
38
|
-
return {
|
|
39
|
-
authRequired: false,
|
|
40
|
-
passkeyEnabled: false,
|
|
41
|
-
passkeyRegistered: false,
|
|
42
|
-
registrationEnabled: false,
|
|
43
|
-
databaseMode: "none",
|
|
44
|
-
experimentVisibilityMode: "open"
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async function login(username, password) {
|
|
49
|
-
authStore.setLoading(true);
|
|
50
|
-
authStore.setError(null);
|
|
51
|
-
try {
|
|
52
|
-
const response = await axios.post(`${getApiBaseUrl()}/auth/login`, {
|
|
53
|
-
username,
|
|
54
|
-
password
|
|
55
|
-
});
|
|
56
|
-
authStore.setToken(response.data.access_token, response.data.expires_in);
|
|
57
|
-
authStore.setUsername(username);
|
|
58
|
-
await getCurrentUser();
|
|
59
|
-
scheduleTokenRefresh();
|
|
60
|
-
return true;
|
|
61
|
-
} catch (error) {
|
|
62
|
-
if (axios.isAxiosError(error) && error.response) authStore.setError(error.response.data.detail || "Login failed");
|
|
63
|
-
else authStore.setError("Network error. Please try again.");
|
|
64
|
-
return false;
|
|
65
|
-
} finally {
|
|
66
|
-
authStore.setLoading(false);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
async function register(username, password, email) {
|
|
70
|
-
authStore.setLoading(true);
|
|
71
|
-
authStore.setError(null);
|
|
72
|
-
try {
|
|
73
|
-
await axios.post(`${getApiBaseUrl()}/users/register`, {
|
|
74
|
-
username,
|
|
75
|
-
password,
|
|
76
|
-
email
|
|
77
|
-
});
|
|
78
|
-
return await login(username, password);
|
|
79
|
-
} catch (error) {
|
|
80
|
-
if (axios.isAxiosError(error) && error.response) authStore.setError(error.response.data.detail || "Registration failed");
|
|
81
|
-
else authStore.setError("Network error. Please try again.");
|
|
82
|
-
return false;
|
|
83
|
-
} finally {
|
|
84
|
-
authStore.setLoading(false);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async function getCurrentUser() {
|
|
88
|
-
if (!authStore.token) return null;
|
|
89
|
-
try {
|
|
90
|
-
const response = await axios.get(`${getApiBaseUrl()}/users/me`, { headers: getAuthHeader() });
|
|
91
|
-
const userInfo = {
|
|
92
|
-
id: response.data.id,
|
|
93
|
-
username: response.data.username,
|
|
94
|
-
shortname: response.data.shortname,
|
|
95
|
-
email: response.data.email,
|
|
96
|
-
role: response.data.role,
|
|
97
|
-
roleObj: response.data.role_obj ?? null,
|
|
98
|
-
isActive: response.data.is_active
|
|
99
|
-
};
|
|
100
|
-
authStore.setUserInfo(userInfo);
|
|
101
|
-
return userInfo;
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
async function verifyToken() {
|
|
107
|
-
if (!authStore.token) return false;
|
|
108
|
-
try {
|
|
109
|
-
const response = await axios.get(`${getApiBaseUrl()}/auth/verify`, { headers: { Authorization: `Bearer ${authStore.token}` } });
|
|
110
|
-
if (response.data.valid && response.data.username) {
|
|
111
|
-
authStore.setUsername(response.data.username);
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
authStore.clearToken();
|
|
115
|
-
return false;
|
|
116
|
-
} catch {
|
|
117
|
-
authStore.clearToken();
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Refresh the authentication token.
|
|
123
|
-
* Called automatically before token expiration.
|
|
124
|
-
* Uses promise caching to prevent concurrent refresh requests.
|
|
125
|
-
*/
|
|
126
|
-
async function refreshToken() {
|
|
127
|
-
if (!authStore.token) return false;
|
|
128
|
-
if (_refreshPromise) return _refreshPromise;
|
|
129
|
-
_refreshPromise = (async () => {
|
|
130
|
-
isRefreshing.value = true;
|
|
131
|
-
try {
|
|
132
|
-
const response = await axios.post(`${getApiBaseUrl()}/auth/refresh`, {}, { headers: getAuthHeader() });
|
|
133
|
-
authStore.setToken(response.data.access_token, response.data.expires_in);
|
|
134
|
-
scheduleTokenRefresh();
|
|
135
|
-
return true;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
|
138
|
-
console.warn("[Auth] Token refresh failed - session expired");
|
|
139
|
-
authStore.clearToken();
|
|
140
|
-
stopTokenRefresh();
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
} finally {
|
|
144
|
-
isRefreshing.value = false;
|
|
145
|
-
_refreshPromise = null;
|
|
146
|
-
}
|
|
147
|
-
})();
|
|
148
|
-
return _refreshPromise;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Schedule automatic token refresh before expiration.
|
|
152
|
-
*/
|
|
153
|
-
function scheduleTokenRefresh() {
|
|
154
|
-
stopTokenRefresh();
|
|
155
|
-
if (!authStore.tokenExpires) return;
|
|
156
|
-
const refreshAt = authStore.tokenExpires.getTime() - TOKEN_REFRESH_MARGIN_MS;
|
|
157
|
-
const now = Date.now();
|
|
158
|
-
if (refreshAt <= now) {
|
|
159
|
-
refreshToken();
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const delay = refreshAt - now;
|
|
163
|
-
_refreshTimerId = window.setTimeout(() => {
|
|
164
|
-
refreshToken();
|
|
165
|
-
}, delay);
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Stop automatic token refresh.
|
|
169
|
-
*/
|
|
170
|
-
function stopTokenRefresh() {
|
|
171
|
-
if (_refreshTimerId !== null) {
|
|
172
|
-
window.clearTimeout(_refreshTimerId);
|
|
173
|
-
_refreshTimerId = null;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Check if token needs refresh and refresh if necessary.
|
|
178
|
-
* Called periodically as a safety net.
|
|
179
|
-
*/
|
|
180
|
-
function checkAndRefreshIfNeeded() {
|
|
181
|
-
if (!authStore.token || !authStore.tokenExpires) return;
|
|
182
|
-
const refreshAt = authStore.tokenExpires.getTime() - TOKEN_REFRESH_MARGIN_MS;
|
|
183
|
-
if (Date.now() >= refreshAt) refreshToken();
|
|
184
|
-
}
|
|
185
|
-
async function initializeAuth() {
|
|
186
|
-
authStore.initialize();
|
|
187
|
-
await fetchAuthConfig();
|
|
188
|
-
if (authStore.token) {
|
|
189
|
-
if (await verifyToken()) {
|
|
190
|
-
await getCurrentUser();
|
|
191
|
-
scheduleTokenRefresh();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function logout() {
|
|
196
|
-
stopTokenRefresh();
|
|
197
|
-
authStore.logout();
|
|
198
|
-
}
|
|
199
|
-
function getAuthHeader() {
|
|
200
|
-
if (authStore.token) return { Authorization: `Bearer ${authStore.token}` };
|
|
201
|
-
return {};
|
|
202
|
-
}
|
|
203
|
-
async function updateProfile(data) {
|
|
204
|
-
if (!authStore.token) return {
|
|
205
|
-
success: false,
|
|
206
|
-
error: "Not authenticated"
|
|
207
|
-
};
|
|
208
|
-
try {
|
|
209
|
-
const requestData = {};
|
|
210
|
-
if (data.email !== void 0) requestData.email = data.email;
|
|
211
|
-
if (data.shortname !== void 0) requestData.shortname = data.shortname;
|
|
212
|
-
if (data.currentPassword) requestData.current_password = data.currentPassword;
|
|
213
|
-
if (data.newPassword) requestData.new_password = data.newPassword;
|
|
214
|
-
const response = await axios.put(`${getApiBaseUrl()}/users/me`, requestData, { headers: getAuthHeader() });
|
|
215
|
-
const userInfo = {
|
|
216
|
-
id: response.data.id,
|
|
217
|
-
username: response.data.username,
|
|
218
|
-
shortname: response.data.shortname,
|
|
219
|
-
email: response.data.email,
|
|
220
|
-
role: response.data.role,
|
|
221
|
-
roleObj: response.data.role_obj ?? null,
|
|
222
|
-
isActive: response.data.is_active
|
|
223
|
-
};
|
|
224
|
-
authStore.setUserInfo(userInfo);
|
|
225
|
-
return { success: true };
|
|
226
|
-
} catch (error) {
|
|
227
|
-
if (axios.isAxiosError(error) && error.response) return {
|
|
228
|
-
success: false,
|
|
229
|
-
error: error.response.data.detail || "Update failed"
|
|
230
|
-
};
|
|
231
|
-
return {
|
|
232
|
-
success: false,
|
|
233
|
-
error: "Network error. Please try again."
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
let checkInterval = null;
|
|
238
|
-
if (getCurrentInstance()) {
|
|
239
|
-
onMounted(() => {
|
|
240
|
-
_mountedConsumerCount += 1;
|
|
241
|
-
checkInterval = window.setInterval(checkAndRefreshIfNeeded, TOKEN_REFRESH_CHECK_INTERVAL_MS);
|
|
242
|
-
});
|
|
243
|
-
onUnmounted(() => {
|
|
244
|
-
if (checkInterval !== null) {
|
|
245
|
-
window.clearInterval(checkInterval);
|
|
246
|
-
checkInterval = null;
|
|
247
|
-
}
|
|
248
|
-
_mountedConsumerCount = Math.max(0, _mountedConsumerCount - 1);
|
|
249
|
-
if (_mountedConsumerCount === 0) stopTokenRefresh();
|
|
250
|
-
});
|
|
251
|
-
watch(() => authStore.tokenExpires, (newExpires) => {
|
|
252
|
-
if (newExpires) scheduleTokenRefresh();
|
|
253
|
-
else stopTokenRefresh();
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
login,
|
|
258
|
-
logout,
|
|
259
|
-
register,
|
|
260
|
-
verifyToken,
|
|
261
|
-
fetchAuthConfig,
|
|
262
|
-
initializeAuth,
|
|
263
|
-
getCurrentUser,
|
|
264
|
-
getAuthHeader,
|
|
265
|
-
updateProfile,
|
|
266
|
-
refreshToken,
|
|
267
|
-
isRefreshing
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
//#endregion
|
|
271
|
-
//#region src/composables/usePasskey.ts
|
|
272
|
-
async function loadWebAuthn() {
|
|
273
|
-
try {
|
|
274
|
-
return await import("@simplewebauthn/browser");
|
|
275
|
-
} catch {
|
|
276
|
-
throw new Error("@simplewebauthn/browser is required for passkey support. Install it: bun add @simplewebauthn/browser");
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
/** Registers and authenticates passkeys using WebAuthn, lazily loading the browser dependency. */
|
|
280
|
-
function usePasskey() {
|
|
281
|
-
const authStore = useAuthStore();
|
|
282
|
-
const settingsStore = useSettingsStore();
|
|
283
|
-
function getApiBaseUrl() {
|
|
284
|
-
return settingsStore.getApiBaseUrl();
|
|
285
|
-
}
|
|
286
|
-
async function isSupported() {
|
|
287
|
-
try {
|
|
288
|
-
const { browserSupportsWebAuthn } = await loadWebAuthn();
|
|
289
|
-
return browserSupportsWebAuthn();
|
|
290
|
-
} catch {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
async function registerPasskey(deviceName) {
|
|
295
|
-
const webauthn = await loadWebAuthn();
|
|
296
|
-
if (!webauthn.browserSupportsWebAuthn()) {
|
|
297
|
-
authStore.setError("WebAuthn is not supported in this browser");
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
authStore.setLoading(true);
|
|
301
|
-
authStore.setError(null);
|
|
302
|
-
try {
|
|
303
|
-
const optionsResponse = await axios.get(`${getApiBaseUrl()}/auth/passkey/register/options`, {
|
|
304
|
-
headers: { Authorization: `Bearer ${authStore.token}` },
|
|
305
|
-
withCredentials: true
|
|
306
|
-
});
|
|
307
|
-
const options = JSON.parse(optionsResponse.data.options);
|
|
308
|
-
const credential = await webauthn.startRegistration(options);
|
|
309
|
-
await axios.post(`${getApiBaseUrl()}/auth/passkey/register/verify`, {
|
|
310
|
-
credential: JSON.stringify(credential),
|
|
311
|
-
device_name: deviceName
|
|
312
|
-
}, {
|
|
313
|
-
headers: { Authorization: `Bearer ${authStore.token}` },
|
|
314
|
-
withCredentials: true
|
|
315
|
-
});
|
|
316
|
-
authStore.setAuthConfig({
|
|
317
|
-
...authStore.authConfig,
|
|
318
|
-
passkeyRegistered: true
|
|
319
|
-
});
|
|
320
|
-
return true;
|
|
321
|
-
} catch (error) {
|
|
322
|
-
if (axios.isAxiosError(error) && error.response) authStore.setError(error.response.data.detail || "Passkey registration failed");
|
|
323
|
-
else if (error instanceof Error) if (error.name === "NotAllowedError") authStore.setError("Registration was cancelled or timed out");
|
|
324
|
-
else if (error.name === "InvalidStateError") authStore.setError("This authenticator is already registered");
|
|
325
|
-
else authStore.setError(error.message);
|
|
326
|
-
else authStore.setError("Passkey registration failed");
|
|
327
|
-
return false;
|
|
328
|
-
} finally {
|
|
329
|
-
authStore.setLoading(false);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
async function loginWithPasskey() {
|
|
333
|
-
const webauthn = await loadWebAuthn();
|
|
334
|
-
if (!webauthn.browserSupportsWebAuthn()) {
|
|
335
|
-
authStore.setError("WebAuthn is not supported in this browser");
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
authStore.setLoading(true);
|
|
339
|
-
authStore.setError(null);
|
|
340
|
-
try {
|
|
341
|
-
const optionsResponse = await axios.get(`${getApiBaseUrl()}/auth/passkey/login/options`, { withCredentials: true });
|
|
342
|
-
const options = JSON.parse(optionsResponse.data.options);
|
|
343
|
-
const credential = await webauthn.startAuthentication(options);
|
|
344
|
-
const response = await axios.post(`${getApiBaseUrl()}/auth/passkey/login/verify`, { credential: JSON.stringify(credential) }, { withCredentials: true });
|
|
345
|
-
authStore.setToken(response.data.access_token, response.data.expires_in);
|
|
346
|
-
return true;
|
|
347
|
-
} catch (error) {
|
|
348
|
-
if (axios.isAxiosError(error) && error.response) if (error.response.status === 404) authStore.setError("No passkeys registered. Please login with password first.");
|
|
349
|
-
else authStore.setError(error.response.data.detail || "Passkey login failed");
|
|
350
|
-
else if (error instanceof Error) if (error.name === "NotAllowedError") authStore.setError("Authentication was cancelled or timed out");
|
|
351
|
-
else authStore.setError(error.message);
|
|
352
|
-
else authStore.setError("Passkey login failed");
|
|
353
|
-
return false;
|
|
354
|
-
} finally {
|
|
355
|
-
authStore.setLoading(false);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
async function listCredentials() {
|
|
359
|
-
try {
|
|
360
|
-
return (await axios.get(`${getApiBaseUrl()}/auth/passkey/credentials`, { headers: { Authorization: `Bearer ${authStore.token}` } })).data.credentials;
|
|
361
|
-
} catch {
|
|
362
|
-
return [];
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
async function deleteCredential(credentialId) {
|
|
366
|
-
try {
|
|
367
|
-
await axios.delete(`${getApiBaseUrl()}/auth/passkey/credentials/${encodeURIComponent(credentialId)}`, { headers: { Authorization: `Bearer ${authStore.token}` } });
|
|
368
|
-
if ((await listCredentials()).length === 0) authStore.setAuthConfig({
|
|
369
|
-
...authStore.authConfig,
|
|
370
|
-
passkeyRegistered: false
|
|
371
|
-
});
|
|
372
|
-
return true;
|
|
373
|
-
} catch {
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
async function deleteAllCredentials() {
|
|
378
|
-
try {
|
|
379
|
-
await axios.delete(`${getApiBaseUrl()}/auth/passkey/credentials`, { headers: { Authorization: `Bearer ${authStore.token}` } });
|
|
380
|
-
authStore.setAuthConfig({
|
|
381
|
-
...authStore.authConfig,
|
|
382
|
-
passkeyRegistered: false
|
|
383
|
-
});
|
|
384
|
-
return true;
|
|
385
|
-
} catch {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return {
|
|
390
|
-
isSupported,
|
|
391
|
-
registerPasskey,
|
|
392
|
-
loginWithPasskey,
|
|
393
|
-
listCredentials,
|
|
394
|
-
deleteCredential,
|
|
395
|
-
deleteAllCredentials
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
//#endregion
|
|
399
|
-
//#region src/composables/pluginFrontendBase.ts
|
|
400
|
-
function normalizePluginRoutePrefix(prefix) {
|
|
401
|
-
const candidate = prefix?.trim();
|
|
402
|
-
if (!candidate) return void 0;
|
|
403
|
-
const path = candidate.split("/").filter(Boolean).join("/");
|
|
404
|
-
return path ? `/${path}/` : "/";
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Resolve the Vue Router base for a plugin frontend.
|
|
408
|
-
*
|
|
409
|
-
* Integrated plugins use the platform-injected mount prefix. Standalone and
|
|
410
|
-
* development builds retain the caller's Vite base unchanged.
|
|
411
|
-
*/
|
|
412
|
-
function resolvePluginFrontendBase(fallbackBase = "/") {
|
|
413
|
-
const context = getInjectedPlatformContext();
|
|
414
|
-
if (!context?.isIntegrated) return fallbackBase;
|
|
415
|
-
return normalizePluginRoutePrefix(context.plugin?.route_prefix) ?? fallbackBase;
|
|
416
|
-
}
|
|
417
|
-
//#endregion
|
|
418
|
-
//#region src/composables/useAsync.ts
|
|
419
|
-
/**
|
|
420
|
-
* Default error transformer.
|
|
421
|
-
*/
|
|
422
|
-
function defaultTransformError(error) {
|
|
423
|
-
if (error instanceof Error) return {
|
|
424
|
-
message: error.message,
|
|
425
|
-
originalError: error
|
|
426
|
-
};
|
|
427
|
-
if (typeof error === "object" && error !== null) {
|
|
428
|
-
const errorObj = error;
|
|
429
|
-
if ("response" in errorObj && errorObj.response) {
|
|
430
|
-
const response = errorObj.response;
|
|
431
|
-
const data = response.data;
|
|
432
|
-
return {
|
|
433
|
-
message: data?.detail || data?.message || "Request failed",
|
|
434
|
-
code: String(response.status),
|
|
435
|
-
details: data,
|
|
436
|
-
originalError: error
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
message: errorObj.message || "Unknown error",
|
|
441
|
-
code: errorObj.code,
|
|
442
|
-
details: errorObj,
|
|
443
|
-
originalError: error
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
return {
|
|
447
|
-
message: String(error),
|
|
448
|
-
originalError: error
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Composable for managing async operation state.
|
|
453
|
-
*
|
|
454
|
-
* Provides standardized loading, error, and success state management
|
|
455
|
-
* for any async function.
|
|
456
|
-
*
|
|
457
|
-
* @param asyncFn - The async function to wrap
|
|
458
|
-
* @param options - Configuration options
|
|
459
|
-
*
|
|
460
|
-
* @example
|
|
461
|
-
* ```typescript
|
|
462
|
-
* // Basic usage
|
|
463
|
-
* const { data, isLoading, error, execute } = useAsync(
|
|
464
|
-
* async (id: string) => {
|
|
465
|
-
* const response = await api.get(`/users/${id}`)
|
|
466
|
-
* return response.data
|
|
467
|
-
* }
|
|
468
|
-
* )
|
|
469
|
-
*
|
|
470
|
-
* // Execute the function
|
|
471
|
-
* await execute('user-123')
|
|
472
|
-
*
|
|
473
|
-
* // In template
|
|
474
|
-
* <div v-if="isLoading">Loading...</div>
|
|
475
|
-
* <div v-else-if="error">{{ error.message }}</div>
|
|
476
|
-
* <div v-else-if="data">{{ data.name }}</div>
|
|
477
|
-
* ```
|
|
478
|
-
*
|
|
479
|
-
* @example
|
|
480
|
-
* ```typescript
|
|
481
|
-
* // With options
|
|
482
|
-
* const { data, execute } = useAsync(
|
|
483
|
-
* fetchUser,
|
|
484
|
-
* {
|
|
485
|
-
* immediate: true,
|
|
486
|
-
* immediateArgs: ['default-user'],
|
|
487
|
-
* onSuccess: (user) => console.log('Fetched:', user.name),
|
|
488
|
-
* onError: (error) => toast.error(error.message),
|
|
489
|
-
* }
|
|
490
|
-
* )
|
|
491
|
-
* ```
|
|
492
|
-
*
|
|
493
|
-
* @example
|
|
494
|
-
* ```typescript
|
|
495
|
-
* // Form submission
|
|
496
|
-
* const { isLoading, error, execute: submit } = useAsync(
|
|
497
|
-
* async (formData: FormData) => {
|
|
498
|
-
* await api.post('/submit', formData)
|
|
499
|
-
* },
|
|
500
|
-
* {
|
|
501
|
-
* onSuccess: () => {
|
|
502
|
-
* toast.success('Submitted!')
|
|
503
|
-
* router.push('/success')
|
|
504
|
-
* },
|
|
505
|
-
* }
|
|
506
|
-
* )
|
|
507
|
-
*
|
|
508
|
-
* const handleSubmit = () => submit(new FormData(formRef.value))
|
|
509
|
-
* ```
|
|
510
|
-
*/
|
|
511
|
-
/**
|
|
512
|
-
* Wraps an async function with reactive loading, error, and success state plus optional auto-execute.
|
|
513
|
-
*
|
|
514
|
-
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useRequestSyncState()` instead.
|
|
515
|
-
*/
|
|
516
|
-
function useAsync(asyncFn, options = {}) {
|
|
517
|
-
const { initialData = null, immediate = false, immediateArgs = [], transformError = defaultTransformError, onSuccess, onError, resetOnExecute = false } = options;
|
|
518
|
-
const data = ref(initialData);
|
|
519
|
-
const error = ref(null);
|
|
520
|
-
const state = ref("idle");
|
|
521
|
-
const isIdle = computed(() => state.value === "idle");
|
|
522
|
-
const isLoading = computed(() => state.value === "loading");
|
|
523
|
-
const isSuccess = computed(() => state.value === "success");
|
|
524
|
-
const isError = computed(() => state.value === "error");
|
|
525
|
-
async function execute(...args) {
|
|
526
|
-
state.value = "loading";
|
|
527
|
-
error.value = null;
|
|
528
|
-
if (resetOnExecute) data.value = null;
|
|
529
|
-
try {
|
|
530
|
-
const result = await asyncFn(...args);
|
|
531
|
-
data.value = result;
|
|
532
|
-
state.value = "success";
|
|
533
|
-
onSuccess?.(result);
|
|
534
|
-
return result;
|
|
535
|
-
} catch (e) {
|
|
536
|
-
const asyncError = transformError(e);
|
|
537
|
-
error.value = asyncError;
|
|
538
|
-
state.value = "error";
|
|
539
|
-
onError?.(asyncError);
|
|
540
|
-
return null;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
function reset() {
|
|
544
|
-
data.value = initialData;
|
|
545
|
-
error.value = null;
|
|
546
|
-
state.value = "idle";
|
|
547
|
-
}
|
|
548
|
-
function setData(newData) {
|
|
549
|
-
data.value = newData;
|
|
550
|
-
if (newData !== null) {
|
|
551
|
-
state.value = "success";
|
|
552
|
-
error.value = null;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
function setError(newError) {
|
|
556
|
-
error.value = newError;
|
|
557
|
-
if (newError !== null) state.value = "error";
|
|
558
|
-
}
|
|
559
|
-
if (immediate) execute(...immediateArgs);
|
|
560
|
-
return {
|
|
561
|
-
data,
|
|
562
|
-
error,
|
|
563
|
-
state,
|
|
564
|
-
isIdle,
|
|
565
|
-
isLoading,
|
|
566
|
-
isSuccess,
|
|
567
|
-
isError,
|
|
568
|
-
execute,
|
|
569
|
-
reset,
|
|
570
|
-
setData,
|
|
571
|
-
setError
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Create a batch of async operations that can be executed in parallel.
|
|
576
|
-
*
|
|
577
|
-
* @example
|
|
578
|
-
* ```typescript
|
|
579
|
-
* const { results, isLoading, execute } = useAsyncBatch([
|
|
580
|
-
* () => fetchUser(userId),
|
|
581
|
-
* () => fetchPosts(userId),
|
|
582
|
-
* () => fetchComments(userId),
|
|
583
|
-
* ])
|
|
584
|
-
*
|
|
585
|
-
* await execute()
|
|
586
|
-
* // results.value = [user, posts, comments]
|
|
587
|
-
* ```
|
|
588
|
-
*/
|
|
589
|
-
function useAsyncBatch(asyncFns) {
|
|
590
|
-
const results = ref(asyncFns.map(() => null));
|
|
591
|
-
const errors = ref(asyncFns.map(() => null));
|
|
592
|
-
const isLoading = ref(false);
|
|
593
|
-
async function execute() {
|
|
594
|
-
isLoading.value = true;
|
|
595
|
-
errors.value = asyncFns.map(() => null);
|
|
596
|
-
const promises = asyncFns.map(async (fn, index) => {
|
|
597
|
-
try {
|
|
598
|
-
const result = await fn();
|
|
599
|
-
results.value[index] = result;
|
|
600
|
-
} catch (e) {
|
|
601
|
-
errors.value[index] = defaultTransformError(e);
|
|
602
|
-
results.value[index] = null;
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
|
-
await Promise.all(promises);
|
|
606
|
-
isLoading.value = false;
|
|
607
|
-
}
|
|
608
|
-
function reset() {
|
|
609
|
-
results.value = asyncFns.map(() => null);
|
|
610
|
-
errors.value = asyncFns.map(() => null);
|
|
611
|
-
isLoading.value = false;
|
|
612
|
-
}
|
|
613
|
-
return {
|
|
614
|
-
results,
|
|
615
|
-
errors,
|
|
616
|
-
isLoading,
|
|
617
|
-
execute,
|
|
618
|
-
reset
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
//#endregion
|
|
622
|
-
//#region src/composables/useManualLayoutResize.ts
|
|
623
|
-
function clamp(value, min, max) {
|
|
624
|
-
return Math.min(max, Math.max(min, value));
|
|
625
|
-
}
|
|
626
|
-
function availablePanelWidth(containerWidth, constraints) {
|
|
627
|
-
return Math.min(constraints.maxWidth, containerWidth - (constraints.mainMinWidth ?? 0) - (constraints.layoutGap ?? 0));
|
|
628
|
-
}
|
|
629
|
-
function resizedLeadingPanelWidth(startWidth, deltaX, containerWidth, constraints) {
|
|
630
|
-
return Math.round(clamp(startWidth + deltaX, constraints.minWidth, Math.max(constraints.minWidth, availablePanelWidth(containerWidth, constraints))));
|
|
631
|
-
}
|
|
632
|
-
function resizedTrailingPanelWidth(startWidth, deltaX, containerWidth, constraints) {
|
|
633
|
-
return resizedLeadingPanelWidth(startWidth, -deltaX, containerWidth, constraints);
|
|
634
|
-
}
|
|
635
|
-
function resizedVerticalSplitPercent(startPercent, deltaY, containerHeight, constraints) {
|
|
636
|
-
if (containerHeight <= 0) return startPercent;
|
|
637
|
-
return Math.round(clamp(startPercent + deltaY / containerHeight * 100, constraints.minPercent, constraints.maxPercent));
|
|
638
|
-
}
|
|
639
|
-
/** Coordinates pointer-driven resizing for one or more layout splitters. */
|
|
640
|
-
function useManualLayoutResize(options) {
|
|
641
|
-
const activeResize = shallowRef(null);
|
|
642
|
-
const isResizing = computed(() => activeResize.value !== null);
|
|
643
|
-
let previousBodyCursor = "";
|
|
644
|
-
let previousBodyUserSelect = "";
|
|
645
|
-
function stopResize() {
|
|
646
|
-
if (!activeResize.value) return;
|
|
647
|
-
activeResize.value = null;
|
|
648
|
-
window.removeEventListener("pointermove", handlePointerMove);
|
|
649
|
-
window.removeEventListener("pointerup", stopResize);
|
|
650
|
-
window.removeEventListener("pointercancel", stopResize);
|
|
651
|
-
if (typeof document !== "undefined") {
|
|
652
|
-
document.body.style.cursor = previousBodyCursor;
|
|
653
|
-
document.body.style.userSelect = previousBodyUserSelect;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
function handlePointerMove(event) {
|
|
657
|
-
const state = activeResize.value;
|
|
658
|
-
if (!state) return;
|
|
659
|
-
options.resolveTarget(state.kind)?.onResize({
|
|
660
|
-
kind: state.kind,
|
|
661
|
-
startValue: state.startValue,
|
|
662
|
-
deltaX: event.clientX - state.startClientX,
|
|
663
|
-
deltaY: event.clientY - state.startClientY,
|
|
664
|
-
containerRect: state.containerRect,
|
|
665
|
-
secondaryRect: state.secondaryRect
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
function startResize(kind, event) {
|
|
669
|
-
if (!toValue(options.enabled) || event.button !== 0) return;
|
|
670
|
-
const target = options.resolveTarget(kind);
|
|
671
|
-
const containerRect = target?.getContainerElement()?.getBoundingClientRect();
|
|
672
|
-
if (!target || !containerRect) return;
|
|
673
|
-
event.preventDefault();
|
|
674
|
-
if (event.currentTarget instanceof HTMLElement) event.currentTarget.setPointerCapture(event.pointerId);
|
|
675
|
-
const secondary = target.getSecondaryElement?.() ?? null;
|
|
676
|
-
activeResize.value = {
|
|
677
|
-
kind,
|
|
678
|
-
startClientX: event.clientX,
|
|
679
|
-
startClientY: event.clientY,
|
|
680
|
-
startValue: target.getStartValue(),
|
|
681
|
-
containerRect,
|
|
682
|
-
secondaryRect: secondary?.getBoundingClientRect() ?? null,
|
|
683
|
-
cursor: target.cursor ?? "col-resize"
|
|
684
|
-
};
|
|
685
|
-
if (typeof document !== "undefined") {
|
|
686
|
-
previousBodyCursor = document.body.style.cursor;
|
|
687
|
-
previousBodyUserSelect = document.body.style.userSelect;
|
|
688
|
-
document.body.style.cursor = activeResize.value.cursor;
|
|
689
|
-
document.body.style.userSelect = "none";
|
|
690
|
-
}
|
|
691
|
-
window.addEventListener("pointermove", handlePointerMove);
|
|
692
|
-
window.addEventListener("pointerup", stopResize);
|
|
693
|
-
window.addEventListener("pointercancel", stopResize);
|
|
694
|
-
}
|
|
695
|
-
function isResizingKind(kind) {
|
|
696
|
-
return activeResize.value?.kind === kind;
|
|
697
|
-
}
|
|
698
|
-
watch(() => toValue(options.enabled), (enabled) => {
|
|
699
|
-
if (!enabled) stopResize();
|
|
700
|
-
});
|
|
701
|
-
onBeforeUnmount(stopResize);
|
|
702
|
-
return {
|
|
703
|
-
activeResize,
|
|
704
|
-
isResizing,
|
|
705
|
-
isResizingKind,
|
|
706
|
-
startResize,
|
|
707
|
-
stopResize
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
//#endregion
|
|
711
|
-
//#region src/composables/usePluginConfig.ts
|
|
712
|
-
var PLATFORM_OWNED_SETTINGS_KEY$1 = "allowed_experiment_types";
|
|
713
|
-
function cloneConfig(config) {
|
|
714
|
-
return JSON.parse(JSON.stringify(config));
|
|
715
|
-
}
|
|
716
|
-
function hasOwnConfig(config, key) {
|
|
717
|
-
return Object.prototype.hasOwnProperty.call(config, key);
|
|
718
|
-
}
|
|
719
|
-
function changedTopLevelKeys(current, saved) {
|
|
720
|
-
return new Set([...new Set([...Object.keys(current), ...Object.keys(saved)])].filter((key) => JSON.stringify(current[key]) !== JSON.stringify(saved[key])));
|
|
721
|
-
}
|
|
722
|
-
function topLevelConfigPatch(current, keys) {
|
|
723
|
-
return Object.fromEntries([...keys].map((key) => [key, hasOwnConfig(current, key) ? current[key] : null]));
|
|
724
|
-
}
|
|
725
|
-
function platformOwnedConfigPatch(config) {
|
|
726
|
-
return { [PLATFORM_OWNED_SETTINGS_KEY$1]: hasOwnConfig(config, PLATFORM_OWNED_SETTINGS_KEY$1) ? config[PLATFORM_OWNED_SETTINGS_KEY$1] : null };
|
|
727
|
-
}
|
|
728
|
-
function responseConfigPayload(response) {
|
|
729
|
-
return response.config ?? response.settings ?? {};
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `usePluginSettings()` for typed plugin settings.
|
|
733
|
-
*/
|
|
734
|
-
function usePluginConfig(pluginName) {
|
|
735
|
-
const injectedContext = getInjectedPlatformContext();
|
|
736
|
-
const { plugin } = usePlatformContext();
|
|
737
|
-
const platformApi = useApi();
|
|
738
|
-
const pluginApi = useApi({ baseUrl: injectedContext?.plugin?.api_prefix ?? plugin.value?.api_prefix });
|
|
739
|
-
const resolvedName = computed(() => pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? "");
|
|
740
|
-
const usesPluginSettingsApi = computed(() => {
|
|
741
|
-
const currentPlugin = injectedContext?.plugin ?? plugin.value;
|
|
742
|
-
return currentPlugin?.settings_api === "plugin" && resolvedName.value === currentPlugin.name;
|
|
743
|
-
});
|
|
744
|
-
const config = ref({});
|
|
745
|
-
const savedConfig = ref({});
|
|
746
|
-
const revision = ref(null);
|
|
747
|
-
const request = useRequestSyncState("Plugin config request failed.");
|
|
748
|
-
const isLoading = ref(false);
|
|
749
|
-
const isSaving = ref(false);
|
|
750
|
-
const error = request.error;
|
|
751
|
-
const lastLoadedAt = request.lastLoadedAt;
|
|
752
|
-
const lastSavedAt = request.lastSavedAt;
|
|
753
|
-
const isDirty = computed(() => {
|
|
754
|
-
return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value);
|
|
755
|
-
});
|
|
756
|
-
async function load() {
|
|
757
|
-
const name = resolvedName.value;
|
|
758
|
-
if (!name) return;
|
|
759
|
-
isLoading.value = true;
|
|
760
|
-
try {
|
|
761
|
-
const response = usesPluginSettingsApi.value ? await request.run(() => pluginApi.get("/settings"), {
|
|
762
|
-
success: "load",
|
|
763
|
-
errorMessage: "Failed to load plugin config"
|
|
764
|
-
}) : await request.run(() => platformApi.get(`/plugins/${encodeURIComponent(name)}/config`), {
|
|
765
|
-
success: "load",
|
|
766
|
-
errorMessage: "Failed to load plugin config"
|
|
767
|
-
});
|
|
768
|
-
const responseConfig = responseConfigPayload(response);
|
|
769
|
-
config.value = cloneConfig(responseConfig);
|
|
770
|
-
savedConfig.value = cloneConfig(responseConfig);
|
|
771
|
-
revision.value = response.revision ?? null;
|
|
772
|
-
} catch {} finally {
|
|
773
|
-
isLoading.value = false;
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
async function save() {
|
|
777
|
-
const name = resolvedName.value;
|
|
778
|
-
if (!name) return false;
|
|
779
|
-
isSaving.value = true;
|
|
780
|
-
try {
|
|
781
|
-
const payload = cloneConfig(config.value);
|
|
782
|
-
let response;
|
|
783
|
-
let nextRevision = revision.value;
|
|
784
|
-
if (usesPluginSettingsApi.value) {
|
|
785
|
-
response = await request.run(() => pluginApi.put("/settings", payload, revision.value ? { headers: { "If-Match": `"${revision.value}"` } } : void 0), {
|
|
786
|
-
success: "save",
|
|
787
|
-
errorMessage: "Failed to save plugin config"
|
|
788
|
-
});
|
|
789
|
-
nextRevision = response.revision ?? nextRevision;
|
|
790
|
-
} else {
|
|
791
|
-
const dirtyKeys = changedTopLevelKeys(payload, savedConfig.value);
|
|
792
|
-
const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY$1);
|
|
793
|
-
const typedKeys = [...dirtyKeys].filter((key) => key !== PLATFORM_OWNED_SETTINGS_KEY$1);
|
|
794
|
-
if (typedKeys.length) {
|
|
795
|
-
response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(name)}/config`, { config: topLevelConfigPatch(payload, typedKeys) }), {
|
|
796
|
-
success: "save",
|
|
797
|
-
errorMessage: "Failed to save plugin config"
|
|
798
|
-
});
|
|
799
|
-
nextRevision = response.revision ?? nextRevision;
|
|
800
|
-
}
|
|
801
|
-
if (platformOwnedChanged) {
|
|
802
|
-
response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(name)}/config`, { config: platformOwnedConfigPatch(payload) }), {
|
|
803
|
-
success: "save",
|
|
804
|
-
errorMessage: "Failed to save plugin config"
|
|
805
|
-
});
|
|
806
|
-
nextRevision = response.revision ?? nextRevision;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
if (!response) return true;
|
|
810
|
-
const responseConfig = responseConfigPayload(response);
|
|
811
|
-
config.value = cloneConfig(responseConfig);
|
|
812
|
-
savedConfig.value = cloneConfig(responseConfig);
|
|
813
|
-
revision.value = response.revision ?? nextRevision;
|
|
814
|
-
return true;
|
|
815
|
-
} catch {
|
|
816
|
-
return false;
|
|
817
|
-
} finally {
|
|
818
|
-
isSaving.value = false;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
function reset() {
|
|
822
|
-
config.value = cloneConfig(savedConfig.value);
|
|
823
|
-
request.clearError();
|
|
824
|
-
}
|
|
825
|
-
onMounted(() => {
|
|
826
|
-
load();
|
|
827
|
-
});
|
|
828
|
-
return {
|
|
829
|
-
config,
|
|
830
|
-
isLoading,
|
|
831
|
-
isSaving,
|
|
832
|
-
error,
|
|
833
|
-
lastLoadedAt,
|
|
834
|
-
lastSavedAt,
|
|
835
|
-
isDirty,
|
|
836
|
-
load,
|
|
837
|
-
save,
|
|
838
|
-
reset
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
//#endregion
|
|
842
|
-
//#region src/composables/useFileBrowser.ts
|
|
843
|
-
/**
|
|
844
|
-
* Drives FileBrowserModal against the platform's `/api/filesystem` routes.
|
|
845
|
-
*
|
|
846
|
-
* Requires the `filesystem.browse` permission and a deployment that declares
|
|
847
|
-
* mounts; with none configured the routes are absent and `mounts` stays empty.
|
|
848
|
-
*/
|
|
849
|
-
function useFileBrowser(options = {}) {
|
|
850
|
-
const injectedContext = getInjectedPlatformContext();
|
|
851
|
-
const api = useApi({ baseUrl: options.apiBaseUrl ?? injectedContext?.platformApiUrl });
|
|
852
|
-
const sync = useRequestSyncState("Could not read the server mounts.");
|
|
853
|
-
const mounts = ref([]);
|
|
854
|
-
const mountId = shallowRef("");
|
|
855
|
-
const path = shallowRef("");
|
|
856
|
-
const parent = ref(null);
|
|
857
|
-
const entries = ref([]);
|
|
858
|
-
const breadcrumbs = ref([]);
|
|
859
|
-
const totalCount = ref(null);
|
|
860
|
-
const matchCount = ref(null);
|
|
861
|
-
const shownCount = ref(null);
|
|
862
|
-
const truncated = ref(false);
|
|
863
|
-
const search = shallowRef("");
|
|
864
|
-
const sort = shallowRef("name");
|
|
865
|
-
const selected = ref([]);
|
|
866
|
-
const mountsLoading = shallowRef(false);
|
|
867
|
-
const listingLoading = shallowRef(false);
|
|
868
|
-
const listingReady = shallowRef(false);
|
|
869
|
-
const isLoading = computed(() => mountsLoading.value || listingLoading.value);
|
|
870
|
-
let listToken = 0;
|
|
871
|
-
let mountsToken = 0;
|
|
872
|
-
let disposed = false;
|
|
873
|
-
let listingController;
|
|
874
|
-
let mountsController;
|
|
875
|
-
let searchTimer;
|
|
876
|
-
function resetListing() {
|
|
877
|
-
listingReady.value = false;
|
|
878
|
-
entries.value = [];
|
|
879
|
-
breadcrumbs.value = [];
|
|
880
|
-
parent.value = null;
|
|
881
|
-
totalCount.value = null;
|
|
882
|
-
matchCount.value = null;
|
|
883
|
-
shownCount.value = null;
|
|
884
|
-
truncated.value = false;
|
|
885
|
-
}
|
|
886
|
-
function typeRuleParams() {
|
|
887
|
-
return (toValue(options.typeRules) ?? []).map((rule) => `&type_rules=${encodeURIComponent(rule)}`).join("");
|
|
888
|
-
}
|
|
889
|
-
/** Prefer the API's own `detail` over axios's "Request failed with status N". */
|
|
890
|
-
function describe(cause, fallback) {
|
|
891
|
-
const detail = cause?.response?.data?.detail;
|
|
892
|
-
return typeof detail === "string" && detail ? detail : sync.readErrorMessage(cause, fallback);
|
|
893
|
-
}
|
|
894
|
-
function fail(cause, fallback) {
|
|
895
|
-
sync.setError(new Error(describe(cause, fallback)), fallback);
|
|
896
|
-
}
|
|
897
|
-
function statusOf(cause) {
|
|
898
|
-
return cause?.response?.status;
|
|
899
|
-
}
|
|
900
|
-
async function loadMounts() {
|
|
901
|
-
const token = ++mountsToken;
|
|
902
|
-
mountsController?.abort();
|
|
903
|
-
mountsController = new AbortController();
|
|
904
|
-
mountsLoading.value = true;
|
|
905
|
-
try {
|
|
906
|
-
const data = await api.get("/filesystem/mounts", { signal: mountsController.signal });
|
|
907
|
-
if (disposed || token !== mountsToken) return false;
|
|
908
|
-
mounts.value = data.mounts ?? [];
|
|
909
|
-
sync.clearError();
|
|
910
|
-
sync.markLoaded();
|
|
911
|
-
return true;
|
|
912
|
-
} catch (cause) {
|
|
913
|
-
if (disposed || token !== mountsToken) return false;
|
|
914
|
-
if (statusOf(cause) === 404) {
|
|
915
|
-
mounts.value = [];
|
|
916
|
-
sync.clearError();
|
|
917
|
-
sync.markLoaded();
|
|
918
|
-
return true;
|
|
919
|
-
}
|
|
920
|
-
mounts.value = [];
|
|
921
|
-
fail(cause, "Could not read the server mounts.");
|
|
922
|
-
return false;
|
|
923
|
-
} finally {
|
|
924
|
-
if (token === mountsToken) mountsLoading.value = false;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
async function navigate(location, refresh = false) {
|
|
928
|
-
if (disposed || !location.mount_id) return;
|
|
929
|
-
clearTimeout(searchTimer);
|
|
930
|
-
const token = ++listToken;
|
|
931
|
-
listingController?.abort();
|
|
932
|
-
listingController = new AbortController();
|
|
933
|
-
listingLoading.value = true;
|
|
934
|
-
mountId.value = location.mount_id;
|
|
935
|
-
path.value = location.path;
|
|
936
|
-
resetListing();
|
|
937
|
-
sync.clearError();
|
|
938
|
-
try {
|
|
939
|
-
const query = `mount_id=${encodeURIComponent(location.mount_id)}&path=${encodeURIComponent(location.path)}&sort=${sort.value}` + (refresh ? "&refresh=true" : "") + (search.value ? `&search=${encodeURIComponent(search.value)}` : "") + typeRuleParams();
|
|
940
|
-
const listing = await api.get(`/filesystem/browse?${query}`, { signal: listingController.signal });
|
|
941
|
-
if (disposed || token !== listToken) return;
|
|
942
|
-
mountId.value = listing.mount_id;
|
|
943
|
-
path.value = listing.path;
|
|
944
|
-
parent.value = listing.parent ?? null;
|
|
945
|
-
entries.value = listing.entries ?? [];
|
|
946
|
-
breadcrumbs.value = listing.breadcrumbs ?? [];
|
|
947
|
-
totalCount.value = listing.total_count ?? null;
|
|
948
|
-
matchCount.value = listing.match_count ?? null;
|
|
949
|
-
shownCount.value = listing.shown_count ?? listing.entries?.length ?? null;
|
|
950
|
-
truncated.value = listing.truncated ?? false;
|
|
951
|
-
listingReady.value = true;
|
|
952
|
-
sync.clearError();
|
|
953
|
-
sync.markLoaded();
|
|
954
|
-
} catch (cause) {
|
|
955
|
-
if (disposed || token !== listToken) return;
|
|
956
|
-
entries.value = [];
|
|
957
|
-
fail(cause, "Could not read that folder.");
|
|
958
|
-
} finally {
|
|
959
|
-
if (token === listToken) listingLoading.value = false;
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
async function init() {
|
|
963
|
-
if (disposed) return;
|
|
964
|
-
const navigationToken = listToken;
|
|
965
|
-
if (!await loadMounts() || disposed || navigationToken !== listToken) return;
|
|
966
|
-
const preferred = toValue(options.initialMountId);
|
|
967
|
-
const reachable = (mount) => mount.available !== false;
|
|
968
|
-
const target = mounts.value.find((mount) => mount.id === preferred && reachable(mount)) ?? mounts.value.find(reachable);
|
|
969
|
-
if (target) await navigate({
|
|
970
|
-
mount_id: target.id,
|
|
971
|
-
path: ""
|
|
972
|
-
});
|
|
973
|
-
else {
|
|
974
|
-
mountId.value = "";
|
|
975
|
-
path.value = "";
|
|
976
|
-
resetListing();
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
async function refresh() {
|
|
980
|
-
if (disposed) return;
|
|
981
|
-
if (mountId.value) await navigate({
|
|
982
|
-
mount_id: mountId.value,
|
|
983
|
-
path: path.value
|
|
984
|
-
}, true);
|
|
985
|
-
else await init();
|
|
986
|
-
}
|
|
987
|
-
function selectCurrentFolder() {
|
|
988
|
-
if (!mountId.value || !listingReady.value || isLoading.value || sync.error.value) return;
|
|
989
|
-
const crumb = breadcrumbs.value[breadcrumbs.value.length - 1];
|
|
990
|
-
const key = JSON.stringify([mountId.value, path.value]);
|
|
991
|
-
if (selected.value.some((item) => JSON.stringify([item.mount_id, item.path]) === key)) return;
|
|
992
|
-
selected.value = [...selected.value, {
|
|
993
|
-
mount_id: mountId.value,
|
|
994
|
-
path: path.value,
|
|
995
|
-
name: crumb?.label ?? path.value,
|
|
996
|
-
kind: "directory",
|
|
997
|
-
child_count: totalCount.value
|
|
998
|
-
}];
|
|
999
|
-
}
|
|
1000
|
-
function clear() {
|
|
1001
|
-
selected.value = [];
|
|
1002
|
-
}
|
|
1003
|
-
watch(search, () => {
|
|
1004
|
-
if (!mountId.value) return;
|
|
1005
|
-
clearTimeout(searchTimer);
|
|
1006
|
-
searchTimer = setTimeout(() => {
|
|
1007
|
-
navigate({
|
|
1008
|
-
mount_id: mountId.value,
|
|
1009
|
-
path: path.value
|
|
1010
|
-
});
|
|
1011
|
-
}, 250);
|
|
1012
|
-
});
|
|
1013
|
-
watch(sort, () => {
|
|
1014
|
-
if (mountId.value) navigate({
|
|
1015
|
-
mount_id: mountId.value,
|
|
1016
|
-
path: path.value
|
|
1017
|
-
});
|
|
1018
|
-
});
|
|
1019
|
-
watch(() => toValue(options.typeRules), () => {
|
|
1020
|
-
if (mountId.value) navigate({
|
|
1021
|
-
mount_id: mountId.value,
|
|
1022
|
-
path: path.value
|
|
1023
|
-
});
|
|
1024
|
-
}, { deep: true });
|
|
1025
|
-
if (getCurrentScope()) onScopeDispose(() => {
|
|
1026
|
-
disposed = true;
|
|
1027
|
-
++listToken;
|
|
1028
|
-
++mountsToken;
|
|
1029
|
-
clearTimeout(searchTimer);
|
|
1030
|
-
listingController?.abort();
|
|
1031
|
-
mountsController?.abort();
|
|
1032
|
-
});
|
|
1033
|
-
return {
|
|
1034
|
-
mounts,
|
|
1035
|
-
mountId,
|
|
1036
|
-
path,
|
|
1037
|
-
parent,
|
|
1038
|
-
entries,
|
|
1039
|
-
breadcrumbs,
|
|
1040
|
-
totalCount,
|
|
1041
|
-
matchCount,
|
|
1042
|
-
shownCount,
|
|
1043
|
-
truncated,
|
|
1044
|
-
search,
|
|
1045
|
-
sort,
|
|
1046
|
-
selected,
|
|
1047
|
-
isLoading,
|
|
1048
|
-
error: sync.error,
|
|
1049
|
-
init,
|
|
1050
|
-
navigate,
|
|
1051
|
-
refresh,
|
|
1052
|
-
selectCurrentFolder,
|
|
1053
|
-
clear
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
//#endregion
|
|
1057
|
-
//#region src/composables/analysisArtifactTarget.ts
|
|
1058
|
-
var EXPERIMENT_ID = "experiment_id";
|
|
1059
|
-
var ARTIFACT_PLUGIN_ID = "artifact_plugin_id";
|
|
1060
|
-
var ARTIFACT_KEY = "artifact_key";
|
|
1061
|
-
var ARTIFACT_ID = "artifact_id";
|
|
1062
|
-
function parsePositiveInteger(value) {
|
|
1063
|
-
if (value === null || !/^\d+$/.test(value)) return void 0;
|
|
1064
|
-
const parsed = Number(value);
|
|
1065
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
1066
|
-
}
|
|
1067
|
-
/** Build the canonical query used to open one analysis artifact in a plugin. */
|
|
1068
|
-
function buildAnalysisArtifactTargetQuery(target) {
|
|
1069
|
-
const query = new URLSearchParams();
|
|
1070
|
-
query.set(EXPERIMENT_ID, String(target.experimentId));
|
|
1071
|
-
query.set(ARTIFACT_PLUGIN_ID, target.artifactPluginId);
|
|
1072
|
-
query.set(ARTIFACT_KEY, target.artifactKey);
|
|
1073
|
-
if (target.artifactId !== void 0) query.set(ARTIFACT_ID, String(target.artifactId));
|
|
1074
|
-
return query;
|
|
1075
|
-
}
|
|
1076
|
-
/** Parse a complete canonical target. Incomplete or invalid input is not guessed. */
|
|
1077
|
-
function parseAnalysisArtifactTarget(query) {
|
|
1078
|
-
const experimentId = parsePositiveInteger(query.get(EXPERIMENT_ID));
|
|
1079
|
-
const artifactPluginId = query.get(ARTIFACT_PLUGIN_ID)?.trim();
|
|
1080
|
-
const artifactKey = query.get(ARTIFACT_KEY)?.trim();
|
|
1081
|
-
const rawArtifactId = query.get(ARTIFACT_ID);
|
|
1082
|
-
const artifactId = parsePositiveInteger(rawArtifactId);
|
|
1083
|
-
if (experimentId === void 0 || !artifactPluginId || !artifactKey || rawArtifactId !== null && artifactId === void 0) return null;
|
|
1084
|
-
return {
|
|
1085
|
-
experimentId,
|
|
1086
|
-
artifactPluginId,
|
|
1087
|
-
artifactKey,
|
|
1088
|
-
...artifactId === void 0 ? {} : { artifactId }
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
//#endregion
|
|
1092
|
-
//#region src/composables/useCommandHistory.ts
|
|
1093
|
-
/**
|
|
1094
|
-
* Generic command-stack state for plugin editors with undo/redo controls.
|
|
1095
|
-
*
|
|
1096
|
-
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; keep command stacks plugin-local.
|
|
1097
|
-
*/
|
|
1098
|
-
function useCommandHistory(options = {}) {
|
|
1099
|
-
const maxDepth = Math.max(1, options.maxDepth ?? 50);
|
|
1100
|
-
const undoStack = ref([]);
|
|
1101
|
-
const redoStack = ref([]);
|
|
1102
|
-
const canUndo = computed(() => undoStack.value.length > 0);
|
|
1103
|
-
const canRedo = computed(() => redoStack.value.length > 0);
|
|
1104
|
-
const undoDescription = computed(() => undoStack.value[undoStack.value.length - 1]?.description ?? "");
|
|
1105
|
-
const redoDescription = computed(() => redoStack.value[redoStack.value.length - 1]?.description ?? "");
|
|
1106
|
-
function push(command) {
|
|
1107
|
-
undoStack.value = [...undoStack.value.slice(-(maxDepth - 1)), command];
|
|
1108
|
-
redoStack.value = [];
|
|
1109
|
-
}
|
|
1110
|
-
function execute(command, executeOptions = {}) {
|
|
1111
|
-
if (executeOptions.skipExecute) {
|
|
1112
|
-
push(command);
|
|
1113
|
-
return;
|
|
1114
|
-
}
|
|
1115
|
-
return afterMaybePromise(command.execute(), () => push(command));
|
|
1116
|
-
}
|
|
1117
|
-
function undo() {
|
|
1118
|
-
const command = undoStack.value[undoStack.value.length - 1];
|
|
1119
|
-
if (!command) return;
|
|
1120
|
-
return afterMaybePromise(command.undo(), () => {
|
|
1121
|
-
undoStack.value = undoStack.value.slice(0, -1);
|
|
1122
|
-
redoStack.value = [...redoStack.value, command];
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
function redo() {
|
|
1126
|
-
const command = redoStack.value[redoStack.value.length - 1];
|
|
1127
|
-
if (!command) return;
|
|
1128
|
-
return afterMaybePromise((command.redo ?? command.execute)(), () => {
|
|
1129
|
-
redoStack.value = redoStack.value.slice(0, -1);
|
|
1130
|
-
undoStack.value = [...undoStack.value.slice(-(maxDepth - 1)), command];
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
function clear() {
|
|
1134
|
-
undoStack.value = [];
|
|
1135
|
-
redoStack.value = [];
|
|
1136
|
-
}
|
|
1137
|
-
return {
|
|
1138
|
-
undoStack,
|
|
1139
|
-
redoStack,
|
|
1140
|
-
canUndo,
|
|
1141
|
-
canRedo,
|
|
1142
|
-
undoDescription,
|
|
1143
|
-
redoDescription,
|
|
1144
|
-
execute,
|
|
1145
|
-
push,
|
|
1146
|
-
undo,
|
|
1147
|
-
redo,
|
|
1148
|
-
clear
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
function afterMaybePromise(result, callback) {
|
|
1152
|
-
if (isPromiseLike(result)) return result.then(callback);
|
|
1153
|
-
callback();
|
|
1154
|
-
}
|
|
1155
|
-
function isPromiseLike(value) {
|
|
1156
|
-
return !!value && typeof value.then === "function";
|
|
1157
|
-
}
|
|
1158
|
-
//#endregion
|
|
1159
|
-
//#region src/composables/useOptimisticMutation.ts
|
|
1160
|
-
/**
|
|
1161
|
-
* Run async mutations with optional optimistic local updates and rollback.
|
|
1162
|
-
*
|
|
1163
|
-
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useRequestSyncState()` instead.
|
|
1164
|
-
*/
|
|
1165
|
-
function useOptimisticMutation(options) {
|
|
1166
|
-
const loading = shallowRef(false);
|
|
1167
|
-
const error = ref(null);
|
|
1168
|
-
const data = ref(null);
|
|
1169
|
-
const lastSnapshot = ref(void 0);
|
|
1170
|
-
const isIdle = computed(() => !loading.value && data.value === null && error.value === null);
|
|
1171
|
-
function clearError() {
|
|
1172
|
-
error.value = null;
|
|
1173
|
-
}
|
|
1174
|
-
function reset() {
|
|
1175
|
-
loading.value = false;
|
|
1176
|
-
error.value = null;
|
|
1177
|
-
data.value = null;
|
|
1178
|
-
lastSnapshot.value = void 0;
|
|
1179
|
-
}
|
|
1180
|
-
async function run(variables) {
|
|
1181
|
-
const snapshot = options.snapshot?.(variables);
|
|
1182
|
-
const context = {
|
|
1183
|
-
variables,
|
|
1184
|
-
snapshot
|
|
1185
|
-
};
|
|
1186
|
-
loading.value = true;
|
|
1187
|
-
error.value = null;
|
|
1188
|
-
lastSnapshot.value = snapshot;
|
|
1189
|
-
options.apply?.(variables, snapshot);
|
|
1190
|
-
try {
|
|
1191
|
-
const result = await options.mutation(variables);
|
|
1192
|
-
data.value = result;
|
|
1193
|
-
options.commit?.(result, context);
|
|
1194
|
-
options.onSuccess?.(result, context);
|
|
1195
|
-
return result;
|
|
1196
|
-
} catch (err) {
|
|
1197
|
-
options.rollback?.(snapshot, variables, err);
|
|
1198
|
-
error.value = options.readErrorMessage?.(err) ?? readErrorMessage(err);
|
|
1199
|
-
options.onError?.(err, context);
|
|
1200
|
-
throw err;
|
|
1201
|
-
} finally {
|
|
1202
|
-
loading.value = false;
|
|
1203
|
-
options.onSettled?.(context);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
return {
|
|
1207
|
-
loading,
|
|
1208
|
-
error,
|
|
1209
|
-
data,
|
|
1210
|
-
lastSnapshot,
|
|
1211
|
-
isIdle,
|
|
1212
|
-
run,
|
|
1213
|
-
clearError,
|
|
1214
|
-
reset
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
//#endregion
|
|
1218
|
-
//#region src/composables/useResourceCrud.ts
|
|
1219
|
-
/**
|
|
1220
|
-
* Common list/create/update/delete state wrapper for generated plugin clients.
|
|
1221
|
-
*
|
|
1222
|
-
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useGeneratedPluginClient()` instead.
|
|
1223
|
-
*/
|
|
1224
|
-
function useResourceCrud(options) {
|
|
1225
|
-
const items = ref([...options.initialItems ?? []]);
|
|
1226
|
-
const loading = shallowRef(false);
|
|
1227
|
-
const saving = shallowRef(false);
|
|
1228
|
-
const deleting = shallowRef(false);
|
|
1229
|
-
const error = ref(null);
|
|
1230
|
-
const isBusy = computed(() => loading.value || saving.value || deleting.value);
|
|
1231
|
-
const readMessage = options.readErrorMessage ?? readErrorMessage;
|
|
1232
|
-
const getKey = options.getKey ?? defaultGetKey;
|
|
1233
|
-
function clearError() {
|
|
1234
|
-
error.value = null;
|
|
1235
|
-
}
|
|
1236
|
-
function setError(err) {
|
|
1237
|
-
error.value = readMessage(err);
|
|
1238
|
-
}
|
|
1239
|
-
function setItems(nextItems) {
|
|
1240
|
-
items.value = [...nextItems];
|
|
1241
|
-
}
|
|
1242
|
-
function upsertLocal(item) {
|
|
1243
|
-
const key = getKey(item);
|
|
1244
|
-
const index = items.value.findIndex((existing) => getKey(existing) === key);
|
|
1245
|
-
if (index === -1) {
|
|
1246
|
-
items.value = [...items.value, item];
|
|
1247
|
-
return;
|
|
1248
|
-
}
|
|
1249
|
-
items.value = [
|
|
1250
|
-
...items.value.slice(0, index),
|
|
1251
|
-
item,
|
|
1252
|
-
...items.value.slice(index + 1)
|
|
1253
|
-
];
|
|
1254
|
-
}
|
|
1255
|
-
function removeLocal(keyOrItem) {
|
|
1256
|
-
const key = resolveKey(keyOrItem, getKey);
|
|
1257
|
-
items.value = items.value.filter((item) => getKey(item) !== key);
|
|
1258
|
-
}
|
|
1259
|
-
async function load() {
|
|
1260
|
-
loading.value = true;
|
|
1261
|
-
clearError();
|
|
1262
|
-
try {
|
|
1263
|
-
const result = await options.adapter.list();
|
|
1264
|
-
setItems(result);
|
|
1265
|
-
return result;
|
|
1266
|
-
} catch (err) {
|
|
1267
|
-
setError(err);
|
|
1268
|
-
throw err;
|
|
1269
|
-
} finally {
|
|
1270
|
-
loading.value = false;
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
async function createItem(payload) {
|
|
1274
|
-
if (!options.adapter.create) throw new Error("[MINT SDK] Resource adapter does not implement create().");
|
|
1275
|
-
saving.value = true;
|
|
1276
|
-
clearError();
|
|
1277
|
-
try {
|
|
1278
|
-
const result = await options.adapter.create(payload);
|
|
1279
|
-
upsertLocal(result);
|
|
1280
|
-
return result;
|
|
1281
|
-
} catch (err) {
|
|
1282
|
-
setError(err);
|
|
1283
|
-
throw err;
|
|
1284
|
-
} finally {
|
|
1285
|
-
saving.value = false;
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
async function updateItem(key, payload) {
|
|
1289
|
-
if (!options.adapter.update) throw new Error("[MINT SDK] Resource adapter does not implement update().");
|
|
1290
|
-
saving.value = true;
|
|
1291
|
-
clearError();
|
|
1292
|
-
try {
|
|
1293
|
-
const result = await options.adapter.update(key, payload);
|
|
1294
|
-
upsertLocal(result);
|
|
1295
|
-
return result;
|
|
1296
|
-
} catch (err) {
|
|
1297
|
-
setError(err);
|
|
1298
|
-
throw err;
|
|
1299
|
-
} finally {
|
|
1300
|
-
saving.value = false;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
async function deleteItem(keyOrItem) {
|
|
1304
|
-
if (!options.adapter.remove) throw new Error("[MINT SDK] Resource adapter does not implement remove().");
|
|
1305
|
-
const key = resolveKey(keyOrItem, getKey);
|
|
1306
|
-
deleting.value = true;
|
|
1307
|
-
clearError();
|
|
1308
|
-
try {
|
|
1309
|
-
await options.adapter.remove(key);
|
|
1310
|
-
removeLocal(key);
|
|
1311
|
-
} catch (err) {
|
|
1312
|
-
setError(err);
|
|
1313
|
-
throw err;
|
|
1314
|
-
} finally {
|
|
1315
|
-
deleting.value = false;
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
return {
|
|
1319
|
-
items,
|
|
1320
|
-
loading,
|
|
1321
|
-
saving,
|
|
1322
|
-
deleting,
|
|
1323
|
-
error,
|
|
1324
|
-
isBusy,
|
|
1325
|
-
load,
|
|
1326
|
-
createItem,
|
|
1327
|
-
updateItem,
|
|
1328
|
-
deleteItem,
|
|
1329
|
-
setItems,
|
|
1330
|
-
upsertLocal,
|
|
1331
|
-
removeLocal,
|
|
1332
|
-
clearError
|
|
1333
|
-
};
|
|
1334
|
-
}
|
|
1335
|
-
function createPluginResourceClient(options) {
|
|
1336
|
-
return {
|
|
1337
|
-
list: () => options.list(options.client),
|
|
1338
|
-
create: options.create ? (payload) => options.create(options.client, payload) : void 0,
|
|
1339
|
-
update: options.update ? (key, payload) => options.update(options.client, key, payload) : void 0,
|
|
1340
|
-
remove: options.remove ? (key) => options.remove(options.client, key) : void 0
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
|
-
function defaultGetKey(item) {
|
|
1344
|
-
if (item && typeof item === "object" && "id" in item) return item.id;
|
|
1345
|
-
throw new Error("[MINT SDK] Resource items need an id field or a getKey option.");
|
|
1346
|
-
}
|
|
1347
|
-
function resolveKey(keyOrItem, getKey) {
|
|
1348
|
-
if (typeof keyOrItem === "string" || typeof keyOrItem === "number") return keyOrItem;
|
|
1349
|
-
return getKey(keyOrItem);
|
|
1350
|
-
}
|
|
1351
|
-
//#endregion
|
|
1352
|
-
//#region src/composables/useWellPainting.ts
|
|
1353
|
-
/**
|
|
1354
|
-
* Pointer painting/rectangle selection state for WellPlate-based editors.
|
|
1355
|
-
*
|
|
1356
|
-
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useWellPlateEditor()` instead.
|
|
1357
|
-
*/
|
|
1358
|
-
function useWellPainting(options) {
|
|
1359
|
-
const activeMode = ref(options.initialMode ?? "select");
|
|
1360
|
-
const activePayload = ref(options.initialPayload);
|
|
1361
|
-
const isDragging = ref(false);
|
|
1362
|
-
const dragStart = ref(null);
|
|
1363
|
-
const dragEnd = ref(null);
|
|
1364
|
-
const paintTrail = ref(/* @__PURE__ */ new Set());
|
|
1365
|
-
const previewWellIds = computed(() => {
|
|
1366
|
-
if (!isDragging.value) return [];
|
|
1367
|
-
if (activeMode.value === "select") {
|
|
1368
|
-
if (!dragStart.value || !dragEnd.value) return [];
|
|
1369
|
-
return wellIdsInRectangle(dragStart.value, dragEnd.value, toValue(options.format));
|
|
1370
|
-
}
|
|
1371
|
-
return [...paintTrail.value];
|
|
1372
|
-
});
|
|
1373
|
-
function setMode(mode, payload) {
|
|
1374
|
-
activeMode.value = mode;
|
|
1375
|
-
activePayload.value = payload;
|
|
1376
|
-
}
|
|
1377
|
-
function setPayload(payload) {
|
|
1378
|
-
activePayload.value = payload;
|
|
1379
|
-
}
|
|
1380
|
-
function onWellMouseDown(wellId) {
|
|
1381
|
-
const coord = wellIdToCoordinate(wellId);
|
|
1382
|
-
if (!coord) return;
|
|
1383
|
-
isDragging.value = true;
|
|
1384
|
-
dragStart.value = coord;
|
|
1385
|
-
dragEnd.value = coord;
|
|
1386
|
-
paintTrail.value = new Set([wellId]);
|
|
1387
|
-
}
|
|
1388
|
-
function onWellMouseMove(wellId) {
|
|
1389
|
-
if (!isDragging.value) return;
|
|
1390
|
-
const coord = wellIdToCoordinate(wellId);
|
|
1391
|
-
if (!coord) return;
|
|
1392
|
-
dragEnd.value = coord;
|
|
1393
|
-
if (activeMode.value !== "select") paintTrail.value = new Set([...paintTrail.value, wellId]);
|
|
1394
|
-
}
|
|
1395
|
-
function onWellMouseUp() {
|
|
1396
|
-
if (!isDragging.value) return;
|
|
1397
|
-
const wellIds = previewWellIds.value;
|
|
1398
|
-
isDragging.value = false;
|
|
1399
|
-
if (wellIds.length > 0) options.onComplete?.({
|
|
1400
|
-
wellIds,
|
|
1401
|
-
mode: activeMode.value,
|
|
1402
|
-
payload: activePayload.value
|
|
1403
|
-
});
|
|
1404
|
-
clearDragState();
|
|
1405
|
-
}
|
|
1406
|
-
function cancel() {
|
|
1407
|
-
isDragging.value = false;
|
|
1408
|
-
clearDragState();
|
|
1409
|
-
}
|
|
1410
|
-
function clearDragState() {
|
|
1411
|
-
dragStart.value = null;
|
|
1412
|
-
dragEnd.value = null;
|
|
1413
|
-
paintTrail.value = /* @__PURE__ */ new Set();
|
|
1414
|
-
}
|
|
1415
|
-
return {
|
|
1416
|
-
activeMode,
|
|
1417
|
-
activePayload,
|
|
1418
|
-
isDragging,
|
|
1419
|
-
previewWellIds,
|
|
1420
|
-
setMode,
|
|
1421
|
-
setPayload,
|
|
1422
|
-
onWellMouseDown,
|
|
1423
|
-
onWellMouseMove,
|
|
1424
|
-
onWellMouseUp,
|
|
1425
|
-
cancel
|
|
1426
|
-
};
|
|
1427
|
-
}
|
|
1428
|
-
function wellIdToCoordinate(wellId) {
|
|
1429
|
-
const match = /^([A-Z]+)(\d+)$/i.exec(wellId.trim());
|
|
1430
|
-
if (!match) return null;
|
|
1431
|
-
return {
|
|
1432
|
-
row: rowIndex(match[1].toUpperCase()),
|
|
1433
|
-
col: Number(match[2]) - 1
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
function coordinateToWellId(coord) {
|
|
1437
|
-
return `${rowLabel(coord.row)}${coord.col + 1}`;
|
|
1438
|
-
}
|
|
1439
|
-
function wellIdsInRectangle(start, end, format) {
|
|
1440
|
-
const dimensions = PLATE_CONFIGS[format];
|
|
1441
|
-
const minRow = Math.max(0, Math.min(start.row, end.row));
|
|
1442
|
-
const maxRow = Math.min(dimensions.rows - 1, Math.max(start.row, end.row));
|
|
1443
|
-
const minCol = Math.max(0, Math.min(start.col, end.col));
|
|
1444
|
-
const maxCol = Math.min(dimensions.cols - 1, Math.max(start.col, end.col));
|
|
1445
|
-
const ids = [];
|
|
1446
|
-
for (let row = minRow; row <= maxRow; row++) for (let col = minCol; col <= maxCol; col++) ids.push(coordinateToWellId({
|
|
1447
|
-
row,
|
|
1448
|
-
col
|
|
1449
|
-
}));
|
|
1450
|
-
return ids;
|
|
1451
|
-
}
|
|
1452
|
-
//#endregion
|
|
1453
|
-
//#region src/composables/useWellPlateAdapter.ts
|
|
1454
|
-
/**
|
|
1455
|
-
* Convert domain rows into WellPlate's wells prop without hand-written adapters.
|
|
1456
|
-
*
|
|
1457
|
-
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useWellPlateEditor()` instead.
|
|
1458
|
-
*/
|
|
1459
|
-
function useWellPlateAdapter(options) {
|
|
1460
|
-
return { wells: computed(() => createWellPlateWells(toValue(options.entries), options)) };
|
|
1461
|
-
}
|
|
1462
|
-
function createWellPlateWells(entries, options) {
|
|
1463
|
-
return Object.fromEntries(entries.map((entry) => {
|
|
1464
|
-
const wellId = options.getWellId(entry);
|
|
1465
|
-
const coord = parseWellId(wellId);
|
|
1466
|
-
return [wellId, {
|
|
1467
|
-
id: wellId,
|
|
1468
|
-
row: coord.row,
|
|
1469
|
-
col: coord.col,
|
|
1470
|
-
state: options.getState?.(entry) ?? "filled",
|
|
1471
|
-
sampleType: options.getSampleType?.(entry),
|
|
1472
|
-
value: options.getValue?.(entry),
|
|
1473
|
-
metadata: options.getMetadata?.(entry)
|
|
1474
|
-
}];
|
|
1475
|
-
}));
|
|
1476
|
-
}
|
|
1477
|
-
function createColumnConditions(entries) {
|
|
1478
|
-
return entries.map((entry) => {
|
|
1479
|
-
const startCol = typeof entry.start === "number" ? entry.start : Number.parseInt(entry.start, 10);
|
|
1480
|
-
const cols = [];
|
|
1481
|
-
const concentrations = [];
|
|
1482
|
-
for (const concentration of entry.concentrations) {
|
|
1483
|
-
const replicates = concentration.replicates ?? 1;
|
|
1484
|
-
for (let index = 0; index < replicates; index++) {
|
|
1485
|
-
cols.push(startCol + cols.length);
|
|
1486
|
-
concentrations.push(concentration.value);
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
return {
|
|
1490
|
-
label: entry.label,
|
|
1491
|
-
color: entry.color,
|
|
1492
|
-
unit: entry.unit,
|
|
1493
|
-
cols,
|
|
1494
|
-
concentrations
|
|
1495
|
-
};
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
function createRowConditions(entries) {
|
|
1499
|
-
return entries.map((entry) => {
|
|
1500
|
-
const startRow = typeof entry.start === "number" ? entry.start : rowLabelToIndex(entry.start);
|
|
1501
|
-
const rows = [];
|
|
1502
|
-
const concentrations = [];
|
|
1503
|
-
for (const concentration of entry.concentrations) {
|
|
1504
|
-
const replicates = concentration.replicates ?? 1;
|
|
1505
|
-
for (let index = 0; index < replicates; index++) {
|
|
1506
|
-
rows.push(indexToRowLabel(startRow + rows.length));
|
|
1507
|
-
concentrations.push(concentration.value);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
return {
|
|
1511
|
-
label: entry.label,
|
|
1512
|
-
color: entry.color,
|
|
1513
|
-
unit: entry.unit,
|
|
1514
|
-
rows,
|
|
1515
|
-
concentrations
|
|
1516
|
-
};
|
|
1517
|
-
});
|
|
1518
|
-
}
|
|
1519
|
-
function parseWellId(wellId) {
|
|
1520
|
-
const match = /^([A-Z]+)(\d+)$/i.exec(wellId.trim());
|
|
1521
|
-
if (!match) throw new Error(`[MINT SDK] Invalid well id "${wellId}".`);
|
|
1522
|
-
return {
|
|
1523
|
-
row: rowLabelToIndex(match[1].toUpperCase()),
|
|
1524
|
-
col: Number(match[2]) - 1
|
|
1525
|
-
};
|
|
1526
|
-
}
|
|
1527
|
-
function rowLabelToIndex(label) {
|
|
1528
|
-
let result = 0;
|
|
1529
|
-
for (const char of label.toUpperCase()) result = result * 26 + (char.charCodeAt(0) - 64);
|
|
1530
|
-
return result - 1;
|
|
1531
|
-
}
|
|
1532
|
-
function indexToRowLabel(index) {
|
|
1533
|
-
let value = index + 1;
|
|
1534
|
-
let label = "";
|
|
1535
|
-
while (value > 0) {
|
|
1536
|
-
value--;
|
|
1537
|
-
label = String.fromCharCode(65 + value % 26) + label;
|
|
1538
|
-
value = Math.floor(value / 26);
|
|
1539
|
-
}
|
|
1540
|
-
return label;
|
|
1541
|
-
}
|
|
1542
|
-
//#endregion
|
|
1543
|
-
//#region src/composables/useWellPlateValidation.ts
|
|
1544
|
-
/**
|
|
1545
|
-
* Rule-driven validation state for WellPlate layout/design screens.
|
|
1546
|
-
*
|
|
1547
|
-
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `useWellPlateEditor()` instead.
|
|
1548
|
-
*/
|
|
1549
|
-
function useWellPlateValidation(options) {
|
|
1550
|
-
const warnings = computed(() => runWellPlateValidation(toValue(options.context), toValue(options.rules)));
|
|
1551
|
-
return {
|
|
1552
|
-
warnings,
|
|
1553
|
-
hasBlockingIssues: computed(() => warnings.value.some((warning) => warning.severity === "error")),
|
|
1554
|
-
warningCount: computed(() => warnings.value.length),
|
|
1555
|
-
errorCount: computed(() => warnings.value.filter((warning) => warning.severity === "error").length)
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
|
-
function runWellPlateValidation(context, rules) {
|
|
1559
|
-
const warnings = [];
|
|
1560
|
-
for (const rule of rules) {
|
|
1561
|
-
const result = rule.validate(context);
|
|
1562
|
-
if (!result) continue;
|
|
1563
|
-
warnings.push(normalizeWarning(rule, result));
|
|
1564
|
-
}
|
|
1565
|
-
return warnings;
|
|
1566
|
-
}
|
|
1567
|
-
function normalizeWarning(rule, result) {
|
|
1568
|
-
if (typeof result === "string") return {
|
|
1569
|
-
type: rule.type,
|
|
1570
|
-
severity: rule.severity,
|
|
1571
|
-
message: result
|
|
1572
|
-
};
|
|
1573
|
-
return {
|
|
1574
|
-
type: rule.type,
|
|
1575
|
-
severity: rule.severity,
|
|
1576
|
-
...result
|
|
1577
|
-
};
|
|
1578
|
-
}
|
|
1579
|
-
//#endregion
|
|
1580
|
-
//#region src/composables/pluginEndpointBuilder.ts
|
|
1581
|
-
function normalizeApiBaseUrl(baseUrl) {
|
|
1582
|
-
if (!baseUrl) return void 0;
|
|
1583
|
-
return baseUrl.length > 1 ? baseUrl.replace(/\/+$/, "") : baseUrl;
|
|
1584
|
-
}
|
|
1585
|
-
function normalizeApiPrefix(prefix) {
|
|
1586
|
-
if (!prefix) return void 0;
|
|
1587
|
-
if (prefix.startsWith("/api/")) return normalizeApiBaseUrl(prefix);
|
|
1588
|
-
if (prefix === "/api") return prefix;
|
|
1589
|
-
if (prefix.startsWith("/")) return normalizeApiBaseUrl(`/api${prefix}`);
|
|
1590
|
-
return normalizeApiBaseUrl(`/api/${prefix}`);
|
|
1591
|
-
}
|
|
1592
|
-
function endpointParamName(param) {
|
|
1593
|
-
return typeof param === "string" ? param : param.name;
|
|
1594
|
-
}
|
|
1595
|
-
function endpointParamFieldName(param) {
|
|
1596
|
-
return typeof param === "string" ? param : param.fieldName ?? param.name;
|
|
1597
|
-
}
|
|
1598
|
-
function escapeRegExp(value) {
|
|
1599
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1600
|
-
}
|
|
1601
|
-
function replacePathParam(path, name, encoded) {
|
|
1602
|
-
const escapedName = escapeRegExp(name);
|
|
1603
|
-
return path.replace(new RegExp(`\\{${escapedName}(?::[^{}]+)?\\}`, "g"), () => encoded).replace(new RegExp(`:${escapedName}(?=/|$)`, "g"), () => encoded);
|
|
1604
|
-
}
|
|
1605
|
-
/** Resolve the runtime plugin API base URL from env, explicit options, platform injection, or contract metadata. */
|
|
1606
|
-
function resolvePluginBaseUrl(contract, explicitBaseUrl) {
|
|
1607
|
-
if (explicitBaseUrl) return normalizeApiBaseUrl(explicitBaseUrl) ?? explicitBaseUrl;
|
|
1608
|
-
const injected = getInjectedPlatformContext();
|
|
1609
|
-
const platformPrefix = normalizeApiBaseUrl(injected?.plugin?.api_prefix) || normalizeApiPrefix(injected?.plugin?.route_prefix);
|
|
1610
|
-
if (platformPrefix) return platformPrefix;
|
|
1611
|
-
return normalizeApiBaseUrl(contract.plugin.apiPrefix) || "/api";
|
|
1612
|
-
}
|
|
1613
|
-
function encodePath(path, payload, pathParams) {
|
|
1614
|
-
let nextPath = path;
|
|
1615
|
-
for (const param of pathParams) {
|
|
1616
|
-
const name = endpointParamName(param);
|
|
1617
|
-
const fieldName = endpointParamFieldName(param);
|
|
1618
|
-
const value = payload[fieldName] ?? payload[name];
|
|
1619
|
-
if (value === void 0 || value === null) throw new Error(`[MINT SDK] Missing path parameter '${fieldName}' for plugin endpoint ${path}`);
|
|
1620
|
-
const encoded = encodeURIComponent(String(value));
|
|
1621
|
-
nextPath = replacePathParam(nextPath, name, encoded);
|
|
1622
|
-
if (fieldName !== name) nextPath = replacePathParam(nextPath, fieldName, encoded);
|
|
1623
|
-
}
|
|
1624
|
-
return nextPath;
|
|
1625
|
-
}
|
|
1626
|
-
function withDefaultPathParams(payload, pathParams) {
|
|
1627
|
-
if (!pathParams.length) return payload;
|
|
1628
|
-
const nextPayload = { ...payload };
|
|
1629
|
-
for (const param of pathParams) {
|
|
1630
|
-
const name = endpointParamName(param);
|
|
1631
|
-
const fieldName = endpointParamFieldName(param);
|
|
1632
|
-
if (nextPayload[fieldName] !== void 0 && nextPayload[fieldName] !== null || nextPayload[name] !== void 0 && nextPayload[name] !== null) continue;
|
|
1633
|
-
if (isExperimentPathParam(fieldName) || isExperimentPathParam(name)) {
|
|
1634
|
-
const experimentId = resolveCurrentExperimentId();
|
|
1635
|
-
if (experimentId !== void 0) nextPayload[fieldName] = experimentId;
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
return nextPayload;
|
|
1639
|
-
}
|
|
1640
|
-
function isExperimentPathParam(param) {
|
|
1641
|
-
return param === "experimentId" || param === "experiment_id";
|
|
1642
|
-
}
|
|
1643
|
-
function withoutPathParams(payload, pathParams) {
|
|
1644
|
-
const copy = { ...payload };
|
|
1645
|
-
for (const param of pathParams) {
|
|
1646
|
-
delete copy[endpointParamFieldName(param)];
|
|
1647
|
-
delete copy[endpointParamName(param)];
|
|
1648
|
-
}
|
|
1649
|
-
return copy;
|
|
1650
|
-
}
|
|
1651
|
-
function queryParamsFromPayload(payload, pathParams, queryParams, endpointPath, includeUnspecifiedParams = false) {
|
|
1652
|
-
if (!queryParams?.length) return includeUnspecifiedParams ? withoutPathParams(payload, pathParams) : {};
|
|
1653
|
-
const query = {};
|
|
1654
|
-
for (const param of queryParams) {
|
|
1655
|
-
const name = typeof param === "string" ? param : param.name;
|
|
1656
|
-
const fieldName = typeof param === "string" ? param : param.fieldName ?? param.name;
|
|
1657
|
-
const required = typeof param !== "string" && param.required === true;
|
|
1658
|
-
const value = payload[fieldName];
|
|
1659
|
-
if (value === void 0 || value === null) {
|
|
1660
|
-
if (required) throw new Error(`[MINT SDK] Missing query parameter '${fieldName}' for plugin endpoint ${endpointPath ?? ""}`);
|
|
1661
|
-
continue;
|
|
1662
|
-
}
|
|
1663
|
-
query[name] = value;
|
|
1664
|
-
}
|
|
1665
|
-
return query;
|
|
1666
|
-
}
|
|
1667
|
-
function hasKeys(value) {
|
|
1668
|
-
return Object.keys(value).length > 0;
|
|
1669
|
-
}
|
|
1670
|
-
function isPlainPayloadObject(value) {
|
|
1671
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1672
|
-
}
|
|
1673
|
-
function hasOwn(value, key) {
|
|
1674
|
-
return Object.prototype.hasOwnProperty.call(value, key);
|
|
1675
|
-
}
|
|
1676
|
-
function normalizeStructuredEndpointPayload(payload) {
|
|
1677
|
-
const pathParamPayload = payload.pathParams;
|
|
1678
|
-
const queryPayload = payload.query;
|
|
1679
|
-
const hasStructuredPathParams = hasOwn(payload, "pathParams") && isPlainPayloadObject(pathParamPayload);
|
|
1680
|
-
const hasStructuredQuery = hasOwn(payload, "query") && isPlainPayloadObject(queryPayload);
|
|
1681
|
-
if (!hasStructuredPathParams && !hasStructuredQuery) return payload;
|
|
1682
|
-
const nextPayload = { ...payload };
|
|
1683
|
-
delete nextPayload.pathParams;
|
|
1684
|
-
delete nextPayload.query;
|
|
1685
|
-
if (hasStructuredPathParams) Object.assign(nextPayload, pathParamPayload);
|
|
1686
|
-
if (hasStructuredQuery) Object.assign(nextPayload, queryPayload);
|
|
1687
|
-
return nextPayload;
|
|
1688
|
-
}
|
|
1689
|
-
function requestBodyFromPayload(payload, requestPayload, endpoint) {
|
|
1690
|
-
if (!endpoint.hasBody) return void 0;
|
|
1691
|
-
const body = endpoint.pathParams?.length || endpoint.queryParams?.length ? requestPayload.body : payload;
|
|
1692
|
-
if (body === void 0 || body === null) throw new Error(`[MINT SDK] Missing request body for plugin endpoint ${endpoint.path}`);
|
|
1693
|
-
return body;
|
|
1694
|
-
}
|
|
1695
|
-
function pluginEndpointRequestParts(contract, endpoint, payload, explicitBaseUrl) {
|
|
1696
|
-
const payloadObject = isPlainPayloadObject(payload) ? normalizeStructuredEndpointPayload(payload) : {};
|
|
1697
|
-
const pathParams = endpoint.pathParams ?? [];
|
|
1698
|
-
const requestPayload = withDefaultPathParams(payloadObject, pathParams);
|
|
1699
|
-
const path = encodePath(endpoint.path, requestPayload, pathParams);
|
|
1700
|
-
const query = queryParamsFromPayload(requestPayload, pathParams, endpoint.queryParams, endpoint.path, endpoint.method === "get");
|
|
1701
|
-
return {
|
|
1702
|
-
baseUrl: resolvePluginBaseUrl(contract, explicitBaseUrl),
|
|
1703
|
-
path,
|
|
1704
|
-
query,
|
|
1705
|
-
requestPayload
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
function appendQueryValue(params, key, value) {
|
|
1709
|
-
if (value === void 0 || value === null) return;
|
|
1710
|
-
if (Array.isArray(value)) {
|
|
1711
|
-
for (const item of value) appendQueryValue(params, key, item);
|
|
1712
|
-
return;
|
|
1713
|
-
}
|
|
1714
|
-
if (typeof value === "object") {
|
|
1715
|
-
params.append(key, JSON.stringify(value));
|
|
1716
|
-
return;
|
|
1717
|
-
}
|
|
1718
|
-
params.append(key, String(value));
|
|
1719
|
-
}
|
|
1720
|
-
function queryString(query) {
|
|
1721
|
-
const params = new URLSearchParams();
|
|
1722
|
-
for (const [key, value] of Object.entries(query)) appendQueryValue(params, key, value);
|
|
1723
|
-
const serialized = params.toString();
|
|
1724
|
-
return serialized ? `?${serialized}` : "";
|
|
1725
|
-
}
|
|
1726
|
-
function joinBaseUrl(baseUrl, path) {
|
|
1727
|
-
if (!baseUrl) return path;
|
|
1728
|
-
return `${baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
1729
|
-
}
|
|
1730
|
-
/** Build the concrete URL for a generated plugin endpoint without making a request. */
|
|
1731
|
-
function buildPluginEndpointUrl(contract, endpoint, payload, options = {}) {
|
|
1732
|
-
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
|
|
1733
|
-
const pathWithQuery = `${parts.path}${queryString(parts.query)}`;
|
|
1734
|
-
if (options.includeBaseUrl === false) return pathWithQuery;
|
|
1735
|
-
return joinBaseUrl(parts.baseUrl, pathWithQuery);
|
|
1736
|
-
}
|
|
1737
|
-
//#endregion
|
|
1738
|
-
//#region src/composables/usePluginClient.ts
|
|
1739
|
-
function queryConfig(query) {
|
|
1740
|
-
return hasKeys(query) ? { params: query } : void 0;
|
|
1741
|
-
}
|
|
1742
|
-
var OCTET_STREAM = "application/octet-stream";
|
|
1743
|
-
var MULTIPART_FORM_DATA = "multipart/form-data";
|
|
1744
|
-
function endpointRequestConfig(endpoint, query, options) {
|
|
1745
|
-
const config = {};
|
|
1746
|
-
if (hasKeys(query)) config.params = query;
|
|
1747
|
-
if (endpoint.requestContentType === OCTET_STREAM) config.headers = { "Content-Type": OCTET_STREAM };
|
|
1748
|
-
if (endpoint.requestContentType === MULTIPART_FORM_DATA) config.headers = { "Content-Type": void 0 };
|
|
1749
|
-
if (endpoint.responseContentType === OCTET_STREAM) config.responseType = "blob";
|
|
1750
|
-
if (options?.signal) config.signal = options.signal;
|
|
1751
|
-
return hasKeys(config) ? config : void 0;
|
|
1752
|
-
}
|
|
1753
|
-
function isFormData(value) {
|
|
1754
|
-
return typeof FormData !== "undefined" && value instanceof FormData;
|
|
1755
|
-
}
|
|
1756
|
-
function endpointRequestBody(endpoint, body) {
|
|
1757
|
-
return endpoint.requestContentType === MULTIPART_FORM_DATA ? pluginFormDataFromPayload(body) : body;
|
|
1758
|
-
}
|
|
1759
|
-
function endpointHasPayload(endpoint) {
|
|
1760
|
-
return Boolean(endpoint.hasBody || endpoint.pathParams?.length || endpoint.queryParams?.length);
|
|
1761
|
-
}
|
|
1762
|
-
function isBlob(value) {
|
|
1763
|
-
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
1764
|
-
}
|
|
1765
|
-
function appendFormDataValue(formData, key, value) {
|
|
1766
|
-
if (value === void 0 || value === null) return;
|
|
1767
|
-
if (Array.isArray(value)) {
|
|
1768
|
-
for (const item of value) appendFormDataValue(formData, key, item);
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1771
|
-
if (isBlob(value)) {
|
|
1772
|
-
formData.append(key, value);
|
|
1773
|
-
return;
|
|
1774
|
-
}
|
|
1775
|
-
if (typeof value === "object") {
|
|
1776
|
-
formData.append(key, JSON.stringify(value));
|
|
1777
|
-
return;
|
|
1778
|
-
}
|
|
1779
|
-
formData.append(key, String(value));
|
|
1780
|
-
}
|
|
1781
|
-
/** Convert plain upload payloads into FormData using the same encoding across generated plugin clients. */
|
|
1782
|
-
function pluginFormDataFromPayload(payload) {
|
|
1783
|
-
if (isFormData(payload)) return payload;
|
|
1784
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("[MINT SDK] Plugin upload endpoints require a FormData or plain object payload.");
|
|
1785
|
-
const formData = new FormData();
|
|
1786
|
-
for (const [key, value] of Object.entries(payload)) appendFormDataValue(formData, key, value);
|
|
1787
|
-
return formData;
|
|
1788
|
-
}
|
|
1789
|
-
/** Trigger a browser download for an already-created Blob. */
|
|
1790
|
-
function downloadBlob(blob, filename, options = {}) {
|
|
1791
|
-
if (typeof document === "undefined" || typeof URL === "undefined") throw new Error("[MINT SDK] Cannot trigger a file download outside a browser.");
|
|
1792
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
1793
|
-
const link = document.createElement("a");
|
|
1794
|
-
link.href = blobUrl;
|
|
1795
|
-
link.download = filename;
|
|
1796
|
-
document.body.appendChild(link);
|
|
1797
|
-
link.click();
|
|
1798
|
-
document.body.removeChild(link);
|
|
1799
|
-
setTimeout(() => URL.revokeObjectURL(blobUrl), options.revokeObjectUrlDelayMs ?? 100);
|
|
1800
|
-
}
|
|
1801
|
-
function normalizePluginNavPath(path) {
|
|
1802
|
-
const raw = path.trim() || "/";
|
|
1803
|
-
return (raw.startsWith("/") ? raw : `/${raw}`).replace(/\/+$/, "") || "/";
|
|
1804
|
-
}
|
|
1805
|
-
function pluginPageIdFromPath(path, fallbackIndex) {
|
|
1806
|
-
const normalizedPath = normalizePluginNavPath(path);
|
|
1807
|
-
if (normalizedPath === "/") return "dashboard";
|
|
1808
|
-
return normalizedPath.replace(/^\/+/, "").replace(/\/+/g, "-") || `page-${fallbackIndex + 1}`;
|
|
1809
|
-
}
|
|
1810
|
-
/** Convert resolved @mint_plugin nav_items into page selectors for the topbar and homepage PluginCards. */
|
|
1811
|
-
function getPluginPageSelectorItems(contract) {
|
|
1812
|
-
const pluginIcon = contract.plugin.icon ?? "";
|
|
1813
|
-
return (contract.plugin.navItems ?? []).map((item, index) => ({
|
|
1814
|
-
id: item.id || pluginPageIdFromPath(item.path, index),
|
|
1815
|
-
label: item.label,
|
|
1816
|
-
to: normalizePluginNavPath(item.path),
|
|
1817
|
-
icon: item.icon || pluginIcon || void 0
|
|
1818
|
-
}));
|
|
1819
|
-
}
|
|
1820
|
-
/** Create a typed plugin API client from a generated MINT plugin contract. */
|
|
1821
|
-
function createPluginClient(contract, options) {
|
|
1822
|
-
const client = {};
|
|
1823
|
-
for (const [name, endpoint] of Object.entries(options.endpoints)) client[name] = async (payloadOrOptions, explicitOptions) => {
|
|
1824
|
-
const hasPayload = endpointHasPayload(endpoint);
|
|
1825
|
-
const payload = hasPayload ? payloadOrOptions : void 0;
|
|
1826
|
-
const callOptions = hasPayload ? explicitOptions : payloadOrOptions ?? explicitOptions;
|
|
1827
|
-
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
|
|
1828
|
-
const api = useApi({
|
|
1829
|
-
baseUrl: parts.baseUrl,
|
|
1830
|
-
withAuth: endpoint.requiresAuth !== false,
|
|
1831
|
-
typedErrors: true
|
|
1832
|
-
});
|
|
1833
|
-
const config = endpointRequestConfig(endpoint, parts.query, callOptions);
|
|
1834
|
-
if (endpoint.method === "get") return api.get(parts.path, config);
|
|
1835
|
-
if (endpoint.method === "delete") {
|
|
1836
|
-
const rawBody = requestBodyFromPayload(payload, parts.requestPayload, endpoint);
|
|
1837
|
-
const body = rawBody === void 0 ? void 0 : endpointRequestBody(endpoint, rawBody);
|
|
1838
|
-
const config = body === void 0 ? endpointRequestConfig(endpoint, parts.query, callOptions) : {
|
|
1839
|
-
...endpointRequestConfig(endpoint, parts.query, callOptions),
|
|
1840
|
-
data: body
|
|
1841
|
-
};
|
|
1842
|
-
return api.delete(parts.path, config);
|
|
1843
|
-
}
|
|
1844
|
-
const body = endpointRequestBody(endpoint, requestBodyFromPayload(payload, parts.requestPayload, endpoint));
|
|
1845
|
-
if (endpoint.method === "post") return api.post(parts.path, body, config);
|
|
1846
|
-
if (endpoint.method === "put") return api.put(parts.path, body, config);
|
|
1847
|
-
if (endpoint.method === "patch") return api.patch(parts.path, body, config);
|
|
1848
|
-
throw new Error(`[MINT SDK] Unsupported plugin endpoint method: ${endpoint.method}`);
|
|
1849
|
-
};
|
|
1850
|
-
return client;
|
|
1851
|
-
}
|
|
1852
|
-
/** Send multipart or raw binary data to a generated endpoint with contract URL, cancellation, and auth handling. */
|
|
1853
|
-
async function uploadPluginEndpoint(contract, endpoint, payload, options = {}) {
|
|
1854
|
-
if (![
|
|
1855
|
-
"post",
|
|
1856
|
-
"put",
|
|
1857
|
-
"patch"
|
|
1858
|
-
].includes(endpoint.method)) throw new Error(`[MINT SDK] Plugin upload endpoints must use POST, PUT, or PATCH; got ${endpoint.method}`);
|
|
1859
|
-
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
|
|
1860
|
-
const api = useApi({
|
|
1861
|
-
baseUrl: parts.baseUrl,
|
|
1862
|
-
withAuth: endpoint.requiresAuth !== false,
|
|
1863
|
-
typedErrors: true
|
|
1864
|
-
});
|
|
1865
|
-
const body = endpoint.hasBody ? requestBodyFromPayload(payload, parts.requestPayload, endpoint) : payload;
|
|
1866
|
-
if (endpoint.requestContentType === OCTET_STREAM) {
|
|
1867
|
-
const config = endpointRequestConfig(endpoint, parts.query, { signal: options.signal });
|
|
1868
|
-
if (endpoint.method === "post") return api.post(parts.path, body, config);
|
|
1869
|
-
if (endpoint.method === "put") return api.put(parts.path, body, config);
|
|
1870
|
-
return api.patch(parts.path, body, config);
|
|
1871
|
-
}
|
|
1872
|
-
const formData = pluginFormDataFromPayload(body);
|
|
1873
|
-
const config = {
|
|
1874
|
-
...queryConfig(parts.query) ?? {},
|
|
1875
|
-
...options.signal ? { signal: options.signal } : {},
|
|
1876
|
-
headers: { "Content-Type": void 0 }
|
|
1877
|
-
};
|
|
1878
|
-
if (endpoint.method === "post") return api.post(parts.path, formData, config);
|
|
1879
|
-
if (endpoint.method === "put") return api.put(parts.path, formData, config);
|
|
1880
|
-
return api.patch(parts.path, formData, config);
|
|
1881
|
-
}
|
|
1882
|
-
/** Fetch a generated plugin endpoint as a Blob and optionally trigger a browser download. */
|
|
1883
|
-
async function downloadPluginEndpoint(contract, endpoint, payload, options = {}) {
|
|
1884
|
-
if (endpoint.requestContentType === MULTIPART_FORM_DATA) throw new Error("[MINT SDK] Plugin download endpoints do not support multipart request bodies");
|
|
1885
|
-
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
|
|
1886
|
-
const api = useApi({
|
|
1887
|
-
baseUrl: parts.baseUrl,
|
|
1888
|
-
withAuth: endpoint.requiresAuth !== false,
|
|
1889
|
-
typedErrors: true
|
|
1890
|
-
});
|
|
1891
|
-
const config = {
|
|
1892
|
-
...endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {},
|
|
1893
|
-
responseType: "blob"
|
|
1894
|
-
};
|
|
1895
|
-
const rawBody = requestBodyFromPayload(payload, parts.requestPayload, endpoint);
|
|
1896
|
-
const body = rawBody === void 0 ? void 0 : endpointRequestBody(endpoint, rawBody);
|
|
1897
|
-
let blob;
|
|
1898
|
-
if (endpoint.method === "get") blob = await api.get(parts.path, config);
|
|
1899
|
-
else if (endpoint.method === "post") blob = await api.post(parts.path, body, config);
|
|
1900
|
-
else if (endpoint.method === "put") blob = await api.put(parts.path, body, config);
|
|
1901
|
-
else if (endpoint.method === "patch") blob = await api.patch(parts.path, body, config);
|
|
1902
|
-
else if (endpoint.method === "delete") blob = await api.delete(parts.path, body === void 0 ? config : {
|
|
1903
|
-
...config,
|
|
1904
|
-
data: body
|
|
1905
|
-
});
|
|
1906
|
-
else throw new Error(`[MINT SDK] Unsupported plugin endpoint method: ${endpoint.method}`);
|
|
1907
|
-
if (options.filename) downloadBlob(blob, options.filename, { revokeObjectUrlDelayMs: options.revokeObjectUrlDelayMs });
|
|
1908
|
-
return blob;
|
|
1909
|
-
}
|
|
1910
|
-
function normalizePluginSettingsOptions(options = {}) {
|
|
1911
|
-
return typeof options === "string" ? { pluginName: options } : options;
|
|
1912
|
-
}
|
|
1913
|
-
/** @deprecated Use `useGeneratedPluginClient()` for generated clients. */
|
|
1914
|
-
function usePluginClient(client) {
|
|
1915
|
-
return client;
|
|
1916
|
-
}
|
|
1917
|
-
var PLATFORM_OWNED_SETTINGS_KEY = "allowed_experiment_types";
|
|
1918
|
-
/**
|
|
1919
|
-
* Secret settings travel as `{ "$secret": "<id>" }` references, so an untouched
|
|
1920
|
-
* secret round-trips without ever exposing its value. Declaring the format on
|
|
1921
|
-
* every write keeps a `null` unambiguous: it clears the stored secret instead of
|
|
1922
|
-
* meaning "unchanged", which is what the deprecated placeholder form meant.
|
|
1923
|
-
*/
|
|
1924
|
-
function withSecretFormat(payload, secretFormat) {
|
|
1925
|
-
if (!secretFormat) return payload;
|
|
1926
|
-
return {
|
|
1927
|
-
...payload,
|
|
1928
|
-
[PLUGIN_SECRET_FORMAT_KEY]: secretFormat
|
|
1929
|
-
};
|
|
1930
|
-
}
|
|
1931
|
-
function responseSecretFormat(response) {
|
|
1932
|
-
if (!response || typeof response !== "object") return null;
|
|
1933
|
-
const format = response[PLUGIN_SECRET_FORMAT_KEY];
|
|
1934
|
-
return typeof format === "string" && format ? format : null;
|
|
1935
|
-
}
|
|
1936
|
-
function cloneRecord(value) {
|
|
1937
|
-
return JSON.parse(JSON.stringify(value));
|
|
1938
|
-
}
|
|
1939
|
-
function hasOwnSetting(settings, key) {
|
|
1940
|
-
return Object.prototype.hasOwnProperty.call(settings, key);
|
|
1941
|
-
}
|
|
1942
|
-
function responseSettingsPayload(response) {
|
|
1943
|
-
if (!response || typeof response !== "object") return {};
|
|
1944
|
-
const data = response;
|
|
1945
|
-
if (data.config && typeof data.config === "object") return cloneRecord(data.config);
|
|
1946
|
-
if (data.settings && typeof data.settings === "object") return cloneRecord(data.settings);
|
|
1947
|
-
return {};
|
|
1948
|
-
}
|
|
1949
|
-
function responseSettingsRevision(response) {
|
|
1950
|
-
if (!response || typeof response !== "object") return null;
|
|
1951
|
-
const revision = response.revision;
|
|
1952
|
-
return typeof revision === "string" && revision ? revision : null;
|
|
1953
|
-
}
|
|
1954
|
-
function topLevelSettingsPatch(current, keys) {
|
|
1955
|
-
return Object.fromEntries([...keys].map((key) => [key, hasOwnSetting(current, key) ? current[key] : null]));
|
|
1956
|
-
}
|
|
1957
|
-
function changedTopLevelSettingKeys(current, saved) {
|
|
1958
|
-
return new Set([...new Set([...Object.keys(current), ...Object.keys(saved)])].filter((key) => JSON.stringify(current[key]) !== JSON.stringify(saved[key])));
|
|
1959
|
-
}
|
|
1960
|
-
function platformOwnedSettingsPatch(settings) {
|
|
1961
|
-
return { [PLATFORM_OWNED_SETTINGS_KEY]: hasOwnSetting(settings, PLATFORM_OWNED_SETTINGS_KEY) ? settings[PLATFORM_OWNED_SETTINGS_KEY] : null };
|
|
1962
|
-
}
|
|
1963
|
-
/** Load, edit, and persist plugin settings from platform config or the plugin-local settings router. */
|
|
1964
|
-
function usePluginSettings(options = {}) {
|
|
1965
|
-
const resolvedOptions = normalizePluginSettingsOptions(options);
|
|
1966
|
-
const injectedContext = getInjectedPlatformContext();
|
|
1967
|
-
const { plugin, isIntegrated } = usePlatformContext();
|
|
1968
|
-
const platformApi = useApi({ baseUrl: resolvedOptions.platformApiBaseUrl ?? injectedContext?.platformApiUrl });
|
|
1969
|
-
const pluginApi = useApi({ baseUrl: resolvedOptions.apiBaseUrl ?? injectedContext?.plugin?.api_prefix });
|
|
1970
|
-
const values = ref({});
|
|
1971
|
-
const savedValues = shallowRef({});
|
|
1972
|
-
const revision = ref(null);
|
|
1973
|
-
const secretFormat = ref(null);
|
|
1974
|
-
const request = useRequestSyncState("Plugin settings request failed.");
|
|
1975
|
-
const isLoading = ref(false);
|
|
1976
|
-
const isSaving = ref(false);
|
|
1977
|
-
const resolvedPluginName = computed(() => resolvedOptions.pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? "");
|
|
1978
|
-
const usesPluginSettingsApi = computed(() => {
|
|
1979
|
-
const currentPlugin = injectedContext?.plugin ?? plugin.value;
|
|
1980
|
-
return currentPlugin?.settings_api === "plugin" && resolvedPluginName.value === currentPlugin.name;
|
|
1981
|
-
});
|
|
1982
|
-
const usesPlatformConfig = computed(() => Boolean((isIntegrated.value || injectedContext?.isIntegrated) && resolvedPluginName.value && !usesPluginSettingsApi.value));
|
|
1983
|
-
const settings = computed(() => values.value);
|
|
1984
|
-
const isDirty = computed(() => JSON.stringify(values.value) !== JSON.stringify(savedValues.value));
|
|
1985
|
-
const settingsConfig = computed(() => ({
|
|
1986
|
-
title: resolvedOptions.title,
|
|
1987
|
-
schema: resolvedOptions.schema ?? void 0,
|
|
1988
|
-
showAppearance: resolvedOptions.showAppearance,
|
|
1989
|
-
values: values.value
|
|
1990
|
-
}));
|
|
1991
|
-
function setValues(nextValues) {
|
|
1992
|
-
values.value = cloneRecord(nextValues);
|
|
1993
|
-
}
|
|
1994
|
-
function applyLoaded(nextValues, nextRevision, nextSecretFormat = secretFormat.value) {
|
|
1995
|
-
const cloned = cloneRecord(nextValues);
|
|
1996
|
-
values.value = cloned;
|
|
1997
|
-
savedValues.value = cloneRecord(cloned);
|
|
1998
|
-
revision.value = nextRevision;
|
|
1999
|
-
secretFormat.value = nextSecretFormat;
|
|
2000
|
-
}
|
|
2001
|
-
async function load() {
|
|
2002
|
-
isLoading.value = true;
|
|
2003
|
-
try {
|
|
2004
|
-
const response = usesPlatformConfig.value ? await request.run(() => platformApi.get(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`), {
|
|
2005
|
-
success: "load",
|
|
2006
|
-
errorMessage: "Failed to load plugin settings"
|
|
2007
|
-
}) : await request.run(() => pluginApi.get("/settings"), {
|
|
2008
|
-
success: "load",
|
|
2009
|
-
errorMessage: "Failed to load plugin settings"
|
|
2010
|
-
});
|
|
2011
|
-
applyLoaded(responseSettingsPayload(response), responseSettingsRevision(response), responseSecretFormat(response));
|
|
2012
|
-
} catch {} finally {
|
|
2013
|
-
isLoading.value = false;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
async function save(nextValues) {
|
|
2017
|
-
if (nextValues) setValues(nextValues);
|
|
2018
|
-
isSaving.value = true;
|
|
2019
|
-
try {
|
|
2020
|
-
const payload = cloneRecord(values.value);
|
|
2021
|
-
let response;
|
|
2022
|
-
if (usesPlatformConfig.value) {
|
|
2023
|
-
const dirtyKeys = changedTopLevelSettingKeys(payload, savedValues.value);
|
|
2024
|
-
const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY);
|
|
2025
|
-
const typedKeys = [...dirtyKeys].filter((key) => key !== PLATFORM_OWNED_SETTINGS_KEY);
|
|
2026
|
-
let nextRevision = revision.value;
|
|
2027
|
-
if (typedKeys.length) {
|
|
2028
|
-
response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`, { config: withSecretFormat(topLevelSettingsPatch(payload, typedKeys), secretFormat.value) }), {
|
|
2029
|
-
success: "save",
|
|
2030
|
-
errorMessage: "Failed to save plugin settings"
|
|
2031
|
-
});
|
|
2032
|
-
nextRevision = responseSettingsRevision(response) ?? nextRevision;
|
|
2033
|
-
}
|
|
2034
|
-
if (platformOwnedChanged) {
|
|
2035
|
-
response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`, { config: platformOwnedSettingsPatch(payload) }), {
|
|
2036
|
-
success: "save",
|
|
2037
|
-
errorMessage: "Failed to save plugin settings"
|
|
2038
|
-
});
|
|
2039
|
-
nextRevision = responseSettingsRevision(response) ?? nextRevision;
|
|
2040
|
-
}
|
|
2041
|
-
if (!response) return true;
|
|
2042
|
-
applyLoaded(responseSettingsPayload(response), nextRevision, responseSecretFormat(response) ?? secretFormat.value);
|
|
2043
|
-
return true;
|
|
2044
|
-
} else {
|
|
2045
|
-
const ifMatch = revision.value ? { headers: { "If-Match": `"${revision.value}"` } } : void 0;
|
|
2046
|
-
response = await request.run(() => pluginApi.put("/settings", withSecretFormat(payload, secretFormat.value), ifMatch), {
|
|
2047
|
-
success: "save",
|
|
2048
|
-
errorMessage: "Failed to save plugin settings"
|
|
2049
|
-
});
|
|
2050
|
-
}
|
|
2051
|
-
applyLoaded(responseSettingsPayload(response), responseSettingsRevision(response) ?? revision.value, responseSecretFormat(response) ?? secretFormat.value);
|
|
2052
|
-
return true;
|
|
2053
|
-
} catch {
|
|
2054
|
-
return false;
|
|
2055
|
-
} finally {
|
|
2056
|
-
isSaving.value = false;
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
function reset() {
|
|
2060
|
-
values.value = cloneRecord(savedValues.value);
|
|
2061
|
-
request.clearError();
|
|
2062
|
-
}
|
|
2063
|
-
onMounted(() => {
|
|
2064
|
-
if (resolvedOptions.loadOnMount !== false) load();
|
|
2065
|
-
});
|
|
2066
|
-
return {
|
|
2067
|
-
settings,
|
|
2068
|
-
values,
|
|
2069
|
-
config: values,
|
|
2070
|
-
settingsConfig,
|
|
2071
|
-
isLoading,
|
|
2072
|
-
isSaving,
|
|
2073
|
-
error: request.error,
|
|
2074
|
-
lastLoadedAt: request.lastLoadedAt,
|
|
2075
|
-
lastSavedAt: request.lastSavedAt,
|
|
2076
|
-
isDirty,
|
|
2077
|
-
setValues,
|
|
2078
|
-
load,
|
|
2079
|
-
save,
|
|
2080
|
-
reset
|
|
2081
|
-
};
|
|
2082
|
-
}
|
|
2083
|
-
function streamRequestAuth(requestHeaders, policy) {
|
|
2084
|
-
const headers = new Headers(requestHeaders);
|
|
2085
|
-
const authStore = useAuthStore();
|
|
2086
|
-
if (!authStore.isInitialized) authStore.initialize();
|
|
2087
|
-
if (policy.requiresAuth && authStore.token && !headers.has("Authorization")) headers.set("Authorization", `Bearer ${authStore.token}`);
|
|
2088
|
-
const token = policy.requiresAuth ? authStore.token : void 0;
|
|
2089
|
-
return {
|
|
2090
|
-
headers,
|
|
2091
|
-
token: token && headers.get("Authorization") === `Bearer ${token}` ? token : void 0
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
function pluginEventStreamTransport(endpoint) {
|
|
2095
|
-
return async (url, init = {}) => {
|
|
2096
|
-
if (typeof fetch !== "function") throw new Error("[MINT SDK] fetch is required for plugin event streams.");
|
|
2097
|
-
const requestAuth = streamRequestAuth(init.headers, { requiresAuth: endpoint.requiresAuth !== false });
|
|
2098
|
-
const response = await fetch(url, {
|
|
2099
|
-
...init,
|
|
2100
|
-
headers: requestAuth.headers
|
|
2101
|
-
});
|
|
2102
|
-
if (response.status === 401) throw authenticationRequiredError(response, requestAuth.token);
|
|
2103
|
-
return response;
|
|
2104
|
-
};
|
|
2105
|
-
}
|
|
2106
|
-
function looksLikeEventStreamOptions(value) {
|
|
2107
|
-
if (!value || typeof value !== "object") return false;
|
|
2108
|
-
const candidate = value;
|
|
2109
|
-
return [
|
|
2110
|
-
"baseUrl",
|
|
2111
|
-
"immediate",
|
|
2112
|
-
"reconnect",
|
|
2113
|
-
"reconnectDelayMs",
|
|
2114
|
-
"parseJson",
|
|
2115
|
-
"credentials",
|
|
2116
|
-
"headers",
|
|
2117
|
-
"onOpen",
|
|
2118
|
-
"onMessage",
|
|
2119
|
-
"onError"
|
|
2120
|
-
].some((key) => key in candidate);
|
|
2121
|
-
}
|
|
2122
|
-
function usePluginEventStream(contract, endpoint, payloadOrOptions, maybeOptions) {
|
|
2123
|
-
if (endpoint.method !== "get") throw new Error(`[MINT SDK] Plugin event streams must use GET; got ${endpoint.method}`);
|
|
2124
|
-
const payload = maybeOptions === void 0 && looksLikeEventStreamOptions(payloadOrOptions) ? void 0 : payloadOrOptions;
|
|
2125
|
-
const options = maybeOptions === void 0 && looksLikeEventStreamOptions(payloadOrOptions) ? payloadOrOptions : maybeOptions ?? {};
|
|
2126
|
-
const responseType = endpoint.responseType?.trim();
|
|
2127
|
-
const parseJson = options.parseJson ?? Boolean(responseType && responseType !== "string");
|
|
2128
|
-
return useEventStream(() => buildPluginEndpointUrl(contract, endpoint, payload, { baseUrl: options.baseUrl }), {
|
|
2129
|
-
transport: pluginEventStreamTransport(endpoint),
|
|
2130
|
-
immediate: options.immediate,
|
|
2131
|
-
reconnect: options.reconnect,
|
|
2132
|
-
reconnectDelayMs: options.reconnectDelayMs,
|
|
2133
|
-
parseJson,
|
|
2134
|
-
credentials: options.credentials,
|
|
2135
|
-
headers: options.headers,
|
|
2136
|
-
shouldReconnect: (cause) => !(cause instanceof AuthenticationRequiredError),
|
|
2137
|
-
onOpen: options.onOpen,
|
|
2138
|
-
onMessage: options.onMessage,
|
|
2139
|
-
onError: options.onError,
|
|
2140
|
-
errorLabel: "Plugin event stream"
|
|
2141
|
-
});
|
|
2142
|
-
}
|
|
2143
|
-
/** Read and optionally load the current platform experiment for integrated plugin views. */
|
|
2144
|
-
function useCurrentExperiment(options = {}) {
|
|
2145
|
-
const api = useApi({ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl });
|
|
2146
|
-
const experiment = shallowRef(currentExperimentFromContext());
|
|
2147
|
-
const request = useRequestSyncState("Failed to load current experiment");
|
|
2148
|
-
const isLoading = request.loading;
|
|
2149
|
-
const error = request.error;
|
|
2150
|
-
const lastLoadedAt = request.lastLoadedAt;
|
|
2151
|
-
const experimentId = computed(() => {
|
|
2152
|
-
return resolveCurrentExperimentId();
|
|
2153
|
-
});
|
|
2154
|
-
const hasExperiment = computed(() => experimentId.value !== void 0);
|
|
2155
|
-
function requireExperimentId() {
|
|
2156
|
-
const id = experimentId.value;
|
|
2157
|
-
if (id === void 0) throw new Error("[MINT SDK] No current experiment is selected.");
|
|
2158
|
-
return id;
|
|
2159
|
-
}
|
|
2160
|
-
async function fetchExperiment(id = experimentId.value) {
|
|
2161
|
-
if (id === void 0) {
|
|
2162
|
-
experiment.value = currentExperimentFromContext();
|
|
2163
|
-
return experiment.value;
|
|
2164
|
-
}
|
|
2165
|
-
try {
|
|
2166
|
-
const result = await request.run(() => api.get(`/experiments/${id}`), {
|
|
2167
|
-
success: "load",
|
|
2168
|
-
errorMessage: "Failed to load current experiment"
|
|
2169
|
-
});
|
|
2170
|
-
experiment.value = result;
|
|
2171
|
-
return result;
|
|
2172
|
-
} catch (e) {
|
|
2173
|
-
experiment.value = void 0;
|
|
2174
|
-
return;
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
async function refresh() {
|
|
2178
|
-
return fetchExperiment();
|
|
2179
|
-
}
|
|
2180
|
-
watch(experimentId, (id) => {
|
|
2181
|
-
if (options.immediate === false) return;
|
|
2182
|
-
if (id === void 0) {
|
|
2183
|
-
experiment.value = currentExperimentFromContext();
|
|
2184
|
-
return;
|
|
2185
|
-
}
|
|
2186
|
-
fetchExperiment(id);
|
|
2187
|
-
}, { immediate: options.immediate !== false });
|
|
2188
|
-
return {
|
|
2189
|
-
experimentId,
|
|
2190
|
-
hasExperiment,
|
|
2191
|
-
experiment,
|
|
2192
|
-
isLoading,
|
|
2193
|
-
error,
|
|
2194
|
-
lastLoadedAt,
|
|
2195
|
-
requireExperimentId,
|
|
2196
|
-
fetch: fetchExperiment,
|
|
2197
|
-
refresh
|
|
2198
|
-
};
|
|
2199
|
-
}
|
|
2200
|
-
//#endregion
|
|
2201
|
-
//#region src/composables/createFilePickerAdapter.ts
|
|
2202
|
-
/** Opaque picker identity; decode before passing a selection to a mount-scoped API. */
|
|
2203
|
-
function encodePlatformPickerPath(mountId, path = "") {
|
|
2204
|
-
return JSON.stringify([mountId, path]);
|
|
2205
|
-
}
|
|
2206
|
-
function decodePlatformPickerPath(path) {
|
|
2207
|
-
const decoded = JSON.parse(path);
|
|
2208
|
-
if (!Array.isArray(decoded) || decoded.length !== 2 || !decoded.every((item) => typeof item === "string") || !decoded[0]) throw new Error("Invalid platform picker path.");
|
|
2209
|
-
return {
|
|
2210
|
-
mountId: decoded[0],
|
|
2211
|
-
path: decoded[1]
|
|
2212
|
-
};
|
|
2213
|
-
}
|
|
2214
|
-
function isRelativePath(path) {
|
|
2215
|
-
return path === "" || !/^[a-z]:/i.test(path) && !/[\\\x00-\x1f\x7f]/.test(path) && path.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
2216
|
-
}
|
|
2217
|
-
/** Mount browsing and search shared by platform and generated plugin clients. */
|
|
2218
|
-
function createFilePickerAdapter(transport, options = {}) {
|
|
2219
|
-
const root = options.rootLocation && { ...options.rootLocation };
|
|
2220
|
-
const mountLabels = /* @__PURE__ */ new Map();
|
|
2221
|
-
const listings = /* @__PURE__ */ new Map();
|
|
2222
|
-
let generation = 0;
|
|
2223
|
-
function invalidate() {
|
|
2224
|
-
generation++;
|
|
2225
|
-
listings.clear();
|
|
2226
|
-
mountLabels.clear();
|
|
2227
|
-
}
|
|
2228
|
-
function validateLocation(location) {
|
|
2229
|
-
if (!location.mount_id || !isRelativePath(location.path) || root && (location.mount_id !== root.mount_id || root.path !== "" && location.path !== root.path && !location.path.startsWith(`${root.path}/`))) throw new Error("Invalid file picker location.");
|
|
2230
|
-
}
|
|
2231
|
-
if (root) validateLocation(root);
|
|
2232
|
-
function begin(request) {
|
|
2233
|
-
request?.signal?.throwIfAborted();
|
|
2234
|
-
if (request?.refresh) invalidate();
|
|
2235
|
-
return generation;
|
|
2236
|
-
}
|
|
2237
|
-
function check(request, token) {
|
|
2238
|
-
request?.signal?.throwIfAborted();
|
|
2239
|
-
if (token !== generation) throw new DOMException("Cancelled", "AbortError");
|
|
2240
|
-
}
|
|
2241
|
-
async function read(path, request, token) {
|
|
2242
|
-
check(request, token);
|
|
2243
|
-
const decoded = path ? decodePlatformPickerPath(path) : void 0;
|
|
2244
|
-
const location = decoded ? {
|
|
2245
|
-
mount_id: decoded.mountId,
|
|
2246
|
-
path: decoded.path
|
|
2247
|
-
} : root;
|
|
2248
|
-
if (location) validateLocation(location);
|
|
2249
|
-
const key = location ? encodePlatformPickerPath(location.mount_id, location.path) : "";
|
|
2250
|
-
const cached = listings.get(key);
|
|
2251
|
-
if (cached) return cached;
|
|
2252
|
-
let nodes;
|
|
2253
|
-
if (!location) {
|
|
2254
|
-
const mounts = await transport.listMounts(request);
|
|
2255
|
-
check(request, token);
|
|
2256
|
-
nodes = mounts.map((mount) => {
|
|
2257
|
-
mountLabels.set(mount.id, mount.label);
|
|
2258
|
-
return {
|
|
2259
|
-
kind: "folder",
|
|
2260
|
-
path: encodePlatformPickerPath(mount.id),
|
|
2261
|
-
name: mount.label,
|
|
2262
|
-
parentPath: null,
|
|
2263
|
-
parentName: "Server mounts",
|
|
2264
|
-
unavailableReason: mount.available === false ? "This mount is offline." : void 0
|
|
2265
|
-
};
|
|
2266
|
-
});
|
|
2267
|
-
} else {
|
|
2268
|
-
const listing = await transport.browse(location, request);
|
|
2269
|
-
check(request, token);
|
|
2270
|
-
if (listing.truncated) throw new Error("This directory exceeds the server listing limit. Choose a smaller data directory.");
|
|
2271
|
-
const atRoot = root && location.mount_id === root.mount_id && location.path === root.path;
|
|
2272
|
-
nodes = (listing.entries ?? []).map((entry) => {
|
|
2273
|
-
if (!isRelativePath(entry.path) || !entry.name || entry.path !== `${location.path ? `${location.path}/` : ""}${entry.name}` || entry.name.includes("/")) throw new Error("Invalid file picker directory entry.");
|
|
2274
|
-
const common = {
|
|
2275
|
-
path: encodePlatformPickerPath(location.mount_id, entry.path),
|
|
2276
|
-
parentPath: atRoot ? null : key,
|
|
2277
|
-
parentName: `${mountLabels.get(location.mount_id) ?? location.mount_id}${location.path ? ` / ${location.path}` : ""}`,
|
|
2278
|
-
name: entry.name,
|
|
2279
|
-
bytes: entry.size_bytes
|
|
2280
|
-
};
|
|
2281
|
-
return entry.kind === "directory" ? {
|
|
2282
|
-
...common,
|
|
2283
|
-
kind: "folder"
|
|
2284
|
-
} : {
|
|
2285
|
-
...common,
|
|
2286
|
-
kind: "file",
|
|
2287
|
-
format: pickerFormat(entry.name)
|
|
2288
|
-
};
|
|
2289
|
-
});
|
|
2290
|
-
}
|
|
2291
|
-
listings.set(key, nodes);
|
|
2292
|
-
return nodes;
|
|
2293
|
-
}
|
|
2294
|
-
return {
|
|
2295
|
-
invalidate,
|
|
2296
|
-
listRoot: async (request) => read("", request, begin(request)),
|
|
2297
|
-
listChildren: async (path, request) => read(path, request, begin(request)),
|
|
2298
|
-
async search(query, scope, request) {
|
|
2299
|
-
const token = begin(request);
|
|
2300
|
-
const matches = [];
|
|
2301
|
-
const queue = [scope.path];
|
|
2302
|
-
const visited = /* @__PURE__ */ new Set();
|
|
2303
|
-
const needle = query.toLowerCase();
|
|
2304
|
-
let currentRequest = request;
|
|
2305
|
-
while (queue.length) {
|
|
2306
|
-
check(request, token);
|
|
2307
|
-
const path = queue.shift();
|
|
2308
|
-
if (visited.has(path)) continue;
|
|
2309
|
-
visited.add(path);
|
|
2310
|
-
if (visited.size > 500) throw new Error("Search reached its folder limit. Search a smaller location or turn off Subfolders.");
|
|
2311
|
-
const nodes = await read(path, currentRequest, token);
|
|
2312
|
-
check(request, token);
|
|
2313
|
-
if (currentRequest?.refresh) {
|
|
2314
|
-
currentRequest = { ...currentRequest };
|
|
2315
|
-
delete currentRequest.refresh;
|
|
2316
|
-
}
|
|
2317
|
-
for (const node of nodes) {
|
|
2318
|
-
if (node.name.toLowerCase().includes(needle)) matches.push(node);
|
|
2319
|
-
if (scope.recursive && node.kind === "folder" && !node.unavailableReason) queue.push(node.path);
|
|
2320
|
-
}
|
|
2321
|
-
if (matches.length > 1e4) throw new Error("Too many search results. Use a more specific search.");
|
|
2322
|
-
}
|
|
2323
|
-
return matches;
|
|
2324
|
-
}
|
|
2325
|
-
};
|
|
2326
|
-
}
|
|
2327
|
-
//#endregion
|
|
2328
|
-
//#region src/composables/usePlatformFilePickerAdapter.ts
|
|
2329
|
-
/** Adapt the authenticated MINT mount API without exposing absolute host paths. */
|
|
2330
|
-
function usePlatformFilePickerAdapter(options = {}) {
|
|
2331
|
-
const api = useApi({ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl });
|
|
2332
|
-
return createFilePickerAdapter({
|
|
2333
|
-
async listMounts(request) {
|
|
2334
|
-
try {
|
|
2335
|
-
const url = `/filesystem/mounts${request?.refresh ? "?refresh=true" : ""}`;
|
|
2336
|
-
const { mounts } = await api.get(url, { signal: request?.signal });
|
|
2337
|
-
return mounts;
|
|
2338
|
-
} catch (cause) {
|
|
2339
|
-
if (cause.response?.status === 404) return [];
|
|
2340
|
-
throw cause;
|
|
2341
|
-
}
|
|
2342
|
-
},
|
|
2343
|
-
browse(location, request) {
|
|
2344
|
-
const query = new URLSearchParams({
|
|
2345
|
-
mount_id: location.mount_id,
|
|
2346
|
-
path: location.path
|
|
2347
|
-
});
|
|
2348
|
-
if (request?.refresh) query.set("refresh", "true");
|
|
2349
|
-
return api.get(`/filesystem/browse?${query}`, { signal: request?.signal });
|
|
2350
|
-
}
|
|
2351
|
-
}, options);
|
|
2352
|
-
}
|
|
2353
|
-
//#endregion
|
|
2354
|
-
export { useCommandHistory as A, useAsyncBatch as B, coordinateToWellId as C, createPluginResourceClient as D, wellIdsInRectangle as E, resizedLeadingPanelWidth as F, usePasskey as H, resizedTrailingPanelWidth as I, resizedVerticalSplitPercent as L, parseAnalysisArtifactTarget as M, useFileBrowser as N, useResourceCrud as O, usePluginConfig as P, useManualLayoutResize as R, useWellPlateAdapter as S, wellIdToCoordinate as T, useAuth as U, resolvePluginFrontendBase as V, runWellPlateValidation as _, createPluginClient as a, createRowConditions as b, getPluginPageSelectorItems as c, useCurrentExperiment as d, usePluginClient as f, resolvePluginBaseUrl as g, buildPluginEndpointUrl as h, encodePlatformPickerPath as i, buildAnalysisArtifactTargetQuery as j, useOptimisticMutation as k, pluginFormDataFromPayload as l, usePluginSettings as m, createFilePickerAdapter as n, downloadBlob as o, usePluginEventStream as p, decodePlatformPickerPath as r, downloadPluginEndpoint as s, usePlatformFilePickerAdapter as t, uploadPluginEndpoint as u, useWellPlateValidation as v, useWellPainting as w, createWellPlateWells as x, createColumnConditions as y, useAsync as z };
|
|
2355
|
-
|
|
2356
|
-
//# sourceMappingURL=composables-BCbYb2lA.js.map
|