@morscherlab/mint-sdk 1.0.0-rc.2 → 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/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/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/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-BhK-dW99.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 +7 -0
- package/dist/composables/controlSchemaAdapters.d.ts +20 -0
- package/dist/composables/controlSchemaDoseDesign.d.ts +11 -0
- package/dist/composables/controlSchemaFormFields.d.ts +3 -0
- package/dist/composables/controlSchemaLayout.d.ts +7 -0
- package/dist/composables/controlSchemaModel.d.ts +5 -0
- package/dist/composables/controlSchemaNormalize.d.ts +15 -0
- package/dist/composables/controlSchemaTypes.d.ts +305 -0
- package/dist/composables/controlSchemaUtils.d.ts +9 -0
- package/dist/composables/controlWorkspaceOptions.d.ts +2 -0
- 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 +8 -346
- package/dist/composables/useControlWorkspace.d.ts +5 -0
- 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-Bg7CFuNz.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/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 -67
- 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.js +1 -1
- 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/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/templateAdapterTypes.d.ts +48 -0
- package/dist/templates/templateControlSchemas.d.ts +400 -0
- package/dist/templates/templateCreateOptions.d.ts +165 -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/templateQpcrTypes.d.ts +42 -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 +5 -250
- package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
- package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
- package/dist/{templates-BorLR_7p.js → templates-DtdUvJ4c.js} +3565 -3411
- 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-n6AJqSqv.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 +80 -0
- package/src/composables/controlSchemaAdapters.ts +196 -0
- package/src/composables/controlSchemaDoseDesign.ts +215 -0
- package/src/composables/controlSchemaFormFields.ts +61 -0
- package/src/composables/controlSchemaLayout.ts +59 -0
- package/src/composables/controlSchemaModel.ts +163 -0
- package/src/composables/controlSchemaNormalize.ts +101 -0
- package/src/composables/controlSchemaTypes.ts +364 -0
- package/src/composables/controlSchemaUtils.ts +36 -0
- package/src/composables/controlWorkspaceOptions.ts +21 -0
- 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 +64 -1312
- package/src/composables/useControlWorkspace.ts +201 -0
- 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/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 -2350
- 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 -704
- 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/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/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/templateAdapterTypes.ts +58 -0
- package/src/templates/templateControlSchemas.ts +320 -0
- package/src/templates/templateCreateOptions.ts +208 -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/templateQpcrTypes.ts +48 -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 +79 -275
- package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
- package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -0
- 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-BhK-dW99.js.map +0 -1
- package/dist/composables-Bg7CFuNz.js.map +0 -1
- package/dist/templates-BorLR_7p.js.map +0 -1
- package/dist/useProtocolTemplates-n6AJqSqv.js.map +0 -1
|
@@ -8,9 +8,10 @@ import StepWizard from './StepWizard.vue'
|
|
|
8
8
|
import LoadingSpinner from './LoadingSpinner.vue'
|
|
9
9
|
import AlertBox from './AlertBox.vue'
|
|
10
10
|
import { useAutoGroup, parseCSV } from '../composables/useAutoGroup'
|
|
11
|
+
import { classKey } from '../composables/autoGroup'
|
|
11
12
|
import { useApi } from '../composables/useApi'
|
|
12
13
|
import type { WizardStep } from '../types/components'
|
|
13
|
-
import type { AutoGroupResult } from '../types/auto-group'
|
|
14
|
+
import type { AutoGroupResult, MergeSuggestion, ColumnRole } from '../types/auto-group'
|
|
14
15
|
|
|
15
16
|
export interface Props {
|
|
16
17
|
modelValue: boolean
|
|
@@ -41,7 +42,162 @@ const experimentLoading = ref(false)
|
|
|
41
42
|
const experimentError = ref<string | null>(null)
|
|
42
43
|
const csvError = ref<string | null>(null)
|
|
43
44
|
|
|
44
|
-
//
|
|
45
|
+
// Workspace step refs
|
|
46
|
+
const openPopoverIdx = ref<number | null>(null)
|
|
47
|
+
const qcAsOverlay = ref(true)
|
|
48
|
+
const excludeQc = ref(false)
|
|
49
|
+
|
|
50
|
+
function roleLabel(role?: ColumnRole): string {
|
|
51
|
+
if (!role) return 'Factor'
|
|
52
|
+
const labels: Record<ColumnRole, string> = {
|
|
53
|
+
'constant': 'Constant',
|
|
54
|
+
'factor': 'Factor',
|
|
55
|
+
'replicate': 'Replicate',
|
|
56
|
+
'run-order': 'Run',
|
|
57
|
+
'numeric': 'Numeric',
|
|
58
|
+
'class-tag': 'Class tag',
|
|
59
|
+
'ignore': 'Ignore',
|
|
60
|
+
}
|
|
61
|
+
return labels[role]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const activeSchemaLabel = computed(() => {
|
|
65
|
+
const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)
|
|
66
|
+
return cls?.label ?? '—'
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const activeMemberCount = computed(() => {
|
|
70
|
+
const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)
|
|
71
|
+
return cls?.members.length ?? 0
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const activeColumnCount = computed(() => autoGroup.activeSchema.value?.columns.length ?? 0)
|
|
75
|
+
|
|
76
|
+
const groupKeyNames = computed<string[]>(() => {
|
|
77
|
+
const schema = autoGroup.activeSchema.value
|
|
78
|
+
if (!schema) return []
|
|
79
|
+
return schema.groupBy
|
|
80
|
+
.map(idx => {
|
|
81
|
+
const col = schema.columns.find(c => c.index === idx)
|
|
82
|
+
return col?.displayName ?? col?.name ?? ''
|
|
83
|
+
})
|
|
84
|
+
.filter((s): s is string => s.length > 0)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const totalGrouped = computed(() =>
|
|
88
|
+
(autoGroup.result.value.experimentalGroups ?? []).reduce((acc, g) => acc + g.samples.length, 0),
|
|
89
|
+
)
|
|
90
|
+
const totalQc = computed(() =>
|
|
91
|
+
(autoGroup.qcGroups.value ?? []).reduce((acc, g) => acc + g.samples.length, 0),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
function acceptSuggestion(s: MergeSuggestion) {
|
|
95
|
+
autoGroup.mergeColumns(autoGroup.activeClassKey.value, s.columnIndices)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function dismissSuggestion(_: MergeSuggestion) {
|
|
99
|
+
// Suggestions are derived; "dismiss" is a no-op in MVP (it will re-appear on schema reset).
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// QC routing: a 3-way state replaces the two former booleans. Internally we
|
|
103
|
+
// still store qcAsOverlay + excludeQc (so downstream watchers stay stable),
|
|
104
|
+
// but the UI presents the choice as one radio group: overlay / exclude / mix.
|
|
105
|
+
type QcRouting = 'overlay' | 'exclude' | 'mix'
|
|
106
|
+
const qcRouting = computed<QcRouting>(() => {
|
|
107
|
+
if (excludeQc.value) return 'exclude'
|
|
108
|
+
if (qcAsOverlay.value) return 'overlay'
|
|
109
|
+
return 'mix'
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
function setQcRouting(value: QcRouting) {
|
|
113
|
+
qcAsOverlay.value = value === 'overlay'
|
|
114
|
+
excludeQc.value = value === 'exclude'
|
|
115
|
+
autoGroup.setClassDispositions(
|
|
116
|
+
c => c.kind !== 'biological' && c.kind !== 'unknown',
|
|
117
|
+
value === 'exclude' ? 'exclude' : value === 'overlay' ? 'overlay' : 'group',
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Group statistics for the 3-stat strip — scoped to the ACTIVE class so the
|
|
122
|
+
// numbers correspond to what the user sees in the schema panel below.
|
|
123
|
+
// (The Preview step shows global totals across all classes.)
|
|
124
|
+
const groupStats = computed(() => {
|
|
125
|
+
const cls = autoGroup.classes.value.find(c => classKey(c) === autoGroup.activeClassKey.value)
|
|
126
|
+
if (!cls) return { count: 0, samples: 0, avg: 0, min: 0, max: 0 }
|
|
127
|
+
const schema = autoGroup.activeSchema.value
|
|
128
|
+
const total = cls.members.length
|
|
129
|
+
const buckets = new Map<string, number>()
|
|
130
|
+
if (!schema || schema.groupBy.length === 0) {
|
|
131
|
+
// No factor columns selected → one group containing everyone.
|
|
132
|
+
if (total > 0) buckets.set('__all__', total)
|
|
133
|
+
} else {
|
|
134
|
+
for (const m of cls.members) {
|
|
135
|
+
const tokens = autoGroup.tokenized.value[m]
|
|
136
|
+
const key = schema.groupBy.map(idx => tokens[idx] ?? '').join(' / ')
|
|
137
|
+
buckets.set(key, (buckets.get(key) ?? 0) + 1)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const sizes = [...buckets.values()]
|
|
141
|
+
return {
|
|
142
|
+
count: sizes.length,
|
|
143
|
+
samples: total,
|
|
144
|
+
avg: sizes.length > 0 ? Math.round((total / sizes.length) * 10) / 10 : 0,
|
|
145
|
+
min: sizes.length > 0 ? Math.min(...sizes) : 0,
|
|
146
|
+
max: sizes.length > 0 ? Math.max(...sizes) : 0,
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const activeColumn = computed(() => {
|
|
151
|
+
if (openPopoverIdx.value === null) return null
|
|
152
|
+
return autoGroup.activeSchema.value?.columns.find(c => c.index === openPopoverIdx.value) ?? null
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
function renameActive(name: string) {
|
|
156
|
+
const col = activeColumn.value
|
|
157
|
+
if (!col) return
|
|
158
|
+
autoGroup.setColumnName(autoGroup.activeClassKey.value, col.index, name)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function setAlias(src: string, dst: string) {
|
|
162
|
+
const col = activeColumn.value
|
|
163
|
+
if (!col) return
|
|
164
|
+
const next = { ...(col.ops?.alias ?? {}) }
|
|
165
|
+
if (dst === src) delete next[src]
|
|
166
|
+
else next[src] = dst
|
|
167
|
+
autoGroup.setValueOps(autoGroup.activeClassKey.value, col.index, {
|
|
168
|
+
...col.ops,
|
|
169
|
+
alias: next,
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function toggleExclude(value: string) {
|
|
174
|
+
const col = activeColumn.value
|
|
175
|
+
if (!col) return
|
|
176
|
+
const list = new Set(col.ops?.exclude ?? [])
|
|
177
|
+
if (list.has(value)) list.delete(value)
|
|
178
|
+
else list.add(value)
|
|
179
|
+
autoGroup.setValueOps(autoGroup.activeClassKey.value, col.index, {
|
|
180
|
+
...col.ops,
|
|
181
|
+
exclude: [...list],
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function mergeWithNext() {
|
|
186
|
+
const col = activeColumn.value
|
|
187
|
+
if (!col) return
|
|
188
|
+
const schema = autoGroup.activeSchema.value
|
|
189
|
+
const next = schema?.columns.find(c => c.index === col.index + 1)
|
|
190
|
+
if (!next) return
|
|
191
|
+
autoGroup.mergeColumns(autoGroup.activeClassKey.value, [col.index, next.index])
|
|
192
|
+
openPopoverIdx.value = null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Pre-fill from props.
|
|
196
|
+
// When samples or experiment data are already known we auto-skip the Input
|
|
197
|
+
// step entirely and land on Workspace — there's nothing for the user to
|
|
198
|
+
// paste or upload. The wizard becomes a clean 2-step (Workspace → Preview)
|
|
199
|
+
// in the common case. Input still exists for cold opens (no samples, no
|
|
200
|
+
// experiment) where users paste their own sample list or upload a CSV.
|
|
45
201
|
watch(() => props.modelValue, (open) => {
|
|
46
202
|
if (open) {
|
|
47
203
|
autoGroup.reset()
|
|
@@ -51,26 +207,28 @@ watch(() => props.modelValue, (open) => {
|
|
|
51
207
|
csvError.value = null
|
|
52
208
|
if (props.experimentId || props.designData) {
|
|
53
209
|
fetchExperimentData()
|
|
210
|
+
currentStep.value = 1
|
|
54
211
|
} else if (props.samples.length > 0) {
|
|
55
212
|
autoGroup.rawText.value = props.samples.join('\n')
|
|
213
|
+
autoGroup.inputMode.value = 'paste'
|
|
214
|
+
autoGroup.parseInput()
|
|
215
|
+
currentStep.value = 1
|
|
216
|
+
} else {
|
|
217
|
+
// Cold open: default to Paste so users can type their own sample
|
|
218
|
+
// list immediately. CSV upload is one click away in the mode toggle.
|
|
219
|
+
autoGroup.inputMode.value = 'paste'
|
|
56
220
|
}
|
|
57
221
|
}
|
|
58
222
|
}, { immediate: true })
|
|
59
223
|
|
|
60
|
-
//
|
|
224
|
+
// Wizard steps: three steps, unconditional
|
|
61
225
|
const allSteps: WizardStep[] = [
|
|
62
226
|
{ id: 'input', label: 'Input' },
|
|
63
|
-
{ id: '
|
|
64
|
-
{ id: 'fields', label: 'Fields' },
|
|
227
|
+
{ id: 'workspace', label: 'Workspace' },
|
|
65
228
|
{ id: 'preview', label: 'Preview' },
|
|
66
229
|
]
|
|
67
230
|
|
|
68
|
-
const dynamicSteps = computed<WizardStep[]>(() =>
|
|
69
|
-
if (autoGroup.hasOutliers.value) {
|
|
70
|
-
return allSteps
|
|
71
|
-
}
|
|
72
|
-
return allSteps.filter(s => s.id !== 'outliers')
|
|
73
|
-
})
|
|
231
|
+
const dynamicSteps = computed<WizardStep[]>(() => allSteps)
|
|
74
232
|
|
|
75
233
|
// Map current step index to step ID
|
|
76
234
|
const currentStepId = computed(() => {
|
|
@@ -87,17 +245,16 @@ watch(() => dynamicSteps.value.length, (newLen) => {
|
|
|
87
245
|
|
|
88
246
|
// Step validity
|
|
89
247
|
const inputValid = computed(() => autoGroup.samples.value.length > 0)
|
|
90
|
-
const
|
|
248
|
+
const workspaceValid = computed(() => true) // empty groupBy is allowed; warning is shown in Preview.
|
|
91
249
|
|
|
92
|
-
watch([inputValid,
|
|
250
|
+
watch([inputValid, workspaceValid, dynamicSteps], () => {
|
|
93
251
|
if (!wizardRef.value) return
|
|
94
252
|
const steps = dynamicSteps.value
|
|
95
253
|
for (let i = 0; i < steps.length; i++) {
|
|
96
254
|
const id = steps[i].id
|
|
97
255
|
if (id === 'input') wizardRef.value.setStepValid(i, inputValid.value)
|
|
98
|
-
else if (id === '
|
|
99
|
-
else
|
|
100
|
-
else if (id === 'preview') wizardRef.value.setStepValid(i, true)
|
|
256
|
+
else if (id === 'workspace') wizardRef.value.setStepValid(i, workspaceValid.value)
|
|
257
|
+
else wizardRef.value.setStepValid(i, true)
|
|
101
258
|
}
|
|
102
259
|
}, { immediate: true })
|
|
103
260
|
|
|
@@ -149,11 +306,11 @@ async function fetchExperimentData() {
|
|
|
149
306
|
return
|
|
150
307
|
}
|
|
151
308
|
|
|
152
|
-
// Auto-advance to
|
|
309
|
+
// Auto-advance to Workspace step
|
|
153
310
|
await nextTick()
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
currentStep.value =
|
|
311
|
+
const workspaceIdx = dynamicSteps.value.findIndex(s => s.id === 'workspace')
|
|
312
|
+
if (workspaceIdx >= 0) {
|
|
313
|
+
currentStep.value = workspaceIdx
|
|
157
314
|
}
|
|
158
315
|
} catch (e) {
|
|
159
316
|
experimentError.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'
|
|
@@ -196,8 +353,18 @@ async function processFile(file: File) {
|
|
|
196
353
|
|
|
197
354
|
function clearCsvFile() {
|
|
198
355
|
autoGroup.csvData.value = null
|
|
356
|
+
// Stay in CSV mode after a clear so the user can pick another file
|
|
357
|
+
// without re-clicking the CSV tab. They can switch to Paste manually.
|
|
358
|
+
autoGroup.inputMode.value = 'csv'
|
|
359
|
+
csvFileName.value = ''
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function switchToPasteMode() {
|
|
199
363
|
autoGroup.inputMode.value = 'paste'
|
|
364
|
+
autoGroup.csvData.value = null
|
|
365
|
+
autoGroup.sampleTypeHints.value = []
|
|
200
366
|
csvFileName.value = ''
|
|
367
|
+
csvError.value = null
|
|
201
368
|
}
|
|
202
369
|
|
|
203
370
|
// Experiment metadata field names (for display in experiment mode)
|
|
@@ -215,6 +382,8 @@ const isFirstStep = computed(() => currentStep.value === 0)
|
|
|
215
382
|
:model-value="modelValue"
|
|
216
383
|
title="Smart Group"
|
|
217
384
|
size="lg"
|
|
385
|
+
:close-on-overlay="false"
|
|
386
|
+
:close-on-escape="false"
|
|
218
387
|
@update:model-value="emit('update:modelValue', $event)"
|
|
219
388
|
@close="handleCancel"
|
|
220
389
|
>
|
|
@@ -229,7 +398,10 @@ const isFirstStep = computed(() => currentStep.value === 0)
|
|
|
229
398
|
<!-- Step: Input -->
|
|
230
399
|
<template #step-input>
|
|
231
400
|
<div class="mint-auto-group__input-step">
|
|
232
|
-
<!-- Mode toggle
|
|
401
|
+
<!-- Mode toggle. Paste is the self-designed path (type your
|
|
402
|
+
own sample list); CSV is the structured-upload path; the
|
|
403
|
+
Experiment tab only appears when the parent component
|
|
404
|
+
owns an experimentId / designData. -->
|
|
233
405
|
<div class="mint-auto-group__mode-toggle">
|
|
234
406
|
<BaseButton
|
|
235
407
|
v-if="experimentId || designData"
|
|
@@ -243,7 +415,7 @@ const isFirstStep = computed(() => currentStep.value === 0)
|
|
|
243
415
|
<BaseButton
|
|
244
416
|
:variant="autoGroup.inputMode.value === 'paste' ? 'primary' : 'secondary'"
|
|
245
417
|
size="sm"
|
|
246
|
-
@click="
|
|
418
|
+
@click="switchToPasteMode"
|
|
247
419
|
>
|
|
248
420
|
Paste
|
|
249
421
|
</BaseButton>
|
|
@@ -265,7 +437,7 @@ const isFirstStep = computed(() => currentStep.value === 0)
|
|
|
265
437
|
<div v-else-if="experimentError" class="mint-auto-group__experiment-error">
|
|
266
438
|
<p>{{ experimentError }}</p>
|
|
267
439
|
<p>
|
|
268
|
-
Switch to <button type="button" class="mint-auto-group__link-btn" @click="
|
|
440
|
+
Switch to <button type="button" class="mint-auto-group__link-btn" @click="switchToPasteMode">Paste</button>
|
|
269
441
|
or <button type="button" class="mint-auto-group__link-btn" @click="autoGroup.inputMode.value = 'csv'">CSV</button> mode instead.
|
|
270
442
|
</p>
|
|
271
443
|
</div>
|
|
@@ -288,7 +460,8 @@ const isFirstStep = computed(() => currentStep.value === 0)
|
|
|
288
460
|
</div>
|
|
289
461
|
</div>
|
|
290
462
|
|
|
291
|
-
<!-- Paste mode
|
|
463
|
+
<!-- Paste mode: free-form textarea for users to type or paste
|
|
464
|
+
their own sample list (the "self-designed data" path). -->
|
|
292
465
|
<div v-if="autoGroup.inputMode.value === 'paste'" class="mint-auto-group__paste">
|
|
293
466
|
<textarea
|
|
294
467
|
v-model="autoGroup.rawText.value"
|
|
@@ -347,134 +520,240 @@ const isFirstStep = computed(() => currentStep.value === 0)
|
|
|
347
520
|
</button>
|
|
348
521
|
</div>
|
|
349
522
|
</div>
|
|
350
|
-
</div>
|
|
351
|
-
</template>
|
|
352
523
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
<div class="mint-auto-group__outlier-batch">
|
|
364
|
-
<BaseButton size="sm" variant="secondary" @click="autoGroup.setAllOutlierActions('include')">
|
|
365
|
-
Include All
|
|
366
|
-
</BaseButton>
|
|
367
|
-
<BaseButton size="sm" variant="secondary" @click="autoGroup.setAllOutlierActions('exclude')">
|
|
368
|
-
Exclude All
|
|
369
|
-
</BaseButton>
|
|
370
|
-
</div>
|
|
371
|
-
|
|
372
|
-
<div class="mint-auto-group__outlier-list">
|
|
373
|
-
<div
|
|
374
|
-
v-for="outlier in autoGroup.outliers.value"
|
|
375
|
-
:key="outlier.index"
|
|
376
|
-
class="mint-auto-group__outlier-item"
|
|
377
|
-
>
|
|
378
|
-
<div class="mint-auto-group__outlier-info">
|
|
379
|
-
<code class="mint-auto-group__outlier-name">{{ outlier.sample }}</code>
|
|
380
|
-
<span class="mint-auto-group__outlier-badge">{{ outlier.fieldCount }} fields</span>
|
|
381
|
-
</div>
|
|
382
|
-
<div class="mint-auto-group__outlier-actions">
|
|
383
|
-
<button
|
|
384
|
-
type="button"
|
|
385
|
-
:class="['mint-auto-group__action-btn', outlier.action === 'include' ? 'mint-auto-group__action-btn--active' : '']"
|
|
386
|
-
@click="autoGroup.setOutlierAction(outlier.index, 'include')"
|
|
387
|
-
>Include</button>
|
|
388
|
-
<button
|
|
389
|
-
type="button"
|
|
390
|
-
:class="['mint-auto-group__action-btn', outlier.action === 'exclude' ? 'mint-auto-group__action-btn--active mint-auto-group__action-btn--exclude' : '']"
|
|
391
|
-
@click="autoGroup.setOutlierAction(outlier.index, 'exclude')"
|
|
392
|
-
>Exclude</button>
|
|
393
|
-
<button
|
|
394
|
-
type="button"
|
|
395
|
-
:class="['mint-auto-group__action-btn', outlier.action === 'qc' ? 'mint-auto-group__action-btn--active mint-auto-group__action-btn--qc' : '']"
|
|
396
|
-
@click="autoGroup.setOutlierAction(outlier.index, 'qc')"
|
|
397
|
-
>QC</button>
|
|
398
|
-
</div>
|
|
524
|
+
<div class="mint-auto-group__template" v-if="autoGroup.canDownloadTemplate.value">
|
|
525
|
+
<div class="mint-auto-group__template-label">Or hand-edit in Excel:</div>
|
|
526
|
+
<div class="mint-auto-group__template-actions">
|
|
527
|
+
<BaseButton size="sm" variant="ghost" @click="autoGroup.downloadTemplate('prefilled', 'csv')">
|
|
528
|
+
Download pre-filled CSV
|
|
529
|
+
</BaseButton>
|
|
530
|
+
<BaseButton size="sm" variant="ghost" @click="autoGroup.downloadTemplate('blank', 'csv')">
|
|
531
|
+
Download blank CSV
|
|
532
|
+
</BaseButton>
|
|
399
533
|
</div>
|
|
400
534
|
</div>
|
|
401
535
|
</div>
|
|
402
536
|
</template>
|
|
403
537
|
|
|
404
|
-
<!-- Step:
|
|
405
|
-
<template #step-
|
|
406
|
-
<div class="mint-auto-
|
|
407
|
-
<
|
|
408
|
-
<
|
|
409
|
-
|
|
410
|
-
|
|
538
|
+
<!-- Step: Workspace -->
|
|
539
|
+
<template #step-workspace>
|
|
540
|
+
<div class="mint-auto-group__workspace">
|
|
541
|
+
<aside class="mint-auto-group__classes" aria-label="Detected sample classes">
|
|
542
|
+
<h3>Detected classes</h3>
|
|
543
|
+
<button
|
|
544
|
+
v-for="cls in autoGroup.classes.value"
|
|
545
|
+
:key="classKey(cls)"
|
|
546
|
+
type="button"
|
|
411
547
|
:class="[
|
|
412
|
-
'mint-auto-
|
|
413
|
-
autoGroup.
|
|
548
|
+
'mint-auto-group__class',
|
|
549
|
+
autoGroup.activeClassKey.value === classKey(cls) ? 'mint-auto-group__class--active' : '',
|
|
550
|
+
cls.kind !== 'biological' && cls.kind !== 'unknown' ? 'mint-auto-group__class--qc' : '',
|
|
414
551
|
]"
|
|
552
|
+
@click="autoGroup.activeClassKey.value = classKey(cls)"
|
|
415
553
|
>
|
|
416
|
-
<
|
|
417
|
-
|
|
554
|
+
<span class="mint-auto-group__class-dot" />
|
|
555
|
+
<span class="mint-auto-group__class-label">{{ cls.label }}</span>
|
|
556
|
+
<span class="mint-auto-group__class-count">{{ cls.members.length }}</span>
|
|
557
|
+
</button>
|
|
558
|
+
|
|
559
|
+
<div class="mint-auto-group__routing">
|
|
560
|
+
<h4 class="mint-auto-group__routing-head">QC routing</h4>
|
|
561
|
+
<label class="mint-auto-group__routing-radio">
|
|
562
|
+
<input
|
|
563
|
+
type="radio"
|
|
564
|
+
name="qc-routing"
|
|
565
|
+
:checked="qcRouting === 'overlay'"
|
|
566
|
+
@change="setQcRouting('overlay')"
|
|
567
|
+
/>
|
|
568
|
+
<span>Overlay <small>preview only</small></span>
|
|
569
|
+
</label>
|
|
570
|
+
<label class="mint-auto-group__routing-radio">
|
|
571
|
+
<input
|
|
572
|
+
type="radio"
|
|
573
|
+
name="qc-routing"
|
|
574
|
+
:checked="qcRouting === 'exclude'"
|
|
575
|
+
@change="setQcRouting('exclude')"
|
|
576
|
+
/>
|
|
577
|
+
<span>Exclude entirely</span>
|
|
578
|
+
</label>
|
|
579
|
+
<label class="mint-auto-group__routing-radio">
|
|
580
|
+
<input
|
|
581
|
+
type="radio"
|
|
582
|
+
name="qc-routing"
|
|
583
|
+
:checked="qcRouting === 'mix'"
|
|
584
|
+
@change="setQcRouting('mix')"
|
|
585
|
+
/>
|
|
586
|
+
<span>Mix into groups</span>
|
|
587
|
+
</label>
|
|
588
|
+
</div>
|
|
589
|
+
</aside>
|
|
590
|
+
|
|
591
|
+
<section class="mint-auto-group__schema" aria-label="Schema editor">
|
|
592
|
+
<div class="mint-auto-group__stats">
|
|
593
|
+
<div class="mint-auto-group__stat mint-auto-group__stat--primary">
|
|
594
|
+
<div class="mint-auto-group__stat-v">{{ groupStats.count }}</div>
|
|
595
|
+
<div class="mint-auto-group__stat-l">Groups</div>
|
|
596
|
+
<div class="mint-auto-group__stat-sub">
|
|
597
|
+
<span v-if="groupKeyNames.length === 0" class="mint-auto-group__stat-empty">Toggle <strong>Use</strong> on a column to start splitting</span>
|
|
598
|
+
<template v-else>
|
|
599
|
+
<span
|
|
600
|
+
v-for="name in groupKeyNames"
|
|
601
|
+
:key="name"
|
|
602
|
+
class="mint-auto-group__group-chip"
|
|
603
|
+
>{{ name }}</span>
|
|
604
|
+
</template>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
<div class="mint-auto-group__stat">
|
|
608
|
+
<div class="mint-auto-group__stat-v">{{ activeMemberCount }}</div>
|
|
609
|
+
<div class="mint-auto-group__stat-l">Samples</div>
|
|
610
|
+
<div class="mint-auto-group__stat-sub mint-auto-group__stat-sub--muted">{{ activeSchemaLabel }} · {{ activeColumnCount }} token positions</div>
|
|
611
|
+
</div>
|
|
612
|
+
<div class="mint-auto-group__stat">
|
|
613
|
+
<div class="mint-auto-group__stat-v">{{ groupStats.avg }}</div>
|
|
614
|
+
<div class="mint-auto-group__stat-l">Avg / group</div>
|
|
615
|
+
<div class="mint-auto-group__stat-sub mint-auto-group__stat-sub--muted">
|
|
616
|
+
<template v-if="groupStats.count > 0">Min {{ groupStats.min }} · Max {{ groupStats.max }}</template>
|
|
617
|
+
<template v-else>—</template>
|
|
618
|
+
</div>
|
|
619
|
+
</div>
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<div v-if="autoGroup.suggestions.value.length > 0" class="mint-auto-group__suggest">
|
|
623
|
+
<strong>Suggested merge:</strong>
|
|
624
|
+
columns {{ autoGroup.suggestions.value[0].columnIndices.map((i: number) => i + 1).join(' + ') }}
|
|
625
|
+
always co-occur — combine into <code>{{ autoGroup.suggestions.value[0].proposedName }}</code>?
|
|
626
|
+
<button type="button" class="mint-auto-group__suggest-btn" @click="acceptSuggestion(autoGroup.suggestions.value[0])">
|
|
627
|
+
Accept · merge
|
|
628
|
+
</button>
|
|
629
|
+
<button type="button" class="mint-auto-group__suggest-btn mint-auto-group__suggest-btn--ghost" @click="dismissSuggestion(autoGroup.suggestions.value[0])">
|
|
630
|
+
Dismiss
|
|
631
|
+
</button>
|
|
632
|
+
</div>
|
|
633
|
+
|
|
634
|
+
<div class="mint-auto-group__token-table" role="table" aria-label="Token positions">
|
|
635
|
+
<div class="mint-auto-group__token-row mint-auto-group__token-row--head" role="row">
|
|
636
|
+
<span role="columnheader" aria-label="Token number">#</span>
|
|
637
|
+
<span role="columnheader">Name</span>
|
|
638
|
+
<span role="columnheader">Role</span>
|
|
639
|
+
<span role="columnheader" class="mint-auto-group__token-num" aria-label="Unique value count">Unique</span>
|
|
640
|
+
<span role="columnheader">Preview</span>
|
|
641
|
+
<span role="columnheader" class="mint-auto-group__token-use-head">Use</span>
|
|
642
|
+
<span role="columnheader" aria-label="More actions" />
|
|
643
|
+
</div>
|
|
644
|
+
<button
|
|
645
|
+
v-for="col in (autoGroup.activeSchema.value?.columns ?? [])"
|
|
646
|
+
:key="col.index"
|
|
647
|
+
type="button"
|
|
648
|
+
role="row"
|
|
649
|
+
:class="[
|
|
650
|
+
'mint-auto-group__token-row',
|
|
651
|
+
openPopoverIdx === col.index ? 'mint-auto-group__token-row--active' : '',
|
|
652
|
+
]"
|
|
653
|
+
@click="openPopoverIdx = col.index"
|
|
654
|
+
>
|
|
655
|
+
<span class="mint-auto-group__token-ix" role="cell">{{ col.index + 1 }}</span>
|
|
656
|
+
<span class="mint-auto-group__token-nm" role="cell">{{ col.displayName ?? col.name }}</span>
|
|
657
|
+
<span
|
|
658
|
+
:class="['mint-auto-group__role', `mint-auto-group__role--${col.role}`]"
|
|
659
|
+
role="cell"
|
|
660
|
+
>{{ roleLabel(col.role) }}</span>
|
|
661
|
+
<span class="mint-auto-group__token-unique" role="cell">{{ col.cardinality }}</span>
|
|
662
|
+
<span class="mint-auto-group__token-preview" role="cell">
|
|
663
|
+
{{ col.uniqueValues.slice(0, 4).join(', ') }}<span
|
|
664
|
+
v-if="col.uniqueValues.length > 4"
|
|
665
|
+
class="mint-auto-group__token-more"
|
|
666
|
+
> +{{ col.uniqueValues.length - 4 }} more</span>
|
|
667
|
+
</span>
|
|
668
|
+
<label
|
|
669
|
+
class="mint-auto-group__token-use"
|
|
670
|
+
role="cell"
|
|
671
|
+
@click.stop
|
|
672
|
+
>
|
|
418
673
|
<input
|
|
419
674
|
type="checkbox"
|
|
420
|
-
:checked="autoGroup.
|
|
421
|
-
@change="autoGroup.
|
|
675
|
+
:checked="(autoGroup.activeSchema.value?.groupBy ?? []).includes(col.index)"
|
|
676
|
+
@change="autoGroup.toggleGroupBy(autoGroup.activeClassKey.value, col.index)"
|
|
422
677
|
/>
|
|
423
|
-
<span class="mint-auto-group__field-toggle-label">Group by</span>
|
|
424
678
|
</label>
|
|
425
|
-
<span class="mint-auto-
|
|
679
|
+
<span class="mint-auto-group__token-trigger" role="cell" aria-hidden="true">⋯</span>
|
|
680
|
+
</button>
|
|
681
|
+
</div>
|
|
682
|
+
</section>
|
|
683
|
+
|
|
684
|
+
<div
|
|
685
|
+
v-if="openPopoverIdx !== null && activeColumn"
|
|
686
|
+
class="mint-auto-group__popover"
|
|
687
|
+
@click.stop
|
|
688
|
+
>
|
|
689
|
+
<div class="mint-auto-group__popover-head">
|
|
690
|
+
<strong>{{ activeColumn.displayName ?? activeColumn.name }}</strong>
|
|
691
|
+
<span class="mono">{{ activeColumn.cardinality }} unique</span>
|
|
692
|
+
<button type="button" class="mint-auto-group__popover-close" @click="openPopoverIdx = null">×</button>
|
|
693
|
+
</div>
|
|
694
|
+
|
|
695
|
+
<div class="mint-auto-group__popover-section">
|
|
696
|
+
<div class="mint-auto-group__popover-label">Role</div>
|
|
697
|
+
<div class="mint-auto-group__pills">
|
|
698
|
+
<button
|
|
699
|
+
v-for="r in (['factor','replicate','run-order','numeric','constant','ignore'] as ColumnRole[])"
|
|
700
|
+
:key="r"
|
|
701
|
+
type="button"
|
|
702
|
+
:class="['mint-auto-group__pill-btn', activeColumn.role === r ? 'mint-auto-group__pill-btn--on' : '']"
|
|
703
|
+
@click="autoGroup.setRole(autoGroup.activeClassKey.value, activeColumn.index, r)"
|
|
704
|
+
>{{ roleLabel(r) }}</button>
|
|
426
705
|
</div>
|
|
706
|
+
</div>
|
|
427
707
|
|
|
708
|
+
<div class="mint-auto-group__popover-section">
|
|
709
|
+
<div class="mint-auto-group__popover-label">Rename</div>
|
|
428
710
|
<BaseInput
|
|
429
|
-
:model-value="
|
|
430
|
-
|
|
431
|
-
class="mint-auto-group__field-name-input"
|
|
432
|
-
@update:model-value="autoGroup.renameField(col.index, String($event ?? ''))"
|
|
711
|
+
:model-value="activeColumn.displayName ?? activeColumn.name"
|
|
712
|
+
@update:model-value="renameActive(String($event ?? ''))"
|
|
433
713
|
/>
|
|
714
|
+
</div>
|
|
434
715
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
class="mint-auto-
|
|
444
|
-
|
|
716
|
+
<div class="mint-auto-group__popover-section">
|
|
717
|
+
<div class="mint-auto-group__popover-label">Values ({{ activeColumn.uniqueValues.length }})</div>
|
|
718
|
+
<div class="mint-auto-group__values-list">
|
|
719
|
+
<div
|
|
720
|
+
v-for="v in activeColumn.uniqueValues"
|
|
721
|
+
:key="v"
|
|
722
|
+
class="mint-auto-group__value-row"
|
|
723
|
+
>
|
|
724
|
+
<span class="mint-auto-group__value-src">{{ v }} →</span>
|
|
725
|
+
<input
|
|
726
|
+
class="mint-auto-group__value-input"
|
|
727
|
+
:value="activeColumn.ops?.alias?.[v] ?? v"
|
|
728
|
+
@change="setAlias(v, ($event.target as HTMLInputElement).value)"
|
|
729
|
+
/>
|
|
730
|
+
<label class="mint-auto-group__value-excl">
|
|
731
|
+
<input
|
|
732
|
+
type="checkbox"
|
|
733
|
+
:checked="activeColumn.ops?.exclude?.includes(v) ?? false"
|
|
734
|
+
@change="toggleExclude(v)"
|
|
735
|
+
/>
|
|
736
|
+
excl
|
|
737
|
+
</label>
|
|
738
|
+
</div>
|
|
445
739
|
</div>
|
|
446
740
|
</div>
|
|
447
|
-
</div>
|
|
448
|
-
|
|
449
|
-
<div v-if="autoGroup.enabledFields.value.size > 0" class="mint-auto-group__field-summary">
|
|
450
|
-
Grouping by:
|
|
451
|
-
<strong>{{
|
|
452
|
-
autoGroup.effectiveColumns.value
|
|
453
|
-
.filter(c => autoGroup.enabledFields.value.has(c.index))
|
|
454
|
-
.map(c => c.name)
|
|
455
|
-
.join(' / ')
|
|
456
|
-
}}</strong>
|
|
457
|
-
</div>
|
|
458
741
|
|
|
459
|
-
|
|
460
|
-
|
|
742
|
+
<div class="mint-auto-group__popover-section">
|
|
743
|
+
<button type="button" class="mint-auto-group__link" @click="mergeWithNext">▸ Merge with next column</button>
|
|
744
|
+
</div>
|
|
461
745
|
</div>
|
|
462
746
|
</div>
|
|
463
747
|
</template>
|
|
464
748
|
|
|
465
749
|
<!-- Step: Preview -->
|
|
466
750
|
<template #step-preview>
|
|
467
|
-
<div class="mint-auto-group__preview
|
|
751
|
+
<div class="mint-auto-group__preview">
|
|
468
752
|
<div class="mint-auto-group__preview-summary">
|
|
469
|
-
<
|
|
470
|
-
|
|
471
|
-
</
|
|
472
|
-
<
|
|
473
|
-
<strong>{{ autoGroup.groups.value.reduce((sum, g) => sum + g.samples.length, 0) }}</strong> samples
|
|
474
|
-
</span>
|
|
475
|
-
<span v-if="autoGroup.excludedSamples.value.length > 0" class="mint-auto-group__preview-stat mint-auto-group__preview-stat--excluded">
|
|
476
|
-
<strong>{{ autoGroup.excludedSamples.value.length }}</strong> excluded
|
|
477
|
-
</span>
|
|
753
|
+
<strong>{{ autoGroup.groups.value.length }}</strong> groups ·
|
|
754
|
+
<strong>{{ totalGrouped }}</strong> samples ·
|
|
755
|
+
<strong>{{ totalQc }}</strong> QC kept aside ·
|
|
756
|
+
<strong>{{ autoGroup.excludedSamples.value.length }}</strong> excluded
|
|
478
757
|
</div>
|
|
479
758
|
|
|
480
759
|
<AlertBox
|
|
@@ -482,44 +761,48 @@ const isFirstStep = computed(() => currentStep.value === 0)
|
|
|
482
761
|
type="warning"
|
|
483
762
|
title="Each group has only one sample"
|
|
484
763
|
>
|
|
485
|
-
|
|
764
|
+
Group key produced a unique key per row. Go back and disable any column whose
|
|
765
|
+
values are unique per sample.
|
|
486
766
|
</AlertBox>
|
|
487
767
|
|
|
488
|
-
<div class="mint-auto-group__preview-
|
|
489
|
-
<
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
:style="{
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
768
|
+
<div class="mint-auto-group__preview-grid">
|
|
769
|
+
<div class="mint-auto-group__preview-panel">
|
|
770
|
+
<h4>Experimental groups</h4>
|
|
771
|
+
<details
|
|
772
|
+
v-for="group in (autoGroup.result.value.experimentalGroups ?? [])"
|
|
773
|
+
:key="group.name"
|
|
774
|
+
class="mint-auto-group__preview-group"
|
|
775
|
+
>
|
|
776
|
+
<summary>
|
|
777
|
+
<span class="mint-auto-group__preview-dot" :style="{ background: group.color }" />
|
|
778
|
+
<span>{{ group.name }}</span>
|
|
779
|
+
<span class="mint-auto-group__preview-count">{{ group.samples.length }}</span>
|
|
780
|
+
</summary>
|
|
781
|
+
<div class="mint-auto-group__preview-samples">
|
|
782
|
+
<span
|
|
783
|
+
v-for="s in group.samples"
|
|
784
|
+
:key="s"
|
|
785
|
+
class="mint-auto-group__preview-sample"
|
|
786
|
+
>{{ s }}</span>
|
|
787
|
+
</div>
|
|
788
|
+
</details>
|
|
789
|
+
</div>
|
|
790
|
+
|
|
791
|
+
<div class="mint-auto-group__preview-panel">
|
|
792
|
+
<h4>QC kept aside (overlay)</h4>
|
|
793
|
+
<div class="mint-auto-group__qc-chips">
|
|
506
794
|
<span
|
|
507
|
-
v-for="
|
|
508
|
-
:key="
|
|
509
|
-
class="mint-auto-
|
|
510
|
-
>
|
|
795
|
+
v-for="g in (autoGroup.qcGroups.value ?? [])"
|
|
796
|
+
:key="g.name"
|
|
797
|
+
class="mint-auto-group__qc-chip"
|
|
798
|
+
>
|
|
799
|
+
{{ g.name }} <code>×{{ g.samples.length }}</code>
|
|
800
|
+
</span>
|
|
511
801
|
</div>
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
<div class="mint-auto-group__preview-excluded-title">Excluded Samples</div>
|
|
517
|
-
<div class="mint-auto-group__preview-excluded-list">
|
|
518
|
-
<span
|
|
519
|
-
v-for="sample in autoGroup.excludedSamples.value"
|
|
520
|
-
:key="sample"
|
|
521
|
-
class="mint-auto-group__preview-sample mint-auto-group__preview-sample--excluded"
|
|
522
|
-
>{{ sample }}</span>
|
|
802
|
+
<details class="mint-auto-group__fingerprint">
|
|
803
|
+
<summary>Schema fingerprint</summary>
|
|
804
|
+
<pre>{{ JSON.stringify(autoGroup.fingerprint.value, null, 2) }}</pre>
|
|
805
|
+
</details>
|
|
523
806
|
</div>
|
|
524
807
|
</div>
|
|
525
808
|
</div>
|