@morscherlab/mint-sdk 1.0.0-rc.1 → 1.0.0-rc.4
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/AppSidebar.vue.d.ts +3 -3
- 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/ControlWorkspaceView.vue.d.ts +3 -3
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +2 -2
- 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/PluginWorkspaceView.vue.d.ts +3 -3
- 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-DafPc4rM.js} +2091 -1051
- package/dist/components-DafPc4rM.js.map +1 -0
- package/dist/composables/controlComponentBindings.d.ts +7 -0
- package/dist/composables/controlSchemaAdapters.d.ts +20 -0
- package/dist/composables/controlSchemaFormFields.d.ts +3 -0
- package/dist/composables/controlSchemaLayout.d.ts +7 -0
- package/dist/composables/controlSchemaNormalize.d.ts +15 -0
- package/dist/composables/controlSchemaUtils.d.ts +9 -0
- package/dist/composables/controlWorkspaceOptions.d.ts +2 -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 +15 -30
- package/dist/composables/useControlWorkspace.d.ts +5 -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-BMkPQhVK.js} +5 -3
- package/dist/composables-BMkPQhVK.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/adapters.d.ts +14 -47
- package/dist/templates/assayLookups.d.ts +3 -0
- package/dist/templates/assayMatrixAdapters.d.ts +6 -0
- package/dist/templates/assayMatrixBuilder.d.ts +2 -0
- package/dist/templates/assayNormalizers.d.ts +4 -0
- package/dist/templates/builderDefaults.d.ts +1 -0
- package/dist/templates/builderIdUtils.d.ts +4 -0
- package/dist/templates/builderPresetControls.d.ts +10 -0
- package/dist/templates/builderReadUtils.d.ts +8 -0
- package/dist/templates/builders.d.ts +26 -63
- package/dist/templates/calibrationCurveAdapters.d.ts +4 -0
- package/dist/templates/calibrationCurveBuilder.d.ts +2 -0
- package/dist/templates/calibrationNormalizers.d.ts +5 -0
- package/dist/templates/componentBindingCatalog.d.ts +25 -0
- package/dist/templates/componentBindingHelpers.d.ts +17 -0
- package/dist/templates/componentDoseResponseProps.d.ts +3 -0
- package/dist/templates/componentGenericProps.d.ts +8 -0
- package/dist/templates/componentPlateHelpers.d.ts +5 -0
- package/dist/templates/componentPlateMapProps.d.ts +3 -0
- package/dist/templates/componentPropsFactory.d.ts +3 -0
- package/dist/templates/componentQpcrPlateProps.d.ts +3 -0
- package/dist/templates/componentTargetResolvers.d.ts +5 -0
- package/dist/templates/componentTemplateProps.d.ts +3 -0
- package/dist/templates/controlSchemaClone.d.ts +4 -0
- package/dist/templates/controlSchemaConstants.d.ts +10 -0
- package/dist/templates/controlSchemaMerge.d.ts +3 -0
- package/dist/templates/controlSchemaTargets.d.ts +17 -0
- package/dist/templates/controlSchemaTypes.d.ts +4 -0
- package/dist/templates/controlSchemas.d.ts +4 -4
- package/dist/templates/defaultBioTemplateBuilder.d.ts +2 -0
- package/dist/templates/doseResponseAdapters.d.ts +4 -0
- package/dist/templates/doseResponseBuilder.d.ts +2 -0
- package/dist/templates/elisaAssayCollectionBuilder.d.ts +2 -0
- package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +2 -0
- package/dist/templates/flowCytometryPanelBuilder.d.ts +2 -0
- package/dist/templates/flowNormalizers.d.ts +8 -0
- package/dist/templates/flowPanelAdapters.d.ts +4 -0
- package/dist/templates/index.d.ts +2 -2
- package/dist/templates/index.js +2 -2
- package/dist/templates/instrumentRunAdapterHelpers.d.ts +8 -0
- package/dist/templates/instrumentRunAdapters.d.ts +8 -0
- package/dist/templates/instrumentRunBuilder.d.ts +2 -0
- package/dist/templates/lcmsBatchCollectionBuilder.d.ts +2 -0
- package/dist/templates/plateGeometry.d.ts +4 -0
- package/dist/templates/plateMapAdapters.d.ts +3 -0
- package/dist/templates/plateMapBuilder.d.ts +2 -0
- package/dist/templates/presetControlSchemas.d.ts +534 -0
- package/dist/templates/presets.d.ts +12 -0
- package/dist/templates/protocolAdapters.d.ts +5 -0
- package/dist/templates/protocolNormalizers.d.ts +6 -0
- package/dist/templates/protocolStepsBuilder.d.ts +2 -0
- package/dist/templates/qpcrAdapters.d.ts +5 -0
- package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +2 -0
- package/dist/templates/qpcrPlateBuilder.d.ts +2 -0
- package/dist/templates/reagentAdapters.d.ts +5 -0
- package/dist/templates/reagentListBuilder.d.ts +2 -0
- package/dist/templates/runNormalizers.d.ts +10 -0
- package/dist/templates/sampleNormalizers.d.ts +4 -0
- package/dist/templates/samplePrepAdapters.d.ts +4 -0
- package/dist/templates/samplePrepBuilder.d.ts +2 -0
- package/dist/templates/sampleSheetAdapters.d.ts +5 -0
- package/dist/templates/sampleSheetBuilder.d.ts +2 -0
- package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +2 -0
- package/dist/templates/targetedMetabolomicsHelpers.d.ts +5 -0
- package/dist/templates/templateControlSchemas.d.ts +400 -0
- package/dist/templates/templateEnvelopes.d.ts +9 -0
- package/dist/templates/templatePackCollectionBuilder.d.ts +2 -0
- package/dist/templates/templatePresetCollectionBuilder.d.ts +18 -0
- package/dist/templates/templateValidators.d.ts +13 -0
- package/dist/templates/timeCourseAdapters.d.ts +5 -0
- package/dist/templates/timeCourseBuilder.d.ts +2 -0
- package/dist/templates/types.d.ts +16 -1
- package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
- package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
- package/dist/{templates-BSlxwV2c.js → templates-bUAWMn5L.js} +3610 -3154
- package/dist/templates-bUAWMn5L.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-QZtHFFH2.js} +534 -359
- package/dist/useProtocolTemplates-QZtHFFH2.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/controlComponentBindings.ts +80 -0
- package/src/composables/controlSchemaAdapters.ts +196 -0
- package/src/composables/controlSchemaFormFields.ts +61 -0
- package/src/composables/controlSchemaLayout.ts +59 -0
- package/src/composables/controlSchemaNormalize.ts +101 -0
- package/src/composables/controlSchemaUtils.ts +36 -0
- package/src/composables/controlWorkspaceOptions.ts +21 -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 +62 -618
- package/src/composables/useControlWorkspace.ts +207 -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/adapters.ts +89 -930
- package/src/templates/assayLookups.ts +33 -0
- package/src/templates/assayMatrixAdapters.ts +78 -0
- package/src/templates/assayMatrixBuilder.ts +59 -0
- package/src/templates/assayNormalizers.ts +34 -0
- package/src/templates/builderDefaults.ts +11 -0
- package/src/templates/builderIdUtils.ts +20 -0
- package/src/templates/builderPresetControls.ts +165 -0
- package/src/templates/builderReadUtils.ts +57 -0
- package/src/templates/builders.ts +122 -2149
- package/src/templates/calibrationCurveAdapters.ts +59 -0
- package/src/templates/calibrationCurveBuilder.ts +99 -0
- package/src/templates/calibrationNormalizers.ts +60 -0
- package/src/templates/componentBindingCatalog.ts +90 -0
- package/src/templates/componentBindingHelpers.ts +93 -0
- package/src/templates/componentBindings.ts +12 -461
- package/src/templates/componentDoseResponseProps.ts +42 -0
- package/src/templates/componentGenericProps.ts +77 -0
- package/src/templates/componentPlateHelpers.ts +29 -0
- package/src/templates/componentPlateMapProps.ts +32 -0
- package/src/templates/componentPropsFactory.ts +21 -0
- package/src/templates/componentQpcrPlateProps.ts +28 -0
- package/src/templates/componentTargetResolvers.ts +69 -0
- package/src/templates/componentTemplateProps.ts +78 -0
- package/src/templates/controlSchemaClone.ts +32 -0
- package/src/templates/controlSchemaConstants.ts +11 -0
- package/src/templates/controlSchemaMerge.ts +40 -0
- package/src/templates/controlSchemaTargets.ts +87 -0
- package/src/templates/controlSchemaTypes.ts +20 -0
- package/src/templates/controlSchemas.ts +22 -636
- package/src/templates/defaultBioTemplateBuilder.ts +124 -0
- package/src/templates/doseResponseAdapters.ts +45 -0
- package/src/templates/doseResponseBuilder.ts +44 -0
- package/src/templates/elisaAssayCollectionBuilder.ts +62 -0
- package/src/templates/flowCytometryAssayCollectionBuilder.ts +41 -0
- package/src/templates/flowCytometryPanelBuilder.ts +53 -0
- package/src/templates/flowNormalizers.ts +58 -0
- package/src/templates/flowPanelAdapters.ts +58 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/instrumentRunAdapterHelpers.ts +94 -0
- package/src/templates/instrumentRunAdapters.ts +163 -0
- package/src/templates/instrumentRunBuilder.ts +97 -0
- package/src/templates/lcmsBatchCollectionBuilder.ts +38 -0
- package/src/templates/plateGeometry.ts +62 -0
- package/src/templates/plateMapAdapters.ts +36 -0
- package/src/templates/plateMapBuilder.ts +43 -0
- package/src/templates/presetControlSchemas.ts +258 -0
- package/src/templates/presets.ts +23 -0
- package/src/templates/protocolAdapters.ts +69 -0
- package/src/templates/protocolNormalizers.ts +37 -0
- package/src/templates/protocolStepsBuilder.ts +36 -0
- package/src/templates/qpcrAdapters.ts +104 -0
- package/src/templates/qpcrExpressionCollectionBuilder.ts +33 -0
- package/src/templates/qpcrPlateBuilder.ts +96 -0
- package/src/templates/reagentAdapters.ts +77 -0
- package/src/templates/reagentListBuilder.ts +30 -0
- package/src/templates/runNormalizers.ts +63 -0
- package/src/templates/sampleNormalizers.ts +58 -0
- package/src/templates/samplePrepAdapters.ts +63 -0
- package/src/templates/samplePrepBuilder.ts +51 -0
- package/src/templates/sampleSheetAdapters.ts +75 -0
- package/src/templates/sampleSheetBuilder.ts +23 -0
- package/src/templates/targetedMetabolomicsCollectionBuilder.ts +79 -0
- package/src/templates/targetedMetabolomicsHelpers.ts +102 -0
- package/src/templates/templateControlSchemas.ts +320 -0
- package/src/templates/templateEnvelopes.ts +137 -0
- package/src/templates/templatePackCollectionBuilder.ts +23 -0
- package/src/templates/templatePresetCollectionBuilder.ts +139 -0
- package/src/templates/templateValidators.ts +414 -0
- package/src/templates/timeCourseAdapters.ts +73 -0
- package/src/templates/timeCourseBuilder.ts +64 -0
- package/src/templates/types.ts +17 -0
- package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
- package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -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
|
/** Interactive 96/384-well plate grid with drag-to-select, heatmap overlays, per-well editing, and sample-color mapping. */
|
|
3
3
|
import { ref, computed } from 'vue'
|
|
4
|
-
import type { WellPlateFormat, WellPlateSelectionMode, WellPlateSize, Well, HeatmapConfig, WellShape, WellEditField, WellEditData, WellLegendItem, ColumnCondition, RowCondition } from '../types'
|
|
4
|
+
import type { WellPlateFormat, WellPlateSelectionMode, WellPlateSize, Well, HeatmapConfig, WellShape, WellEditField, WellEditData, WellLegendItem, ColumnCondition, RowCondition, WellSampleDropData, WellSampleDropParser } from '../types'
|
|
5
5
|
import { useEventListener } from '../composables/useEventListener'
|
|
6
6
|
import WellEditPopupInternal from './internal/WellEditPopupInternal.vue'
|
|
7
7
|
|
|
@@ -29,6 +29,8 @@ interface Props {
|
|
|
29
29
|
legendItems?: WellLegendItem[]
|
|
30
30
|
columnConditions?: ColumnCondition[]
|
|
31
31
|
rowConditions?: RowCondition[]
|
|
32
|
+
allowSampleDrop?: boolean
|
|
33
|
+
sampleDropParser?: WellSampleDropParser
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
// Drag state for moving wells
|
|
@@ -59,6 +61,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
59
61
|
legendItems: undefined,
|
|
60
62
|
columnConditions: () => [],
|
|
61
63
|
rowConditions: () => [],
|
|
64
|
+
allowSampleDrop: false,
|
|
65
|
+
sampleDropParser: undefined,
|
|
62
66
|
})
|
|
63
67
|
|
|
64
68
|
const emit = defineEmits<{
|
|
@@ -70,6 +74,22 @@ const emit = defineEmits<{
|
|
|
70
74
|
'well-move': [sourceWellId: string, targetWellId: string]
|
|
71
75
|
'well-edit': [wellId: string, data: WellEditData]
|
|
72
76
|
'well-clear': [wellId: string]
|
|
77
|
+
'sample-drop': [wellId: string, data: WellSampleDropData, event: DragEvent]
|
|
78
|
+
}>()
|
|
79
|
+
|
|
80
|
+
interface WellEditorSlotProps {
|
|
81
|
+
wellId: string
|
|
82
|
+
wellData?: Partial<Well>
|
|
83
|
+
editFields: WellEditField[]
|
|
84
|
+
defaultInjectionVolume: number
|
|
85
|
+
position: { x: number; y: number }
|
|
86
|
+
save: (data: WellEditData) => void
|
|
87
|
+
clear: () => void
|
|
88
|
+
close: () => void
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
defineSlots<{
|
|
92
|
+
'well-editor'?: (props: WellEditorSlotProps) => unknown
|
|
73
93
|
}>()
|
|
74
94
|
|
|
75
95
|
const plateRef = ref<HTMLElement | null>(null)
|
|
@@ -164,6 +184,7 @@ const defaultLegendItems: WellLegendItem[] = [
|
|
|
164
184
|
{ type: 'sample', label: 'Sample', color: '#10b981' },
|
|
165
185
|
{ type: 'blank', label: 'Blank', color: '#f97316' },
|
|
166
186
|
{ type: 'qc', label: 'QC', color: '#8b5cf6' },
|
|
187
|
+
{ type: 'iqc', label: 'IQC', color: '#ec4899' },
|
|
167
188
|
]
|
|
168
189
|
|
|
169
190
|
const activeLegendItems = computed(() => props.legendItems ?? defaultLegendItems)
|
|
@@ -285,6 +306,7 @@ const defaultSampleTypeColors: Record<string, { bg: string; border: string }> =
|
|
|
285
306
|
control: { bg: 'rgba(59, 130, 246, 0.15)', border: 'rgba(59, 130, 246, 0.4)' },
|
|
286
307
|
blank: { bg: 'rgba(249, 115, 22, 0.15)', border: 'rgba(249, 115, 22, 0.4)' },
|
|
287
308
|
qc: { bg: 'rgba(139, 92, 246, 0.15)', border: 'rgba(139, 92, 246, 0.4)' },
|
|
309
|
+
iqc: { bg: 'rgba(236, 72, 153, 0.15)', border: 'rgba(236, 72, 153, 0.4)' },
|
|
288
310
|
}
|
|
289
311
|
|
|
290
312
|
const heatmapColors: Record<string, string[]> = {
|
|
@@ -373,6 +395,7 @@ function getSampleTypeIndicator(well: Well): string | null {
|
|
|
373
395
|
control: 'C',
|
|
374
396
|
blank: 'B',
|
|
375
397
|
qc: 'Q',
|
|
398
|
+
iqc: 'I',
|
|
376
399
|
}
|
|
377
400
|
return typeMap[well.sampleType] || well.sampleType.charAt(0).toUpperCase()
|
|
378
401
|
}
|
|
@@ -511,10 +534,12 @@ function handleDragStart(well: Well, event: DragEvent) {
|
|
|
511
534
|
}
|
|
512
535
|
|
|
513
536
|
function handleDragOver(well: Well, event: DragEvent) {
|
|
514
|
-
|
|
537
|
+
const isMovingWell = props.selectionMode === 'drag' && !!dragSourceWell.value
|
|
538
|
+
if (!isMovingWell && !props.allowSampleDrop) return
|
|
539
|
+
|
|
515
540
|
event.preventDefault()
|
|
516
541
|
if (event.dataTransfer) {
|
|
517
|
-
event.dataTransfer.dropEffect = 'move'
|
|
542
|
+
event.dataTransfer.dropEffect = isMovingWell ? 'move' : 'copy'
|
|
518
543
|
}
|
|
519
544
|
dragTargetWell.value = well.id
|
|
520
545
|
}
|
|
@@ -524,14 +549,27 @@ function handleDragLeave() {
|
|
|
524
549
|
}
|
|
525
550
|
|
|
526
551
|
function handleDrop(well: Well, event: DragEvent) {
|
|
552
|
+
const isMovingWell = props.selectionMode === 'drag' && !!dragSourceWell.value
|
|
553
|
+
if (!isMovingWell && !props.allowSampleDrop) return
|
|
554
|
+
|
|
527
555
|
event.preventDefault()
|
|
528
|
-
if (props.selectionMode !== 'drag' || !dragSourceWell.value) return
|
|
529
556
|
|
|
530
|
-
|
|
531
|
-
|
|
557
|
+
if (isMovingWell && dragSourceWell.value) {
|
|
558
|
+
const sourceId = dragSourceWell.value
|
|
559
|
+
const targetId = well.id
|
|
560
|
+
|
|
561
|
+
if (sourceId !== targetId) {
|
|
562
|
+
emit('well-move', sourceId, targetId)
|
|
563
|
+
}
|
|
532
564
|
|
|
533
|
-
|
|
534
|
-
|
|
565
|
+
dragSourceWell.value = null
|
|
566
|
+
dragTargetWell.value = null
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const droppedSample = parseDroppedSample(event, well.id)
|
|
571
|
+
if (droppedSample) {
|
|
572
|
+
emit('sample-drop', well.id, droppedSample, event)
|
|
535
573
|
}
|
|
536
574
|
|
|
537
575
|
dragSourceWell.value = null
|
|
@@ -543,6 +581,76 @@ function handleDragEnd() {
|
|
|
543
581
|
dragTargetWell.value = null
|
|
544
582
|
}
|
|
545
583
|
|
|
584
|
+
function parseDroppedSample(event: DragEvent, wellId: string): WellSampleDropData | null {
|
|
585
|
+
if (props.sampleDropParser) {
|
|
586
|
+
return props.sampleDropParser(event, { wellId, event }) ?? null
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const transfer = event.dataTransfer
|
|
590
|
+
if (!transfer) return null
|
|
591
|
+
|
|
592
|
+
const json = transfer.getData('application/json') || transfer.getData('text/json')
|
|
593
|
+
if (json) {
|
|
594
|
+
try {
|
|
595
|
+
return normalizeDroppedSample(JSON.parse(json))
|
|
596
|
+
} catch {
|
|
597
|
+
return null
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const text = transfer.getData('text/plain')?.trim()
|
|
602
|
+
if (!text) return null
|
|
603
|
+
return {
|
|
604
|
+
sampleName: text,
|
|
605
|
+
label: text,
|
|
606
|
+
raw: text,
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function normalizeDroppedSample(raw: unknown): WellSampleDropData | null {
|
|
611
|
+
if (!raw || typeof raw !== 'object') return null
|
|
612
|
+
|
|
613
|
+
const record = raw as Record<string, unknown>
|
|
614
|
+
const sampleName =
|
|
615
|
+
getString(record.sampleName) ??
|
|
616
|
+
getString(record.sample_name) ??
|
|
617
|
+
getString(record.name) ??
|
|
618
|
+
getString(record.label)
|
|
619
|
+
const label = getString(record.label) ?? sampleName
|
|
620
|
+
const sampleType =
|
|
621
|
+
getString(record.sampleType) ??
|
|
622
|
+
getString(record.sample_type) ??
|
|
623
|
+
(record.type === 'sample' ? 'sample' : undefined)
|
|
624
|
+
|
|
625
|
+
if (!sampleName && !label && !getString(record.id)) return null
|
|
626
|
+
|
|
627
|
+
return {
|
|
628
|
+
id: getString(record.id),
|
|
629
|
+
sampleName,
|
|
630
|
+
label,
|
|
631
|
+
sampleType,
|
|
632
|
+
injectionVolume: getNumber(record.injectionVolume ?? record.injection_volume),
|
|
633
|
+
injectionCount: getNumber(record.injectionCount ?? record.injection_count),
|
|
634
|
+
customMethod: getString(record.customMethod ?? record.custom_method) ?? null,
|
|
635
|
+
metadata: getRecord(record.metadata),
|
|
636
|
+
raw,
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function getString(value: unknown): string | undefined {
|
|
641
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function getNumber(value: unknown): number | undefined {
|
|
645
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function getRecord(value: unknown): Record<string, unknown> | undefined {
|
|
649
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
650
|
+
? value as Record<string, unknown>
|
|
651
|
+
: undefined
|
|
652
|
+
}
|
|
653
|
+
|
|
546
654
|
function handleKeyDown(event: KeyboardEvent) {
|
|
547
655
|
if (props.disabled || props.readonly) return
|
|
548
656
|
|
|
@@ -726,9 +834,9 @@ const tableStyle = computed(() => ({
|
|
|
726
834
|
@mouseleave="handleWellMouseLeave"
|
|
727
835
|
@contextmenu="handleContextMenu(well, $event)"
|
|
728
836
|
@dragstart="handleDragStart(well, $event)"
|
|
729
|
-
@dragover
|
|
837
|
+
@dragover="handleDragOver(well, $event)"
|
|
730
838
|
@dragleave="handleDragLeave"
|
|
731
|
-
@drop
|
|
839
|
+
@drop="handleDrop(well, $event)"
|
|
732
840
|
@dragend="handleDragEnd"
|
|
733
841
|
@keydown.enter="handleWellClick(well, $event as unknown as MouseEvent)"
|
|
734
842
|
@keydown.space.prevent="handleWellClick(well, $event as unknown as MouseEvent)"
|
|
@@ -805,17 +913,30 @@ const tableStyle = computed(() => ({
|
|
|
805
913
|
</div>
|
|
806
914
|
|
|
807
915
|
<!-- Edit popup -->
|
|
808
|
-
<
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
916
|
+
<template v-if="editable && editingWellId">
|
|
917
|
+
<slot
|
|
918
|
+
name="well-editor"
|
|
919
|
+
:well-id="editingWellId"
|
|
920
|
+
:well-data="wells[editingWellId]"
|
|
921
|
+
:edit-fields="editFields"
|
|
922
|
+
:default-injection-volume="defaultInjectionVolume"
|
|
923
|
+
:position="editPopupPosition"
|
|
924
|
+
:save="handleEditSave"
|
|
925
|
+
:clear="handleEditClear"
|
|
926
|
+
:close="handleEditClose"
|
|
927
|
+
>
|
|
928
|
+
<WellEditPopupInternal
|
|
929
|
+
:well-id="editingWellId"
|
|
930
|
+
:well-data="wells[editingWellId]"
|
|
931
|
+
:edit-fields="editFields"
|
|
932
|
+
:default-injection-volume="defaultInjectionVolume"
|
|
933
|
+
:position="editPopupPosition"
|
|
934
|
+
@save="handleEditSave"
|
|
935
|
+
@clear="handleEditClear"
|
|
936
|
+
@close="handleEditClose"
|
|
937
|
+
/>
|
|
938
|
+
</slot>
|
|
939
|
+
</template>
|
|
819
940
|
</div>
|
|
820
941
|
</template>
|
|
821
942
|
|
package/src/components/index.ts
CHANGED
|
@@ -89,6 +89,11 @@ export { default as ProtocolStepEditor } from './ProtocolStepEditor.vue'
|
|
|
89
89
|
// Scientific display components
|
|
90
90
|
export { default as ScientificNumber } from './ScientificNumber.vue'
|
|
91
91
|
export { default as ChemicalFormula } from './ChemicalFormula.vue'
|
|
92
|
+
export { default as SequenceProgressBar } from './SequenceProgressBar.vue'
|
|
93
|
+
export { default as InstrumentAlertLog } from './InstrumentAlertLog.vue'
|
|
94
|
+
export { default as InstrumentStateBadge } from './InstrumentStateBadge.vue'
|
|
95
|
+
export { default as InstrumentStatusCard } from './InstrumentStatusCard.vue'
|
|
96
|
+
export { default as LcmsSequenceTable } from './LcmsSequenceTable.vue'
|
|
92
97
|
|
|
93
98
|
// Scientific input components
|
|
94
99
|
export { default as FormulaInput } from './FormulaInput.vue'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ControlComponentBinding,
|
|
3
|
+
ControlComponentBindingConfig,
|
|
4
|
+
ControlComponentBindingRecordConfig,
|
|
5
|
+
ControlComponentBindingsById,
|
|
6
|
+
ControlComponentBindingsConfig,
|
|
7
|
+
ControlComponentPropsMap,
|
|
8
|
+
ControlComponentPropSource,
|
|
9
|
+
} from './useControlSchema'
|
|
10
|
+
|
|
11
|
+
/** Map control workspace values into component props for direct `v-bind` usage. */
|
|
12
|
+
export function controlValuesToComponentProps<TValues extends Record<string, unknown>>(
|
|
13
|
+
values: TValues,
|
|
14
|
+
mapping?: ControlComponentPropsMap<TValues>,
|
|
15
|
+
): Record<string, unknown> {
|
|
16
|
+
if (mapping === undefined) return { ...values }
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(mapping)) {
|
|
19
|
+
const props: Record<string, unknown> = {}
|
|
20
|
+
for (const key of mapping) {
|
|
21
|
+
props[key] = values[key]
|
|
22
|
+
}
|
|
23
|
+
return props
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const props: Record<string, unknown> = {}
|
|
27
|
+
for (const [prop, source] of Object.entries(mapping) as Array<[string, ControlComponentPropSource<TValues>]>) {
|
|
28
|
+
props[prop] = typeof source === 'function' ? source(values) : values[source]
|
|
29
|
+
}
|
|
30
|
+
return props
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Map control workspace values into named SDK component bindings for direct slot rendering. */
|
|
34
|
+
export function controlValuesToComponentBindings<TValues extends Record<string, unknown>>(
|
|
35
|
+
values: TValues,
|
|
36
|
+
bindings?: ControlComponentBindingsConfig<TValues>,
|
|
37
|
+
): ControlComponentBinding[] {
|
|
38
|
+
if (bindings === undefined) return []
|
|
39
|
+
|
|
40
|
+
return normalizeControlComponentBindingConfigs(bindings).map(binding => ({
|
|
41
|
+
id: binding.id,
|
|
42
|
+
component: binding.component,
|
|
43
|
+
props: controlValuesToComponentProps(values, binding.props),
|
|
44
|
+
}))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Map control workspace values into SDK component bindings keyed by binding id. */
|
|
48
|
+
export function controlValuesToComponentBindingsById<TValues extends Record<string, unknown>>(
|
|
49
|
+
values: TValues,
|
|
50
|
+
bindings?: ControlComponentBindingsConfig<TValues>,
|
|
51
|
+
): ControlComponentBindingsById {
|
|
52
|
+
return Object.fromEntries(
|
|
53
|
+
controlValuesToComponentBindings(values, bindings).map(binding => [binding.id, binding]),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeControlComponentBindingConfigs<TValues extends Record<string, unknown>>(
|
|
58
|
+
bindings: ControlComponentBindingsConfig<TValues>,
|
|
59
|
+
): Array<Required<Pick<ControlComponentBindingConfig<TValues>, 'id' | 'component'>> & Pick<ControlComponentBindingConfig<TValues>, 'props'>> {
|
|
60
|
+
if (Array.isArray(bindings)) {
|
|
61
|
+
const usedIds = new Map<string, number>()
|
|
62
|
+
return bindings.map(binding => ({
|
|
63
|
+
id: uniqueComponentBindingId(binding.id ?? binding.component, usedIds),
|
|
64
|
+
component: binding.component,
|
|
65
|
+
props: binding.props,
|
|
66
|
+
}))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return Object.entries(bindings).map(([id, binding]: [string, ControlComponentBindingRecordConfig<TValues>]) => ({
|
|
70
|
+
id,
|
|
71
|
+
component: binding.component,
|
|
72
|
+
props: binding.props,
|
|
73
|
+
}))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function uniqueComponentBindingId(id: string, usedIds: Map<string, number>): string {
|
|
77
|
+
const count = usedIds.get(id) ?? 0
|
|
78
|
+
usedIds.set(id, count + 1)
|
|
79
|
+
return count === 0 ? id : `${id}-${count + 1}`
|
|
80
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PillNavItem,
|
|
3
|
+
SidebarToolSection,
|
|
4
|
+
SettingsModalSchema,
|
|
5
|
+
TopBarSettingsConfig,
|
|
6
|
+
} from '../types'
|
|
7
|
+
import type {
|
|
8
|
+
FormSectionSchema,
|
|
9
|
+
} from '../types/form-builder'
|
|
10
|
+
import {
|
|
11
|
+
controlToFormField,
|
|
12
|
+
} from './controlSchemaFormFields'
|
|
13
|
+
import {
|
|
14
|
+
controlViewItem,
|
|
15
|
+
firstSidebarConfig,
|
|
16
|
+
isSidebarEnabled,
|
|
17
|
+
sectionConfig,
|
|
18
|
+
} from './controlSchemaLayout'
|
|
19
|
+
import {
|
|
20
|
+
normalizeControls,
|
|
21
|
+
} from './controlSchemaNormalize'
|
|
22
|
+
import {
|
|
23
|
+
humanize,
|
|
24
|
+
orderedUnique,
|
|
25
|
+
} from './controlSchemaUtils'
|
|
26
|
+
import type {
|
|
27
|
+
ControlFormSchema,
|
|
28
|
+
ControlSchema,
|
|
29
|
+
ControlSchemaOptions,
|
|
30
|
+
} from './useControlSchema'
|
|
31
|
+
|
|
32
|
+
/** Convert a compact control schema into a FormBuilder schema. */
|
|
33
|
+
export function controlsToFormSchema(
|
|
34
|
+
controls: ControlSchema,
|
|
35
|
+
options: ControlSchemaOptions = {},
|
|
36
|
+
): ControlFormSchema {
|
|
37
|
+
const normalized = normalizeControls(controls, options)
|
|
38
|
+
const sectionIds = orderedUnique(normalized.map(control => control.sectionId))
|
|
39
|
+
const sections = sectionIds.map((sectionId): FormSectionSchema => {
|
|
40
|
+
const sectionControls = normalized.filter(control => control.sectionId === sectionId)
|
|
41
|
+
const config = sectionConfig(sectionId, sectionControls, options)
|
|
42
|
+
return {
|
|
43
|
+
id: sectionId,
|
|
44
|
+
title: config.title ?? config.label ?? humanize(sectionId),
|
|
45
|
+
description: config.description,
|
|
46
|
+
columns: config.columns ?? options.columns ?? 1,
|
|
47
|
+
defaultOpen: config.defaultOpen,
|
|
48
|
+
condition: config.condition,
|
|
49
|
+
fields: sectionControls.map(controlToFormField),
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
sections,
|
|
55
|
+
submitLabel: options.submitLabel,
|
|
56
|
+
cancelLabel: options.cancelLabel,
|
|
57
|
+
showCancel: options.showCancel,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Convert controls into AppSidebar panels grouped by view and section. */
|
|
62
|
+
export function controlsToSidebarPanels(
|
|
63
|
+
controls: ControlSchema,
|
|
64
|
+
options: ControlSchemaOptions = {},
|
|
65
|
+
): Record<string, SidebarToolSection[]> {
|
|
66
|
+
const normalized = normalizeControls(controls, options)
|
|
67
|
+
.filter(control => isSidebarEnabled(control.definition.sidebar))
|
|
68
|
+
const viewIds = orderedUnique(normalized.map(control => control.viewId))
|
|
69
|
+
const panels: Record<string, SidebarToolSection[]> = {}
|
|
70
|
+
|
|
71
|
+
for (const viewId of viewIds) {
|
|
72
|
+
const viewControls = normalized.filter(control => control.viewId === viewId)
|
|
73
|
+
const sectionIds = orderedUnique(viewControls.map(control => control.sectionId))
|
|
74
|
+
panels[viewId] = sectionIds.map((sectionId): SidebarToolSection => {
|
|
75
|
+
const sectionControls = viewControls.filter(control => control.sectionId === sectionId)
|
|
76
|
+
const config = sectionConfig(sectionId, sectionControls, options)
|
|
77
|
+
const sidebarConfig = firstSidebarConfig(sectionControls)
|
|
78
|
+
return {
|
|
79
|
+
id: sectionId,
|
|
80
|
+
label: sidebarConfig?.label ?? config.label ?? config.title ?? humanize(sectionId),
|
|
81
|
+
subtitle: sidebarConfig?.subtitle ?? config.subtitle,
|
|
82
|
+
icon: sidebarConfig?.icon ?? config.icon,
|
|
83
|
+
iconColor: sidebarConfig?.iconColor ?? config.iconColor,
|
|
84
|
+
iconBg: sidebarConfig?.iconBg ?? config.iconBg,
|
|
85
|
+
defaultOpen: sidebarConfig?.defaultOpen ?? config.defaultOpen,
|
|
86
|
+
showToggle: sidebarConfig?.showToggle ?? config.showToggle,
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return panels
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Convert controls into a SettingsModal schema grouped by the same sections. */
|
|
95
|
+
export function controlsToSettingsSchema(
|
|
96
|
+
controls: ControlSchema,
|
|
97
|
+
options: ControlSchemaOptions = {},
|
|
98
|
+
): SettingsModalSchema {
|
|
99
|
+
const normalized = normalizeControls(controls, options)
|
|
100
|
+
const sectionIds = orderedUnique(normalized.map(control => control.sectionId))
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
groups: sectionIds.map((sectionId) => {
|
|
104
|
+
const sectionControls = normalized.filter(control => control.sectionId === sectionId)
|
|
105
|
+
const config = sectionConfig(sectionId, sectionControls, options)
|
|
106
|
+
return {
|
|
107
|
+
id: sectionId,
|
|
108
|
+
label: config.label ?? config.title ?? humanize(sectionId),
|
|
109
|
+
description: config.description ?? config.subtitle,
|
|
110
|
+
icon: typeof config.icon === 'string' ? config.icon : undefined,
|
|
111
|
+
fields: sectionControls.map(controlToFormField),
|
|
112
|
+
columns: config.columns ?? options.columns ?? 1,
|
|
113
|
+
condition: config.condition,
|
|
114
|
+
access: config.access,
|
|
115
|
+
visibleFor: config.visibleFor,
|
|
116
|
+
requiresAdmin: config.requiresAdmin,
|
|
117
|
+
permissions: config.permissions,
|
|
118
|
+
anyPermissions: config.anyPermissions,
|
|
119
|
+
}
|
|
120
|
+
}),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Convert controls into an AppTopBar settingsConfig object that passes compact controls through to SettingsModal. */
|
|
125
|
+
export function controlsToTopBarSettingsConfig(
|
|
126
|
+
controls: ControlSchema,
|
|
127
|
+
options: ControlSchemaOptions = {},
|
|
128
|
+
config: Omit<TopBarSettingsConfig, 'schema' | 'controls' | 'controlOptions'> = {},
|
|
129
|
+
): TopBarSettingsConfig {
|
|
130
|
+
return {
|
|
131
|
+
...config,
|
|
132
|
+
controls,
|
|
133
|
+
controlOptions: options,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Return generated control view IDs that have at least one sidebar panel. */
|
|
138
|
+
export function controlsToViewIds(
|
|
139
|
+
controls: ControlSchema,
|
|
140
|
+
options: ControlSchemaOptions = {},
|
|
141
|
+
): string[] {
|
|
142
|
+
return controlsToViewItems(controls, options).map(item => item.id)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Return AppTopBar pillNav-compatible view items for switching generated control sidebars. */
|
|
146
|
+
export function controlsToViewItems(
|
|
147
|
+
controls: ControlSchema,
|
|
148
|
+
options: ControlSchemaOptions = {},
|
|
149
|
+
): PillNavItem[] {
|
|
150
|
+
return Object.entries(controlsToSidebarPanels(controls, options))
|
|
151
|
+
.filter(([, sections]) => sections.length > 0)
|
|
152
|
+
.map(([id]) => controlViewItem(id, options))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Return the first generated sidebar view ID, or an empty string when controls render no sidebar panels. */
|
|
156
|
+
export function getDefaultControlView(
|
|
157
|
+
controls: ControlSchema,
|
|
158
|
+
options: ControlSchemaOptions = {},
|
|
159
|
+
): string {
|
|
160
|
+
return controlsToViewIds(controls, options)[0] ?? ''
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Return a headerless single-section FormBuilder schema for rendering inside an AppSidebar section slot. */
|
|
164
|
+
export function controlsToSectionFormSchema(
|
|
165
|
+
controls: ControlSchema,
|
|
166
|
+
sectionId: string,
|
|
167
|
+
options: ControlSchemaOptions = {},
|
|
168
|
+
): ControlFormSchema {
|
|
169
|
+
const schema = controlsToFormSchema(controls, options)
|
|
170
|
+
return {
|
|
171
|
+
sections: schema.sections
|
|
172
|
+
.filter(section => section.id === sectionId)
|
|
173
|
+
.map(section => ({ ...section, title: '', description: undefined })),
|
|
174
|
+
submitLabel: schema.submitLabel,
|
|
175
|
+
cancelLabel: schema.cancelLabel,
|
|
176
|
+
showCancel: schema.showCancel,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Return headerless FormBuilder schemas keyed by section ID for AppSidebar auto-rendering. */
|
|
181
|
+
export function controlsToSectionFormSchemas(
|
|
182
|
+
controls: ControlSchema,
|
|
183
|
+
options: ControlSchemaOptions = {},
|
|
184
|
+
): Record<string, ControlFormSchema> {
|
|
185
|
+
const schema = controlsToFormSchema(controls, options)
|
|
186
|
+
const schemas: Record<string, ControlFormSchema> = {}
|
|
187
|
+
for (const section of schema.sections) {
|
|
188
|
+
schemas[section.id] = {
|
|
189
|
+
sections: [{ ...section, title: '', description: undefined }],
|
|
190
|
+
submitLabel: schema.submitLabel,
|
|
191
|
+
cancelLabel: schema.cancelLabel,
|
|
192
|
+
showCancel: schema.showCancel,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return schemas
|
|
196
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FieldValidation,
|
|
3
|
+
FormFieldSchema,
|
|
4
|
+
} from '../types/form-builder'
|
|
5
|
+
import {
|
|
6
|
+
defaultValueForControl,
|
|
7
|
+
normalizeOption,
|
|
8
|
+
type NormalizedControl,
|
|
9
|
+
} from './controlSchemaNormalize'
|
|
10
|
+
import {
|
|
11
|
+
humanize,
|
|
12
|
+
numericValue,
|
|
13
|
+
} from './controlSchemaUtils'
|
|
14
|
+
import type {
|
|
15
|
+
ControlDefinition,
|
|
16
|
+
} from './useControlSchema'
|
|
17
|
+
|
|
18
|
+
export function controlToFormField(control: NormalizedControl): FormFieldSchema {
|
|
19
|
+
const { name, definition, type } = control
|
|
20
|
+
const props = fieldProps(definition)
|
|
21
|
+
return {
|
|
22
|
+
name,
|
|
23
|
+
label: definition.label ?? humanize(name),
|
|
24
|
+
type,
|
|
25
|
+
defaultValue: defaultValueForControl(definition, type),
|
|
26
|
+
placeholder: definition.placeholder,
|
|
27
|
+
hint: definition.hint,
|
|
28
|
+
size: definition.size,
|
|
29
|
+
disabled: definition.disabled,
|
|
30
|
+
readonly: definition.readonly,
|
|
31
|
+
validation: validationForControl(definition),
|
|
32
|
+
condition: definition.condition,
|
|
33
|
+
access: definition.access,
|
|
34
|
+
visibleFor: definition.visibleFor,
|
|
35
|
+
requiresAdmin: definition.requiresAdmin,
|
|
36
|
+
permissions: definition.permissions,
|
|
37
|
+
anyPermissions: definition.anyPermissions,
|
|
38
|
+
colSpan: definition.colSpan,
|
|
39
|
+
props,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fieldProps(definition: ControlDefinition): Record<string, unknown> {
|
|
44
|
+
const props: Record<string, unknown> = {}
|
|
45
|
+
if (definition.min !== undefined) props.min = numericValue(definition.min)
|
|
46
|
+
if (definition.max !== undefined) props.max = numericValue(definition.max)
|
|
47
|
+
if (definition.options) props.options = definition.options.map(normalizeOption)
|
|
48
|
+
return { ...props, ...(definition.props ?? {}) }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validationForControl(definition: ControlDefinition): FieldValidation | undefined {
|
|
52
|
+
const validation: FieldValidation = { ...(definition.validation ?? {}) }
|
|
53
|
+
if (definition.required !== undefined) validation.required = definition.required
|
|
54
|
+
if (definition.min !== undefined) validation.min = definition.min
|
|
55
|
+
if (definition.max !== undefined) validation.max = definition.max
|
|
56
|
+
if (definition.minLength !== undefined) validation.minLength = definition.minLength
|
|
57
|
+
if (definition.maxLength !== undefined) validation.maxLength = definition.maxLength
|
|
58
|
+
if (definition.email !== undefined) validation.email = definition.email
|
|
59
|
+
if (definition.pattern !== undefined) validation.pattern = definition.pattern
|
|
60
|
+
return Object.keys(validation).length > 0 ? validation : undefined
|
|
61
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PillNavItem,
|
|
3
|
+
} from '../types'
|
|
4
|
+
import type {
|
|
5
|
+
NormalizedControl,
|
|
6
|
+
} from './controlSchemaNormalize'
|
|
7
|
+
import {
|
|
8
|
+
humanize,
|
|
9
|
+
} from './controlSchemaUtils'
|
|
10
|
+
import type {
|
|
11
|
+
ControlDefinition,
|
|
12
|
+
ControlSchemaOptions,
|
|
13
|
+
ControlSectionConfig,
|
|
14
|
+
ControlSidebarConfig,
|
|
15
|
+
} from './useControlSchema'
|
|
16
|
+
|
|
17
|
+
export function sectionConfig(
|
|
18
|
+
sectionId: string,
|
|
19
|
+
controls: NormalizedControl[],
|
|
20
|
+
options: ControlSchemaOptions,
|
|
21
|
+
): ControlSectionConfig {
|
|
22
|
+
const configured = options.sections?.[sectionId]
|
|
23
|
+
const first = controls[0]?.definition
|
|
24
|
+
return {
|
|
25
|
+
...configured,
|
|
26
|
+
id: sectionId,
|
|
27
|
+
label: configured?.label ?? first?.sectionLabel,
|
|
28
|
+
title: configured?.title ?? first?.sectionLabel,
|
|
29
|
+
description: configured?.description ?? first?.sectionDescription,
|
|
30
|
+
subtitle: configured?.subtitle ?? first?.sectionSubtitle,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function firstSidebarConfig(controls: NormalizedControl[]): ControlSidebarConfig | undefined {
|
|
35
|
+
for (const control of controls) {
|
|
36
|
+
const sidebar = control.definition.sidebar
|
|
37
|
+
if (sidebar && typeof sidebar === 'object' && sidebar.enabled !== false) return sidebar
|
|
38
|
+
}
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isSidebarEnabled(sidebar: ControlDefinition['sidebar']): boolean {
|
|
43
|
+
if (sidebar === false) return false
|
|
44
|
+
if (sidebar && typeof sidebar === 'object') return sidebar.enabled !== false
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function controlViewItem(viewId: string, options: ControlSchemaOptions): PillNavItem {
|
|
49
|
+
const config = options.views?.[viewId]
|
|
50
|
+
return {
|
|
51
|
+
id: viewId,
|
|
52
|
+
label: config?.label ?? humanize(viewId),
|
|
53
|
+
...(config?.icon !== undefined ? { icon: config.icon } : {}),
|
|
54
|
+
...(config?.to !== undefined ? { to: config.to } : {}),
|
|
55
|
+
...(config?.href !== undefined ? { href: config.href } : {}),
|
|
56
|
+
...(config?.disabled !== undefined ? { disabled: config.disabled } : {}),
|
|
57
|
+
...(config?.children !== undefined ? { children: config.children } : {}),
|
|
58
|
+
}
|
|
59
|
+
}
|