@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
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
hasAllPermissions,
|
|
8
8
|
hasAnyPermission,
|
|
9
9
|
isAdminUser,
|
|
10
|
+
normalizeAccessPolicy,
|
|
10
11
|
} from '../../permissions'
|
|
11
12
|
|
|
12
13
|
describe('permission helpers', () => {
|
|
@@ -60,3 +61,70 @@ describe('permission helpers', () => {
|
|
|
60
61
|
expect(canAccessByPolicy(member, { plugin: 'reports' })).toBe(false)
|
|
61
62
|
})
|
|
62
63
|
})
|
|
64
|
+
|
|
65
|
+
describe('normalizeAccessPolicy precedence', () => {
|
|
66
|
+
it('returns an empty policy for an absent rule', () => {
|
|
67
|
+
expect(normalizeAccessPolicy(undefined)).toEqual({})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('reads a flat-only rule as-is', () => {
|
|
71
|
+
expect(normalizeAccessPolicy({
|
|
72
|
+
visibleFor: 'admin',
|
|
73
|
+
requiresAdmin: true,
|
|
74
|
+
permissions: ['users.manage'],
|
|
75
|
+
anyPermissions: ['plugins.install'],
|
|
76
|
+
})).toEqual({
|
|
77
|
+
visibleFor: 'admin',
|
|
78
|
+
requiresAdmin: true,
|
|
79
|
+
permissions: ['users.manage'],
|
|
80
|
+
anyPermissions: ['plugins.install'],
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('lifts a nested-only policy to the top level', () => {
|
|
85
|
+
expect(normalizeAccessPolicy({
|
|
86
|
+
access: { requiresAuth: true, plugin: 'reports', visibleFor: 'admin' },
|
|
87
|
+
})).toEqual({
|
|
88
|
+
requiresAuth: true,
|
|
89
|
+
plugin: 'reports',
|
|
90
|
+
visibleFor: 'admin',
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('lets the nested policy win over the deprecated flat duplicates', () => {
|
|
95
|
+
expect(normalizeAccessPolicy({
|
|
96
|
+
access: {
|
|
97
|
+
visibleFor: 'admin',
|
|
98
|
+
requiresAdmin: true,
|
|
99
|
+
permissions: ['users.manage'],
|
|
100
|
+
anyPermissions: ['plugins.install'],
|
|
101
|
+
},
|
|
102
|
+
visibleFor: 'user',
|
|
103
|
+
requiresAdmin: false,
|
|
104
|
+
permissions: ['experiments.view'],
|
|
105
|
+
anyPermissions: ['experiments.edit'],
|
|
106
|
+
})).toEqual({
|
|
107
|
+
visibleFor: 'admin',
|
|
108
|
+
requiresAdmin: true,
|
|
109
|
+
permissions: ['users.manage'],
|
|
110
|
+
anyPermissions: ['plugins.install'],
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('treats an undefined nested field as unset instead of masking the flat one', () => {
|
|
115
|
+
expect(normalizeAccessPolicy({
|
|
116
|
+
access: { visibleFor: undefined, requiresAdmin: true },
|
|
117
|
+
visibleFor: 'user',
|
|
118
|
+
})).toEqual({
|
|
119
|
+
visibleFor: 'user',
|
|
120
|
+
requiresAdmin: true,
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('feeds canAccessByPolicy from the nested policy', () => {
|
|
125
|
+
const member = { role: 'member', permissions: ['experiments.view'] }
|
|
126
|
+
|
|
127
|
+
expect(canAccessByPolicy(member, { access: { requiresAdmin: true }, requiresAdmin: false })).toBe(false)
|
|
128
|
+
expect(canAccessByPolicy(member, { access: { visibleFor: 'user' }, visibleFor: 'admin' })).toBe(true)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
/** Compact overflow actions with an IconButton trigger and a viewport-safe teleported menu. */
|
|
3
3
|
import { computePosition, flip, offset, shift } from '@floating-ui/dom'
|
|
4
4
|
import { Ellipsis } from 'lucide-vue-next'
|
|
5
|
-
import { nextTick,
|
|
5
|
+
import { nextTick, onMounted, ref, watch } from 'vue'
|
|
6
|
+
import { useDropdownState } from '../composables/useDropdownState'
|
|
7
|
+
import { useMenuKeyboard } from '../composables/useMenuKeyboard'
|
|
6
8
|
import type { ActionMenuItem } from '../types/components'
|
|
7
9
|
import IconButton from './IconButton.vue'
|
|
8
10
|
|
|
@@ -22,14 +24,26 @@ const emit = defineEmits<{
|
|
|
22
24
|
close: []
|
|
23
25
|
}>()
|
|
24
26
|
|
|
25
|
-
const isOpen = ref(false)
|
|
26
|
-
const triggerRootRef = ref<HTMLElement | null>(null)
|
|
27
27
|
const panelRef = ref<HTMLElement | null>(null)
|
|
28
28
|
const panelStyle = ref<Record<string, string>>({ visibility: 'hidden' })
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
30
|
+
// Disclosure state (outside-click, Escape, scroll/resize closing, focus return)
|
|
31
|
+
// comes from the shared composable; only floating-ui placement stays local.
|
|
32
|
+
const { isOpen, rootRef, triggerRef, open, close } = useDropdownState({
|
|
33
|
+
closeOnScroll: true,
|
|
34
|
+
insideRefs: [panelRef],
|
|
35
|
+
onOpen: () => {
|
|
36
|
+
panelStyle.value = { visibility: 'hidden' }
|
|
37
|
+
emit('open')
|
|
38
|
+
},
|
|
39
|
+
onClose: () => emit('close'),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// IconButton owns the actual <button>, so the trigger element is resolved from
|
|
43
|
+
// the wrapper once mounted rather than bound with a template ref.
|
|
44
|
+
onMounted(() => {
|
|
45
|
+
triggerRef.value = rootRef.value?.querySelector('button') ?? null
|
|
46
|
+
})
|
|
33
47
|
|
|
34
48
|
function menuItems(): HTMLElement[] {
|
|
35
49
|
if (!panelRef.value) return []
|
|
@@ -39,7 +53,7 @@ function menuItems(): HTMLElement[] {
|
|
|
39
53
|
}
|
|
40
54
|
|
|
41
55
|
async function positionPanel() {
|
|
42
|
-
const trigger =
|
|
56
|
+
const trigger = triggerRef.value
|
|
43
57
|
const panel = panelRef.value
|
|
44
58
|
if (!trigger || !panel) return
|
|
45
59
|
|
|
@@ -59,97 +73,43 @@ async function positionPanel() {
|
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
window.addEventListener('resize', handleViewportChange)
|
|
65
|
-
window.addEventListener('scroll', handleViewportChange, true)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function removeGlobalListeners() {
|
|
69
|
-
document.removeEventListener('pointerdown', handleDocumentPointerDown, true)
|
|
70
|
-
window.removeEventListener('resize', handleViewportChange)
|
|
71
|
-
window.removeEventListener('scroll', handleViewportChange, true)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function openMenu() {
|
|
76
|
+
/** `focusFirst` is false when the keyboard composable owns the initial focus. */
|
|
77
|
+
function openMenu(focusFirst = true) {
|
|
75
78
|
if (props.disabled || isOpen.value) return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
addGlobalListeners()
|
|
80
|
-
await nextTick()
|
|
81
|
-
await positionPanel()
|
|
82
|
-
menuItems()[0]?.focus()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function closeMenu(returnFocus = false) {
|
|
86
|
-
if (!isOpen.value) return
|
|
87
|
-
isOpen.value = false
|
|
88
|
-
removeGlobalListeners()
|
|
89
|
-
emit('close')
|
|
90
|
-
if (returnFocus) {
|
|
91
|
-
nextTick(() => triggerElement()?.focus())
|
|
79
|
+
open()
|
|
80
|
+
if (focusFirst) {
|
|
81
|
+
void nextTick(() => menuItems()[0]?.focus())
|
|
92
82
|
}
|
|
93
83
|
}
|
|
94
84
|
|
|
95
85
|
function toggleMenu() {
|
|
96
|
-
if (
|
|
97
|
-
|
|
86
|
+
if (props.disabled) return
|
|
87
|
+
if (isOpen.value) close()
|
|
88
|
+
else openMenu()
|
|
98
89
|
}
|
|
99
90
|
|
|
91
|
+
const { handleTriggerKeydown, handlePanelKeydown } = useMenuKeyboard({
|
|
92
|
+
items: menuItems,
|
|
93
|
+
isOpen: () => isOpen.value,
|
|
94
|
+
open: () => openMenu(false),
|
|
95
|
+
close,
|
|
96
|
+
})
|
|
97
|
+
|
|
100
98
|
function handleSelect(item: ActionMenuItem) {
|
|
101
99
|
if (item.disabled) return
|
|
102
100
|
emit('select', item)
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function handleDocumentPointerDown(event: PointerEvent) {
|
|
107
|
-
const target = event.target
|
|
108
|
-
if (!(target instanceof Node)) return
|
|
109
|
-
if (triggerRootRef.value?.contains(target) || panelRef.value?.contains(target)) return
|
|
110
|
-
closeMenu()
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function handleViewportChange() {
|
|
114
|
-
closeMenu()
|
|
101
|
+
close()
|
|
115
102
|
}
|
|
116
103
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
event.preventDefault()
|
|
123
|
-
closeMenu(true)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
if (event.key === 'Tab') {
|
|
127
|
-
closeMenu()
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
if (event.key === 'Home') {
|
|
131
|
-
event.preventDefault()
|
|
132
|
-
items[0]?.focus()
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
if (event.key === 'End') {
|
|
136
|
-
event.preventDefault()
|
|
137
|
-
items[items.length - 1]?.focus()
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
|
|
141
|
-
|
|
142
|
-
event.preventDefault()
|
|
143
|
-
const direction = event.key === 'ArrowDown' ? 1 : -1
|
|
144
|
-
const nextIndex = (Math.max(currentIndex, 0) + direction + items.length) % items.length
|
|
145
|
-
items[nextIndex]?.focus()
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
onBeforeUnmount(removeGlobalListeners)
|
|
104
|
+
watch(isOpen, async (opened) => {
|
|
105
|
+
if (!opened) return
|
|
106
|
+
await nextTick()
|
|
107
|
+
await positionPanel()
|
|
108
|
+
})
|
|
149
109
|
</script>
|
|
150
110
|
|
|
151
111
|
<template>
|
|
152
|
-
<span ref="
|
|
112
|
+
<span ref="rootRef" class="mint-action-menu">
|
|
153
113
|
<IconButton
|
|
154
114
|
variant="secondary"
|
|
155
115
|
size="sm"
|
|
@@ -158,6 +118,7 @@ onBeforeUnmount(removeGlobalListeners)
|
|
|
158
118
|
aria-haspopup="menu"
|
|
159
119
|
:aria-expanded="isOpen"
|
|
160
120
|
@click="toggleMenu"
|
|
121
|
+
@keydown="handleTriggerKeydown"
|
|
161
122
|
>
|
|
162
123
|
<Ellipsis
|
|
163
124
|
class="mint-action-menu__trigger-icon"
|
|
@@ -175,7 +136,7 @@ onBeforeUnmount(removeGlobalListeners)
|
|
|
175
136
|
class="mint-action-menu__panel"
|
|
176
137
|
:style="panelStyle"
|
|
177
138
|
role="menu"
|
|
178
|
-
@keydown="
|
|
139
|
+
@keydown="handlePanelKeydown"
|
|
179
140
|
>
|
|
180
141
|
<template v-for="item in items" :key="item.id">
|
|
181
142
|
<a
|
|
@@ -203,7 +164,3 @@ onBeforeUnmount(removeGlobalListeners)
|
|
|
203
164
|
</div>
|
|
204
165
|
</Teleport>
|
|
205
166
|
</template>
|
|
206
|
-
|
|
207
|
-
<style>
|
|
208
|
-
@import '../styles/components/action-menu.css';
|
|
209
|
-
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Avatar-triggered dropdown with user name/email header, configurable menu items, dividers, and a sign-out action. */
|
|
3
|
-
import { computed,
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
4
|
import { useDropdownState } from '../composables/useDropdownState'
|
|
5
5
|
import { useMenuKeyboard } from '../composables/useMenuKeyboard'
|
|
6
6
|
import type { AccountMenuItem } from '../types/components'
|
|
@@ -28,12 +28,11 @@ const emit = defineEmits<{
|
|
|
28
28
|
'menu-close': []
|
|
29
29
|
}>()
|
|
30
30
|
|
|
31
|
-
const { isOpen, rootRef, open, close
|
|
31
|
+
const { isOpen, rootRef, triggerRef, open, close, toggle } = useDropdownState({
|
|
32
32
|
onOpen: () => emit('menu-open'),
|
|
33
33
|
onClose: () => emit('menu-close'),
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
-
const triggerRef = ref<HTMLElement | null>(null)
|
|
37
36
|
const panelRef = ref<HTMLElement | null>(null)
|
|
38
37
|
|
|
39
38
|
function menuItems(): HTMLElement[] {
|
|
@@ -41,13 +40,6 @@ function menuItems(): HTMLElement[] {
|
|
|
41
40
|
return Array.from(panelRef.value.querySelectorAll<HTMLElement>('[role="menuitem"]'))
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
function close(returnFocus = false) {
|
|
45
|
-
closeDropdown()
|
|
46
|
-
if (returnFocus) {
|
|
47
|
-
void nextTick(() => triggerRef.value?.focus())
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
43
|
const { handleTriggerKeydown, handlePanelKeydown } = useMenuKeyboard({
|
|
52
44
|
items: menuItems,
|
|
53
45
|
isOpen: () => isOpen.value,
|
|
@@ -158,7 +150,3 @@ function handleSignOut() {
|
|
|
158
150
|
</div>
|
|
159
151
|
</div>
|
|
160
152
|
</template>
|
|
161
|
-
|
|
162
|
-
<style>
|
|
163
|
-
@import '../styles/components/app-avatar-menu.css';
|
|
164
|
-
</style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import AppPluginSwitcher from './AppPluginSwitcher.vue'
|
|
3
|
+
import type { PluginSwitcherPlugin } from '../types/components'
|
|
4
|
+
|
|
5
|
+
const current: PluginSwitcherPlugin = {
|
|
6
|
+
id: 'ms-designer',
|
|
7
|
+
label: 'MS Designer',
|
|
8
|
+
version: '1.4.2',
|
|
9
|
+
color: 'var(--mint-sample-1)',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const plugins: PluginSwitcherPlugin[] = [
|
|
13
|
+
{ id: 'ms-designer', label: 'MS Designer', version: '1.4.2', color: 'var(--mint-sample-1)' },
|
|
14
|
+
{ id: 'ms-uploader', label: 'MS Uploader', version: '0.9.0', color: 'var(--mint-sample-2)' },
|
|
15
|
+
{ id: 'ms-planner', label: 'MS Planner', version: '2.0.1', color: 'var(--mint-sample-5)' },
|
|
16
|
+
{ id: 'drp', label: 'Dose Response', version: '3.1.0', color: 'var(--mint-sample-6)' },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
function handleSelect(plugin: PluginSwitcherPlugin) {
|
|
20
|
+
console.log('Switch to', plugin.id)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function handleInstall() {
|
|
24
|
+
console.log('Install plugin clicked')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function initState() {
|
|
28
|
+
return {
|
|
29
|
+
label: 'MS Designer',
|
|
30
|
+
version: '1.4.2',
|
|
31
|
+
installLabel: 'Install plugin…',
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<Story title="Layout/AppPluginSwitcher">
|
|
38
|
+
<Variant title="Playground" :init-state="initState">
|
|
39
|
+
<template #default="{ state }">
|
|
40
|
+
<div style="padding: 2rem; max-width: 600px; min-height: 22rem; background: var(--bg-primary);">
|
|
41
|
+
<AppPluginSwitcher
|
|
42
|
+
:current="{ id: 'active', label: state.label, version: state.version }"
|
|
43
|
+
:plugins="plugins"
|
|
44
|
+
:install-label="state.installLabel"
|
|
45
|
+
install-href="#install"
|
|
46
|
+
@select="handleSelect"
|
|
47
|
+
@install-click="handleInstall"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<template #controls="{ state }">
|
|
53
|
+
<HstText v-model="state.label" title="Current label" />
|
|
54
|
+
<HstText v-model="state.version" title="Current version" />
|
|
55
|
+
<HstText v-model="state.installLabel" title="Install label" />
|
|
56
|
+
</template>
|
|
57
|
+
</Variant>
|
|
58
|
+
|
|
59
|
+
<Variant title="Closed (top-bar placement)">
|
|
60
|
+
<div style="padding: 2rem; max-width: 600px; background: var(--bg-primary);">
|
|
61
|
+
<AppPluginSwitcher :current="current" :plugins="plugins" @select="handleSelect" />
|
|
62
|
+
</div>
|
|
63
|
+
</Variant>
|
|
64
|
+
|
|
65
|
+
<Variant title="Single plugin (no siblings)">
|
|
66
|
+
<!-- The menu still opens so the install affordance stays reachable. -->
|
|
67
|
+
<div style="padding: 2rem; max-width: 600px; min-height: 16rem; background: var(--bg-primary);">
|
|
68
|
+
<AppPluginSwitcher
|
|
69
|
+
:current="current"
|
|
70
|
+
:plugins="[current]"
|
|
71
|
+
install-href="#install"
|
|
72
|
+
@install-click="handleInstall"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</Variant>
|
|
76
|
+
|
|
77
|
+
<Variant title="Without version badge">
|
|
78
|
+
<div style="padding: 2rem; max-width: 600px; background: var(--bg-primary);">
|
|
79
|
+
<AppPluginSwitcher
|
|
80
|
+
:current="{ id: 'hmdb', label: 'HMDB Lookup' }"
|
|
81
|
+
:plugins="plugins"
|
|
82
|
+
@select="handleSelect"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</Variant>
|
|
86
|
+
</Story>
|
|
87
|
+
</template>
|
|
@@ -22,7 +22,7 @@ const emit = defineEmits<{
|
|
|
22
22
|
'install-click': []
|
|
23
23
|
}>()
|
|
24
24
|
|
|
25
|
-
const { isOpen, rootRef, close, toggle } = useDropdownState()
|
|
25
|
+
const { isOpen, rootRef, triggerRef, close, toggle } = useDropdownState()
|
|
26
26
|
|
|
27
27
|
function initialsOf(plugin: PluginSwitcherPlugin) {
|
|
28
28
|
const label = plugin.label || plugin.id
|
|
@@ -47,6 +47,7 @@ function handleInstall() {
|
|
|
47
47
|
<template>
|
|
48
48
|
<div ref="rootRef" class="mint-plugin-switcher">
|
|
49
49
|
<button
|
|
50
|
+
ref="triggerRef"
|
|
50
51
|
type="button"
|
|
51
52
|
class="mint-plugin-switcher__trigger"
|
|
52
53
|
:class="{ 'mint-plugin-switcher__trigger--open': isOpen }"
|
|
@@ -131,7 +132,3 @@ function handleInstall() {
|
|
|
131
132
|
</div>
|
|
132
133
|
</div>
|
|
133
134
|
</template>
|
|
134
|
-
|
|
135
|
-
<style>
|
|
136
|
-
@import '../styles/components/app-plugin-switcher.css';
|
|
137
|
-
</style>
|
|
@@ -5,11 +5,13 @@ import type { Toast } from '../types'
|
|
|
5
5
|
|
|
6
6
|
const { toasts, dismiss } = useToast()
|
|
7
7
|
|
|
8
|
+
// Semantic accents come from the shared tokens rather than a fourth copy of
|
|
9
|
+
// the same four hexes.
|
|
8
10
|
const accentColors: Record<Toast['type'], string> = {
|
|
9
|
-
success: '
|
|
10
|
-
error: '
|
|
11
|
-
warning: '
|
|
12
|
-
info: '
|
|
11
|
+
success: 'var(--mint-success)',
|
|
12
|
+
error: 'var(--mint-error)',
|
|
13
|
+
warning: 'var(--mint-warning)',
|
|
14
|
+
info: 'var(--mint-info)',
|
|
13
15
|
}
|
|
14
16
|
</script>
|
|
15
17
|
|
|
@@ -56,7 +58,3 @@ const accentColors: Record<Toast['type'], string> = {
|
|
|
56
58
|
</div>
|
|
57
59
|
</Teleport>
|
|
58
60
|
</template>
|
|
59
|
-
|
|
60
|
-
<style>
|
|
61
|
-
@import '../styles/components/toast.css';
|
|
62
|
-
</style>
|
|
@@ -78,6 +78,8 @@ const mockEntries = [
|
|
|
78
78
|
|
|
79
79
|
function initState() {
|
|
80
80
|
return {
|
|
81
|
+
loading: false,
|
|
82
|
+
error: '',
|
|
81
83
|
entries: mockEntries,
|
|
82
84
|
showFilters: false,
|
|
83
85
|
emptyMessage: 'No activity yet',
|
|
@@ -93,6 +95,8 @@ function initState() {
|
|
|
93
95
|
<template #default="{ state }">
|
|
94
96
|
<div style="padding: 2rem; max-width: 600px;">
|
|
95
97
|
<AuditTrail
|
|
98
|
+
:loading="state.loading"
|
|
99
|
+
:error="state.error || null"
|
|
96
100
|
:entries="state.entries"
|
|
97
101
|
:show-filters="state.showFilters"
|
|
98
102
|
:empty-message="state.emptyMessage"
|
|
@@ -102,6 +106,8 @@ function initState() {
|
|
|
102
106
|
</div>
|
|
103
107
|
</template>
|
|
104
108
|
<template #controls="{ state }">
|
|
109
|
+
<HstCheckbox v-model="state.loading" title="Loading" />
|
|
110
|
+
<HstText v-model="state.error" title="Error Message" />
|
|
105
111
|
<HstCheckbox v-model="state.showFilters" title="Show Filters" />
|
|
106
112
|
<HstText v-model="state.emptyMessage" title="Empty Message" />
|
|
107
113
|
<HstSelect
|
|
@@ -159,5 +165,17 @@ function initState() {
|
|
|
159
165
|
/>
|
|
160
166
|
</div>
|
|
161
167
|
</Variant>
|
|
168
|
+
|
|
169
|
+
<Variant title="Loading">
|
|
170
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
171
|
+
<AuditTrail :entries="[]" loading />
|
|
172
|
+
</div>
|
|
173
|
+
</Variant>
|
|
174
|
+
|
|
175
|
+
<Variant title="Error">
|
|
176
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
177
|
+
<AuditTrail :entries="[]" error="Could not load the audit trail." />
|
|
178
|
+
</div>
|
|
179
|
+
</Variant>
|
|
162
180
|
</Story>
|
|
163
181
|
</template>
|
|
@@ -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 { AuditEntryType, AuditEntry } from '../types'
|
|
5
|
+
import DataStateInternal from './internal/DataStateInternal.vue'
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
entries: AuditEntry[]
|
|
@@ -10,6 +11,13 @@ interface Props {
|
|
|
10
11
|
emptyMessage?: string
|
|
11
12
|
order?: 'newest' | 'oldest'
|
|
12
13
|
size?: 'sm' | 'md' | 'lg'
|
|
14
|
+
/** Replace the timeline with a loading placeholder. */
|
|
15
|
+
loading?: boolean
|
|
16
|
+
/**
|
|
17
|
+
* Error message. When set, the timeline is replaced by an error state.
|
|
18
|
+
* `null`/`undefined` renders normally.
|
|
19
|
+
*/
|
|
20
|
+
error?: string | null
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -17,6 +25,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
17
25
|
emptyMessage: 'No activity yet',
|
|
18
26
|
order: 'newest',
|
|
19
27
|
size: 'md',
|
|
28
|
+
loading: false,
|
|
29
|
+
error: null,
|
|
20
30
|
})
|
|
21
31
|
|
|
22
32
|
const emit = defineEmits<{
|
|
@@ -104,7 +114,7 @@ function handleEntryClick(entry: AuditEntry) {
|
|
|
104
114
|
</div>
|
|
105
115
|
|
|
106
116
|
<div
|
|
107
|
-
v-if="sortedEntries.length"
|
|
117
|
+
v-if="!loading && !error && sortedEntries.length"
|
|
108
118
|
class="mint-audit-trail__list"
|
|
109
119
|
:style="maxHeight ? { maxHeight, overflowY: 'auto' } : {}"
|
|
110
120
|
>
|
|
@@ -140,12 +150,22 @@ function handleEntryClick(entry: AuditEntry) {
|
|
|
140
150
|
</div>
|
|
141
151
|
</div>
|
|
142
152
|
|
|
143
|
-
<div v-if="
|
|
144
|
-
<slot name="
|
|
153
|
+
<div v-if="error" class="mint-audit-trail__state">
|
|
154
|
+
<slot name="error" :error="error">
|
|
155
|
+
<DataStateInternal state="error" size="sm" :title="error" />
|
|
156
|
+
</slot>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div v-else-if="loading" class="mint-audit-trail__state">
|
|
160
|
+
<slot name="loading">
|
|
161
|
+
<DataStateInternal state="loading" size="sm" loading-label="Loading activity" />
|
|
162
|
+
</slot>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div v-else-if="!sortedEntries.length" class="mint-audit-trail__state">
|
|
166
|
+
<slot name="empty">
|
|
167
|
+
<DataStateInternal state="empty" size="sm" :title="emptyMessage" />
|
|
168
|
+
</slot>
|
|
145
169
|
</div>
|
|
146
170
|
</div>
|
|
147
171
|
</template>
|
|
148
|
-
|
|
149
|
-
<style>
|
|
150
|
-
@import '../styles/components/audit-trail.css';
|
|
151
|
-
</style>
|