@morscherlab/mint-sdk 1.4.0-beta.8 → 1.4.0
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 +14 -3
- package/dist/{ActionMenu-CScINahU.js → AlertBox-DwgNTEqf.js} +40 -209
- package/dist/AlertBox-DwgNTEqf.js.map +1 -0
- package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-CZZaxni0.js} +1249 -239
- package/dist/AppSidebar-CZZaxni0.js.map +1 -0
- package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CjWoxAD1.js} +2 -2
- package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CjWoxAD1.js.map} +1 -1
- package/dist/ExperimentPopover-DujiuQIO.js +337 -0
- package/dist/ExperimentPopover-DujiuQIO.js.map +1 -0
- package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-B4j4eUOJ.js} +244 -211
- package/dist/ExperimentSelectorModal-B4j4eUOJ.js.map +1 -0
- package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-DyUPH3I5.js} +2 -2
- package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-DyUPH3I5.js.map} +1 -1
- package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-D2MOOtuO.js} +28 -27
- package/dist/SettingsModal-D2MOOtuO.js.map +1 -0
- package/dist/__stories__/experiment-helpers.d.ts +10 -0
- package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
- package/dist/__tests__/components/FloatingPanelsInModal.test.d.ts +1 -0
- package/dist/__tests__/composables/useFloatingPanel.test.d.ts +1 -0
- package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
- package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
- package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AppTopBar.vue.d.ts +20 -5
- package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
- package/dist/components/AuditTrail.vue.d.ts +1 -1
- package/dist/components/BaseButton.vue.d.ts +4 -2
- package/dist/components/BaseSelect.vue.d.ts +2 -2
- package/dist/components/ChartContainer.vue.d.ts +2 -0
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/ExperimentPopover.vue.d.ts +18 -0
- package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
- package/dist/components/FilePicker.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +2 -2
- package/dist/components/JobsStatusTray.vue.d.ts +15 -4
- package/dist/components/PluginIcon.vue.d.ts +2 -2
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
- package/dist/components/SampleSelector.vue.d.ts +6 -6
- package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +3 -3
- package/dist/components/StepWizard.vue.d.ts +3 -0
- package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
- package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +3 -3
- package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
- package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
- package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
- package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
- package/dist/composables/createClient.d.ts +2 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/pluginIdentity.d.ts +24 -0
- package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
- package/dist/composables/useAppExperiment.d.ts +8 -0
- package/dist/composables/useCalendarGrid.d.ts +1 -1
- package/dist/composables/useExperimentSelector.d.ts +6 -2
- package/dist/composables/useFileImport.d.ts +7 -0
- package/dist/composables/useFloatingPanel.d.ts +29 -0
- package/dist/composables/useJobCenter.d.ts +41 -3
- package/dist/composables/useOnboarding.d.ts +119 -0
- package/dist/composables/usePlatform.d.ts +1 -0
- package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-Bsg0t5Ab.js} +2 -2
- package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-Bsg0t5Ab.js.map} +1 -1
- package/dist/{data-C2PwI_dn.js → data-Boeh4uPI.js} +180 -125
- package/dist/data-Boeh4uPI.js.map +1 -0
- package/dist/data.d.ts +1 -1
- package/dist/data.js +9 -9
- package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
- package/dist/experiment-DyO54FQB.js.map +1 -0
- package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-DCn2QKG7.js} +3 -3
- package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-DCn2QKG7.js.map} +1 -1
- package/dist/index.js +20 -20
- package/dist/{install-DAID6c2-.js → install-BEHp7Mo-.js} +15 -14
- package/dist/{install-DAID6c2-.js.map → install-BEHp7Mo-.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-DLwRl_Ql.js → lab-D61eaX6f.js} +136 -148
- package/dist/lab-D61eaX6f.js.map +1 -0
- package/dist/lab.js +4 -3
- package/dist/{records-BzTcxekG.js → records-D4aQXyZu.js} +647 -389
- package/dist/records-D4aQXyZu.js.map +1 -0
- package/dist/{runtime-D6gVat6r.js → runtime-q-R0CNLh.js} +417 -54
- package/dist/runtime-q-R0CNLh.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +7 -7
- package/dist/{shell-5ju97nOS.js → shell-6CGFvUUw.js} +10 -5
- package/dist/{shell-5ju97nOS.js.map → shell-6CGFvUUw.js.map} +1 -1
- package/dist/shell.d.ts +1 -0
- package/dist/shell.js +6 -6
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js +2 -2
- package/dist/stores/settings.d.ts +4 -12
- package/dist/styles.css +2753 -2101
- package/dist/types/componentWorkflowTypes.d.ts +2 -0
- package/dist/types/components.d.ts +2 -0
- package/dist/types/generated/platformContext.d.ts +1 -0
- package/dist/types/generated-ui.d.ts +4 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/jobs.d.ts +9 -0
- package/dist/types/platform.d.ts +1 -1
- package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-uFIz6Yv8.js} +75 -5
- package/dist/useDropdownState-uFIz6Yv8.js.map +1 -0
- package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-QEFoq4tf.js} +48 -6
- package/dist/useExperimentSamples-QEFoq4tf.js.map +1 -0
- package/dist/{useListReorder-CcbEYxX-.js → useListReorder-DsuzwWGS.js} +219 -437
- package/dist/useListReorder-DsuzwWGS.js.map +1 -0
- package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-KpZ4iJ6o.js} +85 -81
- package/dist/useRackEditor-KpZ4iJ6o.js.map +1 -0
- package/dist/useRealtime-B58Lo-Tm.js +508 -0
- package/dist/useRealtime-B58Lo-Tm.js.map +1 -0
- package/dist/useRequest-BuDmW8Jc.js +215 -0
- package/dist/useRequest-BuDmW8Jc.js.map +1 -0
- package/dist/utils/time.d.ts +7 -0
- package/package.json +3 -2
- package/src/__stories__/experiment-helpers.ts +3 -3
- package/src/__tests__/components/ActionMenu.test.ts +2 -2
- package/src/__tests__/components/AppTopBar.test.ts +257 -3
- package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
- package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
- package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
- package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
- package/src/__tests__/components/BaseButton.test.ts +19 -0
- package/src/__tests__/components/BaseModal.test.ts +13 -0
- package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
- package/src/__tests__/components/BaseSelect.test.ts +2 -1
- package/src/__tests__/components/BatchProgressList.test.ts +16 -0
- package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
- package/src/__tests__/components/ChartContainer.test.ts +11 -0
- package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
- package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
- package/src/__tests__/components/DatePicker.test.ts +21 -0
- package/src/__tests__/components/DatePicker.time.test.ts +2 -2
- package/src/__tests__/components/EmptyState.test.ts +1 -1
- package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
- package/src/__tests__/components/FloatingPanelsInModal.test.ts +94 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
- package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
- package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
- package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
- package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
- package/src/__tests__/components/SampleSelector.test.ts +33 -8
- package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
- package/src/__tests__/components/SettingsModal.test.ts +9 -2
- package/src/__tests__/components/StepWizard.test.ts +11 -0
- package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
- package/src/__tests__/components/deprecationTags.test.ts +1 -1
- package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
- package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
- package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
- package/src/__tests__/composables/useFileImport.test.ts +36 -0
- package/src/__tests__/composables/useFloatingPanel.test.ts +68 -0
- package/src/__tests__/composables/useJobCenter.test.ts +262 -3
- package/src/__tests__/composables/useOnboarding.test.ts +317 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
- package/src/__tests__/fixtures/table-import/cells.xlsx +0 -0
- package/src/__tests__/fixtures/table-import/samples.xlsx +0 -0
- package/src/__tests__/stores/settings.test.ts +21 -21
- package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
- package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
- package/src/__tests__/styles/cssTokens.test.ts +6 -3
- package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
- package/src/__tests__/styles/paletteTokens.test.ts +44 -0
- package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
- package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
- package/src/__tests__/utils/time.test.ts +18 -1
- package/src/components/ActionMenu.vue +6 -37
- package/src/components/AdductText.story.vue +7 -0
- package/src/components/AdductText.vue +1 -1
- package/src/components/AlertBox.story.vue +2 -2
- package/src/components/AlertBox.vue +18 -16
- package/src/components/AppSidebar.story.vue +0 -28
- package/src/components/AppTopBar.onboarding.story.vue +69 -0
- package/src/components/AppTopBar.story.vue +105 -15
- package/src/components/AppTopBar.vue +248 -122
- package/src/components/ArtifactSaveDialog.story.vue +23 -0
- package/src/components/ArtifactSaveDialog.vue +102 -12
- package/src/components/AuditTrail.story.vue +2 -1
- package/src/components/AuditTrail.vue +2 -21
- package/src/components/Avatar.vue +10 -14
- package/src/components/BaseButton.vue +16 -2
- package/src/components/BaseModal.vue +2 -1
- package/src/components/BaseSelect.multiple.story.vue +13 -2
- package/src/components/BaseSelect.vue +4 -3
- package/src/components/BatchProgressList.vue +35 -25
- package/src/components/ChartContainer.story.vue +2 -1
- package/src/components/ChartContainer.vue +8 -6
- package/src/components/ConfirmDialog.story.vue +1 -1
- package/src/components/EmptyState.vue +1 -1
- package/src/components/ExperimentPopover.story.vue +165 -53
- package/src/components/ExperimentPopover.vue +169 -187
- package/src/components/ExperimentSelectorModal.story.vue +73 -31
- package/src/components/ExperimentSelectorModal.vue +144 -111
- package/src/components/FormField.story.vue +8 -48
- package/src/components/GeneratedPluginView.vue +11 -10
- package/src/components/GeneratedResultRenderer.vue +3 -4
- package/src/components/InstrumentStateBadge.vue +1 -1
- package/src/components/JobsStatusTray.story.vue +97 -43
- package/src/components/JobsStatusTray.vue +287 -290
- package/src/components/LayoutResizeHandle.story.vue +65 -4
- package/src/components/LoadingSpinner.vue +1 -1
- package/src/components/NumberInput.vue +3 -6
- package/src/components/PluginIcon.story.vue +2 -10
- package/src/components/PluginIcon.vue +2 -2
- package/src/components/PluginWorkspaceView.props.ts +3 -0
- package/src/components/PluginWorkspaceView.story.vue +1 -1
- package/src/components/PluginWorkspaceView.vue +17 -0
- package/src/components/RuntimeUpdateNotice.vue +1 -1
- package/src/components/SampleSelector.story.vue +22 -7
- package/src/components/SampleSelector.vue +34 -37
- package/src/components/SampleSelectorGroupNode.vue +1 -1
- package/src/components/SectionCard.story.vue +35 -2
- package/src/components/SequenceProgressBar.story.vue +9 -0
- package/src/components/SequenceProgressBar.vue +6 -1
- package/src/components/SettingsModal.schema.ts +1 -1
- package/src/components/SettingsModal.vue +9 -14
- package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
- package/src/components/SmartGroupManual.story.vue +1 -1
- package/src/components/StepWizard.story.vue +33 -0
- package/src/components/StepWizard.vue +8 -1
- package/src/components/Tooltip.vue +9 -32
- package/src/components/WellPlate.story.vue +3 -1
- package/src/components/WellPlate.vue +7 -0
- package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
- package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
- package/src/components/internal/DatePickerDateInternal.vue +23 -85
- package/src/components/internal/DatePickerDateTimeInternal.vue +10 -14
- package/src/components/internal/DatePickerTimeInternal.vue +13 -9
- package/src/components/internal/NumberUnitInputInternal.vue +4 -18
- package/src/components/internal/OnboardingLayerInternal.vue +173 -0
- package/src/components/internal/ResourcePickerListInternal.vue +45 -85
- package/src/components/internal/SelectMultipleInternal.vue +187 -65
- package/src/components/internal/SelectSearchableInternal.vue +8 -4
- package/src/components/internal/WellEditPopupInternal.vue +11 -22
- package/src/components/internal/WorkspaceShellInternal.vue +12 -0
- package/src/composables/createClient.ts +2 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/pluginIdentity.ts +31 -0
- package/src/composables/pluginWorkspaceModel.ts +2 -2
- package/src/composables/useAppExperiment.ts +20 -0
- package/src/composables/useCalendarGrid.ts +4 -3
- package/src/composables/useExperimentSelector.ts +77 -31
- package/src/composables/useFileImport.ts +51 -1
- package/src/composables/useFloatingPanel.ts +107 -0
- package/src/composables/useJobCenter.ts +188 -6
- package/src/composables/useOnboarding.ts +465 -0
- package/src/composables/usePluginWorkspace.ts +1 -1
- package/src/data.ts +1 -0
- package/src/runtime.ts +2 -1
- package/src/shell.ts +17 -0
- package/src/stores/index.ts +0 -2
- package/src/stores/settings.ts +20 -50
- package/src/styles/components/action-menu.css +1 -1
- package/src/styles/components/adduct-text.css +13 -5
- package/src/styles/components/alert-box.css +39 -58
- package/src/styles/components/app-layout.css +10 -6
- package/src/styles/components/app-page-selector.css +121 -44
- package/src/styles/components/app-pill-nav.css +11 -12
- package/src/styles/components/app-sidebar.css +9 -18
- package/src/styles/components/app-top-bar.css +219 -55
- package/src/styles/components/artifact-save-dialog.css +153 -2
- package/src/styles/components/audit-trail.css +16 -19
- package/src/styles/components/avatar.css +6 -4
- package/src/styles/components/batch-progress-list.css +38 -36
- package/src/styles/components/button.css +36 -59
- package/src/styles/components/calendar.css +12 -12
- package/src/styles/components/chart-container.css +34 -23
- package/src/styles/components/checkbox.css +2 -12
- package/src/styles/components/collapsible-card.css +27 -29
- package/src/styles/components/confirm-dialog.css +13 -14
- package/src/styles/components/dataframe.css +13 -5
- package/src/styles/components/date-picker.css +84 -94
- package/src/styles/components/datetime-picker.css +35 -47
- package/src/styles/components/divider.css +20 -20
- package/src/styles/components/dose-calculator.css +3 -3
- package/src/styles/components/empty-state.css +20 -17
- package/src/styles/components/experiment-code-badge.css +2 -0
- package/src/styles/components/experiment-popover.css +174 -329
- package/src/styles/components/experiment-selector-modal.css +88 -56
- package/src/styles/components/file-picker.css +3 -3
- package/src/styles/components/file-uploader.css +38 -49
- package/src/styles/components/form-builder.css +3 -3
- package/src/styles/components/formula-input.css +1 -1
- package/src/styles/components/icon-button.css +18 -24
- package/src/styles/components/input.css +5 -4
- package/src/styles/components/instrument-monitor.css +13 -15
- package/src/styles/components/jobs-status-tray.css +140 -296
- package/src/styles/components/layout-resize-handle.css +59 -16
- package/src/styles/components/modal.css +5 -7
- package/src/styles/components/molecule-input.css +2 -2
- package/src/styles/components/multi-select.css +65 -117
- package/src/styles/components/number-input.css +26 -19
- package/src/styles/components/onboarding.css +368 -0
- package/src/styles/components/pill.css +3 -2
- package/src/styles/components/plate-editor.css +0 -1
- package/src/styles/components/plugin-icon.css +26 -23
- package/src/styles/components/popover.css +2 -2
- package/src/styles/components/presence-avatars.css +1 -1
- package/src/styles/components/progress-bar.css +3 -3
- package/src/styles/components/protocol-step-editor.css +1 -3
- package/src/styles/components/resource-card.css +2 -2
- package/src/styles/components/resource-picker-list.css +6 -57
- package/src/styles/components/sample-hierarchy-tree.css +4 -4
- package/src/styles/components/sample-selector.css +138 -79
- package/src/styles/components/schedule-calendar.css +8 -8
- package/src/styles/components/searchable-select.css +15 -19
- package/src/styles/components/section-card.css +54 -49
- package/src/styles/components/segmented-control.css +18 -15
- package/src/styles/components/select.css +3 -3
- package/src/styles/components/sequence-input.css +2 -2
- package/src/styles/components/sequence-progress-bar.css +17 -8
- package/src/styles/components/settings-modal.css +7 -2
- package/src/styles/components/smart-group.css +18 -18
- package/src/styles/components/step-wizard.css +52 -16
- package/src/styles/components/tabs.css +2 -2
- package/src/styles/components/textarea.css +3 -3
- package/src/styles/components/time-picker.css +37 -25
- package/src/styles/components/toast.css +13 -11
- package/src/styles/components/toggle.css +1 -11
- package/src/styles/components/tooltip.css +12 -11
- package/src/styles/components/unit-input.css +9 -8
- package/src/styles/components/well-edit-popup.css +2 -2
- package/src/styles/components/well-plate.css +9 -2
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +247 -78
- package/src/types/componentWorkflowTypes.ts +2 -0
- package/src/types/components.ts +2 -0
- package/src/types/generated/platformContext.ts +1 -0
- package/src/types/generated-ui.ts +4 -0
- package/src/types/index.ts +1 -0
- package/src/types/jobs.ts +10 -0
- package/src/types/platform.ts +1 -1
- package/src/utils/time.ts +21 -0
- package/dist/ActionMenu-CScINahU.js.map +0 -1
- package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
- package/dist/BaseModal-DfRIuVoj.js.map +0 -1
- package/dist/ExperimentPopover-C4sGw-is.js +0 -359
- package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
- package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
- package/dist/SettingsModal-CSLE31yg.js.map +0 -1
- package/dist/data-C2PwI_dn.js.map +0 -1
- package/dist/experiment-43OJtH7R.js.map +0 -1
- package/dist/lab-DLwRl_Ql.js.map +0 -1
- package/dist/records-BzTcxekG.js.map +0 -1
- package/dist/runtime-D6gVat6r.js.map +0 -1
- package/dist/useExperimentSamples-CxcnxZ8h.js.map +0 -1
- package/dist/useListReorder-CcbEYxX-.js.map +0 -1
- package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
- package/dist/useRealtime-PalKue84.js +0 -696
- package/dist/useRealtime-PalKue84.js.map +0 -1
- package/dist/useRequest-D5JDfsgZ.js +0 -76
- package/dist/useRequest-D5JDfsgZ.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Save/update dialog for analysis artifacts; delegates the write to a plugin-supplied handler. */
|
|
3
|
-
import { computed, ref, shallowRef, watch } from 'vue'
|
|
3
|
+
import { computed, getCurrentInstance, ref, shallowRef, watch } from 'vue'
|
|
4
|
+
import { CloudUpload, File as FileIcon } from 'lucide-vue-next'
|
|
4
5
|
import BaseModal from './BaseModal.vue'
|
|
5
6
|
import BaseInput from './BaseInput.vue'
|
|
6
7
|
import BaseButton from './BaseButton.vue'
|
|
@@ -10,6 +11,7 @@ import FileUploader from './FileUploader.vue'
|
|
|
10
11
|
import SegmentedControl from './SegmentedControl.vue'
|
|
11
12
|
import { useRequest } from '../composables/useRequest'
|
|
12
13
|
import { useExperimentStore } from '../stores/experiment'
|
|
14
|
+
import { formatExperimentDate } from '../composables/experiment-utils'
|
|
13
15
|
import {
|
|
14
16
|
ANALYSIS_FILE_ARTIFACT_SCHEMA,
|
|
15
17
|
type AnalysisArtifactDetail,
|
|
@@ -20,7 +22,7 @@ import {
|
|
|
20
22
|
type ArtifactSaveResult,
|
|
21
23
|
type HandlerArtifactSaveHandler,
|
|
22
24
|
} from '../types/analysisArtifactTypes'
|
|
23
|
-
import type { ModalSize } from '../types'
|
|
25
|
+
import type { ExperimentSummary, ModalSize } from '../types'
|
|
24
26
|
|
|
25
27
|
interface Props {
|
|
26
28
|
open: boolean
|
|
@@ -34,6 +36,11 @@ interface Props {
|
|
|
34
36
|
result?: Record<string, unknown>
|
|
35
37
|
/** Experiment id override; defaults to the current experiment (`useExperimentStore`). */
|
|
36
38
|
experimentId?: number | null
|
|
39
|
+
/**
|
|
40
|
+
* Experiment shown in the strip at the top. Defaults to the store's current
|
|
41
|
+
* experiment when it is the one being saved to; no strip when unknown.
|
|
42
|
+
*/
|
|
43
|
+
experiment?: ExperimentSummary | null
|
|
37
44
|
/** Existing artifact detail; when set the dialog updates instead of creating. */
|
|
38
45
|
existingArtifact?: AnalysisArtifactDetail | null
|
|
39
46
|
/** Known artifact keys, used to warn (json) or block (file) on collisions. */
|
|
@@ -60,6 +67,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
60
67
|
mode: 'file',
|
|
61
68
|
fileSource: 'upload',
|
|
62
69
|
experimentId: null,
|
|
70
|
+
experiment: null,
|
|
63
71
|
existingArtifact: null,
|
|
64
72
|
size: 'md',
|
|
65
73
|
defaultArtifactKey: 'default',
|
|
@@ -71,14 +79,25 @@ const emit = defineEmits<{
|
|
|
71
79
|
saved: [result: ArtifactSaveResult & { payload: ArtifactSaveDialogPayload }]
|
|
72
80
|
error: [message: string]
|
|
73
81
|
cancel: []
|
|
82
|
+
/** The "Change" link in the experiment strip; shown only when a listener is bound. */
|
|
83
|
+
'change-experiment': []
|
|
74
84
|
}>()
|
|
75
85
|
|
|
86
|
+
// Declared emits leave $attrs, so the bound listener is read off the vnode.
|
|
87
|
+
const canChangeExperiment = Boolean(getCurrentInstance()?.vnode.props?.onChangeExperiment)
|
|
88
|
+
|
|
76
89
|
const ARTIFACT_KEY_PATTERN = /^[a-z0-9][a-z0-9._-]*$/
|
|
77
90
|
|
|
78
91
|
const experimentStore = useExperimentStore()
|
|
79
92
|
const request = useRequest('Failed to save analysis artifact.')
|
|
80
93
|
|
|
81
|
-
|
|
94
|
+
/** With an existing artifact the user picks: replace it, or save a separate one. */
|
|
95
|
+
const saveMode = ref<'replace' | 'new'>('replace')
|
|
96
|
+
const isUpdate = computed(() => props.existingArtifact !== null && saveMode.value === 'replace')
|
|
97
|
+
const SAVE_MODES = [
|
|
98
|
+
{ value: 'replace', label: 'Replace an existing result', description: 'Keeps the key; collaborators see the new version.' },
|
|
99
|
+
{ value: 'new', label: 'Save as a new result', description: 'Adds a separate artifact next to the others.' },
|
|
100
|
+
] as const
|
|
82
101
|
const existingResult = computed(
|
|
83
102
|
() => (props.existingArtifact?.result ?? {}) as Record<string, unknown>,
|
|
84
103
|
)
|
|
@@ -104,7 +123,13 @@ const selectedFile = shallowRef<File | null>(null)
|
|
|
104
123
|
let initialNote = ''
|
|
105
124
|
|
|
106
125
|
function resetForm(): void {
|
|
107
|
-
|
|
126
|
+
saveMode.value = 'replace'
|
|
127
|
+
syncFields()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Fills the fields for the current save mode; a mode switch calls only this. */
|
|
131
|
+
function syncFields(): void {
|
|
132
|
+
const existing = isUpdate.value ? props.existingArtifact : null
|
|
108
133
|
activeKind.value = existing
|
|
109
134
|
? (existingIsFile.value ? 'file' : 'json')
|
|
110
135
|
: (props.mode === 'json' ? 'json' : 'file')
|
|
@@ -120,6 +145,7 @@ resetForm()
|
|
|
120
145
|
watch(() => props.open, (open) => {
|
|
121
146
|
if (open) resetForm()
|
|
122
147
|
})
|
|
148
|
+
watch(saveMode, syncFields)
|
|
123
149
|
// Key off identity, not object reference: re-fetching the same artifact must not
|
|
124
150
|
// wipe in-progress edits.
|
|
125
151
|
watch(
|
|
@@ -127,8 +153,17 @@ watch(
|
|
|
127
153
|
resetForm,
|
|
128
154
|
)
|
|
129
155
|
|
|
130
|
-
const resolvedTitle = computed(() =>
|
|
131
|
-
|
|
156
|
+
const resolvedTitle = computed(() => props.title ?? 'Save to experiment')
|
|
157
|
+
|
|
158
|
+
const stripExperiment = computed(() => {
|
|
159
|
+
if (props.experiment) return props.experiment
|
|
160
|
+
const current = experimentStore.current
|
|
161
|
+
return current && current.id === resolveExperimentId() ? current : null
|
|
162
|
+
})
|
|
163
|
+
const existingUpdated = computed(() => {
|
|
164
|
+
const date = props.existingArtifact?.updated_at ?? props.existingArtifact?.created_at
|
|
165
|
+
return date ? formatExperimentDate(date) : ''
|
|
166
|
+
})
|
|
132
167
|
const showKindToggle = computed(() => !isUpdate.value && props.mode === 'both')
|
|
133
168
|
const showFileUploader = computed(
|
|
134
169
|
() => activeKind.value === 'file' && props.fileSource === 'upload',
|
|
@@ -143,12 +178,16 @@ const effectiveKey = computed(() => {
|
|
|
143
178
|
return keyInput.value.trim() || props.defaultArtifactKey
|
|
144
179
|
})
|
|
145
180
|
const keyValid = computed(() => ARTIFACT_KEY_PATTERN.test(effectiveKey.value))
|
|
146
|
-
const keyExists = computed(
|
|
147
|
-
(
|
|
148
|
-
)
|
|
181
|
+
const keyExists = computed(() => {
|
|
182
|
+
if (isUpdate.value) return false
|
|
183
|
+
const known = [...(props.existingArtifactKeys ?? []), props.existingArtifact?.artifact_key]
|
|
184
|
+
return known.includes(effectiveKey.value)
|
|
185
|
+
})
|
|
149
186
|
// A file artifact is create-only: reusing a key always fails backend-side, and the
|
|
150
|
-
// dialog has no detail to replace it safely. JSON keys upsert natively, so warn only
|
|
151
|
-
|
|
187
|
+
// dialog has no detail to replace it safely. JSON keys upsert natively, so warn only,
|
|
188
|
+
// unless the user explicitly chose "Save as a new result" over the replace option.
|
|
189
|
+
const keyBlocked = computed(() => keyExists.value
|
|
190
|
+
&& (activeKind.value === 'file' || (props.existingArtifact !== null && saveMode.value === 'new')))
|
|
152
191
|
const keyError = computed(() => {
|
|
153
192
|
if (!keyValid.value) return 'Use lowercase letters, digits, dots, dashes, or underscores.'
|
|
154
193
|
if (keyBlocked.value) return 'This key is already used. Choose another, or replace the existing artifact.'
|
|
@@ -324,6 +363,56 @@ function handleCancel(): void {
|
|
|
324
363
|
@close="emit('cancel')"
|
|
325
364
|
>
|
|
326
365
|
<div class="mint-artifact-save-dialog">
|
|
366
|
+
<div v-if="stripExperiment" class="mint-artifact-save-dialog__experiment">
|
|
367
|
+
<span
|
|
368
|
+
v-if="stripExperiment.experiment_code"
|
|
369
|
+
class="mint-exp-code mint-exp-code--sm"
|
|
370
|
+
>{{ stripExperiment.experiment_code }}</span>
|
|
371
|
+
<span class="mint-artifact-save-dialog__experiment-name">{{ stripExperiment.name }}</span>
|
|
372
|
+
<button
|
|
373
|
+
v-if="canChangeExperiment"
|
|
374
|
+
type="button"
|
|
375
|
+
class="mint-artifact-save-dialog__change"
|
|
376
|
+
@click="emit('change-experiment')"
|
|
377
|
+
>
|
|
378
|
+
Change
|
|
379
|
+
</button>
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<div
|
|
383
|
+
v-if="existingArtifact"
|
|
384
|
+
class="mint-artifact-save-dialog__modes"
|
|
385
|
+
role="radiogroup"
|
|
386
|
+
aria-label="Save mode"
|
|
387
|
+
>
|
|
388
|
+
<button
|
|
389
|
+
v-for="option in SAVE_MODES"
|
|
390
|
+
:key="option.value"
|
|
391
|
+
type="button"
|
|
392
|
+
role="radio"
|
|
393
|
+
:aria-checked="saveMode === option.value"
|
|
394
|
+
class="mint-artifact-save-dialog__mode"
|
|
395
|
+
@click="saveMode = option.value"
|
|
396
|
+
>
|
|
397
|
+
<span class="mint-artifact-save-dialog__mode-dot" aria-hidden="true" />
|
|
398
|
+
<span class="mint-artifact-save-dialog__mode-text">
|
|
399
|
+
<b>{{ option.label }}</b>
|
|
400
|
+
<span>{{ option.description }}</span>
|
|
401
|
+
</span>
|
|
402
|
+
</button>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<div v-if="isUpdate && existingArtifact" class="mint-artifact-save-dialog__list">
|
|
406
|
+
<div class="mint-artifact-save-dialog__row mint-artifact-save-dialog__row--selected">
|
|
407
|
+
<FileIcon class="mint-artifact-save-dialog__row-icon" :size="15" aria-hidden="true" />
|
|
408
|
+
<span class="mint-artifact-save-dialog__row-name">
|
|
409
|
+
{{ existingArtifact.display_name }}
|
|
410
|
+
<span>{{ existingArtifact.artifact_key }}</span>
|
|
411
|
+
</span>
|
|
412
|
+
<span v-if="existingUpdated" class="mint-artifact-save-dialog__row-meta">{{ existingUpdated }}</span>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
|
|
327
416
|
<SegmentedControl
|
|
328
417
|
v-if="showKindToggle"
|
|
329
418
|
v-model="activeKind"
|
|
@@ -416,11 +505,12 @@ function handleCancel(): void {
|
|
|
416
505
|
</BaseButton>
|
|
417
506
|
<BaseButton
|
|
418
507
|
class="mint-artifact-save-dialog__submit"
|
|
508
|
+
:icon="CloudUpload"
|
|
419
509
|
:loading="request.isLoading.value"
|
|
420
510
|
:disabled="!canSubmit"
|
|
421
511
|
@click="handleSubmit"
|
|
422
512
|
>
|
|
423
|
-
{{ isUpdate ? '
|
|
513
|
+
{{ isUpdate ? 'Replace result' : 'Save result' }}
|
|
424
514
|
</BaseButton>
|
|
425
515
|
</template>
|
|
426
516
|
</BaseModal>
|
|
@@ -80,7 +80,8 @@ function initState() {
|
|
|
80
80
|
return {
|
|
81
81
|
loading: false,
|
|
82
82
|
error: '',
|
|
83
|
-
|
|
83
|
+
// Histoire serializes state; Date objects come back unparseable, ISO strings survive.
|
|
84
|
+
entries: mockEntries.map((entry) => ({ ...entry, timestamp: entry.timestamp.toISOString() })),
|
|
84
85
|
showFilters: false,
|
|
85
86
|
emptyText: 'No activity yet',
|
|
86
87
|
order: 'newest' as const,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/** Filterable, sortable timeline of audit entries for tracking experiment or resource history. */
|
|
3
3
|
import { computed, ref } from 'vue'
|
|
4
4
|
import type { AuditEntry } from '../types'
|
|
5
|
+
import { formatRelativeTime } from '../utils/time'
|
|
5
6
|
import BaseSelect from './BaseSelect.vue'
|
|
6
7
|
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
7
8
|
|
|
@@ -79,26 +80,6 @@ function getInitials(name: string): string {
|
|
|
79
80
|
return (words[0]?.[0] ?? '').toUpperCase()
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
|
|
83
|
-
|
|
84
|
-
function formatTimestamp(timestamp: Date | string): string {
|
|
85
|
-
const date = timestamp instanceof Date ? timestamp : new Date(timestamp)
|
|
86
|
-
const now = Date.now()
|
|
87
|
-
const diffMs = date.getTime() - now
|
|
88
|
-
const diffSec = Math.round(diffMs / 1000)
|
|
89
|
-
const diffMin = Math.round(diffSec / 60)
|
|
90
|
-
const diffHr = Math.round(diffMin / 60)
|
|
91
|
-
const diffDay = Math.round(diffHr / 24)
|
|
92
|
-
const diffWeek = Math.round(diffDay / 7)
|
|
93
|
-
|
|
94
|
-
if (Math.abs(diffSec) < 60) return rtf.format(diffSec, 'second')
|
|
95
|
-
if (Math.abs(diffMin) < 60) return rtf.format(diffMin, 'minute')
|
|
96
|
-
if (Math.abs(diffHr) < 24) return rtf.format(diffHr, 'hour')
|
|
97
|
-
if (Math.abs(diffDay) < 7) return rtf.format(diffDay, 'day')
|
|
98
|
-
if (Math.abs(diffWeek) < 5) return rtf.format(diffWeek, 'week')
|
|
99
|
-
return date.toLocaleDateString()
|
|
100
|
-
}
|
|
101
|
-
|
|
102
83
|
function handleEntryClick(entry: AuditEntry) {
|
|
103
84
|
emit('entry-click', entry)
|
|
104
85
|
}
|
|
@@ -146,7 +127,7 @@ function handleEntryClick(entry: AuditEntry) {
|
|
|
146
127
|
<span class="mint-audit-trail__sentence">
|
|
147
128
|
<span v-if="entry.user" class="mint-audit-trail__user">{{ entry.user }}</span> <span class="mint-audit-trail__action">{{ entry.action }}</span>
|
|
148
129
|
</span>
|
|
149
|
-
<span class="mint-audit-trail__timestamp">{{
|
|
130
|
+
<span class="mint-audit-trail__timestamp">{{ formatRelativeTime(entry.timestamp, { weeks: true }) }}</span>
|
|
150
131
|
</div>
|
|
151
132
|
<div v-if="entry.detail" class="mint-audit-trail__detail">{{ entry.detail }}</div>
|
|
152
133
|
<div v-if="entry.metadata && Object.keys(entry.metadata).length" class="mint-audit-trail__metadata">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
/** Displays a user avatar as an image, a single derived initial, or a fallback icon,
|
|
2
|
+
/** Displays a user avatar as an image, a single derived initial, or a fallback icon, on a quiet accent face (or an explicit `color`) with an optional status dot. */
|
|
3
3
|
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -17,24 +17,20 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
17
17
|
size: 'md',
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
const PALETTE = ['#3B82F6', '#8B5CF6', '#EC4899', '#F97316', '#10B981', '#F59E0B', '#06B6D4', '#6366F1']
|
|
21
|
-
|
|
22
20
|
// Default: first letter of `name`, uppercased. Consumers wanting two letters
|
|
23
21
|
// (e.g. role badges like "PI", "QC") pass `:initials="'XX'"` explicitly.
|
|
24
22
|
const computedInitials = computed(() =>
|
|
25
23
|
props.initials || props.name?.charAt(0).toUpperCase() || ''
|
|
26
24
|
)
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return PALETTE[Math.abs(hash) % PALETTE.length]
|
|
37
|
-
})
|
|
26
|
+
// The quiet accent face lives in avatar.css; an explicit `color` fills the
|
|
27
|
+
// face. That fill is a fixed hue (role badges), so the ink stays white in both
|
|
28
|
+
// themes instead of following the theme-dependent --text-on-accent.
|
|
29
|
+
const faceStyle = computed(() =>
|
|
30
|
+
props.color
|
|
31
|
+
? { backgroundColor: props.color, color: '#fff', boxShadow: 'none' }
|
|
32
|
+
: undefined
|
|
33
|
+
)
|
|
38
34
|
</script>
|
|
39
35
|
|
|
40
36
|
<template>
|
|
@@ -45,7 +41,7 @@ const backgroundColor = computed(() => {
|
|
|
45
41
|
>
|
|
46
42
|
<span
|
|
47
43
|
class="mint-avatar__face"
|
|
48
|
-
:style="
|
|
44
|
+
:style="faceStyle"
|
|
49
45
|
>
|
|
50
46
|
<img
|
|
51
47
|
v-if="src"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* <BaseButton variant="ghost" :icon="Trash2" label="Delete" />
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
|
-
import { computed, useSlots, type Component } from 'vue'
|
|
13
|
+
import { Comment, Fragment, Text, computed, useSlots, type Component, type VNode } from 'vue'
|
|
14
14
|
import type { ButtonVariant, ButtonSize } from '../types'
|
|
15
15
|
|
|
16
16
|
interface Props {
|
|
@@ -45,6 +45,19 @@ const slots = useSlots()
|
|
|
45
45
|
const iconOnly = computed(() => Boolean(props.icon) && !slots.default)
|
|
46
46
|
const block = computed(() => (iconOnly.value ? 'mint-icon-button' : 'mint-button'))
|
|
47
47
|
|
|
48
|
+
// A text-only label gets its own span so the stylesheet can centre the cap height.
|
|
49
|
+
// Element children are left as flex items, so their gap and alignment do not change.
|
|
50
|
+
function isTextNode(node: VNode): boolean {
|
|
51
|
+
if (node.type === Text || node.type === Comment) return true
|
|
52
|
+
return node.type === Fragment && Array.isArray(node.children)
|
|
53
|
+
&& (node.children as VNode[]).every(isTextNode)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function hasTextLabel(): boolean {
|
|
57
|
+
const nodes = slots.default?.()
|
|
58
|
+
return !!nodes && nodes.length > 0 && nodes.every(isTextNode)
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
function handleClick(event: MouseEvent) {
|
|
49
62
|
if (!props.disabled && !props.loading) {
|
|
50
63
|
emit('click', event)
|
|
@@ -93,7 +106,8 @@ function handleClick(event: MouseEvent) {
|
|
|
93
106
|
<template v-if="!iconOnly">
|
|
94
107
|
<component :is="icon" v-if="icon" class="mint-button__icon" aria-hidden="true" />
|
|
95
108
|
<slot name="icon-left" />
|
|
96
|
-
<slot
|
|
109
|
+
<span v-if="hasTextLabel()" class="mint-button__label"><slot /></span>
|
|
110
|
+
<slot v-else />
|
|
97
111
|
<slot name="icon-right" />
|
|
98
112
|
</template>
|
|
99
113
|
</button>
|
|
@@ -107,8 +107,9 @@ watch(() => props.open, (isOpen) => {
|
|
|
107
107
|
if (!isOpen && index !== -1) openModals.splice(index, 1)
|
|
108
108
|
}, { immediate: true })
|
|
109
109
|
|
|
110
|
+
// A control inside the modal that handles Escape (an open select) prevents the default first.
|
|
110
111
|
function handleEscapeKeydown(event: KeyboardEvent) {
|
|
111
|
-
if (event.key === 'Escape' && props.closeOnEscape && props.open && openModals[openModals.length - 1] === self) {
|
|
112
|
+
if (event.key === 'Escape' && !event.defaultPrevented && props.closeOnEscape && props.open && openModals[openModals.length - 1] === self) {
|
|
112
113
|
close()
|
|
113
114
|
}
|
|
114
115
|
}
|
|
@@ -39,7 +39,7 @@ function initPlayground() {
|
|
|
39
39
|
<Story title="Base Inputs/BaseSelect · multiple">
|
|
40
40
|
<Variant title="Playground" :init-state="initPlayground">
|
|
41
41
|
<template #default="{ state }">
|
|
42
|
-
<div style="padding: 2rem; max-width: 500px; margin: 0 auto;">
|
|
42
|
+
<div style="padding: 2rem; max-width: 500px; min-height: 22rem; margin: 0 auto;">
|
|
43
43
|
<BaseSelect
|
|
44
44
|
multiple
|
|
45
45
|
v-model="state.selected"
|
|
@@ -91,7 +91,7 @@ function initPlayground() {
|
|
|
91
91
|
</Variant>
|
|
92
92
|
|
|
93
93
|
<Variant title="With Max Selections">
|
|
94
|
-
<div style="padding: 2rem; max-width: 500px; margin: 0 auto;">
|
|
94
|
+
<div style="padding: 2rem; max-width: 500px; min-height: 22rem; margin: 0 auto;">
|
|
95
95
|
<p style="margin: 0 0 0.5rem; font-size: 0.875rem; color: var(--text-primary, #e2e8f0);">
|
|
96
96
|
Maximum 3 selections allowed
|
|
97
97
|
</p>
|
|
@@ -124,6 +124,17 @@ function initPlayground() {
|
|
|
124
124
|
</div>
|
|
125
125
|
</Variant>
|
|
126
126
|
|
|
127
|
+
<Variant title="Invalid">
|
|
128
|
+
<div style="padding: 2rem; max-width: 500px; margin: 0 auto;">
|
|
129
|
+
<BaseSelect
|
|
130
|
+
multiple
|
|
131
|
+
:model-value="['hela']"
|
|
132
|
+
:options="sampleOptions"
|
|
133
|
+
invalid
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
</Variant>
|
|
137
|
+
|
|
127
138
|
<Variant title="Disabled">
|
|
128
139
|
<div style="padding: 2rem; max-width: 500px; margin: 0 auto;">
|
|
129
140
|
<BaseSelect
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Select one or several options.
|
|
4
4
|
* Default: a native `<select>`. `searchable`: a listbox with a search field and the `#option` / `#footer` slots.
|
|
5
|
-
* `multiple`: a checkbox
|
|
5
|
+
* `multiple`: a field of removable chips with a filterable checkbox listbox, bound to an array and capped by `max`.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```vue
|
|
@@ -25,7 +25,7 @@ const props = withDefaults(defineProps<{
|
|
|
25
25
|
/** The selected value; an array with `multiple`. */
|
|
26
26
|
modelValue?: (M extends true ? OptionPrimitive[] : OptionPrimitive) | null
|
|
27
27
|
options: SelectOptionInput[]
|
|
28
|
-
/** Bind an array and pick several options (chip
|
|
28
|
+
/** Bind an array and pick several options (chip field + checkbox listbox). */
|
|
29
29
|
multiple?: M
|
|
30
30
|
/** Single selection in a listbox with a search field. Ignored with `multiple`. */
|
|
31
31
|
searchable?: boolean
|
|
@@ -40,7 +40,7 @@ const props = withDefaults(defineProps<{
|
|
|
40
40
|
invalid?: boolean
|
|
41
41
|
size?: 'sm' | 'md' | 'lg'
|
|
42
42
|
ariaDescribedby?: string
|
|
43
|
-
/** Id of the visible label
|
|
43
|
+
/** Id of the visible label. `searchable`: the trigger is then named by the label and the selected value. */
|
|
44
44
|
ariaLabelledby?: string
|
|
45
45
|
}>(), {
|
|
46
46
|
modelValue: null,
|
|
@@ -118,6 +118,7 @@ function handleChange(event: Event) {
|
|
|
118
118
|
:invalid="invalid"
|
|
119
119
|
:max="max"
|
|
120
120
|
:size="size"
|
|
121
|
+
:aria-labelledby="ariaLabelledby"
|
|
121
122
|
:aria-describedby="ariaDescribedby"
|
|
122
123
|
@update:model-value="update"
|
|
123
124
|
/>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Scrollable list tracking per-item status for a batch operation with overall progress summary. */
|
|
3
3
|
import { computed, ref, watch, nextTick } from 'vue'
|
|
4
|
-
import
|
|
4
|
+
import { CircleCheck, CircleDashed, CircleMinus, CircleX, Loader } from 'lucide-vue-next'
|
|
5
|
+
import type { BatchItem, BatchItemStatus, BatchSummary } from '../types'
|
|
5
6
|
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
@@ -34,6 +35,28 @@ const emit = defineEmits<{
|
|
|
34
35
|
'cancel': [id: string]
|
|
35
36
|
}>()
|
|
36
37
|
|
|
38
|
+
const STATUS_ICONS: Record<BatchItemStatus, typeof CircleCheck> = {
|
|
39
|
+
completed: CircleCheck,
|
|
40
|
+
processing: Loader,
|
|
41
|
+
pending: CircleDashed,
|
|
42
|
+
error: CircleX,
|
|
43
|
+
skipped: CircleMinus,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const STATUS_TEXT: Partial<Record<BatchItemStatus, string>> = {
|
|
47
|
+
pending: 'Queued',
|
|
48
|
+
error: 'Failed',
|
|
49
|
+
skipped: 'Skipped',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function itemPercent(item: BatchItem): number {
|
|
53
|
+
return item.status === 'completed' ? 100 : (item.progress ?? 0)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function statusText(item: BatchItem): string {
|
|
57
|
+
return STATUS_TEXT[item.status] ?? `${itemPercent(item)}%`
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
const listRef = ref<HTMLElement | null>(null)
|
|
38
61
|
// 6b: completed items keep full contrast but collapse behind a toggle.
|
|
39
62
|
const showCompleted = ref(false)
|
|
@@ -141,34 +164,17 @@ watch(
|
|
|
141
164
|
>
|
|
142
165
|
<slot name="item" :item="item">
|
|
143
166
|
<div class="mint-batch-progress__item-row">
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
</svg>
|
|
151
|
-
<svg v-else-if="item.status === 'completed'" class="mint-batch-progress__shape" viewBox="0 0 16 16" fill="none">
|
|
152
|
-
<path class="mint-batch-progress__check" d="M3.5 8.5l3 3 6-7" />
|
|
153
|
-
</svg>
|
|
154
|
-
<svg v-else-if="item.status === 'error'" class="mint-batch-progress__shape" viewBox="0 0 16 16">
|
|
155
|
-
<circle class="mint-batch-progress__error-disc" cx="8" cy="8" r="7" />
|
|
156
|
-
<path class="mint-batch-progress__error-mark" d="M8 4.5v4M8 11v.01" />
|
|
157
|
-
</svg>
|
|
158
|
-
<svg v-else class="mint-batch-progress__shape" viewBox="0 0 16 16" fill="none">
|
|
159
|
-
<path class="mint-batch-progress__dash" d="M4 8h8" />
|
|
160
|
-
</svg>
|
|
161
|
-
</span>
|
|
167
|
+
<component
|
|
168
|
+
:is="STATUS_ICONS[item.status]"
|
|
169
|
+
:class="['mint-batch-progress__item-icon', `mint-batch-progress__item-icon--${item.status}`]"
|
|
170
|
+
:size="16"
|
|
171
|
+
aria-hidden="true"
|
|
172
|
+
/>
|
|
162
173
|
|
|
163
174
|
<span class="mint-batch-progress__item-label">{{ item.label }}</span>
|
|
164
175
|
|
|
165
176
|
<span class="mint-batch-progress__item-meta">
|
|
166
|
-
<
|
|
167
|
-
<span class="mint-batch-progress__item-progress">
|
|
168
|
-
<span class="mint-batch-progress__item-progress-bar" :style="{ width: `${item.progress}%` }" />
|
|
169
|
-
</span>
|
|
170
|
-
{{ item.progress }}%
|
|
171
|
-
</template>
|
|
177
|
+
<span class="mint-batch-progress__item-status">{{ statusText(item) }}</span>
|
|
172
178
|
<button
|
|
173
179
|
v-if="item.status === 'error'"
|
|
174
180
|
type="button"
|
|
@@ -186,6 +192,10 @@ watch(
|
|
|
186
192
|
Cancel
|
|
187
193
|
</button>
|
|
188
194
|
</span>
|
|
195
|
+
|
|
196
|
+
<span class="mint-batch-progress__item-progress" aria-hidden="true">
|
|
197
|
+
<span class="mint-batch-progress__item-progress-bar" :style="{ width: `${itemPercent(item)}%` }" />
|
|
198
|
+
</span>
|
|
189
199
|
</div>
|
|
190
200
|
|
|
191
201
|
<div v-if="item.status === 'error' && item.message" class="mint-batch-progress__item-message">
|
|
@@ -76,7 +76,8 @@ function initLegendState() {
|
|
|
76
76
|
<ChartContainer
|
|
77
77
|
title="Experiment Results"
|
|
78
78
|
empty
|
|
79
|
-
empty-text="No
|
|
79
|
+
empty-text="No samples selected"
|
|
80
|
+
empty-hint="Pick samples to draw the distribution."
|
|
80
81
|
/>
|
|
81
82
|
</div>
|
|
82
83
|
</Variant>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** Wraps a chart with header, toolbar slot, loading overlay, empty state, and legend slot. */
|
|
3
3
|
import type { ChartLegendItem } from '../types'
|
|
4
4
|
import LoadingSpinner from './LoadingSpinner.vue'
|
|
5
|
-
import
|
|
5
|
+
import { ChartLine } from 'lucide-vue-next'
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
8
|
title?: string
|
|
@@ -10,6 +10,8 @@ interface Props {
|
|
|
10
10
|
loading?: boolean
|
|
11
11
|
empty?: boolean
|
|
12
12
|
emptyText?: string
|
|
13
|
+
/** Muted line under the empty title. */
|
|
14
|
+
emptyHint?: string
|
|
13
15
|
/** `frame` fills its parent and uses compact workbench chrome. */
|
|
14
16
|
variant?: 'card' | 'frame'
|
|
15
17
|
/** Series shown as clickable items in the header; a click emits `legend-toggle`. */
|
|
@@ -77,11 +79,11 @@ defineSlots<{
|
|
|
77
79
|
<!-- Empty state -->
|
|
78
80
|
<template v-else-if="empty">
|
|
79
81
|
<slot name="empty">
|
|
80
|
-
<
|
|
81
|
-
:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
<div class="mint-chart__empty">
|
|
83
|
+
<ChartLine :size="22" aria-hidden="true" />
|
|
84
|
+
<span class="mint-chart__empty-title">{{ emptyText }}</span>
|
|
85
|
+
<span v-if="emptyHint" class="mint-chart__empty-hint">{{ emptyHint }}</span>
|
|
86
|
+
</div>
|
|
85
87
|
</slot>
|
|
86
88
|
</template>
|
|
87
89
|
|
|
@@ -49,7 +49,7 @@ function initPlayground() {
|
|
|
49
49
|
<template #default="{ state }">
|
|
50
50
|
<div style="padding: 2rem; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem;">
|
|
51
51
|
<BaseButton variant="secondary" @click="dialogOpen = true">Open dialog</BaseButton>
|
|
52
|
-
<div v-if="lastAction" style="font-family:
|
|
52
|
+
<div v-if="lastAction" style="font-family: var(--font-mono); font-size: 0.75rem; color: var(--text-muted);">
|
|
53
53
|
Last event: <code>{{ lastAction }}</code>
|
|
54
54
|
</div>
|
|
55
55
|
<ConfirmDialog
|
|
@@ -62,7 +62,7 @@ const defaultIconPaths = [
|
|
|
62
62
|
</div>
|
|
63
63
|
</div>
|
|
64
64
|
<div v-if="actionLabel" class="mint-empty-state__action">
|
|
65
|
-
<BaseButton variant="secondary" @click="emit('action')">
|
|
65
|
+
<BaseButton variant="secondary" size="sm" @click="emit('action')">
|
|
66
66
|
{{ actionLabel }}
|
|
67
67
|
</BaseButton>
|
|
68
68
|
</div>
|