@morscherlab/mint-sdk 1.2.0-beta.7 → 1.2.0-beta.9
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 +30 -0
- package/dist/{ExperimentPopover-CfOdh5Rm.js → ExperimentPopover-oL0fg_A0.js} +5 -3
- package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
- package/dist/{ExperimentSelectorModal-DvIagrxY.js → ExperimentSelectorModal-BeEOomw_.js} +4 -4
- package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
- package/dist/{SettingsModal-W3TYfnP9.js → SettingsModal-Dphh3RrI.js} +4 -4
- package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
- package/dist/{EmptyState-NBqf2_8z.js → Skeleton-DxtwJ5gt.js} +110 -92
- package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
- package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
- package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
- package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
- package/dist/components/ActionMenu.vue.d.ts +1 -1
- package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
- package/dist/components/AuditTrail.vue.d.ts +13 -0
- package/dist/components/BatchProgressList.vue.d.ts +17 -0
- package/dist/components/DataFrame.vue.d.ts +14 -2
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +2 -0
- package/dist/components/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/LcmsSequenceTable.vue.d.ts +29 -1
- package/dist/components/ProgressBar.vue.d.ts +1 -1
- package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
- package/dist/components/ReagentList.vue.d.ts +29 -1
- package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
- package/dist/components/SampleSelector.drag.d.ts +13 -0
- package/dist/components/SampleSelector.tree.d.ts +2 -0
- package/dist/components/SampleSelector.vue.d.ts +34 -1
- package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -0
- package/dist/components/TimePicker.vue.d.ts +1 -0
- package/dist/components/WellPlate.geometry.d.ts +15 -0
- package/dist/components/WellPlate.interaction.d.ts +13 -0
- package/dist/components/WellPlate.vue.d.ts +29 -0
- package/dist/components/index.js +6 -6
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
- package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
- package/dist/{components-B80gV0P6.js → components-CgAUzF8F.js} +1930 -1688
- package/dist/components-CgAUzF8F.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +34 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.js +9 -9
- package/dist/composables/useDropdownState.d.ts +18 -2
- package/dist/composables/useEventStream.d.ts +9 -0
- package/dist/composables/useListReorder.d.ts +90 -0
- package/dist/composables/useMenuKeyboard.d.ts +2 -2
- package/dist/composables/usePluginConfig.d.ts +3 -1
- package/dist/composables/useRequestSyncState.d.ts +42 -2
- package/dist/composables/useScheduleDrag.d.ts +1 -0
- package/dist/{composables-C8mmD5ta.js → composables-B_pia3hO.js} +13 -69
- package/dist/composables-B_pia3hO.js.map +1 -0
- package/dist/{useApi-CDBq6z23.js → errors-_W0-LbA6.js} +102 -3
- package/dist/errors-_W0-LbA6.js.map +1 -0
- package/dist/{experiment-Y9Iz7KYi.js → experiment-Cwl9AYxw.js} +3 -8
- package/dist/experiment-Cwl9AYxw.js.map +1 -0
- package/dist/experiment-utils-DV3DrvE_.js.map +1 -1
- package/dist/index.js +14 -14
- 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/index.d.ts +1 -1
- package/dist/stores/index.js +3 -3
- package/dist/stores/settings.d.ts +31 -1
- package/dist/styles.css +9572 -27877
- package/dist/templates/index.js +2 -4
- package/dist/{templates-CK72aKO1.js → templates-Qq113pyZ.js} +291 -85
- package/dist/templates-Qq113pyZ.js.map +1 -0
- package/dist/{useControlSchema-_OVhavKq.js → useControlSchema-aMZlb4Sc.js} +197 -1089
- package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
- package/dist/{useDropdownState-D5G4UvWd.js → useDropdownState-DitCtrTT.js} +21 -5
- package/dist/useDropdownState-DitCtrTT.js.map +1 -0
- package/dist/{useExperimentSelector-DSEckf_g.js → useExperimentSelector-I8HC8hUu.js} +3 -3
- package/dist/{useExperimentSelector-DSEckf_g.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
- package/dist/{useFormBuilder-Bd714C7y.js → useFormBuilder-TCxNnqeb.js} +2 -2
- package/dist/{useFormBuilder-Bd714C7y.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
- package/dist/{useProtocolTemplates-Dq5Pzwyd.js → useProtocolTemplates-BmX_iVBR.js} +185 -157
- package/dist/useProtocolTemplates-BmX_iVBR.js.map +1 -0
- package/dist/utils/color.d.ts +13 -0
- package/dist/utils/errors.d.ts +9 -0
- package/package.json +1 -1
- package/src/__tests__/components/ActionMenu.test.ts +94 -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/ExperimentTimeline.test.ts +63 -0
- package/src/__tests__/components/GroupAssigner.test.ts +53 -0
- package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
- package/src/__tests__/components/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/SegmentedControl.test.ts +88 -0
- package/src/__tests__/components/TimePicker.test.ts +61 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
- package/src/__tests__/composables/useDropdownState.test.ts +58 -1
- package/src/__tests__/composables/useListReorder.test.ts +145 -0
- package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
- package/src/__tests__/stores/settings.test.ts +71 -1
- package/src/__tests__/styles/cssTokens.test.ts +131 -0
- package/src/__tests__/utils/permissions.test.ts +68 -0
- package/src/components/ActionMenu.vue +45 -88
- package/src/components/AlertBox.vue +0 -4
- package/src/components/AppAvatarMenu.vue +2 -14
- 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 +0 -4
- 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 +0 -4
- 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/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/ConcentrationInput.vue +0 -4
- package/src/components/ConfirmDialog.vue +0 -4
- 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/DropdownButton.vue +2 -14
- package/src/components/EmptyState.vue +0 -4
- package/src/components/ExperimentCodeBadge.vue +0 -4
- package/src/components/ExperimentDataViewer.story.vue +3 -0
- package/src/components/ExperimentDataViewer.vue +33 -13
- package/src/components/ExperimentPopover.vue +2 -5
- package/src/components/ExperimentSelectorModal.vue +0 -4
- package/src/components/ExperimentTimeline.story.vue +18 -0
- package/src/components/ExperimentTimeline.vue +81 -75
- package/src/components/FileUploader.vue +0 -4
- package/src/components/FitPanel.vue +0 -4
- package/src/components/FormActions.story.vue +96 -0
- package/src/components/FormActions.vue +0 -4
- package/src/components/FormBuilder.vue +0 -4
- package/src/components/FormField.vue +0 -4
- package/src/components/FormulaInput.vue +0 -4
- package/src/components/GeneratedPathInput.story.vue +68 -0
- package/src/components/GeneratedPlotlyResult.story.vue +96 -0
- package/src/components/GeneratedPlotlyResult.vue +3 -3
- package/src/components/GeneratedPluginView.story.vue +145 -0
- package/src/components/GeneratedPluginView.vue +10 -10
- package/src/components/GeneratedResultRenderer.story.vue +117 -0
- package/src/components/GeneratedResultRenderer.vue +4 -4
- package/src/components/GroupAssigner.vue +67 -5
- package/src/components/IconButton.vue +0 -4
- package/src/components/InstrumentAlertLog.vue +0 -4
- package/src/components/InstrumentStateBadge.vue +0 -4
- package/src/components/InstrumentStatusCard.vue +0 -4
- package/src/components/JobsStatusTray.vue +0 -4
- package/src/components/LcmsSequenceTable.vue +81 -50
- package/src/components/LoadingSpinner.vue +0 -4
- package/src/components/MobileSupportGate.vue +0 -4
- package/src/components/MoleculeInput.vue +0 -4
- package/src/components/MultiSelect.vue +0 -4
- package/src/components/NumberInput.vue +0 -4
- package/src/components/PlateMapEditor.vue +3 -14
- package/src/components/PluginIcon.vue +0 -4
- package/src/components/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/SectionCard.vue +0 -4
- package/src/components/SegmentedControl.vue +48 -5
- package/src/components/SequenceInput.vue +0 -4
- package/src/components/SequenceProgressBar.story.vue +110 -0
- package/src/components/SequenceProgressBar.vue +0 -4
- package/src/components/SettingsModal.vue +0 -4
- package/src/components/Skeleton.vue +0 -4
- package/src/components/SmartGroupFieldRecipe.vue +8 -6
- package/src/components/SmartGroupManual.vue +0 -4
- package/src/components/StatusIndicator.vue +0 -4
- package/src/components/StepWizard.vue +0 -4
- package/src/components/TagsInput.vue +4 -6
- package/src/components/ThemeToggle.vue +0 -4
- package/src/components/TimePicker.vue +36 -7
- package/src/components/TimeRangeInput.vue +0 -4
- package/src/components/Tooltip.story.vue +3 -3
- package/src/components/Tooltip.vue +0 -4
- package/src/components/UnitInput.vue +0 -4
- package/src/components/WellPlate.geometry.ts +60 -1
- package/src/components/WellPlate.interaction.ts +44 -0
- package/src/components/WellPlate.story.vue +8 -0
- package/src/components/WellPlate.vue +53 -7
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
- package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
- package/src/components/internal/DataStateInternal.vue +74 -0
- package/src/components/internal/FormSectionRenderer.vue +0 -4
- package/src/components/internal/WellEditPopupInternal.vue +0 -4
- package/src/composables/controlSchemaTypes.ts +34 -0
- package/src/composables/index.ts +10 -0
- package/src/composables/useDropdownState.ts +35 -5
- package/src/composables/useEventStream.ts +14 -2
- package/src/composables/useFileImport.ts +2 -7
- package/src/composables/useGeneratedAnalysis.ts +8 -9
- package/src/composables/useListReorder.story.vue +143 -0
- package/src/composables/useListReorder.ts +242 -0
- package/src/composables/useMenuKeyboard.ts +2 -2
- package/src/composables/useOptimisticMutation.ts +1 -15
- package/src/composables/usePluginConfig.ts +3 -1
- package/src/composables/useRackEditor.ts +2 -11
- package/src/composables/useRequestSyncState.ts +98 -28
- package/src/composables/useResourceCrud.ts +1 -15
- package/src/composables/useScheduleDrag.ts +37 -0
- package/src/composables/useWellPainting.ts +5 -29
- package/src/composables/useWellPlateEditor.ts +1 -11
- package/src/permissions.ts +48 -9
- package/src/stores/experiment.ts +2 -7
- package/src/stores/index.ts +9 -1
- package/src/stores/settings.ts +93 -1
- package/src/styles/components/audit-trail.css +5 -5
- package/src/styles/components/batch-progress-list.css +7 -0
- package/src/styles/components/color-slider.css +2 -2
- package/src/styles/components/data-state.css +30 -0
- package/src/styles/components/dataframe.css +29 -35
- package/src/styles/components/experiment-data-viewer.css +4 -4
- package/src/styles/components/experiment-timeline.css +3 -30
- package/src/styles/components/form-builder.css +5 -5
- package/src/styles/components/lcms-sequence-table.css +16 -1
- package/src/styles/components/multi-select.css +1 -1
- package/src/styles/components/reagent-list.css +38 -31
- package/src/styles/components/sample-hierarchy-tree.css +5 -19
- package/src/styles/components/sample-selector.css +9 -2
- package/src/styles/components/schedule-calendar.css +8 -0
- package/src/styles/components/scientific-number.css +1 -1
- package/src/styles/components/well-plate.css +8 -0
- package/src/styles/index.css +8 -0
- package/src/styles/variables.css +29 -0
- package/src/templates/builderPresetControls.ts +1 -1
- package/src/templates/componentPlateHelpers.ts +3 -1
- 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/utils/color.ts +51 -0
- package/src/utils/errors.ts +29 -0
- package/dist/EmptyState-NBqf2_8z.js.map +0 -1
- package/dist/ExperimentPopover-CfOdh5Rm.js.map +0 -1
- package/dist/ExperimentSelectorModal-DvIagrxY.js.map +0 -1
- package/dist/SettingsModal-W3TYfnP9.js.map +0 -1
- package/dist/components-B80gV0P6.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-C8mmD5ta.js.map +0 -1
- package/dist/experiment-Y9Iz7KYi.js.map +0 -1
- package/dist/templates/builderDefaults.d.ts +0 -1
- package/dist/templates/plateGeometry.d.ts +0 -4
- package/dist/templates-CK72aKO1.js.map +0 -1
- package/dist/useApi-CDBq6z23.js.map +0 -1
- package/dist/useControlSchema-_OVhavKq.js.map +0 -1
- package/dist/useDropdownState-D5G4UvWd.js.map +0 -1
- package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +0 -1
- package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
- package/src/composables/autoGroupConstants.ts +0 -4
- package/src/composables/autoGroupGrouping.ts +0 -148
- package/src/composables/useAutoGroupInputSources.ts +0 -147
- package/src/templates/builderDefaults.ts +0 -11
- package/src/templates/plateGeometry.ts +0 -62
- /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
|
@@ -34,6 +34,8 @@ export interface SampleGroupTreeContext {
|
|
|
34
34
|
onGroupDragOver: (name: string, kind: GroupDragKind, event: DragEvent) => void
|
|
35
35
|
onGroupDragLeave: (event: DragEvent) => void
|
|
36
36
|
onGroupDrop: (name: string, kind: GroupDragKind, event: DragEvent) => void
|
|
37
|
+
/** Alt+ArrowUp / Alt+ArrowDown reorder a group among its siblings. */
|
|
38
|
+
onGroupReorderKeydown: (name: string, kind: GroupDragKind, event: KeyboardEvent) => void
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/** Every concrete group name beneath a node, in tree order. */
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
removeSampleFromGroup as removeSampleFromSampleGroup,
|
|
26
26
|
removeSampleGroups,
|
|
27
27
|
} from './SampleSelector.groups'
|
|
28
|
+
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
28
29
|
|
|
29
30
|
interface Props {
|
|
30
31
|
samples?: string[]
|
|
@@ -35,6 +36,15 @@ interface Props {
|
|
|
35
36
|
experimentId?: number
|
|
36
37
|
designData?: Record<string, unknown>
|
|
37
38
|
autoloadExperimentData?: boolean
|
|
39
|
+
/** Replace the selector body with a loading placeholder. */
|
|
40
|
+
loading?: boolean
|
|
41
|
+
/**
|
|
42
|
+
* Error message. When set, the selector body is replaced by an error state.
|
|
43
|
+
* `null`/`undefined` renders normally.
|
|
44
|
+
*/
|
|
45
|
+
error?: string | null
|
|
46
|
+
/** Empty-state headline shown when there are no samples to select from. */
|
|
47
|
+
emptyMessage?: string
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -45,6 +55,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
45
55
|
experimentId: undefined,
|
|
46
56
|
designData: undefined,
|
|
47
57
|
autoloadExperimentData: true,
|
|
58
|
+
loading: false,
|
|
59
|
+
error: null,
|
|
60
|
+
emptyMessage: 'No samples available',
|
|
48
61
|
})
|
|
49
62
|
|
|
50
63
|
const emit = defineEmits<{
|
|
@@ -69,6 +82,8 @@ const internalGroups = computed({
|
|
|
69
82
|
})
|
|
70
83
|
|
|
71
84
|
const {
|
|
85
|
+
announcement: reorderAnnouncement,
|
|
86
|
+
announcerStyle,
|
|
72
87
|
draggingSample,
|
|
73
88
|
dragOverGroup,
|
|
74
89
|
draggingGroup,
|
|
@@ -81,6 +96,7 @@ const {
|
|
|
81
96
|
handleDragLeave,
|
|
82
97
|
handleDrop,
|
|
83
98
|
handleGroupDragStart,
|
|
99
|
+
handleGroupReorderKeydown,
|
|
84
100
|
handleGroupDragEnd,
|
|
85
101
|
handleGroupDragOver,
|
|
86
102
|
handleGroupDragLeave,
|
|
@@ -227,6 +243,7 @@ const treeContext = computed<SampleGroupTreeContext>(() => ({
|
|
|
227
243
|
onGroupDragOver: handleGroupDragOver,
|
|
228
244
|
onGroupDragLeave: handleGroupDragLeave,
|
|
229
245
|
onGroupDrop: handleGroupDrop,
|
|
246
|
+
onGroupReorderKeydown: handleGroupReorderKeydown,
|
|
230
247
|
}))
|
|
231
248
|
|
|
232
249
|
// New group
|
|
@@ -237,13 +254,36 @@ function addNewGroup() {
|
|
|
237
254
|
newGroupName.value = ''
|
|
238
255
|
}
|
|
239
256
|
|
|
257
|
+
/** True while a loading, error or empty placeholder stands in for the body. */
|
|
258
|
+
const isPlaceholderState = computed(() =>
|
|
259
|
+
props.loading || Boolean(props.error) || resolvedSamples.value.length === 0,
|
|
260
|
+
)
|
|
261
|
+
|
|
240
262
|
defineExpose({ handleSmartGroupApply })
|
|
241
263
|
</script>
|
|
242
264
|
|
|
243
265
|
<template>
|
|
244
266
|
<div class="mint-sample-selector">
|
|
267
|
+
<div v-if="error" class="mint-sample-selector__state">
|
|
268
|
+
<slot name="error" :error="error">
|
|
269
|
+
<DataStateInternal state="error" size="sm" :title="error" />
|
|
270
|
+
</slot>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<div v-else-if="loading" class="mint-sample-selector__state">
|
|
274
|
+
<slot name="loading">
|
|
275
|
+
<DataStateInternal state="loading" size="sm" loading-label="Loading samples" />
|
|
276
|
+
</slot>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div v-else-if="resolvedSamples.length === 0" class="mint-sample-selector__state">
|
|
280
|
+
<slot name="empty">
|
|
281
|
+
<DataStateInternal state="empty" size="sm" :title="emptyMessage" />
|
|
282
|
+
</slot>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
245
285
|
<!-- Select All Row -->
|
|
246
|
-
<label class="mint-sample-selector__select-all">
|
|
286
|
+
<label v-if="!isPlaceholderState" class="mint-sample-selector__select-all">
|
|
247
287
|
<input
|
|
248
288
|
type="checkbox"
|
|
249
289
|
:checked="isAllSelected"
|
|
@@ -255,7 +295,7 @@ defineExpose({ handleSmartGroupApply })
|
|
|
255
295
|
</label>
|
|
256
296
|
|
|
257
297
|
<!-- Action Buttons Row -->
|
|
258
|
-
<div v-if="enableGrouping" class="mint-sample-selector__actions">
|
|
298
|
+
<div v-if="!isPlaceholderState && enableGrouping" class="mint-sample-selector__actions">
|
|
259
299
|
<div class="mint-sample-selector__actions-row">
|
|
260
300
|
<!-- Grouping Button -->
|
|
261
301
|
<BaseButton
|
|
@@ -291,7 +331,7 @@ defineExpose({ handleSmartGroupApply })
|
|
|
291
331
|
</div>
|
|
292
332
|
|
|
293
333
|
<!-- Grouped View -->
|
|
294
|
-
<div v-if="groupingEnabled" class="mint-sample-selector__grouped">
|
|
334
|
+
<div v-if="!isPlaceholderState && groupingEnabled" class="mint-sample-selector__grouped">
|
|
295
335
|
<!-- Groups Header -->
|
|
296
336
|
<div class="mint-sample-selector__groups-header">
|
|
297
337
|
<span class="mint-sample-selector__groups-title">Groups ({{ internalGroups.length }})</span>
|
|
@@ -320,7 +360,7 @@ defineExpose({ handleSmartGroupApply })
|
|
|
320
360
|
/>
|
|
321
361
|
|
|
322
362
|
<!-- Empty state -->
|
|
323
|
-
<div v-if="internalGroups.length === 0" class="mint-sample-
|
|
363
|
+
<div v-if="internalGroups.length === 0" class="mint-sample-selector__hint">
|
|
324
364
|
Click Smart to auto-group samples
|
|
325
365
|
</div>
|
|
326
366
|
</div>
|
|
@@ -394,7 +434,7 @@ defineExpose({ handleSmartGroupApply })
|
|
|
394
434
|
</div>
|
|
395
435
|
|
|
396
436
|
<!-- Flat View (when no groups) -->
|
|
397
|
-
<div v-if="!groupingEnabled" class="mint-sample-selector__flat">
|
|
437
|
+
<div v-if="!isPlaceholderState && !groupingEnabled" class="mint-sample-selector__flat">
|
|
398
438
|
<!-- Search -->
|
|
399
439
|
<div class="mint-sample-selector__search">
|
|
400
440
|
<svg class="mint-sample-selector__search-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -427,7 +467,7 @@ defineExpose({ handleSmartGroupApply })
|
|
|
427
467
|
<span class="mint-sample-selector__flat-name">{{ sample }}</span>
|
|
428
468
|
</div>
|
|
429
469
|
|
|
430
|
-
<div v-if="filteredSamples.length === 0 && searchQuery.trim()" class="mint-sample-
|
|
470
|
+
<div v-if="filteredSamples.length === 0 && searchQuery.trim()" class="mint-sample-selector__hint">
|
|
431
471
|
No samples match "{{ searchQuery }}"
|
|
432
472
|
</div>
|
|
433
473
|
</div>
|
|
@@ -443,9 +483,8 @@ defineExpose({ handleSmartGroupApply })
|
|
|
443
483
|
:design-data="resolvedDesignData"
|
|
444
484
|
@apply="handleSmartGroupApply"
|
|
445
485
|
/>
|
|
486
|
+
|
|
487
|
+
<!-- Group reorder announcements for screen readers -->
|
|
488
|
+
<div role="status" aria-live="polite" :style="announcerStyle">{{ reorderAnnouncement }}</div>
|
|
446
489
|
</div>
|
|
447
490
|
</template>
|
|
448
|
-
|
|
449
|
-
<style>
|
|
450
|
-
@import '../styles/components/sample-selector.css';
|
|
451
|
-
</style>
|
|
@@ -54,6 +54,18 @@ function onHeaderDrop(event: DragEvent) {
|
|
|
54
54
|
if (dragKind.value === null) return
|
|
55
55
|
props.ctx.onGroupDrop(dragName.value, dragKind.value, event)
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
/** Enter/Space expand the node; Alt+ArrowUp/Down reorder it among its siblings. */
|
|
59
|
+
function onHeaderKeydown(event: KeyboardEvent) {
|
|
60
|
+
if (event.altKey) {
|
|
61
|
+
if (dragKind.value === null) return
|
|
62
|
+
props.ctx.onGroupReorderKeydown(dragName.value, dragKind.value, event)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
66
|
+
event.preventDefault()
|
|
67
|
+
props.ctx.toggleExpanded(props.node.key)
|
|
68
|
+
}
|
|
57
69
|
</script>
|
|
58
70
|
|
|
59
71
|
<template>
|
|
@@ -81,7 +93,14 @@ function onHeaderDrop(event: DragEvent) {
|
|
|
81
93
|
reorderEdge === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',
|
|
82
94
|
]"
|
|
83
95
|
:draggable="dragKind !== null"
|
|
96
|
+
role="button"
|
|
97
|
+
tabindex="0"
|
|
98
|
+
:aria-expanded="expanded"
|
|
99
|
+
:aria-label="dragKind !== null
|
|
100
|
+
? `${node.label}. Press Alt with the up or down arrow keys to reorder.`
|
|
101
|
+
: node.label"
|
|
84
102
|
@click="ctx.toggleExpanded(node.key)"
|
|
103
|
+
@keydown="onHeaderKeydown"
|
|
85
104
|
@dragstart="onHeaderDragStart"
|
|
86
105
|
@dragend="ctx.onGroupDragEnd"
|
|
87
106
|
@dragover="onHeaderDragOver"
|
|
@@ -86,6 +86,8 @@ const mockBlockedSlots: ScheduleBlockedSlot[] = [
|
|
|
86
86
|
|
|
87
87
|
function initState() {
|
|
88
88
|
return {
|
|
89
|
+
loading: false,
|
|
90
|
+
error: '',
|
|
89
91
|
modelValue: today,
|
|
90
92
|
view: 'week' as const,
|
|
91
93
|
events: mockEvents,
|
|
@@ -106,6 +108,8 @@ function initState() {
|
|
|
106
108
|
<template #default="{ state }">
|
|
107
109
|
<div style="padding: 1rem; height: 700px;">
|
|
108
110
|
<ScheduleCalendar
|
|
111
|
+
:loading="state.loading"
|
|
112
|
+
:error="state.error || null"
|
|
109
113
|
v-model="state.modelValue"
|
|
110
114
|
:view="state.view"
|
|
111
115
|
:events="state.events"
|
|
@@ -123,6 +127,8 @@ function initState() {
|
|
|
123
127
|
</div>
|
|
124
128
|
</template>
|
|
125
129
|
<template #controls="{ state }">
|
|
130
|
+
<HstCheckbox v-model="state.loading" title="Loading" />
|
|
131
|
+
<HstText v-model="state.error" title="Error Message" />
|
|
126
132
|
<HstSelect
|
|
127
133
|
v-model="state.view"
|
|
128
134
|
title="View"
|
|
@@ -7,11 +7,13 @@ import type {
|
|
|
7
7
|
ScheduleBlockedSlot,
|
|
8
8
|
} from '../types/components'
|
|
9
9
|
import { useScheduleDrag } from '../composables/useScheduleDrag'
|
|
10
|
+
import { useReorderAnnouncer } from '../composables/useListReorder'
|
|
10
11
|
import {
|
|
11
12
|
SCHEDULE_SLOT_HEIGHT,
|
|
12
13
|
parseScheduleModelDate,
|
|
13
14
|
useScheduleCalendarLayout,
|
|
14
15
|
} from '../composables/useScheduleCalendarLayout'
|
|
16
|
+
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
15
17
|
|
|
16
18
|
interface Props {
|
|
17
19
|
modelValue?: Date | string
|
|
@@ -28,6 +30,19 @@ interface Props {
|
|
|
28
30
|
showNavigation?: boolean
|
|
29
31
|
statusColors?: Record<string, string>
|
|
30
32
|
locale?: string
|
|
33
|
+
/** Replace the calendar grid with a loading placeholder. */
|
|
34
|
+
loading?: boolean
|
|
35
|
+
/**
|
|
36
|
+
* Error message. When set, the calendar grid is replaced by an error state.
|
|
37
|
+
* `null`/`undefined` renders normally.
|
|
38
|
+
*/
|
|
39
|
+
error?: string | null
|
|
40
|
+
/**
|
|
41
|
+
* Opt-in empty-state headline. An empty grid is meaningful on a calendar, so
|
|
42
|
+
* the empty state only replaces it when this message is supplied and there
|
|
43
|
+
* are no events.
|
|
44
|
+
*/
|
|
45
|
+
emptyMessage?: string
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -43,6 +58,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
43
58
|
showViewToggle: true,
|
|
44
59
|
showNavigation: true,
|
|
45
60
|
locale: 'en-US',
|
|
61
|
+
loading: false,
|
|
62
|
+
error: null,
|
|
46
63
|
})
|
|
47
64
|
|
|
48
65
|
const emit = defineEmits<{
|
|
@@ -57,6 +74,11 @@ const emit = defineEmits<{
|
|
|
57
74
|
|
|
58
75
|
const SLOT_HEIGHT = SCHEDULE_SLOT_HEIGHT
|
|
59
76
|
const currentView = ref<ScheduleView>(props.view)
|
|
77
|
+
|
|
78
|
+
/** Empty state is opt-in: it needs an `emptyMessage` and an event-free range. */
|
|
79
|
+
const showEmptyState = computed(() =>
|
|
80
|
+
Boolean(props.emptyMessage) && props.events.length === 0,
|
|
81
|
+
)
|
|
60
82
|
const currentDate = ref<Date>(parseScheduleModelDate(props.modelValue))
|
|
61
83
|
const nowTimer = ref<ReturnType<typeof setInterval>>()
|
|
62
84
|
const now = ref(new Date())
|
|
@@ -76,7 +98,9 @@ const weekStartsOnRef = computed(() => props.weekStartsOn)
|
|
|
76
98
|
const localeRef = computed(() => props.locale)
|
|
77
99
|
const showNowIndicatorRef = computed(() => props.showNowIndicator)
|
|
78
100
|
|
|
79
|
-
const {
|
|
101
|
+
const { announcement, announce, announcerStyle } = useReorderAnnouncer()
|
|
102
|
+
|
|
103
|
+
const { isDragging, ghost, startCreate, startMove, startResize, nudgeEvent } = useScheduleDrag({
|
|
80
104
|
slotDuration: slotDurationRef,
|
|
81
105
|
dayStartHour: dayStartRef,
|
|
82
106
|
dayEndHour: dayEndRef,
|
|
@@ -180,6 +204,35 @@ function onResizePointerDown(event: ScheduleEvent, edge: 'top' | 'bottom', dayIn
|
|
|
180
204
|
startResize(event, edge, e.clientY, dayIndex)
|
|
181
205
|
}
|
|
182
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Keyboard alternative to dragging an event: Alt+ArrowUp/Down move it by one
|
|
209
|
+
* slot, Shift+Alt+ArrowUp/Down shorten or lengthen it, Enter/Space open it.
|
|
210
|
+
*/
|
|
211
|
+
function onEventKeydown(event: ScheduleEvent, e: KeyboardEvent) {
|
|
212
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
213
|
+
e.preventDefault()
|
|
214
|
+
emit('event-click', event)
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
if (!e.altKey) return
|
|
218
|
+
const direction = e.key === 'ArrowUp' ? -1 : e.key === 'ArrowDown' ? 1 : 0
|
|
219
|
+
if (direction === 0) return
|
|
220
|
+
|
|
221
|
+
e.preventDefault()
|
|
222
|
+
const mode = e.shiftKey ? 'resize' : 'move'
|
|
223
|
+
const applied = nudgeEvent(event, direction, mode)
|
|
224
|
+
announce(applied
|
|
225
|
+
? `${event.title} ${mode === 'move' ? 'moved' : 'resized'} to ${formatEventTime(event)}.`
|
|
226
|
+
: `${event.title} cannot move further.`)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Instructions announced with each event. */
|
|
230
|
+
function eventLabel(event: ScheduleEvent): string {
|
|
231
|
+
const base = `${event.title}, ${formatEventTime(event)}`
|
|
232
|
+
if (props.readonly || event.draggable === false) return base
|
|
233
|
+
return `${base}. Press Alt with the up or down arrow keys to reschedule it.`
|
|
234
|
+
}
|
|
235
|
+
|
|
183
236
|
function onMonthDayClick(date: Date) {
|
|
184
237
|
currentDate.value = date
|
|
185
238
|
emit('update:modelValue', date)
|
|
@@ -254,8 +307,27 @@ onUnmounted(() => {
|
|
|
254
307
|
</div>
|
|
255
308
|
</div>
|
|
256
309
|
|
|
310
|
+
<!-- Loading / error / empty placeholders replace the grid -->
|
|
311
|
+
<div v-if="error" class="mint-schedule__state">
|
|
312
|
+
<slot name="error" :error="error">
|
|
313
|
+
<DataStateInternal state="error" :title="error" />
|
|
314
|
+
</slot>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<div v-else-if="loading" class="mint-schedule__state">
|
|
318
|
+
<slot name="loading">
|
|
319
|
+
<DataStateInternal state="loading" loading-label="Loading schedule" />
|
|
320
|
+
</slot>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<div v-else-if="showEmptyState" class="mint-schedule__state">
|
|
324
|
+
<slot name="empty">
|
|
325
|
+
<DataStateInternal state="empty" :title="emptyMessage" />
|
|
326
|
+
</slot>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
257
329
|
<!-- Day / Week View -->
|
|
258
|
-
<template v-if="currentView === 'day' || currentView === 'week'">
|
|
330
|
+
<template v-else-if="currentView === 'day' || currentView === 'week'">
|
|
259
331
|
<!-- Day headers -->
|
|
260
332
|
<div v-if="currentView === 'week'" class="mint-schedule__day-headers">
|
|
261
333
|
<div class="mint-schedule__day-header-gutter" />
|
|
@@ -312,7 +384,11 @@ onUnmounted(() => {
|
|
|
312
384
|
:key="event.id"
|
|
313
385
|
:class="['mint-schedule__event', getStatusClass(event.status)]"
|
|
314
386
|
:style="getEventStyle(event, day) || undefined"
|
|
387
|
+
role="button"
|
|
388
|
+
tabindex="0"
|
|
389
|
+
:aria-label="eventLabel(event)"
|
|
315
390
|
@click="onEventClick(event, $event)"
|
|
391
|
+
@keydown="onEventKeydown(event, $event)"
|
|
316
392
|
@pointerdown="onEventPointerDown(event, dayIdx, $event)"
|
|
317
393
|
>
|
|
318
394
|
<slot name="event" :event="event">
|
|
@@ -364,7 +440,7 @@ onUnmounted(() => {
|
|
|
364
440
|
</template>
|
|
365
441
|
|
|
366
442
|
<!-- Month View -->
|
|
367
|
-
<template v-if="currentView === 'month'">
|
|
443
|
+
<template v-else-if="currentView === 'month'">
|
|
368
444
|
<div class="mint-schedule__month-grid">
|
|
369
445
|
<!-- Weekday headers -->
|
|
370
446
|
<div
|
|
@@ -406,9 +482,8 @@ onUnmounted(() => {
|
|
|
406
482
|
</div>
|
|
407
483
|
</div>
|
|
408
484
|
</template>
|
|
485
|
+
|
|
486
|
+
<!-- Reschedule announcements for screen readers -->
|
|
487
|
+
<div role="status" aria-live="polite" :style="announcerStyle">{{ announcement }}</div>
|
|
409
488
|
</div>
|
|
410
489
|
</template>
|
|
411
|
-
|
|
412
|
-
<style>
|
|
413
|
-
@import '../styles/components/schedule-calendar.css';
|
|
414
|
-
</style>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Renders a segmented button group for single-option selection, with simple or solid variants. */
|
|
3
|
-
import { computed } from 'vue'
|
|
3
|
+
import { computed, type ComponentPublicInstance } from 'vue'
|
|
4
4
|
import type { SegmentedOption, SegmentedOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'
|
|
5
5
|
import { normalizeOptionInput } from '../utils/options'
|
|
6
|
+
import { useRovingFocus } from '../composables/useRovingFocus'
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
modelValue: string | number
|
|
@@ -42,11 +43,55 @@ function handleSelect(option: SegmentedOption) {
|
|
|
42
43
|
emit('update:modelValue', option.value)
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Roving tabindex over the radio group: only the checked option is tabbable and
|
|
47
|
+
// Left/Right (plus Home/End) move between the selectable options, matching BaseTabs.
|
|
48
|
+
const optionRefs = new Map<string, HTMLElement>()
|
|
49
|
+
|
|
50
|
+
function optionId(option: SegmentedOption): string {
|
|
51
|
+
return String(option.value)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setOptionRef(id: string, el: Element | ComponentPublicInstance | null) {
|
|
55
|
+
if (el instanceof HTMLElement) {
|
|
56
|
+
optionRefs.set(id, el)
|
|
57
|
+
} else {
|
|
58
|
+
optionRefs.delete(id)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const selectableOptions = computed(() =>
|
|
63
|
+
normalizedOptions.value.filter(option => !isSelectionDisabled(option)),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
/** The one option carrying `tabindex="0"`: the checked one, else the first selectable. */
|
|
67
|
+
const tabbableId = computed(() => {
|
|
68
|
+
const checked = selectableOptions.value.find(option => option.value === props.modelValue)
|
|
69
|
+
const target = checked ?? selectableOptions.value[0]
|
|
70
|
+
return target ? optionId(target) : ''
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
function isTabbable(option: SegmentedOption): boolean {
|
|
74
|
+
return optionId(option) === tabbableId.value
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { handleKeydown: handleArrowKeydown } = useRovingFocus({
|
|
78
|
+
ids: () => selectableOptions.value.map(optionId),
|
|
79
|
+
activeId: () => tabbableId.value,
|
|
80
|
+
activate: (id) => {
|
|
81
|
+
const option = selectableOptions.value.find(candidate => optionId(candidate) === id)
|
|
82
|
+
if (option) handleSelect(option)
|
|
83
|
+
},
|
|
84
|
+
getElement: (id) => optionRefs.get(id),
|
|
85
|
+
orientation: 'horizontal',
|
|
86
|
+
})
|
|
87
|
+
|
|
45
88
|
function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
|
|
46
89
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
47
90
|
event.preventDefault()
|
|
48
91
|
handleSelect(option)
|
|
92
|
+
return
|
|
49
93
|
}
|
|
94
|
+
handleArrowKeydown(event)
|
|
50
95
|
}
|
|
51
96
|
</script>
|
|
52
97
|
|
|
@@ -64,9 +109,11 @@ function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
|
|
|
64
109
|
<button
|
|
65
110
|
v-for="option in normalizedOptions"
|
|
66
111
|
:key="String(option.value)"
|
|
112
|
+
:ref="(el) => setOptionRef(String(option.value), el)"
|
|
67
113
|
type="button"
|
|
68
114
|
role="radio"
|
|
69
115
|
:aria-checked="modelValue === option.value"
|
|
116
|
+
:tabindex="isTabbable(option) ? 0 : -1"
|
|
70
117
|
:disabled="isSelectionDisabled(option)"
|
|
71
118
|
:class="[
|
|
72
119
|
'mint-segmented-control__option',
|
|
@@ -88,7 +135,3 @@ function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
|
|
|
88
135
|
</button>
|
|
89
136
|
</div>
|
|
90
137
|
</template>
|
|
91
|
-
|
|
92
|
-
<style>
|
|
93
|
-
@import '../styles/components/segmented-control.css';
|
|
94
|
-
</style>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import SequenceProgressBar from './SequenceProgressBar.vue'
|
|
3
|
+
import type { SequenceProgress } from '../types'
|
|
4
|
+
|
|
5
|
+
const running: SequenceProgress = {
|
|
6
|
+
sequence_name: 'HILIC-neg-2026-09-05',
|
|
7
|
+
current_sample: 37,
|
|
8
|
+
total_samples: 96,
|
|
9
|
+
elapsed_seconds: 4_260,
|
|
10
|
+
estimated_remaining_seconds: 6_780,
|
|
11
|
+
avg_sample_seconds: 115,
|
|
12
|
+
current_sample_name: 'PLASMA_QC_04',
|
|
13
|
+
current_method: 'HILIC_neg_12min',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const almostDone: SequenceProgress = {
|
|
17
|
+
...running,
|
|
18
|
+
current_sample: 94,
|
|
19
|
+
estimated_remaining_seconds: 230,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const finished: SequenceProgress = {
|
|
23
|
+
...running,
|
|
24
|
+
current_sample: 96,
|
|
25
|
+
estimated_remaining_seconds: 0,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const justStarted: SequenceProgress = {
|
|
29
|
+
sequence_name: 'RP-pos-batch-7',
|
|
30
|
+
current_sample: 1,
|
|
31
|
+
total_samples: 384,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function initState() {
|
|
35
|
+
return {
|
|
36
|
+
current: 37,
|
|
37
|
+
total: 96,
|
|
38
|
+
remaining: 6780,
|
|
39
|
+
compact: false,
|
|
40
|
+
showEta: true,
|
|
41
|
+
showRemaining: true,
|
|
42
|
+
label: 'samples',
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<Story title="Workflow/SequenceProgressBar">
|
|
49
|
+
<Variant title="Playground" :init-state="initState">
|
|
50
|
+
<template #default="{ state }">
|
|
51
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
52
|
+
<SequenceProgressBar
|
|
53
|
+
:progress="{
|
|
54
|
+
sequence_name: 'HILIC-neg-2026-09-05',
|
|
55
|
+
current_sample: state.current,
|
|
56
|
+
total_samples: state.total,
|
|
57
|
+
estimated_remaining_seconds: state.remaining,
|
|
58
|
+
}"
|
|
59
|
+
:compact="state.compact"
|
|
60
|
+
:label="state.label"
|
|
61
|
+
:show-eta="state.showEta"
|
|
62
|
+
:show-remaining="state.showRemaining"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<template #controls="{ state }">
|
|
68
|
+
<HstNumber v-model="state.current" title="Current sample" />
|
|
69
|
+
<HstNumber v-model="state.total" title="Total samples" />
|
|
70
|
+
<HstNumber v-model="state.remaining" title="Remaining (s)" />
|
|
71
|
+
<HstText v-model="state.label" title="Unit label" />
|
|
72
|
+
<HstCheckbox v-model="state.compact" title="Compact" />
|
|
73
|
+
<HstCheckbox v-model="state.showEta" title="Show ETA" />
|
|
74
|
+
<HstCheckbox v-model="state.showRemaining" title="Show remaining" />
|
|
75
|
+
</template>
|
|
76
|
+
</Variant>
|
|
77
|
+
|
|
78
|
+
<Variant title="Mid-run">
|
|
79
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
80
|
+
<SequenceProgressBar :progress="running" />
|
|
81
|
+
</div>
|
|
82
|
+
</Variant>
|
|
83
|
+
|
|
84
|
+
<Variant title="Compact (no footer)">
|
|
85
|
+
<!-- Compact drops the remaining/ETA row for dense instrument tables. -->
|
|
86
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
87
|
+
<SequenceProgressBar :progress="running" compact />
|
|
88
|
+
</div>
|
|
89
|
+
</Variant>
|
|
90
|
+
|
|
91
|
+
<Variant title="Nearly finished">
|
|
92
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
93
|
+
<SequenceProgressBar :progress="almostDone" />
|
|
94
|
+
</div>
|
|
95
|
+
</Variant>
|
|
96
|
+
|
|
97
|
+
<Variant title="Complete">
|
|
98
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
99
|
+
<SequenceProgressBar :progress="finished" />
|
|
100
|
+
</div>
|
|
101
|
+
</Variant>
|
|
102
|
+
|
|
103
|
+
<Variant title="Queued — no timing yet">
|
|
104
|
+
<!-- Without timing fields the footer collapses on its own. -->
|
|
105
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
106
|
+
<SequenceProgressBar :progress="justStarted" label="injections" />
|
|
107
|
+
</div>
|
|
108
|
+
</Variant>
|
|
109
|
+
</Story>
|
|
110
|
+
</template>
|
|
@@ -144,8 +144,13 @@ function onDrop(event: DragEvent) {
|
|
|
144
144
|
// CSV action menu — reuses the SDK's dropdown state (outside-click / Escape
|
|
145
145
|
// close) but renders a compact action menu in the .fr-* styling, since the
|
|
146
146
|
// items are actions (upload / download), not a persistent value selection.
|
|
147
|
-
const {
|
|
148
|
-
|
|
147
|
+
const {
|
|
148
|
+
isOpen: csvMenuOpen,
|
|
149
|
+
rootRef: csvMenuRoot,
|
|
150
|
+
triggerRef: csvMenuTrigger,
|
|
151
|
+
close: closeCsvMenu,
|
|
152
|
+
toggle: toggleCsvMenu,
|
|
153
|
+
} = useDropdownState()
|
|
149
154
|
|
|
150
155
|
function chooseUpload() {
|
|
151
156
|
closeCsvMenu()
|
|
@@ -294,6 +299,7 @@ function onApply() {
|
|
|
294
299
|
<div class="sg-head__spacer" />
|
|
295
300
|
<div ref="csvMenuRoot" class="fr-csv">
|
|
296
301
|
<button
|
|
302
|
+
ref="csvMenuTrigger"
|
|
297
303
|
type="button"
|
|
298
304
|
class="fr-upload"
|
|
299
305
|
aria-haspopup="menu"
|
|
@@ -493,7 +499,3 @@ function onApply() {
|
|
|
493
499
|
</div>
|
|
494
500
|
</div>
|
|
495
501
|
</template>
|
|
496
|
-
|
|
497
|
-
<style>
|
|
498
|
-
@import '../styles/components/smart-group.css';
|
|
499
|
-
</style>
|