@morscherlab/mint-sdk 1.2.0-beta.1 → 1.2.0-beta.11
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 +35 -5
- package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-oL0fg_A0.js} +6 -4
- package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
- package/dist/{ExperimentSelectorModal-DyPAUCLw.js → ExperimentSelectorModal-BeEOomw_.js} +5 -5
- package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
- package/dist/{SettingsModal-BgKY93zh.js → SettingsModal-Dphh3RrI.js} +5 -8
- package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
- package/dist/{EmptyState-p90quq8Z.js → Skeleton-DxtwJ5gt.js} +111 -93
- package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
- package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
- package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
- package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
- package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
- package/dist/__tests__/composables/usePluginConfig.test.d.ts +15 -0
- package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
- package/dist/components/ActionMenu.vue.d.ts +1 -1
- package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
- package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AuditTrail.vue.d.ts +13 -0
- package/dist/components/AutoGroupModal.vue.d.ts +1 -1
- package/dist/components/BatchProgressList.vue.d.ts +17 -0
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/DataFrame.vue.d.ts +14 -2
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +2 -0
- package/dist/components/DropdownButton.vue.d.ts +2 -0
- package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
- package/dist/components/ExperimentPopover.vue.d.ts +1 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/JobsStatusTray.vue.d.ts +9 -2
- package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
- package/dist/components/ProgressBar.vue.d.ts +1 -1
- package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
- package/dist/components/ReagentList.vue.d.ts +29 -1
- package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
- package/dist/components/SampleLegend.vue.d.ts +1 -1
- package/dist/components/SampleSelector.drag.d.ts +13 -0
- package/dist/components/SampleSelector.tree.d.ts +2 -0
- package/dist/components/SampleSelector.vue.d.ts +35 -2
- package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
- package/dist/components/SecretInput.vue.d.ts +28 -0
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -1
- package/dist/components/TimePicker.vue.d.ts +1 -0
- package/dist/components/WellPlate.geometry.d.ts +15 -0
- package/dist/components/WellPlate.interaction.d.ts +13 -0
- package/dist/components/WellPlate.vue.d.ts +29 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.js +8 -8
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
- package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
- package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
- package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
- package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
- package/dist/{components-DkHddRMl.js → components-CgAUzF8F.js} +2497 -2235
- package/dist/components-CgAUzF8F.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +34 -0
- package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
- package/dist/composables/index.d.ts +9 -2
- package/dist/composables/index.js +10 -10
- package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
- package/dist/composables/useAsync.d.ts +5 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
- package/dist/composables/useCommandHistory.d.ts +5 -1
- package/dist/composables/useDropdownState.d.ts +18 -2
- package/dist/composables/useEventStream.d.ts +9 -0
- package/dist/composables/useFocusTrap.d.ts +21 -0
- package/dist/composables/useGeneratedAnalysis.d.ts +3 -1
- package/dist/composables/useJobsStatusTray.d.ts +25 -1
- package/dist/composables/useListReorder.d.ts +90 -0
- package/dist/composables/useMenuKeyboard.d.ts +29 -0
- package/dist/composables/useOptimisticMutation.d.ts +5 -1
- package/dist/composables/usePluginClient.d.ts +2 -0
- package/dist/composables/usePluginConfig.d.ts +27 -0
- package/dist/composables/usePluginWorkspace.d.ts +51 -0
- package/dist/composables/useRequestSyncState.d.ts +42 -2
- package/dist/composables/useResourceCrud.d.ts +5 -1
- package/dist/composables/useRovingFocus.d.ts +24 -0
- package/dist/composables/useScheduleDrag.d.ts +1 -0
- package/dist/composables/useWellPainting.d.ts +5 -1
- package/dist/composables/useWellPlateAdapter.d.ts +5 -1
- package/dist/composables/useWellPlateValidation.d.ts +5 -1
- package/dist/composables/workspaceRecipes.d.ts +188 -0
- package/dist/{composables-C_i90sGS.js → composables-B_pia3hO.js} +209 -82
- package/dist/composables-B_pia3hO.js.map +1 -0
- package/dist/{useApi-J6zFkHmn.js → errors-_W0-LbA6.js} +103 -3
- package/dist/errors-_W0-LbA6.js.map +1 -0
- package/dist/{experiment-Cq145oEW.js → experiment-Cwl9AYxw.js} +3 -8
- package/dist/experiment-Cwl9AYxw.js.map +1 -0
- package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
- package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -15
- package/dist/install.js +2 -7
- package/dist/install.js.map +1 -1
- package/dist/permissions.d.ts +42 -1
- package/dist/permissions.js +20 -10
- package/dist/permissions.js.map +1 -1
- package/dist/stores/auth.d.ts +6 -0
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js +3 -3
- package/dist/stores/settings.d.ts +31 -1
- package/dist/styles.css +11247 -29501
- package/dist/templates/builderReadUtils.d.ts +1 -1
- package/dist/templates/index.js +2 -4
- package/dist/{templates-DsP51i-S.js → templates-Qq113pyZ.js} +303 -106
- package/dist/templates-Qq113pyZ.js.map +1 -0
- package/dist/types/auth.d.ts +1 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/{useControlSchema-BDx2Olha.js → useControlSchema-aMZlb4Sc.js} +462 -1130
- package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
- package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-DitCtrTT.js} +22 -6
- package/dist/useDropdownState-DitCtrTT.js.map +1 -0
- package/dist/{useExperimentSelector-CV4iNfMS.js → useExperimentSelector-I8HC8hUu.js} +4 -4
- package/dist/{useExperimentSelector-CV4iNfMS.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
- package/dist/{useFormBuilder-BLxG3BOX.js → useFormBuilder-TCxNnqeb.js} +2 -2
- package/dist/{useFormBuilder-BLxG3BOX.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
- package/dist/{useProtocolTemplates-CS_SQl6U.js → useProtocolTemplates-BmX_iVBR.js} +766 -212
- package/dist/useProtocolTemplates-BmX_iVBR.js.map +1 -0
- package/dist/utils/color.d.ts +13 -0
- package/dist/utils/errors.d.ts +9 -0
- package/dist/utils/pluginSecrets.d.ts +53 -0
- package/dist/utils/records.d.ts +4 -0
- package/package.json +1 -1
- package/src/__tests__/components/ActionMenu.test.ts +94 -0
- package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
- package/src/__tests__/components/BaseTabs.test.ts +67 -0
- package/src/__tests__/components/DataFrame.test.ts +183 -11
- package/src/__tests__/components/DataStates.test.ts +176 -0
- package/src/__tests__/components/DatePicker.test.ts +76 -0
- package/src/__tests__/components/DateTimePicker.test.ts +36 -0
- package/src/__tests__/components/DropdownButton.test.ts +134 -0
- package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
- package/src/__tests__/components/GroupAssigner.test.ts +53 -0
- package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
- package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
- package/src/__tests__/components/RackEditor.test.ts +35 -0
- package/src/__tests__/components/ReagentList.test.ts +64 -2
- package/src/__tests__/components/RetiredStyles.test.ts +31 -0
- package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
- package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
- package/src/__tests__/components/SecretInput.test.ts +87 -0
- package/src/__tests__/components/SegmentedControl.test.ts +88 -0
- package/src/__tests__/components/TimePicker.test.ts +61 -0
- package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
- package/src/__tests__/composables/useAuth.test.ts +2 -0
- package/src/__tests__/composables/useDropdownState.test.ts +58 -1
- package/src/__tests__/composables/useListReorder.test.ts +145 -0
- package/src/__tests__/composables/usePluginClient.test.ts +140 -0
- package/src/__tests__/composables/usePluginConfig.test.ts +497 -0
- package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +39 -7
- package/src/__tests__/stores/settings.test.ts +71 -1
- package/src/__tests__/styles/cssTokens.test.ts +131 -0
- package/src/__tests__/utils/permissions.test.ts +68 -0
- package/src/components/ActionMenu.vue +45 -88
- package/src/components/AlertBox.vue +0 -4
- package/src/components/AppAvatarMenu.vue +26 -7
- package/src/components/AppLayout.vue +0 -4
- package/src/components/AppPluginSwitcher.story.vue +87 -0
- package/src/components/AppPluginSwitcher.vue +2 -5
- package/src/components/AppSidebar.vue +0 -4
- package/src/components/AppToastContainer.vue +6 -8
- package/src/components/AppTopBar.vue +0 -4
- package/src/components/ArtifactSaveDialog.vue +0 -4
- package/src/components/ArtifactSelectorModal.vue +0 -4
- package/src/components/AuditTrail.story.vue +18 -0
- package/src/components/AuditTrail.vue +27 -7
- package/src/components/AutoGroupModal.vue +0 -4
- package/src/components/Avatar.vue +0 -4
- package/src/components/BaseButton.vue +0 -4
- package/src/components/BaseCheckbox.vue +0 -4
- package/src/components/BaseInput.vue +0 -4
- package/src/components/BaseModal.vue +8 -60
- package/src/components/BasePill.vue +0 -4
- package/src/components/BaseRadioGroup.vue +0 -4
- package/src/components/BaseSelect.vue +0 -4
- package/src/components/BaseSlider.vue +0 -4
- package/src/components/BaseTabs.vue +26 -5
- package/src/components/BaseTextarea.vue +0 -4
- package/src/components/BaseToggle.vue +0 -4
- package/src/components/BatchProgressList.story.vue +18 -0
- package/src/components/BatchProgressList.vue +32 -4
- package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
- package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
- package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
- package/src/components/Breadcrumb.vue +0 -4
- package/src/components/Calendar.vue +0 -4
- package/src/components/ChartContainer.story.vue +1 -1
- package/src/components/ChartContainer.vue +0 -4
- package/src/components/ChemicalFormula.vue +0 -4
- package/src/components/CollapsibleCard.vue +0 -4
- package/src/components/ColorSlider.vue +0 -4
- package/src/components/ComponentBindingRenderer.vue +1 -3
- package/src/components/ConcentrationInput.vue +0 -4
- package/src/components/ConfirmDialog.vue +0 -4
- package/src/components/ControlWorkspaceView.vue +11 -22
- package/src/components/DataFrame.story.vue +14 -0
- package/src/components/DataFrame.vue +97 -23
- package/src/components/DatePicker.vue +73 -27
- package/src/components/DateTimePicker.vue +45 -17
- package/src/components/Divider.vue +0 -4
- package/src/components/DoseCalculator.vue +0 -4
- package/src/components/DoseDesignWorkspaceView.vue +6 -25
- package/src/components/DropdownButton.vue +24 -6
- package/src/components/EmptyState.vue +0 -4
- package/src/components/ExperimentCodeBadge.vue +0 -4
- package/src/components/ExperimentDataViewer.story.vue +3 -0
- package/src/components/ExperimentDataViewer.vue +33 -13
- package/src/components/ExperimentPopover.vue +2 -5
- package/src/components/ExperimentSelectorModal.vue +0 -4
- package/src/components/ExperimentTimeline.story.vue +18 -0
- package/src/components/ExperimentTimeline.vue +81 -75
- package/src/components/FileUploader.vue +0 -4
- package/src/components/FitPanel.vue +0 -4
- package/src/components/FormActions.story.vue +96 -0
- package/src/components/FormActions.vue +0 -4
- package/src/components/FormBuilder.vue +0 -4
- package/src/components/FormField.vue +0 -4
- package/src/components/FormulaInput.vue +0 -4
- package/src/components/GeneratedPathInput.story.vue +68 -0
- package/src/components/GeneratedPlotlyResult.story.vue +96 -0
- package/src/components/GeneratedPlotlyResult.vue +3 -3
- package/src/components/GeneratedPluginView.story.vue +145 -0
- package/src/components/GeneratedPluginView.vue +10 -10
- package/src/components/GeneratedResultRenderer.story.vue +117 -0
- package/src/components/GeneratedResultRenderer.vue +4 -4
- package/src/components/GroupAssigner.vue +67 -5
- package/src/components/IconButton.vue +0 -4
- package/src/components/InstrumentAlertLog.vue +0 -4
- package/src/components/InstrumentStateBadge.vue +0 -4
- package/src/components/InstrumentStatusCard.vue +0 -4
- package/src/components/JobsStatusTray.vue +16 -5
- package/src/components/LcmsSequenceTable.vue +81 -50
- package/src/components/LoadingSpinner.vue +0 -4
- package/src/components/MobileSupportGate.vue +0 -4
- package/src/components/MoleculeInput.vue +0 -4
- package/src/components/MultiSelect.vue +0 -4
- package/src/components/NumberInput.vue +0 -4
- package/src/components/PlateMapEditor.vue +3 -14
- package/src/components/PluginIcon.vue +0 -4
- package/src/components/PluginWorkspaceView.props.ts +38 -0
- package/src/components/PluginWorkspaceView.vue +53 -224
- package/src/components/ProgressBar.vue +0 -4
- package/src/components/ProtocolStepEditor.vue +2 -4
- package/src/components/RackEditor.vue +51 -70
- package/src/components/ReagentEditor.vue +0 -4
- package/src/components/ReagentList.story.vue +18 -0
- package/src/components/ReagentList.vue +127 -82
- package/src/components/ResourceCard.vue +0 -4
- package/src/components/RuntimeUpdateNotice.story.vue +49 -0
- package/src/components/RuntimeUpdateNotice.vue +4 -4
- package/src/components/SampleHierarchyTree.story.vue +18 -0
- package/src/components/SampleHierarchyTree.vue +32 -9
- package/src/components/SampleLegend.vue +2 -8
- package/src/components/SampleSelector.drag.ts +51 -0
- package/src/components/SampleSelector.story.vue +6 -0
- package/src/components/SampleSelector.tree.ts +2 -0
- package/src/components/SampleSelector.vue +49 -10
- package/src/components/SampleSelectorGroupNode.vue +19 -0
- package/src/components/ScheduleCalendar.story.vue +6 -0
- package/src/components/ScheduleCalendar.vue +82 -7
- package/src/components/ScientificNumber.vue +0 -4
- package/src/components/SecretInput.story.vue +79 -0
- package/src/components/SecretInput.vue +150 -0
- package/src/components/SectionCard.vue +0 -4
- package/src/components/SegmentedControl.vue +48 -5
- package/src/components/SequenceInput.vue +0 -4
- package/src/components/SequenceProgressBar.story.vue +110 -0
- package/src/components/SequenceProgressBar.vue +0 -4
- package/src/components/SettingsModal.vue +0 -4
- package/src/components/Skeleton.vue +0 -4
- package/src/components/SmartGroupFieldRecipe.vue +8 -6
- package/src/components/SmartGroupManual.vue +0 -4
- package/src/components/StatusIndicator.vue +0 -4
- package/src/components/StepWizard.vue +0 -4
- package/src/components/TagsInput.vue +4 -6
- package/src/components/ThemeToggle.vue +0 -4
- package/src/components/TimePicker.vue +36 -7
- package/src/components/TimeRangeInput.vue +0 -4
- package/src/components/Tooltip.story.vue +3 -3
- package/src/components/Tooltip.vue +0 -4
- package/src/components/UnitInput.vue +0 -4
- package/src/components/WellPlate.geometry.ts +60 -1
- package/src/components/WellPlate.interaction.ts +44 -0
- package/src/components/WellPlate.story.vue +8 -0
- package/src/components/WellPlate.vue +53 -7
- package/src/components/index.ts +9 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
- package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
- package/src/components/internal/DataStateInternal.vue +74 -0
- package/src/components/internal/FormSectionRenderer.vue +0 -4
- package/src/components/internal/WellEditPopupInternal.vue +20 -5
- package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
- package/src/components/internal/WorkspaceShellInternal.vue +125 -0
- package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
- package/src/composables/controlSchemaTypes.ts +34 -0
- package/src/composables/experimentDesignData.ts +1 -3
- package/src/composables/flattenWorkspaceBindings.ts +104 -0
- package/src/composables/formBuilderRegistry.ts +5 -0
- package/src/composables/index.ts +43 -0
- package/src/composables/pluginWorkspaceModel.ts +182 -0
- package/src/composables/useAsync.ts +5 -1
- package/src/composables/useAuth.ts +3 -0
- package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
- package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
- package/src/composables/useBioTemplateWorkspace.ts +18 -43
- package/src/composables/useCommandHistory.ts +5 -1
- package/src/composables/useDropdownState.ts +35 -5
- package/src/composables/useEventStream.ts +14 -2
- package/src/composables/useFileImport.ts +2 -7
- package/src/composables/useFocusTrap.ts +91 -0
- package/src/composables/useGeneratedAnalysis.ts +22 -10
- package/src/composables/useJobsStatusTray.ts +184 -17
- package/src/composables/useListReorder.story.vue +143 -0
- package/src/composables/useListReorder.ts +242 -0
- package/src/composables/useMenuKeyboard.ts +90 -0
- package/src/composables/useOptimisticMutation.ts +6 -16
- package/src/composables/usePluginClient.ts +47 -4
- package/src/composables/usePluginConfig.ts +252 -0
- package/src/composables/usePluginWorkspace.ts +208 -0
- package/src/composables/useRackEditor.ts +2 -11
- package/src/composables/useRequestSyncState.ts +98 -28
- package/src/composables/useResourceCrud.ts +6 -16
- package/src/composables/useRovingFocus.ts +66 -0
- package/src/composables/useScheduleDrag.ts +37 -0
- package/src/composables/useWellPainting.ts +10 -30
- package/src/composables/useWellPlateAdapter.ts +5 -1
- package/src/composables/useWellPlateEditor.ts +1 -11
- package/src/composables/useWellPlateValidation.ts +5 -1
- package/src/composables/workspaceRecipes.ts +267 -0
- package/src/index.ts +16 -0
- package/src/permissions.ts +48 -9
- package/src/stores/auth.ts +1 -0
- package/src/stores/experiment.ts +2 -7
- package/src/stores/index.ts +9 -1
- package/src/stores/settings.ts +93 -1
- package/src/styles/components/audit-trail.css +5 -5
- package/src/styles/components/batch-progress-list.css +7 -0
- package/src/styles/components/color-slider.css +2 -2
- package/src/styles/components/data-state.css +30 -0
- package/src/styles/components/dataframe.css +29 -35
- package/src/styles/components/experiment-data-viewer.css +4 -4
- package/src/styles/components/experiment-timeline.css +3 -30
- package/src/styles/components/form-builder.css +5 -5
- package/src/styles/components/lcms-sequence-table.css +16 -1
- package/src/styles/components/multi-select.css +1 -1
- package/src/styles/components/reagent-list.css +38 -31
- package/src/styles/components/sample-hierarchy-tree.css +5 -19
- package/src/styles/components/sample-selector.css +9 -2
- package/src/styles/components/schedule-calendar.css +8 -0
- package/src/styles/components/scientific-number.css +1 -1
- package/src/styles/components/secret-input.css +60 -0
- package/src/styles/components/well-plate.css +8 -0
- package/src/styles/index.css +9 -0
- package/src/styles/variables.css +29 -0
- package/src/templates/builderPresetControls.ts +1 -1
- package/src/templates/builderReadUtils.ts +1 -3
- package/src/templates/componentPlateHelpers.ts +3 -1
- package/src/templates/componentTargetResolvers.ts +1 -3
- package/src/templates/controlSchemaTargets.ts +1 -3
- package/src/templates/doseResponseAdapters.ts +2 -13
- package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
- package/src/templates/plateMapBuilder.ts +2 -2
- package/src/templates/qpcrPlateBuilder.ts +1 -1
- package/src/templates/templateValidators.ts +1 -1
- package/src/templates/timeCourseBuilder.ts +2 -2
- package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
- package/src/types/auth.ts +1 -0
- package/src/types/form-builder.ts +1 -0
- package/src/utils/color.ts +51 -0
- package/src/utils/errors.ts +29 -0
- package/src/utils/formModelSync.ts +1 -3
- package/src/utils/pluginIcon.ts +1 -3
- package/src/utils/pluginSecrets.ts +102 -0
- package/src/utils/records.ts +9 -0
- package/dist/EmptyState-p90quq8Z.js.map +0 -1
- package/dist/ExperimentPopover-tLwJ7XF4.js.map +0 -1
- package/dist/ExperimentSelectorModal-DyPAUCLw.js.map +0 -1
- package/dist/SettingsModal-BgKY93zh.js.map +0 -1
- package/dist/components-DkHddRMl.js.map +0 -1
- package/dist/composables/autoGroupConstants.d.ts +0 -1
- package/dist/composables/autoGroupGrouping.d.ts +0 -3
- package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
- package/dist/composables-C_i90sGS.js.map +0 -1
- package/dist/experiment-Cq145oEW.js.map +0 -1
- package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
- package/dist/templates/builderDefaults.d.ts +0 -1
- package/dist/templates/plateGeometry.d.ts +0 -4
- package/dist/templates-DsP51i-S.js.map +0 -1
- package/dist/useApi-J6zFkHmn.js.map +0 -1
- package/dist/useControlSchema-BDx2Olha.js.map +0 -1
- package/dist/useDropdownState-DSb3BC3N.js.map +0 -1
- package/dist/useProtocolTemplates-CS_SQl6U.js.map +0 -1
- package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
- package/src/composables/autoGroupConstants.ts +0 -4
- package/src/composables/autoGroupGrouping.ts +0 -148
- package/src/composables/useAutoGroupInputSources.ts +0 -147
- package/src/templates/builderDefaults.ts +0 -11
- package/src/templates/plateGeometry.ts +0 -62
- /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
|
|
2
|
+
import { useApi } from './useApi'
|
|
3
|
+
import { usePlatformContext } from './usePlatformContext'
|
|
4
|
+
import { getInjectedPlatformContext } from './platformContextHelpers'
|
|
5
|
+
import { useRequestSyncState } from './useRequestSyncState'
|
|
6
|
+
|
|
7
|
+
const PLATFORM_OWNED_SETTINGS_KEY = 'allowed_experiment_types'
|
|
8
|
+
|
|
9
|
+
function cloneConfig(config: Record<string, unknown>): Record<string, unknown> {
|
|
10
|
+
return JSON.parse(JSON.stringify(config)) as Record<string, unknown>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function hasOwnConfig(config: Record<string, unknown>, key: string): boolean {
|
|
14
|
+
return Object.prototype.hasOwnProperty.call(config, key)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function changedTopLevelKeys(
|
|
18
|
+
current: Record<string, unknown>,
|
|
19
|
+
saved: Record<string, unknown>,
|
|
20
|
+
): Set<string> {
|
|
21
|
+
return new Set(
|
|
22
|
+
[...new Set([...Object.keys(current), ...Object.keys(saved)])].filter(
|
|
23
|
+
key => JSON.stringify(current[key]) !== JSON.stringify(saved[key]),
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function topLevelConfigPatch(
|
|
29
|
+
current: Record<string, unknown>,
|
|
30
|
+
keys: Iterable<string>,
|
|
31
|
+
): Record<string, unknown> {
|
|
32
|
+
return Object.fromEntries(
|
|
33
|
+
[...keys].map(key => [
|
|
34
|
+
key,
|
|
35
|
+
hasOwnConfig(current, key) ? current[key] : null,
|
|
36
|
+
]),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function platformOwnedConfigPatch(config: Record<string, unknown>): Record<string, unknown> {
|
|
41
|
+
return {
|
|
42
|
+
[PLATFORM_OWNED_SETTINGS_KEY]: hasOwnConfig(config, PLATFORM_OWNED_SETTINGS_KEY)
|
|
43
|
+
? config[PLATFORM_OWNED_SETTINGS_KEY]
|
|
44
|
+
: null,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function responseConfigPayload(response: {
|
|
49
|
+
config?: Record<string, unknown>
|
|
50
|
+
settings?: Record<string, unknown>
|
|
51
|
+
}): Record<string, unknown> {
|
|
52
|
+
return response.config ?? response.settings ?? {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UsePluginConfigReturn {
|
|
56
|
+
/** Editable plugin configuration values. */
|
|
57
|
+
config: Ref<Record<string, unknown>>
|
|
58
|
+
/** Whether the config is currently loading. */
|
|
59
|
+
isLoading: Ref<boolean>
|
|
60
|
+
/** Whether the config is currently saving. */
|
|
61
|
+
isSaving: Ref<boolean>
|
|
62
|
+
/** Error message from the last failed operation, or null. */
|
|
63
|
+
error: Ref<string | null>
|
|
64
|
+
/** Timestamp of the last successful load, or null. */
|
|
65
|
+
lastLoadedAt: Ref<Date | null>
|
|
66
|
+
/** Timestamp of the last successful save, or null. */
|
|
67
|
+
lastSavedAt: Ref<Date | null>
|
|
68
|
+
/** Whether local config differs from the last loaded or saved snapshot. */
|
|
69
|
+
isDirty: ComputedRef<boolean>
|
|
70
|
+
/** Load plugin configuration from the platform. */
|
|
71
|
+
load: () => Promise<void>
|
|
72
|
+
/** Save plugin configuration to the platform. */
|
|
73
|
+
save: () => Promise<boolean>
|
|
74
|
+
/** Restore the last loaded or saved configuration snapshot. */
|
|
75
|
+
reset: () => void
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `usePluginSettings()` for typed plugin settings.
|
|
80
|
+
*/
|
|
81
|
+
export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
|
|
82
|
+
const injectedContext = getInjectedPlatformContext()
|
|
83
|
+
const { plugin } = usePlatformContext()
|
|
84
|
+
const platformApi = useApi()
|
|
85
|
+
const pluginApi = useApi({
|
|
86
|
+
baseUrl: injectedContext?.plugin?.api_prefix ?? plugin.value?.api_prefix,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const resolvedName = computed(
|
|
90
|
+
() => pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? '',
|
|
91
|
+
)
|
|
92
|
+
const usesPluginSettingsApi = computed(() => {
|
|
93
|
+
const currentPlugin = injectedContext?.plugin ?? plugin.value
|
|
94
|
+
return currentPlugin?.settings_api === 'plugin'
|
|
95
|
+
&& resolvedName.value === currentPlugin.name
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const config = ref<Record<string, unknown>>({})
|
|
99
|
+
const savedConfig = ref<Record<string, unknown>>({})
|
|
100
|
+
const revision = ref<string | null>(null)
|
|
101
|
+
const request = useRequestSyncState('Plugin config request failed.')
|
|
102
|
+
const isLoading = ref(false)
|
|
103
|
+
const isSaving = ref(false)
|
|
104
|
+
const error = request.error
|
|
105
|
+
const lastLoadedAt = request.lastLoadedAt
|
|
106
|
+
const lastSavedAt = request.lastSavedAt
|
|
107
|
+
|
|
108
|
+
const isDirty = computed(() => {
|
|
109
|
+
return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
async function load(): Promise<void> {
|
|
113
|
+
const name = resolvedName.value
|
|
114
|
+
if (!name) return
|
|
115
|
+
|
|
116
|
+
isLoading.value = true
|
|
117
|
+
try {
|
|
118
|
+
const response = usesPluginSettingsApi.value
|
|
119
|
+
? await request.run(
|
|
120
|
+
() => pluginApi.get<{
|
|
121
|
+
settings: Record<string, unknown>
|
|
122
|
+
mode?: string
|
|
123
|
+
revision?: string
|
|
124
|
+
}>('/settings'),
|
|
125
|
+
{ success: 'load', errorMessage: 'Failed to load plugin config' },
|
|
126
|
+
)
|
|
127
|
+
: await request.run(
|
|
128
|
+
() => platformApi.get<{
|
|
129
|
+
plugin_name: string
|
|
130
|
+
config: Record<string, unknown>
|
|
131
|
+
revision?: string
|
|
132
|
+
}>(
|
|
133
|
+
`/plugins/${encodeURIComponent(name)}/config`,
|
|
134
|
+
),
|
|
135
|
+
{ success: 'load', errorMessage: 'Failed to load plugin config' },
|
|
136
|
+
)
|
|
137
|
+
const responseConfig = responseConfigPayload(response)
|
|
138
|
+
config.value = cloneConfig(responseConfig)
|
|
139
|
+
savedConfig.value = cloneConfig(responseConfig)
|
|
140
|
+
revision.value = response.revision ?? null
|
|
141
|
+
} catch {
|
|
142
|
+
// Error state is handled by request.run().
|
|
143
|
+
} finally {
|
|
144
|
+
isLoading.value = false
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function save(): Promise<boolean> {
|
|
149
|
+
const name = resolvedName.value
|
|
150
|
+
if (!name) return false
|
|
151
|
+
|
|
152
|
+
isSaving.value = true
|
|
153
|
+
try {
|
|
154
|
+
const payload = cloneConfig(config.value)
|
|
155
|
+
let response: {
|
|
156
|
+
plugin_name?: string
|
|
157
|
+
config?: Record<string, unknown>
|
|
158
|
+
settings?: Record<string, unknown>
|
|
159
|
+
mode?: string
|
|
160
|
+
revision?: string
|
|
161
|
+
} | undefined
|
|
162
|
+
let nextRevision = revision.value
|
|
163
|
+
|
|
164
|
+
if (usesPluginSettingsApi.value) {
|
|
165
|
+
response = await request.run(
|
|
166
|
+
() => pluginApi.put<{
|
|
167
|
+
settings: Record<string, unknown>
|
|
168
|
+
mode?: string
|
|
169
|
+
revision?: string
|
|
170
|
+
}>(
|
|
171
|
+
'/settings',
|
|
172
|
+
payload,
|
|
173
|
+
revision.value
|
|
174
|
+
? { headers: { 'If-Match': `"${revision.value}"` } }
|
|
175
|
+
: undefined,
|
|
176
|
+
),
|
|
177
|
+
{ success: 'save', errorMessage: 'Failed to save plugin config' },
|
|
178
|
+
)
|
|
179
|
+
nextRevision = response.revision ?? nextRevision
|
|
180
|
+
} else {
|
|
181
|
+
const dirtyKeys = changedTopLevelKeys(payload, savedConfig.value)
|
|
182
|
+
const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY)
|
|
183
|
+
const typedKeys = [...dirtyKeys].filter(
|
|
184
|
+
key => key !== PLATFORM_OWNED_SETTINGS_KEY,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if (typedKeys.length) {
|
|
188
|
+
response = await request.run(
|
|
189
|
+
() => platformApi.patch<{
|
|
190
|
+
plugin_name: string
|
|
191
|
+
config: Record<string, unknown>
|
|
192
|
+
revision?: string
|
|
193
|
+
}>(
|
|
194
|
+
`/plugins/${encodeURIComponent(name)}/config`,
|
|
195
|
+
{ config: topLevelConfigPatch(payload, typedKeys) },
|
|
196
|
+
),
|
|
197
|
+
{ success: 'save', errorMessage: 'Failed to save plugin config' },
|
|
198
|
+
)
|
|
199
|
+
nextRevision = response.revision ?? nextRevision
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (platformOwnedChanged) {
|
|
203
|
+
response = await request.run(
|
|
204
|
+
() => platformApi.patch<{
|
|
205
|
+
plugin_name: string
|
|
206
|
+
config: Record<string, unknown>
|
|
207
|
+
revision?: string
|
|
208
|
+
}>(
|
|
209
|
+
`/plugins/${encodeURIComponent(name)}/config`,
|
|
210
|
+
{ config: platformOwnedConfigPatch(payload) },
|
|
211
|
+
),
|
|
212
|
+
{ success: 'save', errorMessage: 'Failed to save plugin config' },
|
|
213
|
+
)
|
|
214
|
+
nextRevision = response.revision ?? nextRevision
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!response) return true
|
|
219
|
+
const responseConfig = responseConfigPayload(response)
|
|
220
|
+
config.value = cloneConfig(responseConfig)
|
|
221
|
+
savedConfig.value = cloneConfig(responseConfig)
|
|
222
|
+
revision.value = response.revision ?? nextRevision
|
|
223
|
+
return true
|
|
224
|
+
} catch {
|
|
225
|
+
return false
|
|
226
|
+
} finally {
|
|
227
|
+
isSaving.value = false
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function reset(): void {
|
|
232
|
+
config.value = cloneConfig(savedConfig.value)
|
|
233
|
+
request.clearError()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
onMounted(() => {
|
|
237
|
+
load()
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
config,
|
|
242
|
+
isLoading,
|
|
243
|
+
isSaving,
|
|
244
|
+
error,
|
|
245
|
+
lastLoadedAt,
|
|
246
|
+
lastSavedAt,
|
|
247
|
+
isDirty,
|
|
248
|
+
load,
|
|
249
|
+
save,
|
|
250
|
+
reset,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
import type { ComputedRef } from 'vue'
|
|
3
|
+
import type { PageSelectorItem, PillNavItem } from '../types'
|
|
4
|
+
import type {
|
|
5
|
+
PluginWorkspaceViewEmit,
|
|
6
|
+
PluginWorkspaceViewProps,
|
|
7
|
+
} from '../components/PluginWorkspaceView.props'
|
|
8
|
+
import { usePluginWorkspaceControls } from '../components/PluginWorkspaceView.controls'
|
|
9
|
+
import { usePluginWorkspaceNavigation } from '../components/PluginWorkspaceView.navigation'
|
|
10
|
+
import {
|
|
11
|
+
getPluginWorkspaceSidebarSlotNames,
|
|
12
|
+
getPluginWorkspaceTopBarSlotNames,
|
|
13
|
+
hasPluginWorkspaceSidebarSurface,
|
|
14
|
+
} from '../components/PluginWorkspaceView.shell'
|
|
15
|
+
import type {
|
|
16
|
+
PluginWorkspaceControlsModel,
|
|
17
|
+
PluginWorkspaceShellModel,
|
|
18
|
+
PluginWorkspaceSidebarModel,
|
|
19
|
+
} from './pluginWorkspaceModel'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Loosely-typed view of a component's `useSlots()` result: only slot presence and names are
|
|
23
|
+
* read here, so this accepts any `defineSlots()` shape without fighting Vue's `Slot` render
|
|
24
|
+
* function type. Mirrors the `SlotKeyMap` helper type in PluginWorkspaceView.shell.ts.
|
|
25
|
+
*/
|
|
26
|
+
export type PluginWorkspaceSlotsLike = Readonly<Record<string, unknown>>
|
|
27
|
+
|
|
28
|
+
export interface UsePluginWorkspaceOptions {
|
|
29
|
+
/** PluginWorkspaceView's own reactive props object (from `defineProps`). */
|
|
30
|
+
props: PluginWorkspaceViewProps
|
|
31
|
+
/** PluginWorkspaceView's own slots (from `useSlots()`), used to detect provided passthrough slots. */
|
|
32
|
+
slots: PluginWorkspaceSlotsLike
|
|
33
|
+
/** PluginWorkspaceView's own typed `emit` function (from `defineEmits<PluginWorkspaceViewEmits>()`). */
|
|
34
|
+
emit: PluginWorkspaceViewEmit
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UsePluginWorkspaceReturn {
|
|
38
|
+
/** Model consumed by WorkspaceShellInternal's `shell` prop. */
|
|
39
|
+
shell: ComputedRef<PluginWorkspaceShellModel>
|
|
40
|
+
/** Model consumed by WorkspaceSidebarInternal's `sidebar` prop. */
|
|
41
|
+
sidebar: ComputedRef<PluginWorkspaceSidebarModel>
|
|
42
|
+
/** Model consumed by WorkspaceControlsInternal's `controls` prop, and by the raw `default` slot. */
|
|
43
|
+
controls: ComputedRef<PluginWorkspaceControlsModel>
|
|
44
|
+
/** Whether AppLayout should render a sidebar surface at all. */
|
|
45
|
+
hasSidebarSurface: ComputedRef<boolean>
|
|
46
|
+
/** Applies an AppSidebar `update:model-value` payload to the shared control values and emits `update:modelValue`/`update:values`. */
|
|
47
|
+
handleControlValuesUpdate: (values: Record<string, unknown>) => void
|
|
48
|
+
/** Resolves an AppTopBar `page-selector-select` payload into the active view and emits `page-selector-select`. */
|
|
49
|
+
handlePageSelectorSelect: (page: PageSelectorItem) => void
|
|
50
|
+
/** Resolves an AppTopBar `pill-select` payload into the active view and emits `pill-select`. */
|
|
51
|
+
handlePillSelect: (item: PillNavItem) => void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Maps PluginWorkspaceView's flat 72-prop surface into the three typed model objects
|
|
56
|
+
* (`shell`, `sidebar`, `controls`) rendered by its internal pieces
|
|
57
|
+
* (WorkspaceShellInternal, WorkspaceSidebarInternal, WorkspaceControlsInternal). Reuses the
|
|
58
|
+
* existing `usePluginWorkspaceControls` / `usePluginWorkspaceNavigation` composables and the
|
|
59
|
+
* PluginWorkspaceView.shell slot-detection helpers, so it changes only how that state is
|
|
60
|
+
* assembled and exposed, not the underlying behavior.
|
|
61
|
+
*
|
|
62
|
+
* @example Recommended
|
|
63
|
+
* ```ts
|
|
64
|
+
* const props = withDefaults(defineProps<PluginWorkspaceViewProps>(), { ... })
|
|
65
|
+
* const emit = defineEmits<PluginWorkspaceViewEmits>()
|
|
66
|
+
* const slots = useSlots()
|
|
67
|
+
* const { shell, sidebar, controls } = usePluginWorkspace({ props, slots, emit })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function usePluginWorkspace(options: UsePluginWorkspaceOptions): UsePluginWorkspaceReturn {
|
|
71
|
+
const { props, slots, emit } = options
|
|
72
|
+
|
|
73
|
+
const controlsState = usePluginWorkspaceControls({
|
|
74
|
+
model: () => props.model,
|
|
75
|
+
controls: () => props.controls,
|
|
76
|
+
controlOptions: () => props.controlOptions ?? {},
|
|
77
|
+
modelValue: () => props.modelValue,
|
|
78
|
+
values: () => props.values,
|
|
79
|
+
componentBindings: () => props.componentBindings,
|
|
80
|
+
componentProps: () => props.componentProps,
|
|
81
|
+
componentPropsById: () => props.componentPropsById,
|
|
82
|
+
emitValues: (values) => {
|
|
83
|
+
emit('update:modelValue', values)
|
|
84
|
+
emit('update:values', values)
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const navigationState = usePluginWorkspaceNavigation({
|
|
89
|
+
activeView: () => props.activeView,
|
|
90
|
+
defaultActiveView: () => props.defaultActiveView,
|
|
91
|
+
currentPillId: () => props.currentPillId,
|
|
92
|
+
currentPageSelectorId: () => props.currentPageSelectorId,
|
|
93
|
+
pillNav: () => props.pillNav,
|
|
94
|
+
pageSelector: () => props.pageSelector,
|
|
95
|
+
panelIds: () => Object.keys(props.panels ?? {}),
|
|
96
|
+
generatedPillNav: controlsState.generatedPillNav,
|
|
97
|
+
generatedDefaultView: controlsState.generatedDefaultView,
|
|
98
|
+
emitActiveView: viewId => emit('update:activeView', viewId),
|
|
99
|
+
emitPageSelectorSelect: page => emit('page-selector-select', page),
|
|
100
|
+
emitPillSelect: item => emit('pill-select', item),
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const topBarSlotNames = computed<string[]>(() => getPluginWorkspaceTopBarSlotNames(slots))
|
|
104
|
+
const sidebarSlotNames = computed<string[]>(() => getPluginWorkspaceSidebarSlotNames(slots))
|
|
105
|
+
|
|
106
|
+
const hasSidebarSurface = computed(() => hasPluginWorkspaceSidebarSurface({
|
|
107
|
+
showSidebar: props.showSidebar ?? true,
|
|
108
|
+
panels: props.panels ?? {},
|
|
109
|
+
forms: props.forms ?? {},
|
|
110
|
+
model: props.model,
|
|
111
|
+
controls: props.controls,
|
|
112
|
+
showSidebarWhenEmpty: props.showSidebarWhenEmpty ?? false,
|
|
113
|
+
sidebarContentId: props.sidebarContentId,
|
|
114
|
+
sidebarTitle: props.sidebarTitle,
|
|
115
|
+
sidebarSubtitle: props.sidebarSubtitle,
|
|
116
|
+
sidebarBadge: props.sidebarBadge,
|
|
117
|
+
hasSidebarSlot: Boolean(slots.sidebar),
|
|
118
|
+
sidebarSlotCount: sidebarSlotNames.value.length,
|
|
119
|
+
}))
|
|
120
|
+
|
|
121
|
+
const controls = computed<PluginWorkspaceControlsModel>(() => ({
|
|
122
|
+
activeView: navigationState.resolvedActiveView.value,
|
|
123
|
+
setActiveView: navigationState.setActiveView,
|
|
124
|
+
values: controlsState.internalControlValues,
|
|
125
|
+
componentBindings: controlsState.resolvedComponentBindings.value,
|
|
126
|
+
componentBindingsById: controlsState.resolvedComponentBindingsById.value,
|
|
127
|
+
componentProps: controlsState.resolvedComponentProps.value,
|
|
128
|
+
componentPropsById: controlsState.resolvedComponentPropsById.value,
|
|
129
|
+
}))
|
|
130
|
+
|
|
131
|
+
const shell = computed<PluginWorkspaceShellModel>(() => ({
|
|
132
|
+
sidebarPosition: props.sidebarPosition ?? 'left',
|
|
133
|
+
sidebarWidth: props.sidebarWidth,
|
|
134
|
+
floating: props.floating ?? true,
|
|
135
|
+
responsiveSidebar: props.responsiveSidebar ?? true,
|
|
136
|
+
sidebarToggleLabel: props.sidebarToggleLabel ?? 'Open workspace sidebar',
|
|
137
|
+
sidebarCloseLabel: props.sidebarCloseLabel ?? 'Close workspace sidebar',
|
|
138
|
+
hasSidebarSurface: hasSidebarSurface.value,
|
|
139
|
+
title: props.title ?? 'Workspace',
|
|
140
|
+
subtitle: props.subtitle,
|
|
141
|
+
topBarVariant: props.topBarVariant ?? 'card',
|
|
142
|
+
homePath: props.homePath ?? '/',
|
|
143
|
+
showLogo: props.showLogo ?? true,
|
|
144
|
+
pageSelector: props.pageSelector,
|
|
145
|
+
currentPageSelectorId: navigationState.resolvedCurrentPageSelectorId.value,
|
|
146
|
+
pluginSwitcher: props.pluginSwitcher,
|
|
147
|
+
pillNav: navigationState.resolvedPillNav.value,
|
|
148
|
+
currentPillId: navigationState.resolvedCurrentPillId.value,
|
|
149
|
+
showThemeToggle: props.showThemeToggle ?? true,
|
|
150
|
+
showSettings: props.showSettings ?? true,
|
|
151
|
+
settingsConfig: props.settingsConfig,
|
|
152
|
+
showStandaloneLabel: props.showStandaloneLabel ?? false,
|
|
153
|
+
standaloneLabel: props.standaloneLabel ?? 'Standalone',
|
|
154
|
+
accountMenu: props.accountMenu,
|
|
155
|
+
showNotifications: props.showNotifications ?? false,
|
|
156
|
+
hasNotificationDot: props.hasNotificationDot ?? false,
|
|
157
|
+
showAdmin: props.showAdmin ?? false,
|
|
158
|
+
adminPath: props.adminPath ?? '/admin',
|
|
159
|
+
userName: props.userName,
|
|
160
|
+
userInitial: props.userInitial,
|
|
161
|
+
userEmail: props.userEmail,
|
|
162
|
+
topBarSlotNames: topBarSlotNames.value,
|
|
163
|
+
controls: controls.value,
|
|
164
|
+
}))
|
|
165
|
+
|
|
166
|
+
const sidebar = computed<PluginWorkspaceSidebarModel>(() => ({
|
|
167
|
+
title: props.sidebarTitle,
|
|
168
|
+
subtitle: props.sidebarSubtitle,
|
|
169
|
+
badge: props.sidebarBadge,
|
|
170
|
+
variant: props.sidebarVariant ?? 'analysis',
|
|
171
|
+
panels: props.panels ?? {},
|
|
172
|
+
activeView: navigationState.resolvedActiveView.value,
|
|
173
|
+
floating: false,
|
|
174
|
+
dense: props.dense ?? true,
|
|
175
|
+
width: props.sidebarWidth,
|
|
176
|
+
side: props.sidebarPosition ?? 'left',
|
|
177
|
+
forms: props.forms ?? {},
|
|
178
|
+
contentId: props.sidebarContentId,
|
|
179
|
+
showWhenEmpty: (props.showSidebarWhenEmpty ?? false) || props.sidebarContentId !== undefined,
|
|
180
|
+
model: props.model,
|
|
181
|
+
controlSchema: props.controls,
|
|
182
|
+
controlOptions: props.controlOptions ?? {},
|
|
183
|
+
values: controlsState.internalControlValues,
|
|
184
|
+
formEnhancements: props.formEnhancements,
|
|
185
|
+
showFormActions: props.showFormActions ?? false,
|
|
186
|
+
formLoading: props.formLoading ?? false,
|
|
187
|
+
formDisabled: props.formDisabled ?? false,
|
|
188
|
+
formReadonly: props.formReadonly ?? false,
|
|
189
|
+
formSize: props.formSize ?? 'sm',
|
|
190
|
+
collapsible: props.sidebarCollapsible,
|
|
191
|
+
collapsed: props.sidebarCollapsed,
|
|
192
|
+
defaultCollapsed: props.defaultSidebarCollapsed ?? false,
|
|
193
|
+
collapsedWidth: props.sidebarCollapsedWidth ?? '3rem',
|
|
194
|
+
collapseButtonLabel: props.sidebarCollapseButtonLabel ?? 'Collapse sidebar',
|
|
195
|
+
expandButtonLabel: props.sidebarExpandButtonLabel ?? 'Expand sidebar',
|
|
196
|
+
slotNames: sidebarSlotNames.value,
|
|
197
|
+
}))
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
shell,
|
|
201
|
+
sidebar,
|
|
202
|
+
controls,
|
|
203
|
+
hasSidebarSurface,
|
|
204
|
+
handleControlValuesUpdate: controlsState.handleControlValuesUpdate,
|
|
205
|
+
handlePageSelectorSelect: navigationState.handlePageSelectorSelect,
|
|
206
|
+
handlePillSelect: navigationState.handlePillSelect,
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ref, computed, type Ref, type ComputedRef } from 'vue'
|
|
2
2
|
import type { Rack, SlotPosition, WellPlateFormat, Well } from '../types'
|
|
3
|
+
import { PLATE_CONFIGS, rowLabel as plateRowLabel } from '../components/WellPlate.geometry'
|
|
3
4
|
|
|
4
5
|
export interface UseRackEditorOptions {
|
|
5
6
|
defaultFormat?: WellPlateFormat
|
|
@@ -31,15 +32,6 @@ export interface UseRackEditorReturn {
|
|
|
31
32
|
|
|
32
33
|
const SLOT_CYCLE: SlotPosition[] = ['R', 'G', 'B', 'Y']
|
|
33
34
|
|
|
34
|
-
const PLATE_CONFIGS: Record<WellPlateFormat, { rows: number; cols: number }> = {
|
|
35
|
-
6: { rows: 2, cols: 3 },
|
|
36
|
-
12: { rows: 3, cols: 4 },
|
|
37
|
-
24: { rows: 4, cols: 6 },
|
|
38
|
-
48: { rows: 6, cols: 8 },
|
|
39
|
-
54: { rows: 6, cols: 9 },
|
|
40
|
-
96: { rows: 8, cols: 12 },
|
|
41
|
-
384: { rows: 16, cols: 24 },
|
|
42
|
-
}
|
|
43
35
|
|
|
44
36
|
let globalIdCounter = 0
|
|
45
37
|
|
|
@@ -156,8 +148,7 @@ export function useRackEditor(
|
|
|
156
148
|
let counter = 1
|
|
157
149
|
for (let row = 0; row < config.rows; row++) {
|
|
158
150
|
for (let col = 0; col < config.cols; col++) {
|
|
159
|
-
const
|
|
160
|
-
const wellId = `${rowLabel}${col + 1}`
|
|
151
|
+
const wellId = `${plateRowLabel(row)}${col + 1}`
|
|
161
152
|
// Only fill empty wells
|
|
162
153
|
if (!rack.wells[wellId]?.sampleType) {
|
|
163
154
|
const paddedNum = String(counter).padStart(3, '0')
|
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import { ref, type Ref } from 'vue'
|
|
2
|
+
import { readErrorMessage as readSharedErrorMessage } from '../utils/errors'
|
|
2
3
|
|
|
3
4
|
export type RequestSyncSuccessKind = 'load' | 'save' | 'run'
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The unit of work `run()` drives.
|
|
8
|
+
*
|
|
9
|
+
* It receives an `AbortSignal` that fires when the run is superseded by a
|
|
10
|
+
* newer `run()` or by `cancel()`. Zero-argument operations stay valid and
|
|
11
|
+
* simply ignore it.
|
|
12
|
+
*/
|
|
13
|
+
export type RequestSyncOperation<T> = (signal: AbortSignal) => Promise<T>
|
|
14
|
+
|
|
5
15
|
export interface RequestSyncRunOptions {
|
|
6
16
|
success?: RequestSyncSuccessKind
|
|
7
17
|
errorMessage?: string
|
|
18
|
+
/**
|
|
19
|
+
* Abort the previously started run when this one starts. Default `true`;
|
|
20
|
+
* pass `false` to let overlapping runs finish their own work (the stale
|
|
21
|
+
* guard on shared state still applies either way).
|
|
22
|
+
*/
|
|
23
|
+
abortPrevious?: boolean
|
|
24
|
+
/** Caller-owned signal; aborting it aborts this run's signal too. */
|
|
25
|
+
signal?: AbortSignal
|
|
8
26
|
}
|
|
9
27
|
|
|
10
28
|
export interface UseRequestSyncStateReturn {
|
|
@@ -30,8 +48,32 @@ export interface UseRequestSyncStateReturn {
|
|
|
30
48
|
markSaved: (date?: Date) => void
|
|
31
49
|
/** Mark the operation as successfully run. */
|
|
32
50
|
markRun: (date?: Date) => void
|
|
33
|
-
/**
|
|
34
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Run a request with shared loading/error handling and optional sync marking.
|
|
53
|
+
*
|
|
54
|
+
* ## Stale-response semantics
|
|
55
|
+
*
|
|
56
|
+
* Every call takes a monotonically increasing token. Starting a run
|
|
57
|
+
* supersedes any run started before it, and a superseded run writes **no**
|
|
58
|
+
* shared state when it finally settles: it does not clear `loading`, does not
|
|
59
|
+
* set `error`, and does not move `lastLoadedAt` / `lastSavedAt` / `lastRunAt`.
|
|
60
|
+
* Only the newest run owns those.
|
|
61
|
+
*
|
|
62
|
+
* The caller's own promise is untouched by the guard — a superseded run still
|
|
63
|
+
* resolves with its value or rejects with its own error, exactly as the
|
|
64
|
+
* operation did. The guard never invents a rejection, so a caller awaiting
|
|
65
|
+
* `run()` never sees a "superseded" error it did not cause.
|
|
66
|
+
*
|
|
67
|
+
* The operation additionally receives an `AbortSignal` that fires when its
|
|
68
|
+
* run is superseded or `cancel()` is called, so an operation that forwards
|
|
69
|
+
* the signal (`fetch(url, { signal })`) stops work instead of racing.
|
|
70
|
+
*/
|
|
71
|
+
run: <T>(operation: RequestSyncOperation<T>, options?: RequestSyncRunOptions) => Promise<T>
|
|
72
|
+
/**
|
|
73
|
+
* Abort the in-flight run and drop `loading`. The aborted run's completion
|
|
74
|
+
* is treated as stale and writes no state.
|
|
75
|
+
*/
|
|
76
|
+
cancel: () => void
|
|
35
77
|
}
|
|
36
78
|
|
|
37
79
|
/** Shared loading/error/timestamp state for generated plugin request helpers. */
|
|
@@ -49,22 +91,7 @@ export function useRequestSyncState(
|
|
|
49
91
|
}
|
|
50
92
|
|
|
51
93
|
function readErrorMessage(value: unknown, fallback = defaultErrorMessage): string {
|
|
52
|
-
|
|
53
|
-
return value.message
|
|
54
|
-
}
|
|
55
|
-
if (typeof value === 'string' && value.trim()) {
|
|
56
|
-
return value
|
|
57
|
-
}
|
|
58
|
-
if (
|
|
59
|
-
typeof value === 'object'
|
|
60
|
-
&& value !== null
|
|
61
|
-
&& 'message' in value
|
|
62
|
-
&& typeof (value as { message?: unknown }).message === 'string'
|
|
63
|
-
&& (value as { message: string }).message
|
|
64
|
-
) {
|
|
65
|
-
return (value as { message: string }).message
|
|
66
|
-
}
|
|
67
|
-
return fallback
|
|
94
|
+
return readSharedErrorMessage(value, fallback)
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
function setError(value: unknown, fallback?: string): string {
|
|
@@ -85,27 +112,69 @@ export function useRequestSyncState(
|
|
|
85
112
|
lastRunAt.value = date
|
|
86
113
|
}
|
|
87
114
|
|
|
115
|
+
// Monotonic token guarding every write to the shared loading/error/timestamp
|
|
116
|
+
// state. Overlapping runs settle in any order, and a superseded one must not
|
|
117
|
+
// clear a newer run's loading flag or overwrite its error.
|
|
118
|
+
let runToken = 0
|
|
119
|
+
let activeController: AbortController | null = null
|
|
120
|
+
|
|
121
|
+
function cancel(): void {
|
|
122
|
+
runToken += 1
|
|
123
|
+
activeController?.abort()
|
|
124
|
+
activeController = null
|
|
125
|
+
loading.value = false
|
|
126
|
+
}
|
|
127
|
+
|
|
88
128
|
async function run<T>(
|
|
89
|
-
operation:
|
|
129
|
+
operation: RequestSyncOperation<T>,
|
|
90
130
|
options: RequestSyncRunOptions = {},
|
|
91
131
|
): Promise<T> {
|
|
132
|
+
if (options.abortPrevious !== false) {
|
|
133
|
+
activeController?.abort()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const token = (runToken += 1)
|
|
137
|
+
const controller = new AbortController()
|
|
138
|
+
activeController = controller
|
|
139
|
+
|
|
140
|
+
const callerSignal = options.signal
|
|
141
|
+
if (callerSignal) {
|
|
142
|
+
if (callerSignal.aborted) controller.abort(callerSignal.reason)
|
|
143
|
+
else callerSignal.addEventListener(
|
|
144
|
+
'abort',
|
|
145
|
+
() => controller.abort(callerSignal.reason),
|
|
146
|
+
{ once: true },
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const isCurrent = (): boolean => token === runToken
|
|
151
|
+
|
|
92
152
|
loading.value = true
|
|
93
153
|
clearError()
|
|
94
154
|
try {
|
|
95
|
-
const response = await operation()
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
155
|
+
const response = await operation(controller.signal)
|
|
156
|
+
if (isCurrent()) {
|
|
157
|
+
if (options.success === 'load') {
|
|
158
|
+
markLoaded()
|
|
159
|
+
} else if (options.success === 'save') {
|
|
160
|
+
markSaved()
|
|
161
|
+
} else if (options.success === 'run') {
|
|
162
|
+
markRun()
|
|
163
|
+
}
|
|
102
164
|
}
|
|
103
165
|
return response
|
|
104
166
|
} catch (err) {
|
|
105
|
-
|
|
167
|
+
if (isCurrent()) {
|
|
168
|
+
setError(err, options.errorMessage)
|
|
169
|
+
}
|
|
106
170
|
throw err
|
|
107
171
|
} finally {
|
|
108
|
-
|
|
172
|
+
if (isCurrent()) {
|
|
173
|
+
loading.value = false
|
|
174
|
+
}
|
|
175
|
+
if (activeController === controller) {
|
|
176
|
+
activeController = null
|
|
177
|
+
}
|
|
109
178
|
}
|
|
110
179
|
}
|
|
111
180
|
|
|
@@ -122,5 +191,6 @@ export function useRequestSyncState(
|
|
|
122
191
|
markSaved,
|
|
123
192
|
markRun,
|
|
124
193
|
run,
|
|
194
|
+
cancel,
|
|
125
195
|
}
|
|
126
196
|
}
|