@morscherlab/mint-sdk 1.0.0-rc.1 → 1.0.0-rc.2
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 +9 -1
- package/dist/__tests__/components/LcmsSequenceTable.test.d.ts +1 -0
- package/dist/__tests__/components/ProgressBar.test.d.ts +1 -0
- package/dist/__tests__/components/RackEditor.test.d.ts +1 -0
- package/dist/__tests__/components/SequenceProgressBar.test.d.ts +1 -0
- package/dist/__tests__/composables/useExperimentSamples.test.d.ts +1 -0
- package/dist/__tests__/utils/instrument.test.d.ts +1 -0
- package/dist/__tests__/utils/lcms.test.d.ts +1 -0
- package/dist/__tests__/utils/permissions.test.d.ts +1 -0
- package/dist/__tests__/utils/rack.test.d.ts +1 -0
- package/dist/{auth-CBG3bWEc.js → auth-B7g4J4ZF.js} +99 -5
- package/dist/auth-B7g4J4ZF.js.map +1 -0
- package/dist/components/AutoGroupModal.vue.d.ts +1 -1
- package/dist/components/BaseCheckbox.vue.d.ts +1 -1
- package/dist/components/BaseToggle.vue.d.ts +2 -2
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
- package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +1 -1
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +1 -1
- package/dist/components/FormulaInput.vue.d.ts +1 -1
- package/dist/components/InstrumentAlertLog.vue.d.ts +22 -0
- package/dist/components/InstrumentStateBadge.vue.d.ts +11 -0
- package/dist/components/InstrumentStatusCard.vue.d.ts +13 -0
- package/dist/components/LcmsSequenceTable.vue.d.ts +26 -0
- package/dist/components/ProgressBar.vue.d.ts +1 -0
- package/dist/components/RackEditor.vue.d.ts +41 -3
- package/dist/components/ReagentList.vue.d.ts +1 -1
- package/dist/components/SampleSelector.vue.d.ts +5 -2
- package/dist/components/SegmentedControl.vue.d.ts +2 -0
- package/dist/components/SequenceInput.vue.d.ts +1 -1
- package/dist/components/SequenceProgressBar.vue.d.ts +15 -0
- package/dist/components/SettingsModal.vue.d.ts +3 -1
- package/dist/components/TagsInput.vue.d.ts +1 -1
- package/dist/components/WellPlate.vue.d.ts +42 -3
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +3 -3
- package/dist/{components-5KSfsVqf.js → components-BhK-dW99.js} +2091 -1051
- package/dist/components-BhK-dW99.js.map +1 -0
- package/dist/composables/experimentDesignData.d.ts +17 -0
- package/dist/composables/index.d.ts +2 -0
- package/dist/composables/index.js +4 -4
- package/dist/composables/useControlSchema.d.ts +11 -0
- package/dist/composables/useExperimentData.d.ts +11 -3
- package/dist/composables/useExperimentSamples.d.ts +42 -0
- package/dist/composables/usePlatformContext.d.ts +54 -0
- package/dist/{composables-D4Myb30a.js → composables-Bg7CFuNz.js} +5 -3
- package/dist/composables-Bg7CFuNz.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +168 -6
- package/dist/index.js.map +1 -0
- package/dist/install.js +2 -2
- package/dist/instrument.d.ts +7 -0
- package/dist/lcms.d.ts +27 -0
- package/dist/permissions.d.ts +46 -0
- package/dist/stores/auth.d.ts +74 -2
- package/dist/stores/index.js +1 -1
- package/dist/styles.css +3316 -1216
- package/dist/templates/builders.d.ts +7 -3
- package/dist/templates/index.d.ts +2 -2
- package/dist/templates/index.js +2 -2
- package/dist/templates/presets.d.ts +12 -0
- package/dist/templates/types.d.ts +16 -1
- package/dist/{templates-BSlxwV2c.js → templates-BorLR_7p.js} +313 -3
- package/dist/templates-BorLR_7p.js.map +1 -0
- package/dist/types/auth.d.ts +2 -0
- package/dist/types/components.d.ts +32 -3
- package/dist/types/form-builder.d.ts +2 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/instrument.d.ts +56 -0
- package/dist/types/platform.d.ts +3 -0
- package/dist/{useExperimentData-BbbdI5xT.js → useProtocolTemplates-n6AJqSqv.js} +534 -359
- package/dist/useProtocolTemplates-n6AJqSqv.js.map +1 -0
- package/dist/utils/rack.d.ts +47 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.test.ts +15 -0
- package/src/__tests__/components/BaseTabs.test.ts +15 -0
- package/src/__tests__/components/LcmsSequenceTable.test.ts +57 -0
- package/src/__tests__/components/ProgressBar.test.ts +18 -0
- package/src/__tests__/components/RackEditor.test.ts +125 -0
- package/src/__tests__/components/SampleSelector.test.ts +25 -0
- package/src/__tests__/components/SegmentedControl.test.ts +45 -0
- package/src/__tests__/components/SequenceProgressBar.test.ts +39 -0
- package/src/__tests__/components/SettingsModal.test.ts +83 -2
- package/src/__tests__/composables/useControlSchema.test.ts +4 -0
- package/src/__tests__/composables/useExperimentData.test.ts +23 -0
- package/src/__tests__/composables/useExperimentSamples.test.ts +91 -0
- package/src/__tests__/templates/templates.test.ts +86 -0
- package/src/__tests__/utils/instrument.test.ts +47 -0
- package/src/__tests__/utils/lcms.test.ts +73 -0
- package/src/__tests__/utils/permissions.test.ts +50 -0
- package/src/__tests__/utils/rack.test.ts +120 -0
- package/src/components/AppTopBar.vue +1 -0
- package/src/components/BaseTabs.vue +22 -1
- package/src/components/InstrumentAlertLog.vue +191 -0
- package/src/components/InstrumentStateBadge.vue +50 -0
- package/src/components/InstrumentStatusCard.vue +188 -0
- package/src/components/LcmsSequenceTable.vue +191 -0
- package/src/components/ProgressBar.vue +3 -0
- package/src/components/RackEditor.vue +73 -2
- package/src/components/SampleSelector.vue +28 -9
- package/src/components/SegmentedControl.story.vue +17 -0
- package/src/components/SegmentedControl.vue +14 -3
- package/src/components/SequenceProgressBar.vue +71 -0
- package/src/components/SettingsModal.vue +42 -2
- package/src/components/WellPlate.vue +142 -21
- package/src/components/index.ts +5 -0
- package/src/components/internal/WellEditPopupInternal.vue +1 -0
- package/src/composables/experimentDesignData.ts +182 -0
- package/src/composables/index.ts +14 -0
- package/src/composables/useAuth.ts +4 -0
- package/src/composables/useAutoGroup.ts +5 -1
- package/src/composables/useControlSchema.ts +21 -0
- package/src/composables/useExperimentData.ts +57 -16
- package/src/composables/useExperimentSamples.ts +142 -0
- package/src/index.ts +27 -0
- package/src/instrument.ts +90 -0
- package/src/lcms.ts +108 -0
- package/src/permissions.ts +143 -0
- package/src/stores/auth.ts +31 -3
- package/src/styles/components/instrument-monitor.css +478 -0
- package/src/styles/components/lcms-sequence-table.css +189 -0
- package/src/styles/components/sequence-progress-bar.css +63 -0
- package/src/styles/components/tabs.css +9 -0
- package/src/styles/components/well-edit-popup.css +7 -1
- package/src/styles/components/well-plate.css +5 -0
- package/src/styles/index.css +3 -0
- package/src/templates/builders.ts +201 -0
- package/src/templates/controlSchemas.ts +68 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/presets.ts +23 -0
- package/src/templates/types.ts +17 -0
- package/src/types/auth.ts +3 -0
- package/src/types/components.ts +45 -3
- package/src/types/form-builder.ts +2 -1
- package/src/types/index.ts +35 -0
- package/src/types/instrument.ts +61 -0
- package/src/types/platform.ts +4 -0
- package/src/utils/rack.ts +209 -0
- package/dist/auth-CBG3bWEc.js.map +0 -1
- package/dist/components-5KSfsVqf.js.map +0 -1
- package/dist/composables-D4Myb30a.js.map +0 -1
- package/dist/templates-BSlxwV2c.js.map +0 -1
- package/dist/useExperimentData-BbbdI5xT.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Multi-rack editor for managing collections of well-plate racks with add/remove/reorder controls and per-well editing. */
|
|
3
3
|
import { ref, computed, watch } from 'vue'
|
|
4
|
-
import type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well } from '../types'
|
|
4
|
+
import type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well, WellEditField, WellSampleDropData, RackSampleDropMapper } from '../types'
|
|
5
5
|
import { useRackEditor } from '../composables/useRackEditor'
|
|
6
6
|
import WellPlate from './WellPlate.vue'
|
|
7
7
|
|
|
@@ -16,6 +16,8 @@ interface Props {
|
|
|
16
16
|
wellPlateSize?: WellPlateSize
|
|
17
17
|
showLegend?: boolean
|
|
18
18
|
showBadges?: boolean
|
|
19
|
+
allowSampleDrop?: boolean
|
|
20
|
+
sampleDropMapper?: RackSampleDropMapper
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -29,6 +31,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
29
31
|
wellPlateSize: 'md',
|
|
30
32
|
showLegend: true,
|
|
31
33
|
showBadges: true,
|
|
34
|
+
allowSampleDrop: false,
|
|
35
|
+
sampleDropMapper: undefined,
|
|
32
36
|
})
|
|
33
37
|
|
|
34
38
|
const emit = defineEmits<{
|
|
@@ -38,6 +42,24 @@ const emit = defineEmits<{
|
|
|
38
42
|
'rack-remove': [rackId: string]
|
|
39
43
|
'rack-reorder': [rackIds: string[]]
|
|
40
44
|
'well-edit': [rackId: string, wellId: string, data: WellEditData]
|
|
45
|
+
'sample-drop': [rackId: string, wellId: string, data: WellSampleDropData, event: DragEvent]
|
|
46
|
+
}>()
|
|
47
|
+
|
|
48
|
+
interface RackWellEditorSlotProps {
|
|
49
|
+
rack?: Rack
|
|
50
|
+
rackId: string
|
|
51
|
+
wellId: string
|
|
52
|
+
wellData?: Partial<Well>
|
|
53
|
+
editFields: WellEditField[]
|
|
54
|
+
defaultInjectionVolume: number
|
|
55
|
+
position: { x: number; y: number }
|
|
56
|
+
save: (data: WellEditData) => void
|
|
57
|
+
clear: () => void
|
|
58
|
+
close: () => void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
defineSlots<{
|
|
62
|
+
'well-editor'?: (props: RackWellEditorSlotProps) => unknown
|
|
41
63
|
}>()
|
|
42
64
|
|
|
43
65
|
const editor = useRackEditor(props.modelValue, {
|
|
@@ -214,6 +236,37 @@ function handleWellClear(wellId: string) {
|
|
|
214
236
|
editor.clearWell(editor.activeRack.value.id, wellId)
|
|
215
237
|
}
|
|
216
238
|
|
|
239
|
+
function handleSampleDrop(wellId: string, data: WellSampleDropData, event: DragEvent) {
|
|
240
|
+
const rack = editor.activeRack.value
|
|
241
|
+
if (!rack) return
|
|
242
|
+
|
|
243
|
+
emit('sample-drop', rack.id, wellId, data, event)
|
|
244
|
+
|
|
245
|
+
const editData = props.sampleDropMapper?.(data, {
|
|
246
|
+
rack,
|
|
247
|
+
rackId: rack.id,
|
|
248
|
+
wellId,
|
|
249
|
+
event,
|
|
250
|
+
}) ?? defaultSampleDropEditData(wellId, data, rack)
|
|
251
|
+
|
|
252
|
+
if (!editData) return
|
|
253
|
+
handleWellEdit(wellId, { ...editData, wellId })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function defaultSampleDropEditData(wellId: string, data: WellSampleDropData, rack: Rack): WellEditData | null {
|
|
257
|
+
const label = data.label ?? data.sampleName ?? data.id ?? ''
|
|
258
|
+
if (!label.trim()) return null
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
wellId,
|
|
262
|
+
label: label.trim(),
|
|
263
|
+
sampleType: data.sampleType ?? 'sample',
|
|
264
|
+
injectionVolume: data.injectionVolume ?? rack.injectionVolume,
|
|
265
|
+
injectionCount: data.injectionCount ?? 1,
|
|
266
|
+
customMethod: data.customMethod ?? '',
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
217
270
|
// Computed well count per rack
|
|
218
271
|
function getWellCount(rack: Rack): number {
|
|
219
272
|
return Object.keys(rack.wells).length
|
|
@@ -359,9 +412,27 @@ const activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})
|
|
|
359
412
|
:readonly="readonly"
|
|
360
413
|
:size="wellPlateSize"
|
|
361
414
|
:show-sample-type-indicator="true"
|
|
415
|
+
:allow-sample-drop="allowSampleDrop && !readonly"
|
|
362
416
|
@well-edit="handleWellEdit"
|
|
363
417
|
@well-clear="handleWellClear"
|
|
364
|
-
|
|
418
|
+
@sample-drop="handleSampleDrop"
|
|
419
|
+
>
|
|
420
|
+
<template v-if="$slots['well-editor']" #well-editor="slotProps">
|
|
421
|
+
<slot
|
|
422
|
+
name="well-editor"
|
|
423
|
+
:rack="editor.activeRack.value"
|
|
424
|
+
:rack-id="editor.activeRack.value?.id ?? ''"
|
|
425
|
+
:well-id="slotProps.wellId"
|
|
426
|
+
:well-data="slotProps.wellData"
|
|
427
|
+
:edit-fields="slotProps.editFields"
|
|
428
|
+
:default-injection-volume="slotProps.defaultInjectionVolume"
|
|
429
|
+
:position="slotProps.position"
|
|
430
|
+
:save="slotProps.save"
|
|
431
|
+
:clear="slotProps.clear"
|
|
432
|
+
:close="slotProps.close"
|
|
433
|
+
/>
|
|
434
|
+
</template>
|
|
435
|
+
</WellPlate>
|
|
365
436
|
</div>
|
|
366
437
|
</div>
|
|
367
438
|
</template>
|
|
@@ -11,23 +11,27 @@ import { useTextSearch } from '../composables/useTextSearch'
|
|
|
11
11
|
import { useListSelection } from '../composables/useListSelection'
|
|
12
12
|
import { useSampleGroups, type SampleMajorGroup } from '../composables/useSampleGroups'
|
|
13
13
|
import { useExpansionSet } from '../composables/useExpansionSet'
|
|
14
|
+
import { useExperimentSamples } from '../composables/useExperimentSamples'
|
|
14
15
|
|
|
15
16
|
interface Props {
|
|
16
|
-
samples
|
|
17
|
+
samples?: string[]
|
|
17
18
|
modelValue: string[]
|
|
18
19
|
groups?: SampleGroup[]
|
|
19
20
|
enableGrouping?: boolean
|
|
20
21
|
enableSmartGroup?: boolean
|
|
21
22
|
experimentId?: number
|
|
22
23
|
designData?: Record<string, unknown>
|
|
24
|
+
autoloadExperimentData?: boolean
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
const props = withDefaults(defineProps<Props>(), {
|
|
28
|
+
samples: () => [],
|
|
26
29
|
groups: () => [],
|
|
27
30
|
enableGrouping: true,
|
|
28
31
|
enableSmartGroup: true,
|
|
29
32
|
experimentId: undefined,
|
|
30
33
|
designData: undefined,
|
|
34
|
+
autoloadExperimentData: true,
|
|
31
35
|
})
|
|
32
36
|
|
|
33
37
|
const emit = defineEmits<{
|
|
@@ -66,8 +70,23 @@ const internalGroups = computed({
|
|
|
66
70
|
set: (value) => emit('update:groups', value),
|
|
67
71
|
})
|
|
68
72
|
|
|
73
|
+
const providedSamples = computed(() => props.samples)
|
|
74
|
+
const shouldLoadExperimentSamples = computed(() =>
|
|
75
|
+
props.autoloadExperimentData && providedSamples.value.length === 0,
|
|
76
|
+
)
|
|
77
|
+
const experimentSamples = useExperimentSamples({
|
|
78
|
+
experimentId: () => props.experimentId,
|
|
79
|
+
designData: () => props.designData,
|
|
80
|
+
enabled: shouldLoadExperimentSamples,
|
|
81
|
+
})
|
|
82
|
+
const resolvedSamples = computed(() =>
|
|
83
|
+
providedSamples.value.length > 0 ? providedSamples.value : experimentSamples.samples.value,
|
|
84
|
+
)
|
|
85
|
+
const resolvedExperimentId = computed(() => props.experimentId ?? experimentSamples.experimentId.value)
|
|
86
|
+
const resolvedDesignData = computed(() => props.designData ?? experimentSamples.designData.value ?? undefined)
|
|
87
|
+
|
|
69
88
|
const sampleGroups = useSampleGroups({
|
|
70
|
-
samples: () =>
|
|
89
|
+
samples: () => resolvedSamples.value,
|
|
71
90
|
groups: internalGroups,
|
|
72
91
|
})
|
|
73
92
|
const hierarchicalGroups = sampleGroups.hierarchicalGroups
|
|
@@ -77,7 +96,7 @@ const groupingEnabled = computed(() => internalGroups.value.length > 0)
|
|
|
77
96
|
const ungroupedSamples = sampleGroups.ungroupedSamples
|
|
78
97
|
|
|
79
98
|
const sampleSearch = useTextSearch({
|
|
80
|
-
items: () =>
|
|
99
|
+
items: () => resolvedSamples.value,
|
|
81
100
|
query: searchQuery,
|
|
82
101
|
getText: sample => sample,
|
|
83
102
|
})
|
|
@@ -85,7 +104,7 @@ const filteredSamples = sampleSearch.filteredItems
|
|
|
85
104
|
|
|
86
105
|
const sampleSelection = useListSelection({
|
|
87
106
|
selected: () => props.modelValue,
|
|
88
|
-
items: () =>
|
|
107
|
+
items: () => resolvedSamples.value,
|
|
89
108
|
})
|
|
90
109
|
const isAllSelected = sampleSelection.isAllSelected
|
|
91
110
|
|
|
@@ -433,7 +452,7 @@ function addNewGroup() {
|
|
|
433
452
|
class="mint-sample-selector__checkbox"
|
|
434
453
|
/>
|
|
435
454
|
<span class="mint-sample-selector__select-all-label">Select All</span>
|
|
436
|
-
<span class="mint-sample-selector__select-all-count">{{
|
|
455
|
+
<span class="mint-sample-selector__select-all-count">{{ resolvedSamples.length }} samples</span>
|
|
437
456
|
</label>
|
|
438
457
|
|
|
439
458
|
<!-- Action Buttons Row -->
|
|
@@ -444,7 +463,7 @@ function addNewGroup() {
|
|
|
444
463
|
v-if="enableSmartGroup"
|
|
445
464
|
:variant="groupingEnabled ? 'primary' : 'secondary'"
|
|
446
465
|
size="sm"
|
|
447
|
-
:disabled="
|
|
466
|
+
:disabled="resolvedSamples.length === 0"
|
|
448
467
|
class="mint-sample-selector__action-btn"
|
|
449
468
|
@click="showSmartGroupModal = true"
|
|
450
469
|
>
|
|
@@ -940,9 +959,9 @@ function addNewGroup() {
|
|
|
940
959
|
<!-- Smart Grouping Modal -->
|
|
941
960
|
<AutoGroupModal
|
|
942
961
|
v-model="showSmartGroupModal"
|
|
943
|
-
:samples="
|
|
944
|
-
:experiment-id="
|
|
945
|
-
:design-data="
|
|
962
|
+
:samples="resolvedSamples"
|
|
963
|
+
:experiment-id="resolvedExperimentId"
|
|
964
|
+
:design-data="resolvedDesignData"
|
|
946
965
|
@apply="handleSmartGroupApply"
|
|
947
966
|
/>
|
|
948
967
|
</div>
|
|
@@ -33,7 +33,13 @@ const withDisabledOptions: SegmentedOption[] = [
|
|
|
33
33
|
{ value: 'deleted', label: 'Deleted', disabled: true },
|
|
34
34
|
]
|
|
35
35
|
|
|
36
|
+
const twoTabOptions: SegmentedOption[] = [
|
|
37
|
+
{ value: 'table', label: 'Table' },
|
|
38
|
+
{ value: 'chart', label: 'Chart' },
|
|
39
|
+
]
|
|
40
|
+
|
|
36
41
|
const cardSelectionP3 = ref('list')
|
|
42
|
+
const twoTabSelection = ref('table')
|
|
37
43
|
</script>
|
|
38
44
|
|
|
39
45
|
<template>
|
|
@@ -100,6 +106,17 @@ const cardSelectionP3 = ref('list')
|
|
|
100
106
|
</div>
|
|
101
107
|
</Variant>
|
|
102
108
|
|
|
109
|
+
<Variant title="Disabled By Value">
|
|
110
|
+
<div style="padding: 2rem; max-width: 500px; margin: 0 auto;">
|
|
111
|
+
<SegmentedControl
|
|
112
|
+
v-model="twoTabSelection"
|
|
113
|
+
:options="twoTabOptions"
|
|
114
|
+
:disabled-values="['chart']"
|
|
115
|
+
variant="simple"
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
</Variant>
|
|
119
|
+
|
|
103
120
|
<Variant title="Not Full Width">
|
|
104
121
|
<div style="padding: 2rem;">
|
|
105
122
|
<SegmentedControl :model-value="'day'" :options="basicOptions" :full-width="false" />
|
|
@@ -11,6 +11,7 @@ interface Props {
|
|
|
11
11
|
size?: SegmentedControlSize
|
|
12
12
|
fullWidth?: boolean
|
|
13
13
|
disabled?: boolean
|
|
14
|
+
disabledValues?: Array<string | number>
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -18,6 +19,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
18
19
|
size: 'md',
|
|
19
20
|
fullWidth: false,
|
|
20
21
|
disabled: false,
|
|
22
|
+
disabledValues: () => [],
|
|
21
23
|
})
|
|
22
24
|
|
|
23
25
|
const emit = defineEmits<{
|
|
@@ -25,9 +27,18 @@ const emit = defineEmits<{
|
|
|
25
27
|
}>()
|
|
26
28
|
|
|
27
29
|
const normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput))
|
|
30
|
+
const disabledValueSet = computed(() => new Set(props.disabledValues))
|
|
31
|
+
|
|
32
|
+
function isOptionDisabled(option: SegmentedOption) {
|
|
33
|
+
return option.disabled === true || disabledValueSet.value.has(option.value)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isSelectionDisabled(option: SegmentedOption) {
|
|
37
|
+
return props.disabled || isOptionDisabled(option)
|
|
38
|
+
}
|
|
28
39
|
|
|
29
40
|
function handleSelect(option: SegmentedOption) {
|
|
30
|
-
if (
|
|
41
|
+
if (isSelectionDisabled(option)) return
|
|
31
42
|
emit('update:modelValue', option.value)
|
|
32
43
|
}
|
|
33
44
|
|
|
@@ -56,13 +67,13 @@ function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
|
|
|
56
67
|
type="button"
|
|
57
68
|
role="radio"
|
|
58
69
|
:aria-checked="modelValue === option.value"
|
|
59
|
-
:disabled="
|
|
70
|
+
:disabled="isSelectionDisabled(option)"
|
|
60
71
|
:class="[
|
|
61
72
|
'mint-segmented-control__option',
|
|
62
73
|
`mint-segmented-control__option--${variant}`,
|
|
63
74
|
`mint-segmented-control__option--${size}`,
|
|
64
75
|
modelValue === option.value ? 'mint-segmented-control__option--active' : '',
|
|
65
|
-
option
|
|
76
|
+
isOptionDisabled(option) ? 'mint-segmented-control__option--disabled' : '',
|
|
66
77
|
]"
|
|
67
78
|
@click="handleSelect(option)"
|
|
68
79
|
@keydown="handleKeydown($event, option)"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import {
|
|
5
|
+
formatSequenceEta,
|
|
6
|
+
formatSequenceRemaining,
|
|
7
|
+
sequenceProgressPercent,
|
|
8
|
+
} from '../instrument'
|
|
9
|
+
import type { SequenceProgress } from '../types'
|
|
10
|
+
import ProgressBar from './ProgressBar.vue'
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
progress: SequenceProgress
|
|
14
|
+
compact?: boolean
|
|
15
|
+
label?: string
|
|
16
|
+
showEta?: boolean
|
|
17
|
+
showRemaining?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
21
|
+
compact: false,
|
|
22
|
+
label: 'samples',
|
|
23
|
+
showEta: true,
|
|
24
|
+
showRemaining: true,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const percent = computed(() => sequenceProgressPercent(props.progress))
|
|
28
|
+
const remainingLabel = computed(() => formatSequenceRemaining(props.progress))
|
|
29
|
+
const etaLabel = computed(() => formatSequenceEta(props.progress))
|
|
30
|
+
const hasFooter = computed(() => {
|
|
31
|
+
if (props.compact) return false
|
|
32
|
+
return (props.showRemaining && remainingLabel.value) || (props.showEta && etaLabel.value)
|
|
33
|
+
})
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<div class="mint-sequence-progress">
|
|
38
|
+
<div class="mint-sequence-progress__header">
|
|
39
|
+
<span class="mint-sequence-progress__label">
|
|
40
|
+
{{ progress.current_sample }} / {{ progress.total_samples }} {{ label }}
|
|
41
|
+
</span>
|
|
42
|
+
<span class="mint-sequence-progress__percent">{{ percent }}%</span>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<ProgressBar
|
|
46
|
+
:value="percent"
|
|
47
|
+
color="info"
|
|
48
|
+
size="sm"
|
|
49
|
+
aria-label="Sequence progress"
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<div v-if="hasFooter" class="mint-sequence-progress__footer">
|
|
53
|
+
<span
|
|
54
|
+
v-if="showRemaining && remainingLabel"
|
|
55
|
+
class="mint-sequence-progress__remaining"
|
|
56
|
+
>
|
|
57
|
+
{{ remainingLabel }}
|
|
58
|
+
</span>
|
|
59
|
+
<span
|
|
60
|
+
v-if="showEta && etaLabel"
|
|
61
|
+
class="mint-sequence-progress__eta"
|
|
62
|
+
>
|
|
63
|
+
ETA {{ etaLabel }}
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<style>
|
|
70
|
+
@import '../styles/components/sequence-progress-bar.css';
|
|
71
|
+
</style>
|
|
@@ -32,6 +32,9 @@ import {
|
|
|
32
32
|
type ControlWorkspaceOptions,
|
|
33
33
|
} from '../composables/useControlSchema'
|
|
34
34
|
import { useSettingsStore, colorPalettes } from '../stores/settings'
|
|
35
|
+
import { useAuthStore } from '../stores/auth'
|
|
36
|
+
import { usePlatformContext } from '../composables/usePlatformContext'
|
|
37
|
+
import { canAccessByPolicy } from '../permissions'
|
|
35
38
|
import {
|
|
36
39
|
formSchemaFieldNames,
|
|
37
40
|
pickExistingRecordKeys,
|
|
@@ -46,10 +49,12 @@ import type {
|
|
|
46
49
|
SettingsTabInput,
|
|
47
50
|
SettingsModalLayout,
|
|
48
51
|
SettingsModalSchema,
|
|
52
|
+
SettingsUserType,
|
|
49
53
|
FormSchema,
|
|
50
54
|
FormSectionSchema,
|
|
51
55
|
FormEnhancements,
|
|
52
56
|
} from '../types'
|
|
57
|
+
import type { AccessControlled, PermissionUser } from '../permissions'
|
|
53
58
|
import { normalizeItemInput } from '../utils/items'
|
|
54
59
|
|
|
55
60
|
// Map our settings groups onto the form-builder's flat-section shape.
|
|
@@ -86,6 +91,8 @@ interface Props {
|
|
|
86
91
|
values?: Record<string, unknown>
|
|
87
92
|
/** Optional dynamic enhancements (validators, dynamic options, callbacks). */
|
|
88
93
|
enhancements?: FormEnhancements<Record<string, unknown>>
|
|
94
|
+
/** Optional user type override for permission-filtered settings content. Defaults to SDK auth/platform context. */
|
|
95
|
+
userType?: SettingsUserType
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -104,6 +111,8 @@ const emit = defineEmits<{
|
|
|
104
111
|
}>()
|
|
105
112
|
|
|
106
113
|
const settings = useSettingsStore()
|
|
114
|
+
const auth = useAuthStore()
|
|
115
|
+
const { user: platformUser } = usePlatformContext()
|
|
107
116
|
|
|
108
117
|
const APPEARANCE_TAB_ID = 'appearance'
|
|
109
118
|
|
|
@@ -130,13 +139,34 @@ const resolvedControls = computed<ControlSchema | undefined>(() =>
|
|
|
130
139
|
const resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>
|
|
131
140
|
mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)
|
|
132
141
|
)
|
|
133
|
-
const
|
|
142
|
+
const sourceSettingsSchema = computed<SettingsModalSchema | undefined>(() =>
|
|
134
143
|
props.schema ?? (
|
|
135
144
|
resolvedControls.value
|
|
136
145
|
? controlsToSettingsSchema(resolvedControls.value, resolvedControlOptions.value)
|
|
137
146
|
: undefined
|
|
138
147
|
),
|
|
139
148
|
)
|
|
149
|
+
const currentAccessUser = computed<PermissionUser | null>(() => {
|
|
150
|
+
if (props.userType) {
|
|
151
|
+
return { role: props.userType === 'admin' ? 'admin' : 'user' }
|
|
152
|
+
}
|
|
153
|
+
return auth.userInfo ?? platformUser.value ?? null
|
|
154
|
+
})
|
|
155
|
+
const isAccessUserAuthenticated = computed(() =>
|
|
156
|
+
props.userType !== undefined || auth.isAuthenticated || !!platformUser.value,
|
|
157
|
+
)
|
|
158
|
+
const settingsSchema = computed<SettingsModalSchema | undefined>(() => {
|
|
159
|
+
if (!sourceSettingsSchema.value) return undefined
|
|
160
|
+
const groups = sourceSettingsSchema.value.groups.flatMap((group) => {
|
|
161
|
+
if (!canShowForCurrentUser(group)) return []
|
|
162
|
+
|
|
163
|
+
const fields = group.fields.filter(canShowForCurrentUser)
|
|
164
|
+
if (fields.length === 0) return []
|
|
165
|
+
|
|
166
|
+
return [{ ...group, fields }]
|
|
167
|
+
})
|
|
168
|
+
return { groups }
|
|
169
|
+
})
|
|
140
170
|
const isSchemaDriven = computed(() => !!settingsSchema.value)
|
|
141
171
|
const resolvedValues = computed<Record<string, unknown>>(() => ({
|
|
142
172
|
...(resolvedControlOptions.value.initialValues ?? {}),
|
|
@@ -201,7 +231,9 @@ const visibleSchemaGroups = computed(() =>
|
|
|
201
231
|
: [],
|
|
202
232
|
)
|
|
203
233
|
|
|
204
|
-
const manualTabs = computed<SettingsTab[]>(() =>
|
|
234
|
+
const manualTabs = computed<SettingsTab[]>(() =>
|
|
235
|
+
props.tabs.map(normalizeItemInput).filter(canShowForCurrentUser)
|
|
236
|
+
)
|
|
205
237
|
|
|
206
238
|
const allTabs = computed<SettingsTab[]>(() => {
|
|
207
239
|
const base: SettingsTab[] = settingsSchema.value
|
|
@@ -256,6 +288,14 @@ function builderFieldNames(): string[] {
|
|
|
256
288
|
function isControlModelBinding(model: ControlModel | ControlModelBinding): model is ControlModelBinding {
|
|
257
289
|
return 'controls' in model && 'controlOptions' in model
|
|
258
290
|
}
|
|
291
|
+
|
|
292
|
+
function canShowForCurrentUser(item: AccessControlled): boolean {
|
|
293
|
+
return canAccessByPolicy(
|
|
294
|
+
currentAccessUser.value,
|
|
295
|
+
item,
|
|
296
|
+
isAccessUserAuthenticated.value,
|
|
297
|
+
)
|
|
298
|
+
}
|
|
259
299
|
</script>
|
|
260
300
|
|
|
261
301
|
<template>
|