@morscherlab/mint-sdk 1.0.0-rc.4 → 1.0.0-rc.5
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/dist/__tests__/components/AppTopBar.navigation.test.d.ts +1 -0
- package/dist/__tests__/components/DoseCalculatorVolumeField.test.d.ts +1 -0
- package/dist/__tests__/components/PlateMapEditorToolbarInternal.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.controls.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.navigation.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.shell.test.d.ts +1 -0
- package/dist/__tests__/components/ProtocolStep.presentation.test.d.ts +1 -0
- package/dist/__tests__/components/ProtocolStepEditor.state.test.d.ts +1 -0
- package/dist/__tests__/components/ProtocolStepParameterField.test.d.ts +1 -0
- package/dist/__tests__/components/ReagentList.presentation.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.colors.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.drag.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.groups.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.selection.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelectorSampleRow.test.d.ts +1 -0
- package/dist/__tests__/components/ScheduleCalendar.test.d.ts +1 -0
- package/dist/__tests__/components/SettingsModal.schema.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.colors.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.conditions.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.geometry.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.interaction.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.legend.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.rendering.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.sampleDrop.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/classify.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/columns.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/compose.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/cooccurrence.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/fingerprint.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/integration.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/template.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/tokenize.test.d.ts +1 -0
- package/dist/__tests__/composables/useAutoGroupInputSources.test.d.ts +1 -0
- package/dist/__tests__/composables/useScheduleCalendarLayout.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsComponents.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsExports.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsParsing.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsTemplates.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsTheme.test.d.ts +1 -0
- package/dist/components/AppSidebar.vue.d.ts +3 -3
- package/dist/components/AppTopBar.navigation.d.ts +11 -0
- package/dist/components/BaseButton.vue.d.ts +1 -1
- package/dist/components/BaseCheckbox.vue.d.ts +1 -1
- package/dist/components/BaseInput.vue.d.ts +2 -2
- package/dist/components/BasePill.vue.d.ts +1 -1
- package/dist/components/BaseRadioGroup.vue.d.ts +1 -1
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseSlider.vue.d.ts +2 -2
- package/dist/components/BaseTextarea.vue.d.ts +2 -2
- package/dist/components/BaseToggle.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
- package/dist/components/ColorSlider.vue.d.ts +2 -2
- package/dist/components/ConcentrationInput.vue.d.ts +2 -2
- package/dist/components/ControlWorkspaceView.vue.d.ts +3 -3
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +2 -2
- package/dist/components/DoseCalculatorVolumeField.vue.d.ts +15 -0
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +1 -1
- package/dist/components/DropdownButton.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +2 -2
- package/dist/components/FormulaInput.vue.d.ts +2 -2
- package/dist/components/IconButton.vue.d.ts +1 -1
- package/dist/components/LoadingSpinner.vue.d.ts +1 -1
- package/dist/components/MoleculeInput.vue.d.ts +2 -2
- package/dist/components/MultiSelect.vue.d.ts +1 -1
- package/dist/components/NumberInput.vue.d.ts +1 -1
- package/dist/components/PlateMapEditor.vue.d.ts +6 -6
- package/dist/components/PluginWorkspaceView.controls.d.ts +28 -0
- package/dist/components/PluginWorkspaceView.navigation.d.ts +29 -0
- package/dist/components/PluginWorkspaceView.props.d.ts +151 -0
- package/dist/components/PluginWorkspaceView.shell.d.ts +19 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +46 -195
- package/dist/components/ProgressBar.vue.d.ts +1 -1
- package/dist/components/ProtocolStep.presentation.d.ts +4 -0
- package/dist/components/ProtocolStepEditor.state.d.ts +18 -0
- package/dist/components/ProtocolStepParameterField.vue.d.ts +12 -0
- package/dist/components/ReagentList.presentation.d.ts +16 -0
- package/dist/components/ResourceCard.vue.d.ts +1 -1
- package/dist/components/SampleSelector.colors.d.ts +13 -0
- package/dist/components/SampleSelector.drag.d.ts +24 -0
- package/dist/components/SampleSelector.groups.d.ts +15 -0
- package/dist/components/SampleSelector.selection.d.ts +26 -0
- package/dist/components/SampleSelector.vue.d.ts +4 -1
- package/dist/components/SampleSelectorSampleRow.vue.d.ts +21 -0
- package/dist/components/SegmentedControl.vue.d.ts +1 -1
- package/dist/components/SequenceInput.vue.d.ts +2 -2
- package/dist/components/SequenceProgressBar.vue.d.ts +1 -1
- package/dist/components/SettingsModal.schema.d.ts +9 -0
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/components/TagsInput.vue.d.ts +2 -2
- package/dist/components/TimePicker.vue.d.ts +2 -2
- package/dist/components/TimeRangeInput.vue.d.ts +1 -1
- package/dist/components/UnitInput.vue.d.ts +2 -2
- package/dist/components/WellPlate.colors.d.ts +9 -0
- package/dist/components/WellPlate.conditions.d.ts +26 -0
- package/dist/components/WellPlate.geometry.d.ts +23 -0
- package/dist/components/WellPlate.interaction.d.ts +71 -0
- package/dist/components/WellPlate.legend.d.ts +2 -0
- package/dist/components/WellPlate.rendering.d.ts +24 -0
- package/dist/components/WellPlate.sampleDrop.d.ts +8 -0
- package/dist/components/WellPlate.vue.d.ts +1 -1
- package/dist/components/index.js +2 -2
- package/dist/components/internal/ActionItemInternal.vue.d.ts +1 -1
- package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +28 -0
- package/dist/{components-DafPc4rM.js → components-DtHA2bgp.js} +3754 -2991
- package/dist/components-DtHA2bgp.js.map +1 -0
- package/dist/composables/autoGroup/classKey.d.ts +4 -0
- package/dist/composables/autoGroup/classify.d.ts +28 -0
- package/dist/composables/autoGroup/colors.d.ts +2 -0
- package/dist/composables/autoGroup/columns.d.ts +10 -0
- package/dist/composables/autoGroup/compose.d.ts +8 -0
- package/dist/composables/autoGroup/cooccurrence.d.ts +2 -0
- package/dist/composables/autoGroup/csv-shim.d.ts +2 -0
- package/dist/composables/autoGroup/fingerprint.d.ts +3 -0
- package/dist/composables/autoGroup/index.d.ts +16 -0
- package/dist/composables/autoGroup/replicatePreGroup.d.ts +38 -0
- package/dist/composables/autoGroup/template.d.ts +15 -0
- package/dist/composables/autoGroup/tokenize.d.ts +8 -0
- package/dist/composables/autoGroupConstants.d.ts +1 -0
- package/dist/composables/autoGroupGrouping.d.ts +3 -0
- package/dist/composables/controlComponentBindings.d.ts +1 -1
- package/dist/composables/controlSchemaAdapters.d.ts +1 -1
- package/dist/composables/controlSchemaDoseDesign.d.ts +11 -0
- package/dist/composables/controlSchemaLayout.d.ts +1 -1
- package/dist/composables/controlSchemaModel.d.ts +5 -0
- package/dist/composables/controlSchemaNormalize.d.ts +1 -1
- package/dist/composables/controlSchemaTypes.d.ts +305 -0
- package/dist/composables/controlWorkspaceOptions.d.ts +1 -1
- package/dist/composables/formBuilderSchema.d.ts +18 -0
- package/dist/composables/index.js +3 -3
- package/dist/composables/pluginEndpointBuilder.d.ts +13 -0
- package/dist/composables/protocolTemplateCatalog.d.ts +26 -0
- package/dist/composables/useAutoGroup.d.ts +61 -74
- package/dist/composables/useAutoGroupInputSources.d.ts +32 -0
- package/dist/composables/useBioTemplateControls.d.ts +1 -1
- package/dist/composables/useBioTemplatePresetWorkspace.d.ts +1 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +1 -1
- package/dist/composables/useControlSchema.d.ts +4 -316
- package/dist/composables/useControlWorkspace.d.ts +1 -1
- package/dist/composables/useForm.d.ts +2 -33
- package/dist/composables/useFormBuilder.d.ts +2 -9
- package/dist/composables/useFormValidation.d.ts +34 -0
- package/dist/composables/usePluginClient.d.ts +1 -4
- package/dist/composables/useProtocolTemplates.d.ts +2 -24
- package/dist/composables/useScheduleCalendarLayout.d.ts +49 -0
- package/dist/{composables-BMkPQhVK.js → composables-Dlg8jenH.js} +33 -31
- package/dist/composables-Dlg8jenH.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/install.js +2 -2
- package/dist/styles.css +547 -516
- package/dist/templates/controlSchemaTypes.d.ts +1 -1
- package/dist/templates/index.js +1 -1
- package/dist/templates/templateAdapterTypes.d.ts +48 -0
- package/dist/templates/templateCreateOptions.d.ts +165 -0
- package/dist/templates/templateQpcrTypes.d.ts +42 -0
- package/dist/templates/types.d.ts +5 -250
- package/dist/{templates-bUAWMn5L.js → templates-DtdUvJ4c.js} +144 -136
- package/dist/templates-DtdUvJ4c.js.map +1 -0
- package/dist/types/auto-group.d.ts +79 -9
- package/dist/types/componentLabTypes.d.ts +161 -0
- package/dist/types/componentWorkflowTypes.d.ts +150 -0
- package/dist/types/components.d.ts +2 -311
- package/dist/{useProtocolTemplates-QZtHFFH2.js → useProtocolTemplates-Bm5vyH4_.js} +1220 -454
- package/dist/useProtocolTemplates-Bm5vyH4_.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.navigation.test.ts +70 -0
- package/src/__tests__/components/DoseCalculatorVolumeField.test.ts +53 -0
- package/src/__tests__/components/PlateMapEditorToolbarInternal.test.ts +54 -0
- package/src/__tests__/components/PluginWorkspaceView.controls.test.ts +156 -0
- package/src/__tests__/components/PluginWorkspaceView.navigation.test.ts +102 -0
- package/src/__tests__/components/PluginWorkspaceView.shell.test.ts +41 -0
- package/src/__tests__/components/ProtocolStep.presentation.test.ts +31 -0
- package/src/__tests__/components/ProtocolStepEditor.state.test.ts +165 -0
- package/src/__tests__/components/ProtocolStepParameterField.test.ts +44 -0
- package/src/__tests__/components/ReagentList.presentation.test.ts +68 -0
- package/src/__tests__/components/SampleSelector.colors.test.ts +49 -0
- package/src/__tests__/components/SampleSelector.drag.test.ts +100 -0
- package/src/__tests__/components/SampleSelector.groups.test.ts +81 -0
- package/src/__tests__/components/SampleSelector.selection.test.ts +70 -0
- package/src/__tests__/components/SampleSelector.test.ts +32 -0
- package/src/__tests__/components/SampleSelectorSampleRow.test.ts +37 -0
- package/src/__tests__/components/ScheduleCalendar.test.ts +44 -0
- package/src/__tests__/components/SettingsModal.schema.test.ts +97 -0
- package/src/__tests__/components/WellPlate.colors.test.ts +28 -0
- package/src/__tests__/components/WellPlate.conditions.test.ts +68 -0
- package/src/__tests__/components/WellPlate.geometry.test.ts +54 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +171 -0
- package/src/__tests__/components/WellPlate.legend.test.ts +13 -0
- package/src/__tests__/components/WellPlate.rendering.test.ts +122 -0
- package/src/__tests__/components/WellPlate.sampleDrop.test.ts +70 -0
- package/src/__tests__/composables/autoGroup/classify.test.ts +107 -0
- package/src/__tests__/composables/autoGroup/columns.test.ts +135 -0
- package/src/__tests__/composables/autoGroup/compose.test.ts +227 -0
- package/src/__tests__/composables/autoGroup/cooccurrence.test.ts +91 -0
- package/src/__tests__/composables/autoGroup/fingerprint.test.ts +50 -0
- package/src/__tests__/composables/autoGroup/integration.test.ts +79 -0
- package/src/__tests__/composables/autoGroup/template.test.ts +70 -0
- package/src/__tests__/composables/autoGroup/tokenize.test.ts +33 -0
- package/src/__tests__/composables/useAutoGroup.test.ts +129 -625
- package/src/__tests__/composables/useAutoGroupInputSources.test.ts +107 -0
- package/src/__tests__/composables/useControlSchema.test.ts +23 -0
- package/src/__tests__/composables/useScheduleCalendarLayout.test.ts +89 -0
- package/src/__tests__/docs/extractDocsComponents.test.ts +142 -0
- package/src/__tests__/docs/extractDocsExports.test.ts +77 -0
- package/src/__tests__/docs/extractDocsParsing.test.ts +69 -0
- package/src/__tests__/docs/extractDocsTemplates.test.ts +54 -0
- package/src/__tests__/docs/extractDocsTheme.test.ts +89 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +1 -1
- package/src/__tests__/fixtures/auto-group/mixed-lc-ms-batch.txt +187 -0
- package/src/components/AppSidebar.vue +2 -6
- package/src/components/AppTopBar.navigation.ts +62 -0
- package/src/components/AppTopBar.vue +17 -44
- package/src/components/AutoGroupModal.story.vue +50 -0
- package/src/components/AutoGroupModal.vue +441 -158
- package/src/components/ControlWorkspaceView.vue +2 -6
- package/src/components/DoseCalculator.vue +13 -73
- package/src/components/DoseCalculatorVolumeField.vue +61 -0
- package/src/components/ExperimentTimeline.vue +6 -31
- package/src/components/FormBuilder.vue +2 -7
- package/src/components/PlateMapEditor.vue +32 -106
- package/src/components/PluginWorkspaceView.controls.ts +182 -0
- package/src/components/PluginWorkspaceView.navigation.ts +106 -0
- package/src/components/PluginWorkspaceView.props.ts +174 -0
- package/src/components/PluginWorkspaceView.shell.ts +66 -0
- package/src/components/PluginWorkspaceView.vue +85 -404
- package/src/components/ProtocolStep.presentation.ts +31 -0
- package/src/components/ProtocolStepEditor.state.ts +104 -0
- package/src/components/ProtocolStepEditor.vue +48 -179
- package/src/components/ProtocolStepParameterField.vue +134 -0
- package/src/components/ReagentList.presentation.ts +105 -0
- package/src/components/ReagentList.vue +16 -79
- package/src/components/SampleSelector.colors.ts +43 -0
- package/src/components/SampleSelector.drag.ts +164 -0
- package/src/components/SampleSelector.groups.ts +109 -0
- package/src/components/SampleSelector.selection.ts +103 -0
- package/src/components/SampleSelector.vue +82 -349
- package/src/components/SampleSelectorSampleRow.vue +64 -0
- package/src/components/ScheduleCalendar.vue +44 -199
- package/src/components/SettingsModal.schema.ts +71 -0
- package/src/components/SettingsModal.vue +16 -46
- package/src/components/WellPlate.colors.ts +56 -0
- package/src/components/WellPlate.conditions.ts +100 -0
- package/src/components/WellPlate.geometry.ts +91 -0
- package/src/components/WellPlate.interaction.ts +272 -0
- package/src/components/WellPlate.legend.ts +8 -0
- package/src/components/WellPlate.rendering.ts +105 -0
- package/src/components/WellPlate.sampleDrop.ts +73 -0
- package/src/components/WellPlate.vue +102 -550
- package/src/components/internal/PlateMapEditorToolbarInternal.vue +128 -0
- package/src/composables/autoGroup/classKey.ts +5 -0
- package/src/composables/autoGroup/classify.ts +205 -0
- package/src/composables/autoGroup/colors.ts +6 -0
- package/src/composables/autoGroup/columns.ts +226 -0
- package/src/composables/autoGroup/compose.ts +156 -0
- package/src/composables/autoGroup/cooccurrence.ts +46 -0
- package/src/composables/autoGroup/csv-shim.ts +44 -0
- package/src/composables/autoGroup/fingerprint.ts +49 -0
- package/src/composables/autoGroup/index.ts +20 -0
- package/src/composables/autoGroup/replicatePreGroup.ts +90 -0
- package/src/composables/autoGroup/template.ts +126 -0
- package/src/composables/autoGroup/tokenize.ts +41 -0
- package/src/composables/autoGroup/vocab.json +67 -0
- package/src/composables/autoGroupConstants.ts +4 -0
- package/src/composables/autoGroupGrouping.ts +148 -0
- package/src/composables/controlComponentBindings.ts +1 -1
- package/src/composables/controlSchemaAdapters.ts +1 -1
- package/src/composables/controlSchemaDoseDesign.ts +215 -0
- package/src/composables/controlSchemaFormFields.ts +1 -1
- package/src/composables/controlSchemaLayout.ts +1 -1
- package/src/composables/controlSchemaModel.ts +163 -0
- package/src/composables/controlSchemaNormalize.ts +1 -1
- package/src/composables/controlSchemaTypes.ts +364 -0
- package/src/composables/controlWorkspaceOptions.ts +1 -1
- package/src/composables/formBuilderSchema.ts +153 -0
- package/src/composables/pluginEndpointBuilder.ts +203 -0
- package/src/composables/protocolTemplateCatalog.ts +325 -0
- package/src/composables/useAutoGroup.ts +395 -549
- package/src/composables/useAutoGroupInputSources.ts +147 -0
- package/src/composables/useBioTemplateControls.ts +1 -1
- package/src/composables/useBioTemplatePresetWorkspace.ts +1 -1
- package/src/composables/useBioTemplateWorkspace.ts +1 -1
- package/src/composables/useControlSchema.ts +21 -692
- package/src/composables/useControlWorkspace.ts +7 -13
- package/src/composables/useForm.ts +5 -187
- package/src/composables/useFormBuilder.ts +11 -153
- package/src/composables/useFormValidation.ts +154 -0
- package/src/composables/usePluginClient.ts +10 -193
- package/src/composables/useProtocolTemplates.ts +10 -328
- package/src/composables/useScheduleCalendarLayout.ts +287 -0
- package/src/styles/components/auto-group-modal.css +248 -310
- package/src/templates/controlSchemaTypes.ts +1 -1
- package/src/templates/templateAdapterTypes.ts +58 -0
- package/src/templates/templateCreateOptions.ts +208 -0
- package/src/templates/templateQpcrTypes.ts +48 -0
- package/src/templates/types.ts +79 -275
- package/src/types/auto-group.ts +107 -9
- package/src/types/componentLabTypes.ts +235 -0
- package/src/types/componentWorkflowTypes.ts +190 -0
- package/src/types/components.ts +74 -424
- package/dist/components-DafPc4rM.js.map +0 -1
- package/dist/composables-BMkPQhVK.js.map +0 -1
- package/dist/templates-bUAWMn5L.js.map +0 -1
- package/dist/useProtocolTemplates-QZtHFFH2.js.map +0 -1
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
ControlWorkspaceOptions,
|
|
14
14
|
UseControlWorkspaceReturn,
|
|
15
15
|
} from '../composables/useControlSchema'
|
|
16
|
-
import {
|
|
16
|
+
import { mergeControlWorkspaceOptions, resolveControlModel, useControlWorkspace } from '../composables/useControlSchema'
|
|
17
17
|
import AppLayout from './AppLayout.vue'
|
|
18
18
|
import AppSidebar from './AppSidebar.vue'
|
|
19
19
|
import AppTopBar from './AppTopBar.vue'
|
|
@@ -156,8 +156,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
156
156
|
|
|
157
157
|
const externalValues = computed(() => props.modelValue ?? props.values)
|
|
158
158
|
const resolvedModel = computed<ControlModelBinding | undefined>(() => {
|
|
159
|
-
|
|
160
|
-
return isControlModelBinding(props.model) ? props.model : defineControlModel(props.model)
|
|
159
|
+
return resolveControlModel(props.model)
|
|
161
160
|
})
|
|
162
161
|
const resolvedControls = computed<ControlSchema>(() => props.controls ?? resolvedModel.value?.controls ?? {})
|
|
163
162
|
const baseControlOptions = computed<ControlWorkspaceOptions>(() =>
|
|
@@ -282,9 +281,6 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
|
282
281
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
283
282
|
}
|
|
284
283
|
|
|
285
|
-
function isControlModelBinding(model: ControlModel | ControlModelBinding): model is ControlModelBinding {
|
|
286
|
-
return 'controlOptions' in model
|
|
287
|
-
}
|
|
288
284
|
</script>
|
|
289
285
|
|
|
290
286
|
<template>
|
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
useDoseCalculator,
|
|
11
11
|
type VolumeValue,
|
|
12
|
-
type VolumeUnit,
|
|
13
12
|
type DilutionResult,
|
|
14
13
|
type SerialDilutionResult,
|
|
15
14
|
type WellConcentration,
|
|
16
15
|
} from '../composables/useDoseCalculator'
|
|
16
|
+
import DoseCalculatorVolumeField from './DoseCalculatorVolumeField.vue'
|
|
17
17
|
|
|
18
18
|
type CalculatorMode = 'dilution' | 'serial' | 'conversion'
|
|
19
19
|
|
|
@@ -140,30 +140,6 @@ function handleApplyToWells() {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
// Volume unit change handler
|
|
144
|
-
function handleVolumeChange(event: Event, target: 'final' | 'serial') {
|
|
145
|
-
const input = event.target as HTMLInputElement
|
|
146
|
-
const value = parseFloat(input.value)
|
|
147
|
-
if (isNaN(value)) return
|
|
148
|
-
|
|
149
|
-
if (target === 'final') {
|
|
150
|
-
finalVolume.value = { ...finalVolume.value, value }
|
|
151
|
-
} else {
|
|
152
|
-
serialVolumePerWell.value = { ...serialVolumePerWell.value, value }
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function handleVolumeUnitChange(event: Event, target: 'final' | 'serial') {
|
|
157
|
-
const select = event.target as HTMLSelectElement
|
|
158
|
-
const unit = select.value as VolumeUnit
|
|
159
|
-
|
|
160
|
-
if (target === 'final') {
|
|
161
|
-
finalVolume.value = { ...finalVolume.value, unit }
|
|
162
|
-
} else {
|
|
163
|
-
serialVolumePerWell.value = { ...serialVolumePerWell.value, unit }
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
143
|
// Watch for changes and recalculate
|
|
168
144
|
watch(
|
|
169
145
|
[stockConcentration, finalConcentration, finalVolume],
|
|
@@ -267,30 +243,12 @@ watch(activeMode, (mode) => {
|
|
|
267
243
|
/>
|
|
268
244
|
</div>
|
|
269
245
|
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
:disabled="disabled"
|
|
277
|
-
class="mint-dose-calculator__input"
|
|
278
|
-
min="0"
|
|
279
|
-
step="any"
|
|
280
|
-
@input="handleVolumeChange($event, 'final')"
|
|
281
|
-
/>
|
|
282
|
-
<select
|
|
283
|
-
:value="finalVolume.unit"
|
|
284
|
-
:disabled="disabled"
|
|
285
|
-
class="mint-dose-calculator__select"
|
|
286
|
-
@change="handleVolumeUnitChange($event, 'final')"
|
|
287
|
-
>
|
|
288
|
-
<option v-for="unit in volumeUnits" :key="unit" :value="unit">
|
|
289
|
-
{{ unit }}
|
|
290
|
-
</option>
|
|
291
|
-
</select>
|
|
292
|
-
</div>
|
|
293
|
-
</div>
|
|
246
|
+
<DoseCalculatorVolumeField
|
|
247
|
+
v-model="finalVolume"
|
|
248
|
+
label="Final Volume"
|
|
249
|
+
:units="volumeUnits"
|
|
250
|
+
:disabled="disabled"
|
|
251
|
+
/>
|
|
294
252
|
|
|
295
253
|
<div class="mint-dose-calculator__divider" />
|
|
296
254
|
|
|
@@ -367,30 +325,12 @@ watch(activeMode, (mode) => {
|
|
|
367
325
|
</div>
|
|
368
326
|
</div>
|
|
369
327
|
|
|
370
|
-
<
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
:disabled="disabled"
|
|
377
|
-
class="mint-dose-calculator__input"
|
|
378
|
-
min="0"
|
|
379
|
-
step="any"
|
|
380
|
-
@input="handleVolumeChange($event, 'serial')"
|
|
381
|
-
/>
|
|
382
|
-
<select
|
|
383
|
-
:value="serialVolumePerWell.unit"
|
|
384
|
-
:disabled="disabled"
|
|
385
|
-
class="mint-dose-calculator__select"
|
|
386
|
-
@change="handleVolumeUnitChange($event, 'serial')"
|
|
387
|
-
>
|
|
388
|
-
<option v-for="unit in volumeUnits" :key="unit" :value="unit">
|
|
389
|
-
{{ unit }}
|
|
390
|
-
</option>
|
|
391
|
-
</select>
|
|
392
|
-
</div>
|
|
393
|
-
</div>
|
|
328
|
+
<DoseCalculatorVolumeField
|
|
329
|
+
v-model="serialVolumePerWell"
|
|
330
|
+
label="Volume per Well"
|
|
331
|
+
:units="volumeUnits"
|
|
332
|
+
:disabled="disabled"
|
|
333
|
+
/>
|
|
394
334
|
|
|
395
335
|
<div class="mint-dose-calculator__divider" />
|
|
396
336
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Number + unit control used by DoseCalculator volume fields. */
|
|
3
|
+
import type { VolumeUnit, VolumeValue } from '../composables/useDoseCalculator'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
label: string
|
|
7
|
+
modelValue: VolumeValue
|
|
8
|
+
units: VolumeUnit[]
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
13
|
+
disabled: false,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const emit = defineEmits<{
|
|
17
|
+
'update:modelValue': [value: VolumeValue]
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
function handleValueInput(event: Event) {
|
|
21
|
+
const input = event.target as HTMLInputElement
|
|
22
|
+
const value = parseFloat(input.value)
|
|
23
|
+
if (Number.isNaN(value)) return
|
|
24
|
+
emit('update:modelValue', { ...props.modelValue, value })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleUnitChange(event: Event) {
|
|
28
|
+
const select = event.target as HTMLSelectElement
|
|
29
|
+
emit('update:modelValue', {
|
|
30
|
+
...props.modelValue,
|
|
31
|
+
unit: select.value as VolumeUnit,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<div class="mint-dose-calculator__field">
|
|
38
|
+
<label class="mint-dose-calculator__field-label">{{ label }}</label>
|
|
39
|
+
<div class="mint-dose-calculator__field-row">
|
|
40
|
+
<input
|
|
41
|
+
type="number"
|
|
42
|
+
:value="modelValue.value"
|
|
43
|
+
:disabled="disabled"
|
|
44
|
+
class="mint-dose-calculator__input"
|
|
45
|
+
min="0"
|
|
46
|
+
step="any"
|
|
47
|
+
@input="handleValueInput"
|
|
48
|
+
/>
|
|
49
|
+
<select
|
|
50
|
+
:value="modelValue.unit"
|
|
51
|
+
:disabled="disabled"
|
|
52
|
+
class="mint-dose-calculator__select"
|
|
53
|
+
@change="handleUnitChange"
|
|
54
|
+
>
|
|
55
|
+
<option v-for="unit in units" :key="unit" :value="unit">
|
|
56
|
+
{{ unit }}
|
|
57
|
+
</option>
|
|
58
|
+
</select>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Horizontal or vertical timeline of ordered protocol steps with status indicators, drag-to-reorder, and step-type iconography. */
|
|
3
3
|
import { ref, computed } from 'vue'
|
|
4
|
-
import type { ProtocolStep,
|
|
4
|
+
import type { ProtocolStep, ProtocolStepStatus } from '../types'
|
|
5
|
+
import {
|
|
6
|
+
PROTOCOL_STEP_TYPE_COLORS as stepTypeColors,
|
|
7
|
+
PROTOCOL_STEP_TYPE_ICONS as stepTypeIcons,
|
|
8
|
+
formatProtocolDuration as formatDuration,
|
|
9
|
+
} from './ProtocolStep.presentation'
|
|
5
10
|
|
|
6
11
|
interface Props {
|
|
7
12
|
modelValue?: ProtocolStep[]
|
|
@@ -59,28 +64,6 @@ const sortedSteps = computed(() => {
|
|
|
59
64
|
return [...props.modelValue].sort((a, b) => a.order - b.order)
|
|
60
65
|
})
|
|
61
66
|
|
|
62
|
-
const stepTypeIcons: Record<ProtocolStepType, string> = {
|
|
63
|
-
incubation: 'M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z',
|
|
64
|
-
wash: 'M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z',
|
|
65
|
-
addition: 'M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
66
|
-
measurement: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z',
|
|
67
|
-
transfer: 'M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4',
|
|
68
|
-
centrifuge: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15',
|
|
69
|
-
mix: 'M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z',
|
|
70
|
-
custom: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z',
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const stepTypeColors: Record<ProtocolStepType, string> = {
|
|
74
|
-
incubation: '#F59E0B',
|
|
75
|
-
wash: '#06B6D4',
|
|
76
|
-
addition: '#10B981',
|
|
77
|
-
measurement: '#8B5CF6',
|
|
78
|
-
transfer: '#3B82F6',
|
|
79
|
-
centrifuge: '#6B7280',
|
|
80
|
-
mix: '#F97316',
|
|
81
|
-
custom: '#EC4899',
|
|
82
|
-
}
|
|
83
|
-
|
|
84
67
|
const statusClasses: Record<ProtocolStepStatus, string> = {
|
|
85
68
|
pending: 'mint-timeline__card--pending',
|
|
86
69
|
in_progress: 'mint-timeline__card--in-progress',
|
|
@@ -97,14 +80,6 @@ const iconStatusClasses: Record<ProtocolStepStatus, string> = {
|
|
|
97
80
|
skipped: 'mint-timeline__icon--skipped',
|
|
98
81
|
}
|
|
99
82
|
|
|
100
|
-
function formatDuration(minutes: number | undefined): string {
|
|
101
|
-
if (minutes === undefined) return ''
|
|
102
|
-
if (minutes < 60) return `${minutes}m`
|
|
103
|
-
const hours = Math.floor(minutes / 60)
|
|
104
|
-
const mins = minutes % 60
|
|
105
|
-
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`
|
|
106
|
-
}
|
|
107
|
-
|
|
108
83
|
function getStepColor(step: ProtocolStep): string {
|
|
109
84
|
return props.colorByType && !props.colorByStatus ? stepTypeColors[step.type] : ''
|
|
110
85
|
}
|
|
@@ -27,9 +27,9 @@ import type { FormSchema, FormEnhancements, UseFormBuilderReturn } from '../type
|
|
|
27
27
|
import type { WizardStep } from '../types'
|
|
28
28
|
import {
|
|
29
29
|
controlsToFormSchema,
|
|
30
|
-
defineControlModel,
|
|
31
30
|
getControlDefaults,
|
|
32
31
|
mergeControlWorkspaceOptions,
|
|
32
|
+
resolveControlModel,
|
|
33
33
|
type ControlModel,
|
|
34
34
|
type ControlModelBinding,
|
|
35
35
|
type ControlSchema,
|
|
@@ -80,8 +80,7 @@ const emit = defineEmits<{
|
|
|
80
80
|
}>()
|
|
81
81
|
|
|
82
82
|
const resolvedModel = computed<ControlModelBinding | undefined>(() => {
|
|
83
|
-
|
|
84
|
-
return isControlModelBinding(props.model) ? props.model : defineControlModel(props.model)
|
|
83
|
+
return resolveControlModel(props.model)
|
|
85
84
|
})
|
|
86
85
|
|
|
87
86
|
const resolvedControls = computed<ControlSchema | undefined>(() =>
|
|
@@ -197,10 +196,6 @@ function builderFieldNames(): string[] {
|
|
|
197
196
|
return builder.fields.map(field => field.name)
|
|
198
197
|
}
|
|
199
198
|
|
|
200
|
-
function isControlModelBinding(model: ControlModel | ControlModelBinding): model is ControlModelBinding {
|
|
201
|
-
return 'controls' in model && 'controlOptions' in model
|
|
202
|
-
}
|
|
203
|
-
|
|
204
199
|
defineExpose({
|
|
205
200
|
form: builder.form,
|
|
206
201
|
validate: builder.validate,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Full plate-map design environment for 96/384-well plates with sample assignment, slot-color coding, undo/redo, and CSV/JSON import-export. */
|
|
3
3
|
import { ref, computed, watch } from 'vue'
|
|
4
|
-
import type { PlateMapEditorState, WellPlateFormat, SampleType, Well } from '../types'
|
|
4
|
+
import type { PlateMapEditorState, WellPlateFormat, SampleType, Well, PlateMap } from '../types'
|
|
5
5
|
import { useWellPlateEditor } from '../composables/useWellPlateEditor'
|
|
6
6
|
import { useEventListener } from '../composables/useEventListener'
|
|
7
7
|
import WellPlate from './WellPlate.vue'
|
|
8
8
|
import SampleLegend from './SampleLegend.vue'
|
|
9
|
+
import PlateMapEditorToolbarInternal from './internal/PlateMapEditorToolbarInternal.vue'
|
|
9
10
|
|
|
10
11
|
// Slot colors matching MSExpDesigner
|
|
11
12
|
type SlotPosition = 'R' | 'G' | 'B' | 'Y'
|
|
@@ -73,6 +74,10 @@ function getPlateSlot(plateId: string, plateIndex: number): SlotPosition {
|
|
|
73
74
|
return plateSlots.value.get(plateId)!
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
function getPlateSlotColor(plateId: string, plateIndex: number): string {
|
|
78
|
+
return SLOT_COLORS[getPlateSlot(plateId, plateIndex)]
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
const sampleColors = computed(() => {
|
|
77
82
|
const colors: Record<string, string> = {}
|
|
78
83
|
for (const sample of editor.samples.value) {
|
|
@@ -98,13 +103,18 @@ const wellsData = computed(() => {
|
|
|
98
103
|
return wells
|
|
99
104
|
})
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
function getPlateWellCount(plateId: string): number {
|
|
103
|
-
const plate = editor.plates.value.find(p => p.id === plateId)
|
|
104
|
-
if (!plate) return 0
|
|
106
|
+
function countPlateAssignedWells(plate: PlateMap): number {
|
|
105
107
|
return Object.values(plate.wells).filter(w => w.sampleType).length
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
const plateWellCounts = computed<Record<string, number>>(() => {
|
|
111
|
+
const counts: Record<string, number> = {}
|
|
112
|
+
for (const plate of editor.plates.value) {
|
|
113
|
+
counts[plate.id] = countPlateAssignedWells(plate)
|
|
114
|
+
}
|
|
115
|
+
return counts
|
|
116
|
+
})
|
|
117
|
+
|
|
108
118
|
watch(
|
|
109
119
|
() => editor.state.value,
|
|
110
120
|
(newState) => emit('update:modelValue', { ...newState }),
|
|
@@ -230,107 +240,23 @@ useEventListener(() => document, 'keydown', handleKeyDown)
|
|
|
230
240
|
<!-- Main plate area -->
|
|
231
241
|
<div class="mint-plate-editor__main">
|
|
232
242
|
<!-- Toolbar -->
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
class="mint-plate-editor__tab-count"
|
|
251
|
-
>
|
|
252
|
-
{{ getPlateWellCount(plate.id) }}
|
|
253
|
-
</span>
|
|
254
|
-
<button
|
|
255
|
-
v-if="editor.plates.value.length > 1"
|
|
256
|
-
type="button"
|
|
257
|
-
class="mint-plate-editor__tab-remove"
|
|
258
|
-
:aria-label="`Remove ${plate.name}`"
|
|
259
|
-
@click.stop="handleRemovePlate(plate.id)"
|
|
260
|
-
>
|
|
261
|
-
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
262
|
-
<path d="M18 6 6 18" /><path d="m6 6 12 12" />
|
|
263
|
-
</svg>
|
|
264
|
-
</button>
|
|
265
|
-
</button>
|
|
266
|
-
|
|
267
|
-
<button
|
|
268
|
-
v-if="allowAddPlates && editor.plates.value.length < maxPlates"
|
|
269
|
-
type="button"
|
|
270
|
-
class="mint-plate-editor__add-plate"
|
|
271
|
-
aria-label="Add plate"
|
|
272
|
-
@click="handleAddPlate"
|
|
273
|
-
>
|
|
274
|
-
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
275
|
-
<path d="M5 12h14" /><path d="M12 5v14" />
|
|
276
|
-
</svg>
|
|
277
|
-
<span>Add</span>
|
|
278
|
-
</button>
|
|
279
|
-
</div>
|
|
280
|
-
|
|
281
|
-
<div class="mint-plate-editor__spacer" />
|
|
282
|
-
|
|
283
|
-
<!-- Actions -->
|
|
284
|
-
<div class="mint-plate-editor__actions">
|
|
285
|
-
<button
|
|
286
|
-
type="button"
|
|
287
|
-
:disabled="!editor.canUndo.value"
|
|
288
|
-
class="mint-plate-editor__action-btn"
|
|
289
|
-
title="Undo (Ctrl+Z)"
|
|
290
|
-
@click="handleUndo"
|
|
291
|
-
>
|
|
292
|
-
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
293
|
-
<path d="M9 14 4 9l5-5" /><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" />
|
|
294
|
-
</svg>
|
|
295
|
-
</button>
|
|
296
|
-
|
|
297
|
-
<button
|
|
298
|
-
type="button"
|
|
299
|
-
:disabled="!editor.canRedo.value"
|
|
300
|
-
class="mint-plate-editor__action-btn"
|
|
301
|
-
title="Redo (Ctrl+Shift+Z)"
|
|
302
|
-
@click="handleRedo"
|
|
303
|
-
>
|
|
304
|
-
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
305
|
-
<path d="m15 14 5-5-5-5" /><path d="M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13" />
|
|
306
|
-
</svg>
|
|
307
|
-
</button>
|
|
308
|
-
|
|
309
|
-
<div class="mint-plate-editor__divider" />
|
|
310
|
-
|
|
311
|
-
<button
|
|
312
|
-
type="button"
|
|
313
|
-
class="mint-plate-editor__action-btn"
|
|
314
|
-
title="Import"
|
|
315
|
-
@click="showImportModal = true"
|
|
316
|
-
>
|
|
317
|
-
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
318
|
-
<path d="M12 3v12" /><path d="m17 8-5-5-5 5" /><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
319
|
-
</svg>
|
|
320
|
-
</button>
|
|
321
|
-
|
|
322
|
-
<button
|
|
323
|
-
type="button"
|
|
324
|
-
class="mint-plate-editor__action-btn"
|
|
325
|
-
title="Export JSON"
|
|
326
|
-
@click="handleExport('json')"
|
|
327
|
-
>
|
|
328
|
-
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
329
|
-
<path d="M12 15V3" /><path d="m7 10 5 5 5-5" /><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
330
|
-
</svg>
|
|
331
|
-
</button>
|
|
332
|
-
</div>
|
|
333
|
-
</div>
|
|
243
|
+
<PlateMapEditorToolbarInternal
|
|
244
|
+
v-if="showToolbar"
|
|
245
|
+
:plates="editor.plates.value"
|
|
246
|
+
:active-plate-id="editor.activePlate.value?.id"
|
|
247
|
+
:plate-well-counts="plateWellCounts"
|
|
248
|
+
:can-add-plate="allowAddPlates && editor.plates.value.length < maxPlates"
|
|
249
|
+
:can-undo="editor.canUndo.value"
|
|
250
|
+
:can-redo="editor.canRedo.value"
|
|
251
|
+
:slot-color-for-plate="getPlateSlotColor"
|
|
252
|
+
@select-plate="editor.setActivePlate"
|
|
253
|
+
@remove-plate="handleRemovePlate"
|
|
254
|
+
@add-plate="handleAddPlate"
|
|
255
|
+
@undo="handleUndo"
|
|
256
|
+
@redo="handleRedo"
|
|
257
|
+
@import="showImportModal = true"
|
|
258
|
+
@export-json="handleExport('json')"
|
|
259
|
+
/>
|
|
334
260
|
|
|
335
261
|
<!-- Well plate -->
|
|
336
262
|
<WellPlate
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { computed, reactive, watch } from 'vue'
|
|
2
|
+
import type { ComputedRef } from 'vue'
|
|
3
|
+
import type { PillNavItem } from '../types'
|
|
4
|
+
import type {
|
|
5
|
+
ControlComponentBinding,
|
|
6
|
+
ControlComponentBindingsById,
|
|
7
|
+
ControlComponentBindingsConfig,
|
|
8
|
+
ControlComponentPropsByIdMap,
|
|
9
|
+
ControlComponentPropsMap,
|
|
10
|
+
ControlModel,
|
|
11
|
+
ControlModelBinding,
|
|
12
|
+
ControlSchema,
|
|
13
|
+
ControlWorkspaceOptions,
|
|
14
|
+
} from '../composables/useControlSchema'
|
|
15
|
+
import {
|
|
16
|
+
controlValuesToComponentBindings,
|
|
17
|
+
controlValuesToComponentBindingsById,
|
|
18
|
+
controlValuesToComponentProps,
|
|
19
|
+
controlsToViewItems,
|
|
20
|
+
getControlDefaults,
|
|
21
|
+
getDefaultControlView,
|
|
22
|
+
mergeControlWorkspaceOptions,
|
|
23
|
+
resolveControlModel,
|
|
24
|
+
} from '../composables/useControlSchema'
|
|
25
|
+
|
|
26
|
+
export interface PluginWorkspaceControlsOptions {
|
|
27
|
+
model: () => ControlModel | ControlModelBinding | undefined
|
|
28
|
+
controls: () => ControlSchema | undefined
|
|
29
|
+
controlOptions: () => ControlWorkspaceOptions
|
|
30
|
+
modelValue: () => Record<string, unknown> | undefined
|
|
31
|
+
values: () => Record<string, unknown> | undefined
|
|
32
|
+
componentBindings: () => ControlComponentBindingsConfig | undefined
|
|
33
|
+
componentProps: () => ControlComponentPropsMap | undefined
|
|
34
|
+
componentPropsById: () => ControlComponentPropsByIdMap | undefined
|
|
35
|
+
emitValues: (values: Record<string, unknown>) => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PluginWorkspaceControls {
|
|
39
|
+
resolvedModel: ComputedRef<ControlModelBinding | undefined>
|
|
40
|
+
resolvedControls: ComputedRef<ControlSchema | undefined>
|
|
41
|
+
resolvedControlOptions: ComputedRef<ControlWorkspaceOptions>
|
|
42
|
+
generatedPillNav: ComputedRef<PillNavItem[]>
|
|
43
|
+
generatedDefaultView: ComputedRef<string>
|
|
44
|
+
internalControlValues: Record<string, unknown>
|
|
45
|
+
resolvedComponentBindings: ComputedRef<ControlComponentBinding[]>
|
|
46
|
+
resolvedComponentBindingsById: ComputedRef<ControlComponentBindingsById>
|
|
47
|
+
resolvedComponentProps: ComputedRef<Record<string, unknown>>
|
|
48
|
+
resolvedComponentPropsById: ComputedRef<Record<string, Record<string, unknown>>>
|
|
49
|
+
handleControlValuesUpdate: (values: Record<string, unknown>) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function usePluginWorkspaceControls(
|
|
53
|
+
options: PluginWorkspaceControlsOptions,
|
|
54
|
+
): PluginWorkspaceControls {
|
|
55
|
+
const resolvedModel = computed<ControlModelBinding | undefined>(() =>
|
|
56
|
+
resolveControlModel(options.model()),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const resolvedControls = computed<ControlSchema | undefined>(() =>
|
|
60
|
+
options.controls() ?? resolvedModel.value?.controls,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>
|
|
64
|
+
mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, options.controlOptions()),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const externalControlValues = computed(() => options.modelValue() ?? options.values())
|
|
68
|
+
const internalControlValues = reactive<Record<string, unknown>>({})
|
|
69
|
+
let syncingControlValues = false
|
|
70
|
+
|
|
71
|
+
const generatedControlDefaults = computed<Record<string, unknown>>(() => {
|
|
72
|
+
if (!resolvedControls.value) return {}
|
|
73
|
+
return {
|
|
74
|
+
...getControlDefaults(resolvedControls.value),
|
|
75
|
+
...(resolvedControlOptions.value.initialValues ?? {}),
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
function replaceControlValues(values: Record<string, unknown>) {
|
|
80
|
+
for (const key of Object.keys(internalControlValues)) {
|
|
81
|
+
if (!(key in values)) {
|
|
82
|
+
delete internalControlValues[key]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
Object.assign(internalControlValues, values)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function emitControlValues() {
|
|
89
|
+
options.emitValues({ ...internalControlValues })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function syncResolvedControlValues() {
|
|
93
|
+
syncingControlValues = true
|
|
94
|
+
replaceControlValues({
|
|
95
|
+
...generatedControlDefaults.value,
|
|
96
|
+
...(externalControlValues.value === undefined ? internalControlValues : externalControlValues.value),
|
|
97
|
+
})
|
|
98
|
+
syncingControlValues = false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handleControlValuesUpdate(values: Record<string, unknown>) {
|
|
102
|
+
syncingControlValues = true
|
|
103
|
+
replaceControlValues({
|
|
104
|
+
...generatedControlDefaults.value,
|
|
105
|
+
...values,
|
|
106
|
+
})
|
|
107
|
+
syncingControlValues = false
|
|
108
|
+
emitControlValues()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const resolvedComponentBindings = computed(() =>
|
|
112
|
+
controlValuesToComponentBindings(
|
|
113
|
+
internalControlValues,
|
|
114
|
+
options.componentBindings() ?? resolvedModel.value?.componentBindings,
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const resolvedComponentBindingsById = computed(() =>
|
|
119
|
+
controlValuesToComponentBindingsById(
|
|
120
|
+
internalControlValues,
|
|
121
|
+
options.componentBindings() ?? resolvedModel.value?.componentBindings,
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const resolvedComponentProps = computed(() => {
|
|
126
|
+
const mapping = options.componentProps() ?? resolvedModel.value?.componentProps
|
|
127
|
+
return mapping === undefined ? {} : controlValuesToComponentProps(internalControlValues, mapping)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const resolvedComponentPropsById = computed(() => {
|
|
131
|
+
const mappings = options.componentPropsById() ?? resolvedModel.value?.componentPropsById
|
|
132
|
+
if (mappings === undefined) return {}
|
|
133
|
+
|
|
134
|
+
return Object.fromEntries(
|
|
135
|
+
Object.entries(mappings).map(([id, mapping]) => [
|
|
136
|
+
id,
|
|
137
|
+
controlValuesToComponentProps(internalControlValues, mapping),
|
|
138
|
+
]),
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const generatedPillNav = computed<PillNavItem[]>(() => {
|
|
143
|
+
if (!resolvedControls.value) return []
|
|
144
|
+
return controlsToViewItems(resolvedControls.value, resolvedControlOptions.value)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const generatedDefaultView = computed(() => {
|
|
148
|
+
if (!resolvedControls.value) return ''
|
|
149
|
+
return getDefaultControlView(resolvedControls.value, resolvedControlOptions.value)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
syncResolvedControlValues()
|
|
153
|
+
|
|
154
|
+
watch(
|
|
155
|
+
[generatedControlDefaults, externalControlValues],
|
|
156
|
+
syncResolvedControlValues,
|
|
157
|
+
{ deep: true },
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
watch(
|
|
161
|
+
internalControlValues,
|
|
162
|
+
() => {
|
|
163
|
+
if (syncingControlValues) return
|
|
164
|
+
emitControlValues()
|
|
165
|
+
},
|
|
166
|
+
{ deep: true, flush: 'sync' },
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
resolvedModel,
|
|
171
|
+
resolvedControls,
|
|
172
|
+
resolvedControlOptions,
|
|
173
|
+
generatedPillNav,
|
|
174
|
+
generatedDefaultView,
|
|
175
|
+
internalControlValues,
|
|
176
|
+
resolvedComponentBindings,
|
|
177
|
+
resolvedComponentBindingsById,
|
|
178
|
+
resolvedComponentProps,
|
|
179
|
+
resolvedComponentPropsById,
|
|
180
|
+
handleControlValuesUpdate,
|
|
181
|
+
}
|
|
182
|
+
}
|