@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
|
@@ -1,650 +1,496 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
2
|
import type {
|
|
3
|
+
AutoGroupResult,
|
|
4
|
+
ClassDisposition,
|
|
5
|
+
ClassSchema,
|
|
6
|
+
ColumnInfo,
|
|
7
|
+
ColumnRole,
|
|
3
8
|
InputMode,
|
|
9
|
+
MergeSuggestion,
|
|
10
|
+
NumericBinning,
|
|
4
11
|
OutlierAction,
|
|
5
12
|
OutlierInfo,
|
|
6
|
-
ColumnInfo,
|
|
7
|
-
MetadataRow,
|
|
8
|
-
AutoGroupResult,
|
|
9
13
|
ParsedCsvData,
|
|
14
|
+
SampleClass,
|
|
15
|
+
SchemaFingerprint,
|
|
16
|
+
ValueOps,
|
|
10
17
|
} from '../types/auto-group'
|
|
11
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
buildClassSchema,
|
|
20
|
+
classKey,
|
|
21
|
+
composeGroups,
|
|
22
|
+
composeTemplate,
|
|
23
|
+
detectClass,
|
|
24
|
+
expandGroupsWithReplicates,
|
|
25
|
+
findMerges,
|
|
26
|
+
pickPrimaryDelimiter,
|
|
27
|
+
preGroupReplicates,
|
|
28
|
+
restoreFingerprint,
|
|
29
|
+
serializeFingerprint,
|
|
30
|
+
splitMulti,
|
|
31
|
+
type ReplicatePreGrouping,
|
|
32
|
+
type TemplateOptions,
|
|
33
|
+
} from './autoGroup'
|
|
12
34
|
import { unwrapExperimentDesignData } from './experimentDesignData'
|
|
13
35
|
|
|
14
|
-
export
|
|
15
|
-
'#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6',
|
|
16
|
-
'#EC4899', '#06B6D4', '#84CC16', '#F97316', '#6366F1',
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
const DELIMITER_CANDIDATES = ['_', '-', '.'] as const
|
|
36
|
+
export { DEFAULT_COLORS } from './autoGroup'
|
|
20
37
|
|
|
21
|
-
|
|
38
|
+
let deprecationWarned = false
|
|
39
|
+
let outlierDeprecationWarned = false
|
|
40
|
+
function warnDeprecated(api: string) {
|
|
41
|
+
if (deprecationWarned) return
|
|
42
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') return
|
|
43
|
+
console.warn(
|
|
44
|
+
`[useAutoGroup] ${api} is deprecated; use the new class-first API (setClassDisposition / toggleGroupBy / setValueOps).`,
|
|
45
|
+
)
|
|
46
|
+
deprecationWarned = true
|
|
47
|
+
}
|
|
22
48
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
dominantFieldCount: number
|
|
26
|
-
minFieldCount: number
|
|
27
|
-
consistency: number
|
|
28
|
-
} {
|
|
29
|
-
if (lines.length === 0) {
|
|
30
|
-
return { delimiter: '_', dominantFieldCount: 1, minFieldCount: 1, consistency: 0 }
|
|
31
|
-
}
|
|
49
|
+
// Re-export `parseCSV` for callers that imported it from this file.
|
|
50
|
+
export { parseCSV } from './autoGroup/csv-shim'
|
|
32
51
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
export function useAutoGroup() {
|
|
53
|
+
const inputMode = ref<InputMode>('paste')
|
|
54
|
+
const rawText = ref('')
|
|
55
|
+
const csvData = ref<ParsedCsvData | null>(null)
|
|
56
|
+
const sampleTypeHints = ref<(string | undefined)[]>([])
|
|
36
57
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
const classes = ref<SampleClass[]>([])
|
|
59
|
+
const schemas = ref<Record<string, ClassSchema>>({})
|
|
60
|
+
const activeClassKey = ref<string>('')
|
|
40
61
|
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
const samples = computed(() => {
|
|
63
|
+
if (csvData.value) {
|
|
64
|
+
return csvData.value.rows.map(r => r[csvData.value!.sampleColumn] ?? '')
|
|
43
65
|
}
|
|
66
|
+
return rawText.value.split('\n').map(l => l.trim()).filter(l => l.length > 0)
|
|
67
|
+
})
|
|
44
68
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
}
|
|
69
|
+
const tokenized = ref<string[][]>([])
|
|
70
|
+
const delimiter = ref<'_' | '-' | '.'>('_')
|
|
71
|
+
// Replicate pre-grouping: strip injection-number / T<n> / B<n> / Rep<n> markers,
|
|
72
|
+
// collapse samples with the same base name into one "row" for tokenisation,
|
|
73
|
+
// then expand the resulting groups back to the original samples in `result`.
|
|
74
|
+
// null when the paste path hasn't run (CSV path keeps the old behaviour for now).
|
|
75
|
+
const preGrouping = ref<ReplicatePreGrouping | null>(null)
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
consistency > bestConsistency ||
|
|
61
|
-
(consistency === bestConsistency &&
|
|
62
|
-
DELIMITER_CANDIDATES.indexOf(candidate) < DELIMITER_CANDIDATES.indexOf(bestDelimiter as typeof candidate))
|
|
63
|
-
) {
|
|
64
|
-
bestDelimiter = candidate
|
|
65
|
-
bestConsistency = consistency
|
|
66
|
-
bestFieldCount = modeCount
|
|
77
|
+
function parseInput() {
|
|
78
|
+
if ((inputMode.value === 'csv' || inputMode.value === 'experiment') && csvData.value) {
|
|
79
|
+
parseInputFromCsv()
|
|
80
|
+
return
|
|
67
81
|
}
|
|
82
|
+
parseInputFromPaste()
|
|
68
83
|
}
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
function parseInputFromCsv() {
|
|
86
|
+
// CSV input already exposes structured columns, so replicate pre-grouping
|
|
87
|
+
// isn't needed (and would interfere with header-aware role inference).
|
|
88
|
+
preGrouping.value = null
|
|
89
|
+
const csv = csvData.value!
|
|
90
|
+
const nonSampleCols = csv.columns.filter(c => c !== csv.sampleColumn && c !== 'sample_type')
|
|
91
|
+
if (nonSampleCols.length === 0) {
|
|
92
|
+
// No metadata columns — degenerate, single Unknown class
|
|
93
|
+
tokenized.value = csv.rows.map(() => [])
|
|
94
|
+
classes.value = [{
|
|
95
|
+
kind: 'unknown',
|
|
96
|
+
label: 'Unknown',
|
|
97
|
+
members: csv.rows.map((_, i) => i),
|
|
98
|
+
classTagPositions: [],
|
|
99
|
+
disposition: 'group',
|
|
100
|
+
}]
|
|
101
|
+
schemas.value = { unknown: { classKind: 'unknown', columns: [], groupBy: [] } }
|
|
102
|
+
activeClassKey.value = 'unknown'
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
const tokens = csv.rows.map(r => nonSampleCols.map(col => r[col] ?? ''))
|
|
106
|
+
tokenized.value = tokens
|
|
107
|
+
// Class detection: use explicit sample_type hint from csv.rows[i].sample_type
|
|
108
|
+
const hints = csv.rows.map(r => {
|
|
109
|
+
const t = String(r['sample_type'] ?? '').toLowerCase()
|
|
110
|
+
if (t === 'qc') return 'qc'
|
|
111
|
+
if (t === 'blank') return 'blank'
|
|
112
|
+
return undefined
|
|
113
|
+
})
|
|
114
|
+
const detected = detectClass(tokens, { sampleTypeHints: hints })
|
|
115
|
+
classes.value = detected
|
|
116
|
+
// Build schema per class with CSV header names
|
|
117
|
+
const newSchemas: Record<string, ClassSchema> = {}
|
|
118
|
+
for (const cls of detected) {
|
|
119
|
+
const memberTokens = cls.members.map(i => tokens[i])
|
|
120
|
+
const schema = buildClassSchema(memberTokens, cls.kind, cls.subKind, cls.classTagPositions)
|
|
121
|
+
// Override default Token N names with actual CSV header names
|
|
122
|
+
schema.columns = schema.columns.map((c, i) => ({
|
|
123
|
+
...c,
|
|
124
|
+
name: nonSampleCols[i] ?? c.name,
|
|
125
|
+
originalName: nonSampleCols[i],
|
|
126
|
+
}))
|
|
127
|
+
newSchemas[classKey(cls)] = schema
|
|
128
|
+
}
|
|
129
|
+
schemas.value = newSchemas
|
|
130
|
+
if (detected.length > 0) activeClassKey.value = classKey(detected[0])
|
|
79
131
|
}
|
|
80
|
-
}
|
|
81
132
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
133
|
+
function parseInputFromPaste() {
|
|
134
|
+
const lines = samples.value
|
|
135
|
+
if (lines.length === 0) {
|
|
136
|
+
classes.value = []
|
|
137
|
+
schemas.value = {}
|
|
138
|
+
preGrouping.value = null
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
// Replicate pre-pass: strip run-order / T<n> / B<n> / Rep<n> markers, then
|
|
142
|
+
// collapse samples whose stripped names match into one base entry. The
|
|
143
|
+
// tokenize → classify → schema pipeline runs on the *base* names; the
|
|
144
|
+
// `result` computed expands each resulting group back to the original
|
|
145
|
+
// samples via `expandGroupsWithReplicates`.
|
|
146
|
+
const pre = preGroupReplicates(lines)
|
|
147
|
+
preGrouping.value = pre
|
|
148
|
+
|
|
149
|
+
const d = pickPrimaryDelimiter(pre.baseNames)
|
|
150
|
+
delimiter.value = d
|
|
151
|
+
tokenized.value = pre.baseNames.map(l => splitMulti(l, d))
|
|
152
|
+
// sampleTypeHints align to original samples, so pick the first sample's
|
|
153
|
+
// hint per base group as the representative hint.
|
|
154
|
+
const hints = pre.membersByBase.map(members => sampleTypeHints.value[members[0]])
|
|
155
|
+
const detected = detectClass(tokenized.value, { sampleTypeHints: hints })
|
|
156
|
+
classes.value = detected
|
|
157
|
+
const newSchemas: Record<string, ClassSchema> = {}
|
|
158
|
+
for (const cls of detected) {
|
|
159
|
+
const memberTokens = cls.members.map(i => tokenized.value[i])
|
|
160
|
+
newSchemas[classKey(cls)] = buildClassSchema(
|
|
161
|
+
memberTokens,
|
|
162
|
+
cls.kind,
|
|
163
|
+
cls.subKind,
|
|
164
|
+
cls.classTagPositions,
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
schemas.value = newSchemas
|
|
168
|
+
if (detected.length > 0) {
|
|
169
|
+
activeClassKey.value = classKey(detected[0])
|
|
98
170
|
}
|
|
99
171
|
}
|
|
100
172
|
|
|
101
|
-
|
|
102
|
-
}
|
|
173
|
+
const activeSchema = computed<ClassSchema | null>(() => schemas.value[activeClassKey.value] ?? null)
|
|
103
174
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return segments.some(seg => QC_KEYWORDS.has(seg.toLowerCase()))
|
|
114
|
-
? 'qc'
|
|
115
|
-
: 'include'
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// A column is useful for grouping when it has more than one distinct value
|
|
119
|
-
// AND it does not produce a unique value per row (which would create N
|
|
120
|
-
// singleton groups instead of meaningful aggregation).
|
|
121
|
-
export function isUsefulField(field: ColumnInfo, rowCount: number): boolean {
|
|
122
|
-
return field.cardinality > 1 && !(rowCount > 1 && field.cardinality === rowCount)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function extractColumns(
|
|
126
|
-
samples: string[],
|
|
127
|
-
delimiter: string,
|
|
128
|
-
minFieldCount: number,
|
|
129
|
-
): ColumnInfo[] {
|
|
130
|
-
if (samples.length === 0) return []
|
|
131
|
-
|
|
132
|
-
const suffixCount = minFieldCount - 1
|
|
133
|
-
const rows = samples.map(s => {
|
|
134
|
-
const parts = s.split(delimiter)
|
|
135
|
-
const splitAt = parts.length - suffixCount
|
|
136
|
-
return [
|
|
137
|
-
parts.slice(0, splitAt).join(delimiter),
|
|
138
|
-
...parts.slice(splitAt),
|
|
139
|
-
]
|
|
175
|
+
const suggestions = computed<MergeSuggestion[]>(() => {
|
|
176
|
+
const schema = activeSchema.value
|
|
177
|
+
if (!schema) return []
|
|
178
|
+
const activeClass = classes.value.find(
|
|
179
|
+
c => classKey(c) === activeClassKey.value,
|
|
180
|
+
)
|
|
181
|
+
if (!activeClass) return []
|
|
182
|
+
const memberTokens = activeClass.members.map(i => tokenized.value[i])
|
|
183
|
+
return findMerges(schema, memberTokens)
|
|
140
184
|
})
|
|
141
185
|
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
type: col === 0 ? 'prefix' : 'suffix',
|
|
186
|
+
const result = computed<AutoGroupResult>(() => {
|
|
187
|
+
const pre = preGrouping.value
|
|
188
|
+
const composed = composeGroups({
|
|
189
|
+
tokenizedSamples: tokenized.value,
|
|
190
|
+
// After the pre-pass, sampleNames passed to compose are the *base* names
|
|
191
|
+
// (one per replicate group); fall back to the original samples when
|
|
192
|
+
// pre-grouping hasn't run (e.g. the CSV path or empty input).
|
|
193
|
+
sampleNames: pre ? pre.baseNames : samples.value,
|
|
194
|
+
schemas: schemas.value,
|
|
195
|
+
classes: classes.value,
|
|
153
196
|
})
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
inQuotes = !inQuotes
|
|
168
|
-
} else if (char === delimiter && !inQuotes) {
|
|
169
|
-
result.push(current.trim())
|
|
170
|
-
current = ''
|
|
171
|
-
} else {
|
|
172
|
-
current += char
|
|
197
|
+
if (!pre) return composed
|
|
198
|
+
// Expand each group's `samples` from base names back to the original
|
|
199
|
+
// sample names so the Preview panel shows real filenames, not stripped
|
|
200
|
+
// bases, and so `metadata` reflects each input row exactly once.
|
|
201
|
+
return {
|
|
202
|
+
...composed,
|
|
203
|
+
groups: expandGroupsWithReplicates(composed.groups, pre),
|
|
204
|
+
experimentalGroups: composed.experimentalGroups
|
|
205
|
+
? expandGroupsWithReplicates(composed.experimentalGroups, pre)
|
|
206
|
+
: undefined,
|
|
207
|
+
qcGroups: composed.qcGroups
|
|
208
|
+
? expandGroupsWithReplicates(composed.qcGroups, pre)
|
|
209
|
+
: undefined,
|
|
173
210
|
}
|
|
174
|
-
}
|
|
175
|
-
result.push(current.trim())
|
|
211
|
+
})
|
|
176
212
|
|
|
177
|
-
|
|
178
|
-
|
|
213
|
+
const fingerprint = computed<SchemaFingerprint>(() =>
|
|
214
|
+
serializeFingerprint(result.value.schemas ?? []),
|
|
215
|
+
)
|
|
179
216
|
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
217
|
+
const groups = computed(() => result.value.groups)
|
|
218
|
+
const qcGroups = computed(() => result.value.qcGroups ?? [])
|
|
219
|
+
const metadata = computed(() => result.value.metadata)
|
|
220
|
+
const excludedSamples = computed(() => result.value.excludedSamples)
|
|
185
221
|
|
|
186
|
-
//
|
|
187
|
-
const firstLine = lines[0]
|
|
188
|
-
const csvDelimiter = firstLine.includes('\t') ? '\t' : ','
|
|
222
|
+
// ----- Mutators -----
|
|
189
223
|
|
|
190
|
-
|
|
191
|
-
|
|
224
|
+
function setClassDisposition(key: string, disposition: ClassDisposition) {
|
|
225
|
+
const target = classes.value.find(c => classKey(c) === key)
|
|
226
|
+
if (!target || target.disposition === disposition) return
|
|
227
|
+
classes.value = classes.value.map(c =>
|
|
228
|
+
classKey(c) === key ? { ...c, disposition } : c,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
192
231
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
232
|
+
function setClassDispositions(
|
|
233
|
+
predicate: (c: SampleClass) => boolean,
|
|
234
|
+
disposition: ClassDisposition,
|
|
235
|
+
) {
|
|
236
|
+
let changed = false
|
|
237
|
+
const next = classes.value.map(c => {
|
|
238
|
+
if (predicate(c) && c.disposition !== disposition) {
|
|
239
|
+
changed = true
|
|
240
|
+
return { ...c, disposition }
|
|
241
|
+
}
|
|
242
|
+
return c
|
|
199
243
|
})
|
|
200
|
-
|
|
244
|
+
if (changed) classes.value = next
|
|
201
245
|
}
|
|
202
246
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
allSamples: string[],
|
|
213
|
-
columns: ColumnInfo[],
|
|
214
|
-
enabledFields: Set<number>,
|
|
215
|
-
outlierActions: Map<number, OutlierAction>,
|
|
216
|
-
delimiter: string,
|
|
217
|
-
minFieldCount: number,
|
|
218
|
-
): { groups: SampleGroup[]; metadata: MetadataRow[]; excludedSamples: string[] } {
|
|
219
|
-
const excludedSamples: string[] = []
|
|
220
|
-
const qcSamples: string[] = []
|
|
221
|
-
const conformingSamples: string[] = []
|
|
222
|
-
|
|
223
|
-
for (let i = 0; i < allSamples.length; i++) {
|
|
224
|
-
const action = outlierActions.get(i)
|
|
225
|
-
if (action === 'exclude') {
|
|
226
|
-
excludedSamples.push(allSamples[i])
|
|
227
|
-
} else if (action === 'qc') {
|
|
228
|
-
qcSamples.push(allSamples[i])
|
|
229
|
-
} else {
|
|
230
|
-
conformingSamples.push(allSamples[i])
|
|
247
|
+
function toggleGroupBy(key: string, idx: number) {
|
|
248
|
+
const schema = schemas.value[key]
|
|
249
|
+
if (!schema) return
|
|
250
|
+
const set = new Set(schema.groupBy)
|
|
251
|
+
if (set.has(idx)) set.delete(idx)
|
|
252
|
+
else set.add(idx)
|
|
253
|
+
schemas.value = {
|
|
254
|
+
...schemas.value,
|
|
255
|
+
[key]: { ...schema, groupBy: [...set].sort((a, b) => a - b) },
|
|
231
256
|
}
|
|
232
257
|
}
|
|
233
258
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const row = [
|
|
245
|
-
parts.slice(0, splitAt).join(delimiter),
|
|
246
|
-
...parts.slice(splitAt),
|
|
247
|
-
]
|
|
248
|
-
|
|
249
|
-
// Build group key from enabled columns
|
|
250
|
-
const keyParts: string[] = []
|
|
251
|
-
for (const idx of enabledIndices) {
|
|
252
|
-
if (idx < row.length && idx < columns.length) {
|
|
253
|
-
keyParts.push(row[idx])
|
|
259
|
+
function updateColumn(key: string, idx: number, patch: Partial<ColumnInfo>) {
|
|
260
|
+
const schema = schemas.value[key]
|
|
261
|
+
if (!schema) return
|
|
262
|
+
const col = schema.columns.find(c => c.index === idx)
|
|
263
|
+
if (!col) return
|
|
264
|
+
let changed = false
|
|
265
|
+
for (const k of Object.keys(patch) as (keyof ColumnInfo)[]) {
|
|
266
|
+
if ((col as unknown as Record<string, unknown>)[k as string] !== (patch as unknown as Record<string, unknown>)[k as string]) {
|
|
267
|
+
changed = true
|
|
268
|
+
break
|
|
254
269
|
}
|
|
255
270
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
271
|
+
if (!changed) return
|
|
272
|
+
schemas.value = {
|
|
273
|
+
...schemas.value,
|
|
274
|
+
[key]: {
|
|
275
|
+
...schema,
|
|
276
|
+
columns: schema.columns.map(c => (c.index === idx ? { ...c, ...patch } : c)),
|
|
277
|
+
},
|
|
263
278
|
}
|
|
264
|
-
|
|
265
|
-
// Build metadata row with ALL columns
|
|
266
|
-
const fields: Record<string, string> = {}
|
|
267
|
-
for (const col of columns) {
|
|
268
|
-
if (col.index < row.length) {
|
|
269
|
-
fields[col.name] = row[col.index]
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
metadata.push({ sampleName: sample, fields, group: groupKey })
|
|
273
279
|
}
|
|
274
280
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
let colorIdx = 0
|
|
278
|
-
for (const [name, samples] of groupMap) {
|
|
279
|
-
groups.push({
|
|
280
|
-
name,
|
|
281
|
-
color: DEFAULT_COLORS[colorIdx % DEFAULT_COLORS.length],
|
|
282
|
-
samples,
|
|
283
|
-
})
|
|
284
|
-
colorIdx++
|
|
281
|
+
function setRole(key: string, idx: number, role: ColumnRole) {
|
|
282
|
+
updateColumn(key, idx, { role })
|
|
285
283
|
}
|
|
286
284
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
groups.push({
|
|
290
|
-
name: 'QC',
|
|
291
|
-
color: '#6B7280',
|
|
292
|
-
samples: qcSamples,
|
|
293
|
-
})
|
|
294
|
-
for (const sample of qcSamples) {
|
|
295
|
-
metadata.push({ sampleName: sample, fields: {}, group: 'QC' })
|
|
296
|
-
}
|
|
285
|
+
function setColumnName(key: string, idx: number, name: string) {
|
|
286
|
+
updateColumn(key, idx, { displayName: name })
|
|
297
287
|
}
|
|
298
288
|
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Extract sample metadata from raw design_data into ParsedCsvData format.
|
|
304
|
-
*
|
|
305
|
-
* Looks for a `samples` array in the design data. For each sample, merges
|
|
306
|
-
* the `conditions` dict (the metadata table) with the `sample_name` to
|
|
307
|
-
* produce a flat tabular row. QC and blank samples are filtered out by
|
|
308
|
-
* their explicit `sample_type` field.
|
|
309
|
-
*
|
|
310
|
-
* Returns null if no samples with conditions are found.
|
|
311
|
-
*/
|
|
312
|
-
export function extractSamplesFromDesignData(
|
|
313
|
-
rawData: Record<string, unknown>,
|
|
314
|
-
): ParsedCsvData | null {
|
|
315
|
-
const designData = unwrapExperimentDesignData(rawData)
|
|
316
|
-
if (!designData) return null
|
|
317
|
-
|
|
318
|
-
const samples = designData.samples
|
|
319
|
-
if (!Array.isArray(samples) || samples.length === 0) return null
|
|
320
|
-
|
|
321
|
-
// Single pass: filter QC/blank and collect all condition keys
|
|
322
|
-
const allConditionKeys: string[] = []
|
|
323
|
-
const keySet = new Set<string>()
|
|
324
|
-
const filteredSamples: Record<string, unknown>[] = []
|
|
325
|
-
|
|
326
|
-
for (const sample of samples) {
|
|
327
|
-
const sampleType = String((sample as Record<string, unknown>).sample_type ?? 'sample').toLowerCase()
|
|
328
|
-
if (sampleType === 'qc' || sampleType === 'blank') continue
|
|
329
|
-
|
|
330
|
-
filteredSamples.push(sample as Record<string, unknown>)
|
|
331
|
-
const conditions = (sample as Record<string, unknown>).conditions as Record<string, string> | undefined
|
|
332
|
-
if (conditions && typeof conditions === 'object') {
|
|
333
|
-
for (const key of Object.keys(conditions)) {
|
|
334
|
-
if (!keySet.has(key)) {
|
|
335
|
-
keySet.add(key)
|
|
336
|
-
allConditionKeys.push(key)
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
289
|
+
function setValueOps(key: string, idx: number, ops: ValueOps) {
|
|
290
|
+
updateColumn(key, idx, { ops })
|
|
340
291
|
}
|
|
341
292
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const rows: Record<string, string>[] = filteredSamples.map((sample) => {
|
|
346
|
-
const conditions = (sample.conditions as Record<string, string>) ?? {}
|
|
347
|
-
const row: Record<string, string> = {
|
|
348
|
-
sample_name: String(sample.sample_name ?? ''),
|
|
349
|
-
}
|
|
350
|
-
for (const key of allConditionKeys) {
|
|
351
|
-
row[key] = conditions[key] ?? ''
|
|
352
|
-
}
|
|
353
|
-
return row
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
return { columns, rows, sampleColumn: 'sample_name', delimiter: ',' }
|
|
357
|
-
}
|
|
293
|
+
function setBinning(key: string, idx: number, binning: NumericBinning) {
|
|
294
|
+
updateColumn(key, idx, { binning })
|
|
295
|
+
}
|
|
358
296
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const sampleName = row[csvData.sampleColumn]
|
|
372
|
-
|
|
373
|
-
// Build group key from enabled CSV column values
|
|
374
|
-
// Use originalName for CSV row lookup (survives user renames), display name for group key
|
|
375
|
-
const keyParts = enabledCols.map(col => row[col.originalName ?? col.name])
|
|
376
|
-
const groupKey = keyParts.join(' / ')
|
|
377
|
-
|
|
378
|
-
const group = groupMap.get(groupKey)
|
|
379
|
-
if (group) {
|
|
380
|
-
group.push(sampleName)
|
|
381
|
-
} else {
|
|
382
|
-
groupMap.set(groupKey, [sampleName])
|
|
297
|
+
function mergeColumns(key: string, indices: number[]) {
|
|
298
|
+
const schema = schemas.value[key]
|
|
299
|
+
if (!schema || indices.length < 2) return
|
|
300
|
+
const sorted = [...indices].sort((a, b) => a - b)
|
|
301
|
+
const start = sorted[0]
|
|
302
|
+
const merged: ColumnInfo = {
|
|
303
|
+
index: start,
|
|
304
|
+
name: `Column ${sorted.map(i => i + 1).join('–')}`,
|
|
305
|
+
sourceIndices: sorted.flatMap(i => schema.columns.find(c => c.index === i)?.sourceIndices ?? [i]),
|
|
306
|
+
uniqueValues: [],
|
|
307
|
+
cardinality: 0,
|
|
308
|
+
role: 'factor',
|
|
383
309
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
310
|
+
const dropped = new Set(sorted)
|
|
311
|
+
schemas.value = {
|
|
312
|
+
...schemas.value,
|
|
313
|
+
[key]: {
|
|
314
|
+
...schema,
|
|
315
|
+
columns: [merged, ...schema.columns.filter(c => !dropped.has(c.index))]
|
|
316
|
+
.sort((a, b) => a.index - b.index),
|
|
317
|
+
groupBy: schema.groupBy.filter(i => !dropped.has(i) || i === start),
|
|
318
|
+
},
|
|
389
319
|
}
|
|
390
|
-
metadata.push({ sampleName, fields, group: groupKey })
|
|
391
320
|
}
|
|
392
321
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
name,
|
|
399
|
-
color: DEFAULT_COLORS[colorIdx % DEFAULT_COLORS.length],
|
|
400
|
-
samples,
|
|
401
|
-
})
|
|
402
|
-
colorIdx++
|
|
322
|
+
function loadFingerprint(fp: SchemaFingerprint) {
|
|
323
|
+
const restored = restoreFingerprint(fp, Object.values(schemas.value))
|
|
324
|
+
const next: Record<string, ClassSchema> = {}
|
|
325
|
+
for (const s of restored) next[classKey({ kind: s.classKind, subKind: s.subKind })] = s
|
|
326
|
+
schemas.value = next
|
|
403
327
|
}
|
|
404
328
|
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// --- Reactive composable ---
|
|
409
|
-
|
|
410
|
-
/** Parses sample names or CSV data to propose group assignments with outlier detection and preview. */
|
|
411
|
-
export function useAutoGroup() {
|
|
412
|
-
const inputMode = ref<InputMode>('paste')
|
|
413
|
-
const rawText = ref('')
|
|
414
|
-
const csvData = ref<ParsedCsvData | null>(null)
|
|
415
|
-
const delimiter = ref('_')
|
|
416
|
-
const dominantFieldCount = ref(1)
|
|
417
|
-
const minFieldCount = ref(1)
|
|
418
|
-
const outliers = ref<OutlierInfo[]>([])
|
|
419
|
-
const fields = ref<ColumnInfo[]>([])
|
|
420
|
-
const fieldNames = ref<Record<number, string>>({})
|
|
421
|
-
const enabledFields = ref(new Set<number>())
|
|
422
|
-
|
|
423
|
-
const isTabularMode = computed(() =>
|
|
424
|
-
(inputMode.value === 'csv' || inputMode.value === 'experiment') && csvData.value !== null,
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
const samples = computed(() => {
|
|
428
|
-
const data = csvData.value
|
|
429
|
-
if (isTabularMode.value && data) {
|
|
430
|
-
return data.rows.map(r => r[data.sampleColumn])
|
|
431
|
-
}
|
|
432
|
-
return rawText.value
|
|
433
|
-
.split('\n')
|
|
434
|
-
.map(l => l.trim())
|
|
435
|
-
.filter(l => l.length > 0)
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
const hasOutliers = computed(() => outliers.value.length > 0)
|
|
439
|
-
|
|
440
|
-
const conformingSamples = computed(() => {
|
|
441
|
-
const outlierIndices = new Set(outliers.value.map(o => o.index))
|
|
442
|
-
return samples.value.filter((_, i) => !outlierIndices.has(i))
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
const outlierActions = computed(() => {
|
|
446
|
-
const map = new Map<number, OutlierAction>()
|
|
447
|
-
for (const o of outliers.value) {
|
|
448
|
-
map.set(o.index, o.action)
|
|
449
|
-
}
|
|
450
|
-
return map
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
const effectiveColumns = computed(() => {
|
|
454
|
-
return fields.value.map(col => ({
|
|
455
|
-
...col,
|
|
456
|
-
name: fieldNames.value[col.index] ?? col.name,
|
|
457
|
-
}))
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
const _computedResult = computed((): AutoGroupResult => {
|
|
461
|
-
if (effectiveColumns.value.length === 0 || enabledFields.value.size === 0) {
|
|
462
|
-
return { groups: [], metadata: [], excludedSamples: [] }
|
|
463
|
-
}
|
|
329
|
+
// ----- Template download -----
|
|
464
330
|
|
|
465
|
-
|
|
466
|
-
return computeGroupsFromCsv(
|
|
467
|
-
csvData.value,
|
|
468
|
-
effectiveColumns.value,
|
|
469
|
-
enabledFields.value,
|
|
470
|
-
)
|
|
471
|
-
}
|
|
331
|
+
const canDownloadTemplate = computed(() => samples.value.length > 0)
|
|
472
332
|
|
|
473
|
-
|
|
333
|
+
function downloadTemplate(mode: 'blank' | 'prefilled' = 'prefilled', format: 'csv' | 'tsv' = 'csv') {
|
|
334
|
+
const { content, filename } = composeTemplate(
|
|
474
335
|
samples.value,
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
delimiter.value,
|
|
479
|
-
minFieldCount.value,
|
|
336
|
+
mode === 'prefilled' ? Object.values(schemas.value) : null,
|
|
337
|
+
mode === 'prefilled' ? classes.value : null,
|
|
338
|
+
{ mode, format, tokenizedSamples: tokenized.value },
|
|
480
339
|
)
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const result = _computedResult
|
|
492
|
-
|
|
493
|
-
function parseInput() {
|
|
494
|
-
if (isTabularMode.value) {
|
|
495
|
-
parseCsvInput()
|
|
496
|
-
} else {
|
|
497
|
-
parsePasteInput()
|
|
498
|
-
}
|
|
340
|
+
const blob = new Blob([content], { type: format === 'tsv' ? 'text/tab-separated-values' : 'text/csv' })
|
|
341
|
+
const url = URL.createObjectURL(blob)
|
|
342
|
+
const a = document.createElement('a')
|
|
343
|
+
a.href = url
|
|
344
|
+
a.download = filename
|
|
345
|
+
document.body.appendChild(a)
|
|
346
|
+
a.click()
|
|
347
|
+
document.body.removeChild(a)
|
|
348
|
+
URL.revokeObjectURL(url)
|
|
499
349
|
}
|
|
500
350
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
// Use dominantFieldCount as outlier threshold so QC/test samples with
|
|
510
|
-
// fewer fields than the majority are correctly flagged
|
|
511
|
-
outliers.value = detectOutliers(lines, analysis.delimiter, analysis.dominantFieldCount)
|
|
512
|
-
|
|
513
|
-
// Apply smart default actions: auto-classify QC/test samples
|
|
514
|
-
for (const outlier of outliers.value) {
|
|
515
|
-
outlier.action = classifyOutlierAction(outlier.sample, analysis.delimiter)
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const conforming = lines.filter(
|
|
519
|
-
(_, i) => !outliers.value.some(o => o.index === i)
|
|
520
|
-
)
|
|
521
|
-
|
|
522
|
-
// Recompute minFieldCount from conforming samples only
|
|
523
|
-
const conformingFieldCounts = conforming.map(s => s.split(analysis.delimiter).length)
|
|
524
|
-
minFieldCount.value = conformingFieldCounts.length > 0
|
|
525
|
-
? Math.min(...conformingFieldCounts)
|
|
526
|
-
: analysis.dominantFieldCount
|
|
527
|
-
|
|
528
|
-
fields.value = extractColumns(conforming, analysis.delimiter, minFieldCount.value)
|
|
529
|
-
|
|
530
|
-
fieldNames.value = {}
|
|
531
|
-
const rowCount = conforming.length
|
|
532
|
-
enabledFields.value = new Set(
|
|
533
|
-
fields.value.filter(f => isUsefulField(f, rowCount)).map(f => f.index),
|
|
351
|
+
/** Test seam — exposes the template content/filename without triggering a browser download. */
|
|
352
|
+
function composeTemplateForTest(opts: Pick<TemplateOptions, 'mode' | 'format'>) {
|
|
353
|
+
return composeTemplate(
|
|
354
|
+
samples.value,
|
|
355
|
+
opts.mode === 'prefilled' ? Object.values(schemas.value) : null,
|
|
356
|
+
opts.mode === 'prefilled' ? classes.value : null,
|
|
357
|
+
{ ...opts, tokenizedSamples: tokenized.value },
|
|
534
358
|
)
|
|
535
359
|
}
|
|
536
360
|
|
|
537
|
-
|
|
538
|
-
if (!csvData.value) return
|
|
539
|
-
|
|
540
|
-
const csv = csvData.value
|
|
541
|
-
const nonSampleCols = csv.columns.filter(c => c !== csv.sampleColumn)
|
|
542
|
-
|
|
543
|
-
fields.value = nonSampleCols.map((col, i) => {
|
|
544
|
-
const values = csv.rows.map(r => r[col])
|
|
545
|
-
const unique = [...new Set(values)]
|
|
546
|
-
return {
|
|
547
|
-
index: i,
|
|
548
|
-
name: col,
|
|
549
|
-
originalName: col,
|
|
550
|
-
uniqueValues: unique,
|
|
551
|
-
cardinality: unique.length,
|
|
552
|
-
}
|
|
553
|
-
})
|
|
361
|
+
// ----- Legacy public refs and shims -----
|
|
554
362
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
363
|
+
const outliers = ref<OutlierInfo[]>([])
|
|
364
|
+
const hasOutliers = computed(() => outliers.value.length > 0)
|
|
365
|
+
const conformingSamples = computed(() => samples.value)
|
|
366
|
+
const dominantFieldCount = computed(() => tokenized.value[0]?.length ?? 0)
|
|
367
|
+
const minFieldCount = computed(() => {
|
|
368
|
+
if (tokenized.value.length === 0) return 0
|
|
369
|
+
return Math.min(...tokenized.value.map(t => t.length))
|
|
370
|
+
})
|
|
371
|
+
const fields = computed(() => activeSchema.value?.columns ?? [])
|
|
372
|
+
const fieldNames = computed<Record<number, string>>(() => {
|
|
373
|
+
const out: Record<number, string> = {}
|
|
374
|
+
for (const c of fields.value) out[c.index] = c.displayName ?? c.name
|
|
375
|
+
return out
|
|
376
|
+
})
|
|
377
|
+
const enabledFields = computed<Set<number>>(() =>
|
|
378
|
+
activeSchema.value ? new Set(activeSchema.value.groupBy) : new Set(),
|
|
379
|
+
)
|
|
380
|
+
const effectiveColumns = fields
|
|
381
|
+
const allSingletons = computed(
|
|
382
|
+
() => groups.value.length > 1 && groups.value.every(g => g.samples.length === 1),
|
|
383
|
+
)
|
|
566
384
|
|
|
567
|
-
function setOutlierAction(
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
385
|
+
function setOutlierAction(_: number, __: OutlierAction) {
|
|
386
|
+
if (!outlierDeprecationWarned) {
|
|
387
|
+
if (typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') {
|
|
388
|
+
console.warn(
|
|
389
|
+
`[useAutoGroup] setOutlierAction is deprecated AND inactive in the class-first redesign. ` +
|
|
390
|
+
`Use setClassDisposition('iqc' | 'eqc' | …, 'group' | 'overlay' | 'exclude') instead.`,
|
|
391
|
+
)
|
|
392
|
+
outlierDeprecationWarned = true
|
|
393
|
+
}
|
|
573
394
|
}
|
|
574
395
|
}
|
|
575
396
|
|
|
576
|
-
function setAllOutlierActions(
|
|
577
|
-
|
|
578
|
-
|
|
397
|
+
function setAllOutlierActions(__: OutlierAction) {
|
|
398
|
+
if (!outlierDeprecationWarned) {
|
|
399
|
+
if (typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') {
|
|
400
|
+
console.warn(
|
|
401
|
+
`[useAutoGroup] setAllOutlierActions is deprecated AND inactive in the class-first redesign. ` +
|
|
402
|
+
`Use setClassDisposition('iqc' | 'eqc' | …, 'group' | 'overlay' | 'exclude') instead.`,
|
|
403
|
+
)
|
|
404
|
+
outlierDeprecationWarned = true
|
|
405
|
+
}
|
|
579
406
|
}
|
|
580
|
-
outliers.value = [...outliers.value]
|
|
581
407
|
}
|
|
582
408
|
|
|
583
409
|
function toggleField(index: number) {
|
|
584
|
-
|
|
585
|
-
if (
|
|
586
|
-
newSet.delete(index)
|
|
587
|
-
} else {
|
|
588
|
-
newSet.add(index)
|
|
589
|
-
}
|
|
590
|
-
enabledFields.value = newSet
|
|
410
|
+
warnDeprecated('toggleField')
|
|
411
|
+
if (activeClassKey.value) toggleGroupBy(activeClassKey.value, index)
|
|
591
412
|
}
|
|
592
413
|
|
|
593
414
|
function renameField(index: number, name: string) {
|
|
594
|
-
|
|
415
|
+
warnDeprecated('renameField')
|
|
416
|
+
if (activeClassKey.value) setColumnName(activeClassKey.value, index, name)
|
|
595
417
|
}
|
|
596
418
|
|
|
597
419
|
function loadExperimentData(rawData: Record<string, unknown>): boolean {
|
|
598
420
|
const parsed = extractSamplesFromDesignData(rawData)
|
|
599
421
|
if (!parsed) return false
|
|
600
|
-
|
|
601
422
|
inputMode.value = 'experiment'
|
|
602
423
|
csvData.value = parsed
|
|
603
|
-
|
|
424
|
+
sampleTypeHints.value = parsed.sampleTypeHints
|
|
425
|
+
parseInput()
|
|
604
426
|
return true
|
|
605
427
|
}
|
|
606
428
|
|
|
607
429
|
function reset() {
|
|
608
430
|
rawText.value = ''
|
|
609
431
|
csvData.value = null
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
432
|
+
classes.value = []
|
|
433
|
+
schemas.value = {}
|
|
434
|
+
activeClassKey.value = ''
|
|
435
|
+
tokenized.value = []
|
|
613
436
|
outliers.value = []
|
|
614
|
-
|
|
615
|
-
fieldNames.value = {}
|
|
616
|
-
enabledFields.value = new Set()
|
|
437
|
+
sampleTypeHints.value = []
|
|
617
438
|
}
|
|
618
439
|
|
|
619
440
|
return {
|
|
620
441
|
// State
|
|
621
|
-
inputMode,
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
delimiter,
|
|
625
|
-
dominantFieldCount,
|
|
626
|
-
minFieldCount,
|
|
627
|
-
outliers,
|
|
628
|
-
fields,
|
|
629
|
-
fieldNames,
|
|
630
|
-
enabledFields,
|
|
442
|
+
inputMode, rawText, csvData, sampleTypeHints,
|
|
443
|
+
classes, schemas, activeClassKey,
|
|
444
|
+
delimiter, dominantFieldCount, minFieldCount,
|
|
631
445
|
// Computed
|
|
632
|
-
samples,
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
effectiveColumns,
|
|
446
|
+
samples, tokenized, activeSchema, suggestions,
|
|
447
|
+
groups, qcGroups, metadata, excludedSamples, result, fingerprint,
|
|
448
|
+
fields, fieldNames, enabledFields, effectiveColumns,
|
|
449
|
+
hasOutliers, outliers, conformingSamples, allSingletons,
|
|
450
|
+
canDownloadTemplate,
|
|
451
|
+
// Mutators
|
|
452
|
+
setClassDisposition, setClassDispositions, toggleGroupBy, setRole, setColumnName, setValueOps, setBinning,
|
|
453
|
+
mergeColumns, loadFingerprint,
|
|
641
454
|
// Actions
|
|
642
|
-
parseInput,
|
|
643
|
-
|
|
644
|
-
setOutlierAction,
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
455
|
+
parseInput, downloadTemplate, composeTemplateForTest, loadExperimentData, reset,
|
|
456
|
+
// Deprecated shims
|
|
457
|
+
setOutlierAction, setAllOutlierActions, toggleField, renameField,
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function extractSamplesFromDesignData(
|
|
462
|
+
rawData: Record<string, unknown>,
|
|
463
|
+
): (ParsedCsvData & { sampleTypeHints: (string | undefined)[] }) | null {
|
|
464
|
+
const designData = unwrapExperimentDesignData(rawData)
|
|
465
|
+
if (!designData) return null
|
|
466
|
+
const samples = designData.samples
|
|
467
|
+
if (!Array.isArray(samples) || samples.length === 0) return null
|
|
468
|
+
const allConditionKeys: string[] = []
|
|
469
|
+
const keySet = new Set<string>()
|
|
470
|
+
const sampleTypeHints: (string | undefined)[] = []
|
|
471
|
+
for (const sample of samples) {
|
|
472
|
+
const rawType = (sample as Record<string, unknown>).sample_type
|
|
473
|
+
sampleTypeHints.push(typeof rawType === 'string' ? rawType.toLowerCase() : undefined)
|
|
474
|
+
const conditions = (sample as Record<string, unknown>).conditions as Record<string, string> | undefined
|
|
475
|
+
if (conditions && typeof conditions === 'object') {
|
|
476
|
+
for (const key of Object.keys(conditions)) {
|
|
477
|
+
if (!keySet.has(key)) {
|
|
478
|
+
keySet.add(key)
|
|
479
|
+
allConditionKeys.push(key)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
649
483
|
}
|
|
484
|
+
if (allConditionKeys.length === 0) return null
|
|
485
|
+
const columns = ['sample_name', ...allConditionKeys, 'sample_type']
|
|
486
|
+
const rows = (samples as Record<string, unknown>[]).map(s => {
|
|
487
|
+
const conditions = (s.conditions as Record<string, string>) ?? {}
|
|
488
|
+
const row: Record<string, string> = {
|
|
489
|
+
sample_name: String(s.sample_name ?? ''),
|
|
490
|
+
sample_type: String(s.sample_type ?? 'sample'),
|
|
491
|
+
}
|
|
492
|
+
for (const key of allConditionKeys) row[key] = conditions[key] ?? ''
|
|
493
|
+
return row
|
|
494
|
+
})
|
|
495
|
+
return { columns, rows, sampleColumn: 'sample_name', delimiter: ',', sampleTypeHints }
|
|
650
496
|
}
|