@morscherlab/mint-sdk 1.2.0-beta.2 → 1.2.0-beta.20
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 +65 -5
- package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-CJdNlCZb.js} +24 -15
- package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
- package/dist/{ExperimentSelectorModal-DyPAUCLw.js → ExperimentSelectorModal-DCXmwKNS.js} +5 -5
- package/dist/ExperimentSelectorModal-DCXmwKNS.js.map +1 -0
- package/dist/{SettingsModal-BgKY93zh.js → SettingsModal-BYR20I4c.js} +5 -8
- package/dist/SettingsModal-BYR20I4c.js.map +1 -0
- package/dist/{EmptyState-p90quq8Z.js → Skeleton-BAF3CKY7.js} +111 -93
- package/dist/Skeleton-BAF3CKY7.js.map +1 -0
- package/dist/__tests__/components/DataStates.test.d.ts +1 -0
- package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
- package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
- package/dist/__tests__/components/PlotlyChart.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__/components/iconButtonIconSizing.test.d.ts +1 -0
- package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
- package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
- package/dist/__tests__/composables/usePlatformFilePickerAdapter.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/apiErrors.d.ts +26 -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/BaseInput.vue.d.ts +1 -1
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseTextarea.vue.d.ts +1 -1
- package/dist/components/BatchProgressList.vue.d.ts +17 -0
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +4 -4
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
- package/dist/components/ConcentrationInput.vue.d.ts +1 -1
- package/dist/components/DataFrame.vue.d.ts +14 -2
- package/dist/components/DatePicker.vue.d.ts +2 -2
- package/dist/components/DateTimePicker.vue.d.ts +3 -1
- 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/FileBrowserModal.vue.d.ts +99 -0
- package/dist/components/FilePicker.vue.d.ts +32 -0
- package/dist/components/FileUploader.vue.d.ts +5 -2
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/FormulaInput.vue.d.ts +1 -1
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
- package/dist/components/JobsStatusTray.vue.d.ts +9 -2
- package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
- package/dist/components/MobileSupportGate.vue.d.ts +1 -1
- package/dist/components/MoleculeInput.vue.d.ts +1 -1
- package/dist/components/NumberInput.vue.d.ts +1 -1
- package/dist/components/PlotlyChart.vue.d.ts +59 -0
- 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/ResourceCard.vue.d.ts +1 -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/SequenceInput.vue.d.ts +1 -1
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -1
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/components/TagsInput.vue.d.ts +1 -1
- package/dist/components/TimePicker.vue.d.ts +2 -1
- package/dist/components/TimeRangeInput.vue.d.ts +1 -1
- package/dist/components/UnitInput.vue.d.ts +1 -1
- 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 +30 -1
- package/dist/components/fileBrowserPresentation.d.ts +10 -0
- package/dist/components/index.d.ts +13 -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/FileBrowserDialogInternal.vue.d.ts +34 -0
- package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
- package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
- package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
- package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -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-DIum5hkx.js} +5882 -3200
- package/dist/components-DIum5hkx.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +34 -0
- package/dist/composables/filePicker/localFiles.d.ts +3 -0
- package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
- package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
- package/dist/composables/filePicker/validation.d.ts +5 -0
- package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
- package/dist/composables/index.d.ts +11 -2
- package/dist/composables/index.js +10 -10
- package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
- package/dist/composables/useApi.d.ts +14 -7
- 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/useFileBrowser.d.ts +55 -0
- package/dist/composables/useFilePicker.d.ts +189 -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/usePlatformFilePickerAdapter.d.ts +11 -0
- 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-mFXsTA4g.js} +502 -86
- package/dist/composables-mFXsTA4g.js.map +1 -0
- package/dist/{useApi-J6zFkHmn.js → errors-BLuqMee5.js} +179 -16
- package/dist/errors-BLuqMee5.js.map +1 -0
- package/dist/{experiment-Cq145oEW.js → experiment-CVDRAnqp.js} +3 -8
- package/dist/experiment-CVDRAnqp.js.map +1 -0
- package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-CV2Wza3z.js} +70 -51
- package/dist/experiment-utils-CV2Wza3z.js.map +1 -0
- package/dist/index.d.ts +2 -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 +15135 -32397
- package/dist/templates/builderReadUtils.d.ts +1 -1
- package/dist/templates/index.js +2 -4
- package/dist/{templates-DsP51i-S.js → templates-CmGfmBRA.js} +303 -106
- package/dist/templates-CmGfmBRA.js.map +1 -0
- package/dist/types/auth.d.ts +1 -0
- package/dist/types/fileBrowserTypes.d.ts +77 -0
- package/dist/types/filePicker.d.ts +77 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/{useControlSchema-BDx2Olha.js → useControlSchema-DldpxNOP.js} +499 -1162
- package/dist/useControlSchema-DldpxNOP.js.map +1 -0
- package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-C7hRiVQf.js} +22 -6
- package/dist/useDropdownState-C7hRiVQf.js.map +1 -0
- package/dist/{useExperimentSelector-CV4iNfMS.js → useExperimentSelector-CEcIaueJ.js} +4 -4
- package/dist/{useExperimentSelector-CV4iNfMS.js.map → useExperimentSelector-CEcIaueJ.js.map} +1 -1
- package/dist/{useFormBuilder-BLxG3BOX.js → useFormBuilder-z5L_dUIy.js} +2 -2
- package/dist/{useFormBuilder-BLxG3BOX.js.map → useFormBuilder-z5L_dUIy.js.map} +1 -1
- 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/dist/{useProtocolTemplates-CS_SQl6U.js → validation-uwoJGEb6.js} +811 -212
- package/dist/validation-uwoJGEb6.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/apiErrors.test.ts +81 -0
- 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/ExperimentPopover.test.ts +30 -0
- package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
- package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
- package/src/__tests__/components/FilePicker.state.test.ts +287 -0
- package/src/__tests__/components/FilePicker.test.ts +487 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
- package/src/__tests__/components/GroupAssigner.test.ts +53 -0
- package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
- package/src/__tests__/components/PlotlyChart.test.ts +305 -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 +107 -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__/components/iconButtonIconSizing.test.ts +18 -0
- package/src/__tests__/composables/useApi.test.ts +92 -9
- package/src/__tests__/composables/useAuth.test.ts +2 -0
- package/src/__tests__/composables/useDropdownState.test.ts +58 -1
- package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
- package/src/__tests__/composables/useListReorder.test.ts +145 -0
- package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
- package/src/__tests__/composables/usePluginClient.test.ts +143 -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/apiErrors.ts +85 -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 +12 -7
- 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/FileBrowserModal.story.vue +203 -0
- package/src/components/FileBrowserModal.vue +384 -0
- package/src/components/FilePicker.story.vue +44 -0
- package/src/components/FilePicker.vue +284 -0
- package/src/components/FileUploader.vue +32 -18
- 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 +34 -69
- 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 -9
- 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/PlotlyChart.story.vue +61 -0
- package/src/components/PlotlyChart.vue +233 -0
- 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 +50 -7
- 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/fileBrowserPresentation.ts +39 -0
- package/src/components/index.ts +14 -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/FileBrowserDialogInternal.vue +75 -0
- package/src/components/internal/FileBrowserListingInternal.vue +197 -0
- package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
- package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
- package/src/components/internal/FilePickerTreeInternal.vue +238 -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/filePicker/localFiles.ts +58 -0
- package/src/composables/filePicker/usePickerNavigation.ts +220 -0
- package/src/composables/filePicker/usePickerSelection.ts +259 -0
- package/src/composables/filePicker/validation.ts +41 -0
- package/src/composables/flattenWorkspaceBindings.ts +104 -0
- package/src/composables/formBuilderRegistry.ts +5 -0
- package/src/composables/index.ts +50 -0
- package/src/composables/pluginWorkspaceModel.ts +182 -0
- package/src/composables/useApi.ts +57 -13
- 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/useFileBrowser.ts +314 -0
- package/src/composables/useFileImport.ts +2 -7
- package/src/composables/useFilePicker.ts +366 -0
- package/src/composables/useFocusTrap.ts +91 -0
- package/src/composables/useGeneratedAnalysis.ts +32 -27
- 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/usePlatformFilePickerAdapter.ts +104 -0
- package/src/composables/usePluginClient.ts +50 -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 +19 -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/file-browser-modal.css +489 -0
- package/src/styles/components/file-picker.css +502 -0
- package/src/styles/components/form-builder.css +5 -5
- package/src/styles/components/icon-button.css +9 -0
- 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 +12 -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/fileBrowserTypes.ts +85 -0
- package/src/types/filePicker.ts +72 -0
- package/src/types/form-builder.ts +1 -0
- package/src/types/index.ts +14 -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 → apiErrors.test.d.ts} +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { nextTick, watch, type Ref } from 'vue'
|
|
2
|
+
import { useEventListener } from './useEventListener'
|
|
3
|
+
|
|
4
|
+
export interface UseFocusTrapOptions {
|
|
5
|
+
/** Ref to the trap's container element. Focusable elements are queried within it. */
|
|
6
|
+
containerRef: Ref<HTMLElement | null>
|
|
7
|
+
/** Reactive flag — the trap activates when this becomes `true` and releases when it becomes `false`. */
|
|
8
|
+
active: Ref<boolean>
|
|
9
|
+
/** Restore focus to the element that was focused before activation, when the trap releases. Default `true`. */
|
|
10
|
+
restoreFocus?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseFocusTrapReturn {
|
|
14
|
+
/** Focusable descendants of the container, in DOM order, excluding roving-tabindex (`tabindex="-1"`) elements. */
|
|
15
|
+
getFocusableElements: () => HTMLElement[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FOCUSABLE_SELECTOR = [
|
|
19
|
+
'a[href]',
|
|
20
|
+
'button:not([disabled])',
|
|
21
|
+
'input:not([disabled])',
|
|
22
|
+
'select:not([disabled])',
|
|
23
|
+
'textarea:not([disabled])',
|
|
24
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
25
|
+
].join(', ')
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Traps Tab focus within a container while `active` is true: Tab wraps from the last
|
|
29
|
+
* focusable element back to the first (and Shift+Tab from the first to the last), the
|
|
30
|
+
* first focusable element (or the container itself, if none) receives focus when the
|
|
31
|
+
* trap activates, and focus returns to whatever was focused beforehand when it releases.
|
|
32
|
+
* Used by BaseModal and WellEditPopupInternal.
|
|
33
|
+
*/
|
|
34
|
+
export function useFocusTrap(options: UseFocusTrapOptions): UseFocusTrapReturn {
|
|
35
|
+
const { containerRef, active } = options
|
|
36
|
+
const restoreFocus = options.restoreFocus ?? true
|
|
37
|
+
let previouslyFocused: HTMLElement | null = null
|
|
38
|
+
|
|
39
|
+
function getFocusableElements(): HTMLElement[] {
|
|
40
|
+
if (!containerRef.value) return []
|
|
41
|
+
// Roving tabindex (inactive tabs/rail items) stays out of the Tab cycle. Checking
|
|
42
|
+
// the attribute directly (rather than the `.tabIndex` IDL property) avoids relying
|
|
43
|
+
// on a browser/engine's default-tabindex computation for freshly-patched elements.
|
|
44
|
+
return Array.from(containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR))
|
|
45
|
+
.filter((el) => el.getAttribute('tabindex') !== '-1')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
49
|
+
if (event.key !== 'Tab' || !active.value || !containerRef.value) return
|
|
50
|
+
|
|
51
|
+
const focusable = getFocusableElements()
|
|
52
|
+
if (focusable.length === 0) {
|
|
53
|
+
event.preventDefault()
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const first = focusable[0]
|
|
58
|
+
const last = focusable[focusable.length - 1]
|
|
59
|
+
|
|
60
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
61
|
+
event.preventDefault()
|
|
62
|
+
last.focus()
|
|
63
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
64
|
+
event.preventDefault()
|
|
65
|
+
first.focus()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// `flush: 'sync'` so releasing the trap from `onBeforeUnmount` restores focus
|
|
70
|
+
// before the component's reactive scope is torn down — a batched (default
|
|
71
|
+
// 'pre') watcher would be scheduled but never flushed once that scope stops.
|
|
72
|
+
watch(active, async (isActive) => {
|
|
73
|
+
if (isActive) {
|
|
74
|
+
previouslyFocused = document.activeElement as HTMLElement | null
|
|
75
|
+
await nextTick()
|
|
76
|
+
const focusable = getFocusableElements()
|
|
77
|
+
if (focusable.length > 0) {
|
|
78
|
+
focusable[0].focus()
|
|
79
|
+
} else {
|
|
80
|
+
containerRef.value?.focus()
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
if (restoreFocus) previouslyFocused?.focus()
|
|
84
|
+
previouslyFocused = null
|
|
85
|
+
}
|
|
86
|
+
}, { flush: 'sync' })
|
|
87
|
+
|
|
88
|
+
useEventListener(() => document, 'keydown', handleKeydown)
|
|
89
|
+
|
|
90
|
+
return { getFocusableElements }
|
|
91
|
+
}
|
|
@@ -8,9 +8,13 @@ import {
|
|
|
8
8
|
type ShallowRef,
|
|
9
9
|
} from 'vue'
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
JobsStatusTrayAdapter,
|
|
13
|
+
PluginJobCenterSource,
|
|
14
|
+
} from './useJobsStatusTray'
|
|
12
15
|
import { useEventStream, type EventStreamMessage } from './useEventStream'
|
|
13
|
-
import { normalizeJobState } from '../jobs'
|
|
16
|
+
import { isActiveJobStatus, normalizeJobState } from '../jobs'
|
|
17
|
+
import { MintApiError, mintApiErrorFromResponse } from '../apiErrors'
|
|
14
18
|
import type {
|
|
15
19
|
GeneratedAnalysisJob,
|
|
16
20
|
GeneratedAnalysisJobInput,
|
|
@@ -102,11 +106,12 @@ export interface UsePluginJobsReturn extends PluginJobCenterSource {
|
|
|
102
106
|
pause: () => void
|
|
103
107
|
resume: () => void
|
|
104
108
|
dispose: () => void
|
|
109
|
+
/** @deprecated Pass the runtime itself to SDK job UI as its source. */
|
|
110
|
+
jobsAdapter: JobsStatusTrayAdapter
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
export type UseGeneratedAnalysisReturn = UsePluginJobsReturn
|
|
108
114
|
|
|
109
|
-
const ACTIVE_STATUSES = new Set(['awaiting_input', 'queued', 'running'])
|
|
110
115
|
const AUTH_TOKEN_KEY = 'mint-auth-token'
|
|
111
116
|
const SESSION_NOT_FOUND_DETAIL = 'Session not found'
|
|
112
117
|
const MAX_RESULT_CACHE_ENTRIES = 32
|
|
@@ -125,16 +130,6 @@ type PluginJobStreamData = GeneratedAnalysisJobInput | {
|
|
|
125
130
|
jobs: GeneratedAnalysisJobInput[]
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
class GeneratedAnalysisRequestError extends Error {
|
|
129
|
-
constructor(
|
|
130
|
-
readonly status: number,
|
|
131
|
-
readonly detail: unknown,
|
|
132
|
-
) {
|
|
133
|
-
super(typeof detail === 'string' ? detail : `Request failed (${status})`)
|
|
134
|
-
this.name = 'GeneratedAnalysisRequestError'
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
133
|
export function createGeneratedAnalysisTransport(
|
|
139
134
|
options: GeneratedAnalysisTransportOptions = {},
|
|
140
135
|
): GeneratedAnalysisTransport {
|
|
@@ -468,12 +463,12 @@ function createPluginJobs(
|
|
|
468
463
|
.then((payload) => {
|
|
469
464
|
if (disposed) return jobs.value
|
|
470
465
|
if (revision !== jobsRevision) {
|
|
471
|
-
if (jobs.value.some(job =>
|
|
466
|
+
if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
|
|
472
467
|
return jobs.value
|
|
473
468
|
}
|
|
474
469
|
replaceJobs(payload.jobs)
|
|
475
470
|
error.value = null
|
|
476
|
-
if (!paused && !jobStream.isConnected.value && jobs.value.some(job =>
|
|
471
|
+
if (!paused && !jobStream.isConnected.value && jobs.value.some(job => isActiveJobStatus(job.status))) {
|
|
477
472
|
schedulePoll()
|
|
478
473
|
}
|
|
479
474
|
return jobs.value
|
|
@@ -525,7 +520,7 @@ function createPluginJobs(
|
|
|
525
520
|
const current = typeof job === 'string'
|
|
526
521
|
? jobs.value.find(candidate => candidate.job_id === jobId)
|
|
527
522
|
: jobs.value.find(candidate => candidate.job_id === jobId) ?? job
|
|
528
|
-
if (current && !
|
|
523
|
+
if (current && !isActiveJobStatus(current.status)) {
|
|
529
524
|
return terminalWaitResult(current, options.rejectOnFailure === true)
|
|
530
525
|
}
|
|
531
526
|
if (paused) return Promise.reject(new Error('Plugin jobs runtime is paused'))
|
|
@@ -562,7 +557,7 @@ function createPluginJobs(
|
|
|
562
557
|
}
|
|
563
558
|
|
|
564
559
|
function settleJobWaiters(job: GeneratedAnalysisJob): void {
|
|
565
|
-
if (
|
|
560
|
+
if (isActiveJobStatus(job.status)) return
|
|
566
561
|
const waiting = jobWaiters.get(job.job_id)
|
|
567
562
|
if (!waiting) return
|
|
568
563
|
jobWaiters.delete(job.job_id)
|
|
@@ -834,7 +829,7 @@ function createPluginJobs(
|
|
|
834
829
|
job: GeneratedAnalysisJob,
|
|
835
830
|
cause: unknown,
|
|
836
831
|
): void {
|
|
837
|
-
if (cause instanceof
|
|
832
|
+
if (cause instanceof MintApiError) {
|
|
838
833
|
if (cause.status === 404 || cause.status === 410) {
|
|
839
834
|
discardCachedResult(job.job_id)
|
|
840
835
|
jobs.value = jobs.value.map(candidate => (
|
|
@@ -910,7 +905,7 @@ function createPluginJobs(
|
|
|
910
905
|
await refreshJobs()
|
|
911
906
|
} catch (cause) {
|
|
912
907
|
error.value = errorMessage(cause, 'Failed to refresh analysis jobs')
|
|
913
|
-
if (jobs.value.some(job =>
|
|
908
|
+
if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
|
|
914
909
|
}
|
|
915
910
|
}, pollInterval)
|
|
916
911
|
}
|
|
@@ -922,7 +917,7 @@ function createPluginJobs(
|
|
|
922
917
|
}
|
|
923
918
|
|
|
924
919
|
function schedulePollForActiveJobs(): void {
|
|
925
|
-
if (jobs.value.some(job =>
|
|
920
|
+
if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
|
|
926
921
|
}
|
|
927
922
|
|
|
928
923
|
function applyJobStreamMessage(message: EventStreamMessage<PluginJobStreamData>): void {
|
|
@@ -972,6 +967,13 @@ function createPluginJobs(
|
|
|
972
967
|
return payload.deleted
|
|
973
968
|
}
|
|
974
969
|
|
|
970
|
+
const jobsAdapter: JobsStatusTrayAdapter = {
|
|
971
|
+
listJobs: async () => ({ jobs: await refreshJobs() }),
|
|
972
|
+
cancelJob: async job => cancelJob(job as GeneratedAnalysisJob),
|
|
973
|
+
deleteJob: async job => deleteJob(job as GeneratedAnalysisJob),
|
|
974
|
+
clearFinishedJobs: async () => clearFinishedJobs(),
|
|
975
|
+
}
|
|
976
|
+
|
|
975
977
|
function pause(): void {
|
|
976
978
|
if (disposed || paused) return
|
|
977
979
|
paused = true
|
|
@@ -993,7 +995,7 @@ function createPluginJobs(
|
|
|
993
995
|
scheduleManagedDownloadRefresh(jobId, managedDownloadRefreshDelay(result))
|
|
994
996
|
}
|
|
995
997
|
if (streamEnabled) jobStream.start()
|
|
996
|
-
else if (jobs.value.some(job =>
|
|
998
|
+
else if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
|
|
997
999
|
}
|
|
998
1000
|
|
|
999
1001
|
function dispose(): void {
|
|
@@ -1026,8 +1028,10 @@ function createPluginJobs(
|
|
|
1026
1028
|
},
|
|
1027
1029
|
})
|
|
1028
1030
|
if (!response.ok) {
|
|
1029
|
-
const payload = await response.json().catch(() => null)
|
|
1030
|
-
throw
|
|
1031
|
+
const payload: unknown = await response.json().catch(() => null)
|
|
1032
|
+
throw mintApiErrorFromResponse(response.status, payload, {
|
|
1033
|
+
requestId: response.headers.get('X-Request-ID'),
|
|
1034
|
+
})
|
|
1031
1035
|
}
|
|
1032
1036
|
return await response.json() as T
|
|
1033
1037
|
}
|
|
@@ -1065,6 +1069,7 @@ function createPluginJobs(
|
|
|
1065
1069
|
pause,
|
|
1066
1070
|
resume,
|
|
1067
1071
|
dispose,
|
|
1072
|
+
jobsAdapter,
|
|
1068
1073
|
}
|
|
1069
1074
|
}
|
|
1070
1075
|
|
|
@@ -1165,20 +1170,20 @@ function createGeneratedSessionStorage(candidate?: Storage): GeneratedSessionSto
|
|
|
1165
1170
|
}
|
|
1166
1171
|
|
|
1167
1172
|
function isSessionNotFoundError(error: unknown): boolean {
|
|
1168
|
-
return error instanceof
|
|
1173
|
+
return error instanceof MintApiError
|
|
1169
1174
|
&& error.status === 404
|
|
1170
|
-
&& error.detail === SESSION_NOT_FOUND_DETAIL
|
|
1175
|
+
&& (error.detail === SESSION_NOT_FOUND_DETAIL || error.message === SESSION_NOT_FOUND_DETAIL)
|
|
1171
1176
|
}
|
|
1172
1177
|
|
|
1173
1178
|
function isRetryableResultError(error: unknown): boolean {
|
|
1174
|
-
return !(error instanceof
|
|
1179
|
+
return !(error instanceof MintApiError)
|
|
1175
1180
|
|| error.status === 409
|
|
1176
1181
|
|| error.status === 429
|
|
1177
1182
|
|| error.status >= 500
|
|
1178
1183
|
}
|
|
1179
1184
|
|
|
1180
1185
|
function isRetryableJobLookupError(error: unknown): boolean {
|
|
1181
|
-
return !(error instanceof
|
|
1186
|
+
return !(error instanceof MintApiError)
|
|
1182
1187
|
|| error.status === 408
|
|
1183
1188
|
|| error.status === 429
|
|
1184
1189
|
|| error.status >= 500
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ref,
|
|
6
6
|
shallowRef,
|
|
7
7
|
toValue,
|
|
8
|
+
watch,
|
|
8
9
|
type ComputedRef,
|
|
9
10
|
type MaybeRefOrGetter,
|
|
10
11
|
type Ref,
|
|
@@ -16,7 +17,12 @@ import type {
|
|
|
16
17
|
JobListPayload,
|
|
17
18
|
JobState,
|
|
18
19
|
JobStateInput,
|
|
20
|
+
JobStreamData,
|
|
19
21
|
} from '../types/jobs'
|
|
22
|
+
import type {
|
|
23
|
+
PluginEventStreamMessage,
|
|
24
|
+
UsePluginEventStreamReturn,
|
|
25
|
+
} from './usePluginClient'
|
|
20
26
|
|
|
21
27
|
export type JobsStatusTrayTab = 'active' | 'history'
|
|
22
28
|
|
|
@@ -140,11 +146,32 @@ function duplicatePluginJobId(jobId: string): Error {
|
|
|
140
146
|
return new Error(`Duplicate plugin job id across combined sources: ${jobId}`)
|
|
141
147
|
}
|
|
142
148
|
|
|
149
|
+
/** @deprecated Prefer a {@link PluginJobCenterSource}. */
|
|
150
|
+
export interface JobsStatusTrayAdapter {
|
|
151
|
+
listJobs?: () => Promise<JobListPayload | readonly JobStateInput[]>
|
|
152
|
+
cancelJob?: (job: JobState) => Promise<JobStateInput | void>
|
|
153
|
+
deleteJob?: (job: JobState) => Promise<boolean | void>
|
|
154
|
+
clearFinishedJobs?: (jobs: readonly JobState[]) => Promise<number | void>
|
|
155
|
+
}
|
|
156
|
+
|
|
143
157
|
export interface UsePluginJobCenterOptions {
|
|
144
158
|
source?: MaybeRefOrGetter<PluginJobCenterSource | undefined>
|
|
159
|
+
/** @deprecated Pass a source with its canonical jobs ref. */
|
|
160
|
+
jobs?: MaybeRefOrGetter<readonly JobStateInput[] | undefined>
|
|
161
|
+
/** @deprecated Pass source actions instead. */
|
|
162
|
+
adapter?: MaybeRefOrGetter<JobsStatusTrayAdapter | undefined>
|
|
163
|
+
/** @deprecated Event handling belongs to the source runtime. */
|
|
164
|
+
eventStream?: MaybeRefOrGetter<UsePluginEventStreamReturn<JobStreamData> | undefined>
|
|
145
165
|
loadOnMount?: boolean
|
|
146
166
|
}
|
|
147
167
|
|
|
168
|
+
export interface UseJobsStatusTrayOptions extends UsePluginJobCenterOptions {}
|
|
169
|
+
|
|
170
|
+
export interface JobStreamMessage {
|
|
171
|
+
event: string
|
|
172
|
+
data: JobStreamData
|
|
173
|
+
}
|
|
174
|
+
|
|
148
175
|
export interface UsePluginJobCenterReturn {
|
|
149
176
|
jobs: ComputedRef<JobState[]>
|
|
150
177
|
activeJobs: ComputedRef<JobState[]>
|
|
@@ -164,12 +191,30 @@ export interface UsePluginJobCenterReturn {
|
|
|
164
191
|
close: () => void
|
|
165
192
|
toggle: () => void
|
|
166
193
|
refresh: () => Promise<boolean>
|
|
194
|
+
applyStreamMessage: (message: JobStreamMessage) => void
|
|
167
195
|
cancelJob: (job: JobState) => Promise<boolean>
|
|
168
196
|
deleteJob: (job: JobState) => Promise<boolean>
|
|
169
197
|
clearFinished: () => Promise<boolean>
|
|
170
198
|
isPending: (job: JobState) => boolean
|
|
171
199
|
}
|
|
172
200
|
|
|
201
|
+
export type UseJobsStatusTrayReturn = UsePluginJobCenterReturn
|
|
202
|
+
|
|
203
|
+
interface LegacyJobsOverride {
|
|
204
|
+
readonly input: readonly JobStateInput[] | undefined
|
|
205
|
+
readonly jobs: JobState[]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function responseJobs(
|
|
209
|
+
response: JobListPayload | readonly JobStateInput[],
|
|
210
|
+
): readonly JobStateInput[] {
|
|
211
|
+
return 'jobs' in response ? response.jobs : response
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isSnapshotPayload(data: JobStreamData): data is { jobs: JobStateInput[] } {
|
|
215
|
+
return 'jobs' in data && Array.isArray(data.jobs)
|
|
216
|
+
}
|
|
217
|
+
|
|
173
218
|
function errorMessage(error: unknown, fallback: string): string {
|
|
174
219
|
return error instanceof Error ? error.message : fallback
|
|
175
220
|
}
|
|
@@ -183,30 +228,84 @@ function errorMessage(error: unknown, fallback: string): string {
|
|
|
183
228
|
export function usePluginJobCenter(
|
|
184
229
|
options: UsePluginJobCenterOptions = {},
|
|
185
230
|
): UsePluginJobCenterReturn {
|
|
231
|
+
const legacyOverride = shallowRef<LegacyJobsOverride | null>(null)
|
|
186
232
|
const isOpen = ref(false)
|
|
187
233
|
const activeTab = ref<JobsStatusTrayTab>('active')
|
|
188
234
|
const isLoading = ref(false)
|
|
189
235
|
const error = shallowRef<string | null>(null)
|
|
190
236
|
const pendingJobIds = ref(new Set<string>())
|
|
237
|
+
const jobRevisions = new Map<string, number>()
|
|
238
|
+
let stateRevision = 0
|
|
191
239
|
let requestGeneration = 0
|
|
192
240
|
let active = true
|
|
193
241
|
const source = computed(() => toValue(options.source))
|
|
242
|
+
const adapter = computed(() => toValue(options.adapter))
|
|
194
243
|
|
|
195
|
-
const jobs = computed<JobState[]>(() =>
|
|
196
|
-
|
|
197
|
-
|
|
244
|
+
const jobs = computed<JobState[]>(() => {
|
|
245
|
+
const currentSource = source.value
|
|
246
|
+
if (currentSource) {
|
|
247
|
+
return [...toValue(currentSource.jobs)].map(normalizeJobState)
|
|
248
|
+
}
|
|
249
|
+
const input = toValue(options.jobs)
|
|
250
|
+
const override = legacyOverride.value
|
|
251
|
+
if (override && override.input === input) return override.jobs
|
|
252
|
+
return input ? [...input].map(normalizeJobState) : []
|
|
253
|
+
})
|
|
198
254
|
const activeJobs = computed(() => jobs.value.filter(job => isActiveJobStatus(job.status)))
|
|
199
255
|
const historyJobs = computed(() => jobs.value.filter(job => !isActiveJobStatus(job.status)))
|
|
200
256
|
const deletableJobs = computed(() => historyJobs.value.filter(job => job.can_delete))
|
|
201
257
|
const featuredJob = computed(() => activeJobs.value[0] ?? historyJobs.value[0] ?? null)
|
|
202
|
-
const canRefresh = computed(() => Boolean(source.value?.refreshJobs))
|
|
203
|
-
const canCancel = computed(() => Boolean(source.value?.cancelJob))
|
|
204
|
-
const canDelete = computed(() => Boolean(source.value?.deleteJob))
|
|
258
|
+
const canRefresh = computed(() => Boolean(source.value?.refreshJobs || adapter.value?.listJobs))
|
|
259
|
+
const canCancel = computed(() => Boolean(source.value?.cancelJob || adapter.value?.cancelJob))
|
|
260
|
+
const canDelete = computed(() => Boolean(source.value?.deleteJob || adapter.value?.deleteJob))
|
|
205
261
|
const canClearFinished = computed(() => Boolean(
|
|
206
262
|
source.value?.clearFinishedJobs
|
|
207
263
|
|| source.value?.deleteJob
|
|
264
|
+
|| adapter.value?.clearFinishedJobs
|
|
265
|
+
|| adapter.value?.deleteJob,
|
|
208
266
|
))
|
|
209
267
|
|
|
268
|
+
function bumpJobRevision(jobId: string): void {
|
|
269
|
+
jobRevisions.set(jobId, (jobRevisions.get(jobId) ?? 0) + 1)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function replaceLegacyJobs(nextJobs: readonly JobStateInput[]): void {
|
|
273
|
+
const normalized = nextJobs.map(normalizeJobState)
|
|
274
|
+
const touchedJobIds = new Set([
|
|
275
|
+
...jobs.value.map(job => job.job_id),
|
|
276
|
+
...normalized.map(job => job.job_id),
|
|
277
|
+
])
|
|
278
|
+
touchedJobIds.forEach(bumpJobRevision)
|
|
279
|
+
legacyOverride.value = {
|
|
280
|
+
input: toValue(options.jobs),
|
|
281
|
+
jobs: normalized,
|
|
282
|
+
}
|
|
283
|
+
stateRevision += 1
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function upsertLegacyJob(nextJob: JobStateInput): void {
|
|
287
|
+
const normalized = normalizeJobState(nextJob)
|
|
288
|
+
const existingIndex = jobs.value.findIndex(job => job.job_id === normalized.job_id)
|
|
289
|
+
const nextJobs = [...jobs.value]
|
|
290
|
+
if (existingIndex === -1) nextJobs.unshift(normalized)
|
|
291
|
+
else nextJobs[existingIndex] = normalized
|
|
292
|
+
legacyOverride.value = {
|
|
293
|
+
input: toValue(options.jobs),
|
|
294
|
+
jobs: nextJobs,
|
|
295
|
+
}
|
|
296
|
+
bumpJobRevision(normalized.job_id)
|
|
297
|
+
stateRevision += 1
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function removeLegacyJobs(jobIds: Set<string>): void {
|
|
301
|
+
legacyOverride.value = {
|
|
302
|
+
input: toValue(options.jobs),
|
|
303
|
+
jobs: jobs.value.filter(job => !jobIds.has(job.job_id)),
|
|
304
|
+
}
|
|
305
|
+
jobIds.forEach(bumpJobRevision)
|
|
306
|
+
stateRevision += 1
|
|
307
|
+
}
|
|
308
|
+
|
|
210
309
|
function setPending(jobId: string, pending: boolean): void {
|
|
211
310
|
const next = new Set(pendingJobIds.value)
|
|
212
311
|
if (pending) next.add(jobId)
|
|
@@ -214,15 +313,35 @@ export function usePluginJobCenter(
|
|
|
214
313
|
pendingJobIds.value = next
|
|
215
314
|
}
|
|
216
315
|
|
|
316
|
+
function applyStreamMessage(message: JobStreamMessage): void {
|
|
317
|
+
if (!active || source.value) return
|
|
318
|
+
if (message.event === 'snapshot' && isSnapshotPayload(message.data)) {
|
|
319
|
+
replaceLegacyJobs(message.data.jobs)
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
if (
|
|
323
|
+
(message.event === 'job' || message.event === 'terminal')
|
|
324
|
+
&& !isSnapshotPayload(message.data)
|
|
325
|
+
) {
|
|
326
|
+
upsertLegacyJob(message.data)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
217
330
|
async function refresh(): Promise<boolean> {
|
|
218
|
-
const
|
|
219
|
-
|
|
331
|
+
const currentSource = source.value
|
|
332
|
+
const listJobs = currentSource?.refreshJobs ?? adapter.value?.listJobs
|
|
333
|
+
if (!listJobs) return false
|
|
220
334
|
const generation = ++requestGeneration
|
|
335
|
+
const revision = stateRevision
|
|
221
336
|
isLoading.value = true
|
|
222
337
|
error.value = null
|
|
223
338
|
try {
|
|
224
|
-
await
|
|
225
|
-
|
|
339
|
+
const response = await listJobs()
|
|
340
|
+
if (!active || generation !== requestGeneration || revision !== stateRevision) {
|
|
341
|
+
return false
|
|
342
|
+
}
|
|
343
|
+
if (!currentSource && response) replaceLegacyJobs(responseJobs(response))
|
|
344
|
+
return true
|
|
226
345
|
} catch (cause) {
|
|
227
346
|
if (active && generation === requestGeneration) {
|
|
228
347
|
error.value = errorMessage(cause, 'Failed to load jobs')
|
|
@@ -235,14 +354,23 @@ export function usePluginJobCenter(
|
|
|
235
354
|
|
|
236
355
|
async function cancelJob(job: JobState): Promise<boolean> {
|
|
237
356
|
const sourceCancel = source.value?.cancelJob
|
|
238
|
-
|
|
357
|
+
const adapterCancel = adapter.value?.cancelJob
|
|
358
|
+
if (!job.can_cancel || (!sourceCancel && !adapterCancel) || pendingJobIds.value.has(job.job_id)) {
|
|
239
359
|
return false
|
|
240
360
|
}
|
|
361
|
+
const revision = jobRevisions.get(job.job_id) ?? 0
|
|
241
362
|
setPending(job.job_id, true)
|
|
242
363
|
error.value = null
|
|
243
364
|
try {
|
|
244
|
-
|
|
245
|
-
|
|
365
|
+
const updated = sourceCancel
|
|
366
|
+
? await sourceCancel(job.job_id)
|
|
367
|
+
: await adapterCancel!(job)
|
|
368
|
+
if (!active) return false
|
|
369
|
+
if (!sourceCancel && revision === (jobRevisions.get(job.job_id) ?? 0)) {
|
|
370
|
+
if (updated) upsertLegacyJob(updated)
|
|
371
|
+
else await refresh()
|
|
372
|
+
}
|
|
373
|
+
return true
|
|
246
374
|
} catch (cause) {
|
|
247
375
|
if (active) error.value = errorMessage(cause, 'Failed to cancel job')
|
|
248
376
|
return false
|
|
@@ -253,14 +381,22 @@ export function usePluginJobCenter(
|
|
|
253
381
|
|
|
254
382
|
async function deleteJob(job: JobState): Promise<boolean> {
|
|
255
383
|
const sourceDelete = source.value?.deleteJob
|
|
256
|
-
|
|
384
|
+
const adapterDelete = adapter.value?.deleteJob
|
|
385
|
+
if (!job.can_delete || (!sourceDelete && !adapterDelete) || pendingJobIds.value.has(job.job_id)) {
|
|
257
386
|
return false
|
|
258
387
|
}
|
|
259
388
|
setPending(job.job_id, true)
|
|
260
389
|
error.value = null
|
|
261
390
|
try {
|
|
262
|
-
const deleted =
|
|
263
|
-
|
|
391
|
+
const deleted = sourceDelete
|
|
392
|
+
? await sourceDelete(job.job_id)
|
|
393
|
+
: await adapterDelete!(job)
|
|
394
|
+
if (!active) return false
|
|
395
|
+
if (!sourceDelete) {
|
|
396
|
+
if (deleted === false) await refresh()
|
|
397
|
+
else removeLegacyJobs(new Set([job.job_id]))
|
|
398
|
+
}
|
|
399
|
+
return deleted !== false
|
|
264
400
|
} catch (cause) {
|
|
265
401
|
if (active) error.value = errorMessage(cause, 'Failed to delete job')
|
|
266
402
|
return false
|
|
@@ -284,13 +420,36 @@ export function usePluginJobCenter(
|
|
|
284
420
|
const results = await Promise.all(targets.map(job => deleteJob(job)))
|
|
285
421
|
return results.every(Boolean)
|
|
286
422
|
}
|
|
287
|
-
|
|
423
|
+
const currentAdapter = adapter.value
|
|
424
|
+
if (currentAdapter?.clearFinishedJobs) {
|
|
425
|
+
const deleted = await currentAdapter.clearFinishedJobs(targets)
|
|
426
|
+
if (!active) return false
|
|
427
|
+
if (typeof deleted === 'number' && deleted !== targets.length) {
|
|
428
|
+
await refresh()
|
|
429
|
+
return false
|
|
430
|
+
}
|
|
431
|
+
removeLegacyJobs(new Set(targets.map(job => job.job_id)))
|
|
432
|
+
return true
|
|
433
|
+
}
|
|
434
|
+
if (!currentAdapter?.deleteJob) return false
|
|
435
|
+
const results = await Promise.all(targets.map(job => deleteJob(job)))
|
|
436
|
+
return results.every(Boolean)
|
|
288
437
|
} catch (cause) {
|
|
289
438
|
if (active) error.value = errorMessage(cause, 'Failed to clear jobs')
|
|
290
439
|
return false
|
|
291
440
|
}
|
|
292
441
|
}
|
|
293
442
|
|
|
443
|
+
if (options.eventStream !== undefined) {
|
|
444
|
+
watch(
|
|
445
|
+
() => toValue(options.eventStream)?.lastMessage.value ?? null,
|
|
446
|
+
(message: PluginEventStreamMessage<JobStreamData> | null) => {
|
|
447
|
+
if (message) applyStreamMessage(message)
|
|
448
|
+
},
|
|
449
|
+
{ immediate: true },
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
|
|
294
453
|
onMounted(() => {
|
|
295
454
|
if (options.loadOnMount !== false) void refresh()
|
|
296
455
|
})
|
|
@@ -318,9 +477,17 @@ export function usePluginJobCenter(
|
|
|
318
477
|
close: () => { isOpen.value = false },
|
|
319
478
|
toggle: () => { isOpen.value = !isOpen.value },
|
|
320
479
|
refresh,
|
|
480
|
+
applyStreamMessage,
|
|
321
481
|
cancelJob,
|
|
322
482
|
deleteJob,
|
|
323
483
|
clearFinished,
|
|
324
484
|
isPending: job => pendingJobIds.value.has(job.job_id),
|
|
325
485
|
}
|
|
326
486
|
}
|
|
487
|
+
|
|
488
|
+
/** @deprecated Use `usePluginJobCenter()` for the plugin job-center controller. */
|
|
489
|
+
export function useJobsStatusTray(
|
|
490
|
+
options: UseJobsStatusTrayOptions = {},
|
|
491
|
+
): UseJobsStatusTrayReturn {
|
|
492
|
+
return usePluginJobCenter(options)
|
|
493
|
+
}
|