@opencloning/ui 1.0.0
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/CHANGELOG.md +18 -0
- package/package.json +37 -0
- package/src/components/AppAlerts.jsx +19 -0
- package/src/components/CloningHistory.jsx +40 -0
- package/src/components/DataModelDisplayer.jsx +30 -0
- package/src/components/DescriptionEditor.jsx +68 -0
- package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
- package/src/components/DownloadSequenceFileDialog.jsx +90 -0
- package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
- package/src/components/DraggableDialogPaper.jsx +16 -0
- package/src/components/EditSequenceNameDialog.jsx +90 -0
- package/src/components/ExternalServicesStatusCheck.jsx +92 -0
- package/src/components/HistoryLoadedDialog.jsx +49 -0
- package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
- package/src/components/MainSequenceCheckBox.jsx +83 -0
- package/src/components/MainSequenceEditor.jsx +165 -0
- package/src/components/NetworkNode.jsx +159 -0
- package/src/components/NetworkTree.css +127 -0
- package/src/components/ObjectTable.jsx +24 -0
- package/src/components/OpenCloning.jsx +102 -0
- package/src/components/OverhangsDisplay.jsx +25 -0
- package/src/components/SequenceEditor.jsx +120 -0
- package/src/components/SequenceTab.jsx +14 -0
- package/src/components/TemplateSequence.jsx +38 -0
- package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
- package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
- package/src/components/assembler/AssemblePartWidget.jsx +252 -0
- package/src/components/assembler/Assembler.jsx +273 -0
- package/src/components/assembler/AssemblerPart.jsx +99 -0
- package/src/components/assembler/StopIcon.jsx +34 -0
- package/src/components/assembler/assembler_data2.json +50 -0
- package/src/components/assembler/assembly_component.module.css +81 -0
- package/src/components/assembler/moclo.json +110 -0
- package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
- package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
- package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
- package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
- package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
- package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs.js +36 -0
- package/src/components/assembler/useAssembler.js +71 -0
- package/src/components/dummy/DummyInterface.js +41 -0
- package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
- package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
- package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
- package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
- package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
- package/src/components/eLabFTW/common.js +26 -0
- package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
- package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
- package/src/components/eLabFTW/envValues.js +7 -0
- package/src/components/eLabFTW/utils.js +39 -0
- package/src/components/form/CustomFormHelperText.jsx +10 -0
- package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
- package/src/components/form/EnzymeMultiSelect.jsx +34 -0
- package/src/components/form/GetRequestMultiSelect.jsx +107 -0
- package/src/components/form/LabelWithTooltip.jsx +16 -0
- package/src/components/form/PostRequestSelect.cy.jsx +70 -0
- package/src/components/form/PostRequestSelect.jsx +86 -0
- package/src/components/form/RequestStatusWrapper.jsx +17 -0
- package/src/components/form/RetryAlert.jsx +20 -0
- package/src/components/form/ServerErrorMessage.jsx +10 -0
- package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
- package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
- package/src/components/form/TextFieldValidate.jsx +67 -0
- package/src/components/form/ValidatedTextField.jsx +33 -0
- package/src/components/form/intermediates_disclaimer.svg +181 -0
- package/src/components/navigation/ButtonWithMenu.jsx +43 -0
- package/src/components/navigation/CustomTab.jsx +14 -0
- package/src/components/navigation/FeedbackDialog.jsx +34 -0
- package/src/components/navigation/GithubCornerRight.jsx +29 -0
- package/src/components/navigation/MainAppBar.css +26 -0
- package/src/components/navigation/MainAppBar.jsx +205 -0
- package/src/components/navigation/SelectExampleDialog.jsx +69 -0
- package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
- package/src/components/navigation/TabPanel.jsx +28 -0
- package/src/components/navigation/VersionDialog.jsx +33 -0
- package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
- package/src/components/primers/DownloadPrimersButton.jsx +104 -0
- package/src/components/primers/PrimerForm.css +14 -0
- package/src/components/primers/PrimerForm.jsx +107 -0
- package/src/components/primers/PrimerList.css +46 -0
- package/src/components/primers/PrimerList.cy.jsx +95 -0
- package/src/components/primers/PrimerList.jsx +126 -0
- package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
- package/src/components/primers/PrimerTableRow.jsx +84 -0
- package/src/components/primers/SelectPrimerForm.jsx +66 -0
- package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
- package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
- package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
- package/src/components/primers/import_primers/styles.css +60 -0
- package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
- package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
- package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
- package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
- package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
- package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
- package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
- package/src/components/primers/primer_details/PCRTable.jsx +35 -0
- package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
- package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
- package/src/components/primers/primer_details/TableSection.jsx +17 -0
- package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
- package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
- package/src/components/primers/primer_details/usePCRDetails.js +47 -0
- package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
- package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
- package/src/components/primers/primersToTabularFile.js +49 -0
- package/src/components/primers/primersToTabularFile.test.js +108 -0
- package/src/components/settings/SettingsTab.cy.jsx +267 -0
- package/src/components/settings/SettingsTab.jsx +170 -0
- package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
- package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
- package/src/components/sources/CollectionSource.jsx +97 -0
- package/src/components/sources/FinishedSource.jsx +397 -0
- package/src/components/sources/KnownSourceErrors.jsx +50 -0
- package/src/components/sources/MultipleInputsSelector.jsx +63 -0
- package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
- package/src/components/sources/NewSourceBox.jsx +37 -0
- package/src/components/sources/PCRUnitForm.jsx +102 -0
- package/src/components/sources/SingleInputSelector.jsx +36 -0
- package/src/components/sources/Source.jsx +125 -0
- package/src/components/sources/SourceAnnotation.jsx +44 -0
- package/src/components/sources/SourceAssembly.jsx +201 -0
- package/src/components/sources/SourceBox.css +18 -0
- package/src/components/sources/SourceBox.jsx +60 -0
- package/src/components/sources/SourceCopySequence.jsx +38 -0
- package/src/components/sources/SourceDatabase.jsx +28 -0
- package/src/components/sources/SourceFile.jsx +188 -0
- package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
- package/src/components/sources/SourceGenomeRegion.jsx +486 -0
- package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
- package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
- package/src/components/sources/SourceManuallyTyped.jsx +116 -0
- package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
- package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
- package/src/components/sources/SourceRepositoryId.jsx +409 -0
- package/src/components/sources/SourceRestriction.jsx +41 -0
- package/src/components/sources/SourceReverseComplement.jsx +33 -0
- package/src/components/sources/SourceTypeSelector.jsx +94 -0
- package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
- package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
- package/src/components/sources/repositoryMetadata.js +14 -0
- package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
- package/src/components/verification/SequencingFileRow.jsx +34 -0
- package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
- package/src/components/verification/VerificationFileDialog.jsx +248 -0
- package/src/config/defaultMainEditorProps.js +44 -0
- package/src/hooks/useAlerts.js +16 -0
- package/src/hooks/useBackendAPI.js +51 -0
- package/src/hooks/useBackendRoute.js +22 -0
- package/src/hooks/useDatabase.js +18 -0
- package/src/hooks/useDragAndDropFile.js +31 -0
- package/src/hooks/useGatewaySites.js +40 -0
- package/src/hooks/useHttpClient.js +12 -0
- package/src/hooks/useLoadDatabaseFile.js +108 -0
- package/src/hooks/useStoreEditor.js +101 -0
- package/src/hooks/useValidateState.js +43 -0
- package/vitest.config.js +18 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import InputLabel from '@mui/material/InputLabel';
|
|
3
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
4
|
+
import TextField from '@mui/material/TextField';
|
|
5
|
+
import FormControl from '@mui/material/FormControl';
|
|
6
|
+
import Select from '@mui/material/Select';
|
|
7
|
+
import { Alert, Box, FormHelperText, FormLabel } from '@mui/material';
|
|
8
|
+
import PostRequestSelect from '../form/PostRequestSelect';
|
|
9
|
+
import { getReferenceAssemblyId, taxonSuggest, geneSuggest, getInfoFromAssemblyId, getInfoFromSequenceAccession } from '@opencloning/utils/ncbiRequests';
|
|
10
|
+
import TextFieldValidate from '../form/TextFieldValidate';
|
|
11
|
+
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
12
|
+
import GetRequestMultiSelect from '../form/GetRequestMultiSelect';
|
|
13
|
+
import useHttpClient from '../../hooks/useHttpClient';
|
|
14
|
+
import { formatSequenceLocationString } from '@opencloning/utils/other';
|
|
15
|
+
|
|
16
|
+
function getGeneCoordsInfo(gene) {
|
|
17
|
+
const { range: geneRange, accession_version: accessionVersion } = gene.annotation.genomic_regions[0].gene_range;
|
|
18
|
+
const { begin: start, end, orientation } = geneRange[0];
|
|
19
|
+
const strand = orientation === 'plus' ? 1 : -1;
|
|
20
|
+
return { accessionVersion, start: Number(start), end: Number(end), strand };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function formatGeneCoords(gene) {
|
|
24
|
+
const { accessionVersion, start, end, strand } = getGeneCoordsInfo(gene);
|
|
25
|
+
return `${accessionVersion} (${start}..${end}${strand === -1 ? ', complement' : ''})`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export function formatBackendPayloadWithGene(assemblyId, gene, shiftUpstream, shiftDownstream) {
|
|
30
|
+
const { accessionVersion, start, end, strand } = getGeneCoordsInfo(gene);
|
|
31
|
+
const shiftedStart = start - (strand === 1 ? shiftUpstream : shiftDownstream);
|
|
32
|
+
const shiftedEnd = end + (strand === 1 ? shiftDownstream : shiftUpstream);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
repository_id: accessionVersion,
|
|
36
|
+
assembly_accession: assemblyId,
|
|
37
|
+
locus_tag: gene.annotation.locus_tag ? gene.annotation.locus_tag : null,
|
|
38
|
+
gene_id: gene.annotation.gene_id ? gene.annotation.gene_id : null,
|
|
39
|
+
coordinates: formatSequenceLocationString(shiftedStart, shiftedEnd, strand),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function SpeciesPicker({ setSpecies, setAssemblyId }) {
|
|
44
|
+
const speciesPostRequestSettings = React.useMemo(() => ({
|
|
45
|
+
setValue: (v) => {
|
|
46
|
+
if (v === null) {
|
|
47
|
+
setSpecies(null);
|
|
48
|
+
setAssemblyId('');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
getReferenceAssemblyId(v.tax_id).then((response) => {
|
|
52
|
+
// Set the species
|
|
53
|
+
setSpecies(v);
|
|
54
|
+
// Set the assemblyId
|
|
55
|
+
setAssemblyId(response === null ? '' : response);
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
getOptions: taxonSuggest,
|
|
59
|
+
getOptionLabel: (option) => (option ? `${option.sci_name} - ${option.tax_id}` : ''),
|
|
60
|
+
isOptionEqualToValue: (option, value) => option.tax_id === value.tax_id,
|
|
61
|
+
textLabel: 'Species / taxon ID',
|
|
62
|
+
}), []);
|
|
63
|
+
return (<PostRequestSelect {...speciesPostRequestSettings} fullWidth />);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function SequenceAccessionPicker({ assemblyAccession, setSequenceAccession }) {
|
|
67
|
+
const httpClient = useHttpClient();
|
|
68
|
+
|
|
69
|
+
const url = `https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/${assemblyAccession}/sequence_reports`
|
|
70
|
+
return (
|
|
71
|
+
<FormControl fullWidth>
|
|
72
|
+
<GetRequestMultiSelect
|
|
73
|
+
getOptionsFromResponse={(data) => data.reports}
|
|
74
|
+
httpClient={httpClient}
|
|
75
|
+
url={url}
|
|
76
|
+
label="Chromosome"
|
|
77
|
+
messages={{ loadingMessage: 'Loading chromosomes...', errorMessage: 'Could not load chromosomes' }}
|
|
78
|
+
onChange={(value, options) => { setSequenceAccession(options.find((o) => `${o.chr_name} - ${o.refseq_accession}` === value).refseq_accession)}}
|
|
79
|
+
getOptionLabel={({ chr_name, refseq_accession }) => `${chr_name} - ${refseq_accession}`}
|
|
80
|
+
multiple={false}
|
|
81
|
+
autoComplete={false}
|
|
82
|
+
/>
|
|
83
|
+
</FormControl>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Extra component to be used in SourceGenomeRegion
|
|
88
|
+
function SourceGenomeRegionLocusOnReference({ source, requestStatus, sendPostRequest }) {
|
|
89
|
+
const { id: sourceId } = source;
|
|
90
|
+
const [gene, setGene] = React.useState(null);
|
|
91
|
+
const [species, setSpecies] = React.useState(null);
|
|
92
|
+
const [assemblyId, setAssemblyId] = React.useState('');
|
|
93
|
+
const upstreamBasesRef = React.useRef(null);
|
|
94
|
+
const downstreamBasesRef = React.useRef(null);
|
|
95
|
+
|
|
96
|
+
const onSubmit = (event) => {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
const requestData = formatBackendPayloadWithGene(assemblyId, gene, Number(upstreamBasesRef.current.value), Number(downstreamBasesRef.current.value));
|
|
99
|
+
requestData.id = sourceId;
|
|
100
|
+
sendPostRequest({ endpoint: 'genome_coordinates', requestData, source });
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Reset gene when species changes
|
|
104
|
+
React.useEffect(() => {
|
|
105
|
+
setGene(null);
|
|
106
|
+
}, [species]);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<form onSubmit={onSubmit}>
|
|
110
|
+
<SpeciesPicker {...{ setSpecies, setAssemblyId }} />
|
|
111
|
+
{assemblyId && (
|
|
112
|
+
<>
|
|
113
|
+
<KnownAssemblyField assemblyId={assemblyId} />
|
|
114
|
+
<SourceGenomeRegionSelectGene {...{ gene, upstreamBasesRef, downstreamBasesRef, setGene, assemblyId }} />
|
|
115
|
+
{gene && (
|
|
116
|
+
<SubmitButtonBackendAPI
|
|
117
|
+
requestStatus={requestStatus}
|
|
118
|
+
{...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": "submit-genome-region-locus-reference" })}
|
|
119
|
+
>Submit</SubmitButtonBackendAPI>
|
|
120
|
+
)}
|
|
121
|
+
</>
|
|
122
|
+
)}
|
|
123
|
+
{ (species && assemblyId === '') && (
|
|
124
|
+
<Alert sx={{ alignItems: 'center' }} severity="error">
|
|
125
|
+
The selected species does not have a reference assembly.
|
|
126
|
+
</Alert>
|
|
127
|
+
)}
|
|
128
|
+
</form>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function KnownSpeciesField({ species }) {
|
|
133
|
+
return (
|
|
134
|
+
<FormControl fullWidth>
|
|
135
|
+
<TextField
|
|
136
|
+
label="Species"
|
|
137
|
+
value={`${species.organism_name} - ${species.tax_id}`}
|
|
138
|
+
disabled
|
|
139
|
+
/>
|
|
140
|
+
</FormControl>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function KnownAssemblyField({ assemblyId }) {
|
|
145
|
+
return (
|
|
146
|
+
<FormControl fullWidth>
|
|
147
|
+
<TextField
|
|
148
|
+
label="Assembly ID"
|
|
149
|
+
value={assemblyId}
|
|
150
|
+
disabled
|
|
151
|
+
/>
|
|
152
|
+
</FormControl>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function AssemblyIdSelector({ setAssemblyId, setHasAnnotation = () => {}, onAssemblyIdChange = () => {} }) {
|
|
157
|
+
const [exactMatch, setExactMatch] = React.useState(false);
|
|
158
|
+
const [newerAssembly, setNewerAssembly] = React.useState(false);
|
|
159
|
+
const [species, setSpecies] = React.useState(null);
|
|
160
|
+
const [pairedAccessionWithAnnotation, setPairedAccessionWithAnnotation] = React.useState('');
|
|
161
|
+
|
|
162
|
+
const onChange = async (userInput, resp) => {
|
|
163
|
+
setPairedAccessionWithAnnotation('');
|
|
164
|
+
setSpecies(resp === null ? null : resp.species);
|
|
165
|
+
if (resp === null) {
|
|
166
|
+
setAssemblyId('');
|
|
167
|
+
setExactMatch(true);
|
|
168
|
+
} else if (resp.exactMatch) {
|
|
169
|
+
setAssemblyId(userInput);
|
|
170
|
+
setExactMatch(true);
|
|
171
|
+
} else {
|
|
172
|
+
setAssemblyId(resp.newerAssembly);
|
|
173
|
+
setExactMatch(false);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
setHasAnnotation(resp !== null && resp.hasAnnotation);
|
|
177
|
+
setNewerAssembly(resp !== null && resp.newerAssembly);
|
|
178
|
+
onAssemblyIdChange();
|
|
179
|
+
if (resp !== null && !resp.hasAnnotation && resp.pairedAccession) {
|
|
180
|
+
const pairedAccessionInfo = await getInfoFromAssemblyId(resp.pairedAccession);
|
|
181
|
+
if (pairedAccessionInfo !== null && pairedAccessionInfo.hasAnnotation) {
|
|
182
|
+
setPairedAccessionWithAnnotation(resp.pairedAccession);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<>
|
|
189
|
+
<TextFieldValidate onChange={onChange} getterFunction={getInfoFromAssemblyId} label="Assembly ID" defaultHelperText="Example ID: GCA_000002945.3" />
|
|
190
|
+
{newerAssembly && (
|
|
191
|
+
<Alert severity="warning">
|
|
192
|
+
{!exactMatch ? 'Using assembly ID' : 'Newer assembly exists:'}
|
|
193
|
+
{' '}
|
|
194
|
+
<a href={`https://www.ncbi.nlm.nih.gov/datasets/genome/${newerAssembly}`} target="_blank" rel="noopener noreferrer">{newerAssembly}</a>
|
|
195
|
+
</Alert>
|
|
196
|
+
)}
|
|
197
|
+
{pairedAccessionWithAnnotation && (
|
|
198
|
+
<Alert severity="warning">
|
|
199
|
+
Equivalent assembly <a href={`https://www.ncbi.nlm.nih.gov/datasets/genome/${pairedAccessionWithAnnotation}`} target="_blank" rel="noopener noreferrer">{pairedAccessionWithAnnotation}</a> has annotation.
|
|
200
|
+
</Alert>
|
|
201
|
+
)}
|
|
202
|
+
{species && <KnownSpeciesField species={species} />}
|
|
203
|
+
</>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Extra component to be used in SourceGenomeRegion
|
|
208
|
+
function SourceGenomeRegionLocusOnOther({ source, requestStatus, sendPostRequest }) {
|
|
209
|
+
const { id: sourceId } = source;
|
|
210
|
+
const [gene, setGene] = React.useState(null);
|
|
211
|
+
const [assemblyId, setAssemblyId] = React.useState('');
|
|
212
|
+
const [hasAnnotation, setHasAnnotation] = React.useState(false);
|
|
213
|
+
const upstreamBasesRef = React.useRef(null);
|
|
214
|
+
const downstreamBasesRef = React.useRef(null);
|
|
215
|
+
|
|
216
|
+
const onSubmit = (event) => {
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
const requestData = formatBackendPayloadWithGene(assemblyId, gene, Number(upstreamBasesRef.current.value), Number(downstreamBasesRef.current.value));
|
|
219
|
+
requestData.id = sourceId;
|
|
220
|
+
sendPostRequest({ endpoint: 'genome_coordinates', requestData, source });
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<form onSubmit={onSubmit}>
|
|
225
|
+
<AssemblyIdSelector {...{ setAssemblyId, setHasAnnotation, onAssemblyIdChange: () => setGene(null) }} />
|
|
226
|
+
{assemblyId && hasAnnotation && (
|
|
227
|
+
<>
|
|
228
|
+
<SourceGenomeRegionSelectGene {...{ gene, upstreamBasesRef, downstreamBasesRef, setGene, assemblyId }} />
|
|
229
|
+
{gene && (
|
|
230
|
+
<SubmitButtonBackendAPI
|
|
231
|
+
requestStatus={requestStatus}
|
|
232
|
+
{...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": "submit-genome-region-locus-other" })}
|
|
233
|
+
>Submit</SubmitButtonBackendAPI>
|
|
234
|
+
)}
|
|
235
|
+
</>
|
|
236
|
+
)}
|
|
237
|
+
{assemblyId && !hasAnnotation && (<Alert severity="error">The selected assembly has no gene annotations</Alert>)}
|
|
238
|
+
</form>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Extra component to be used in SourceGenomeRegion
|
|
243
|
+
function SourceGenomeRegionCustomCoordinates({ source, requestStatus, sendPostRequest, selectionMode }) {
|
|
244
|
+
// https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?dbfrom=nuccore&db=assembly&id=CM041205.1&idtype=acc
|
|
245
|
+
const { id: sourceId } = source;
|
|
246
|
+
const [species, setSpecies] = React.useState(null);
|
|
247
|
+
const [sequenceAccession, setSequenceAccession] = React.useState('');
|
|
248
|
+
const [assemblyId, setAssemblyId] = React.useState('');
|
|
249
|
+
const noError = { start: null, end: null, strand: null };
|
|
250
|
+
const [formError, setFormError] = React.useState({ ...noError });
|
|
251
|
+
// I don't manage to use refs for the Select component
|
|
252
|
+
const [coords, setCoords] = React.useState({ start: '', end: '', strand: '' });
|
|
253
|
+
|
|
254
|
+
React.useEffect(() => {
|
|
255
|
+
// Clear the form when the selection mode changes
|
|
256
|
+
setSpecies(null);
|
|
257
|
+
setSequenceAccession('');
|
|
258
|
+
setAssemblyId('');
|
|
259
|
+
setCoords({ start: '', end: '', strand: '' });
|
|
260
|
+
}, [selectionMode]);
|
|
261
|
+
|
|
262
|
+
const onSubmit = (event) => {
|
|
263
|
+
event.preventDefault();
|
|
264
|
+
if (coords.start === '') {
|
|
265
|
+
setFormError({ ...noError, start: 'Field required' });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (coords.end === '') {
|
|
269
|
+
setFormError({ ...noError, end: 'Field required' });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (coords.strand === '') {
|
|
273
|
+
setFormError({ ...noError, strand: 'Field required' });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Start must be greater than zero
|
|
277
|
+
if (Number(coords.start) < 1) {
|
|
278
|
+
setFormError({ ...noError, start: 'Start must be greater than zero' });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (Number(coords.start) >= Number(coords.end)) {
|
|
282
|
+
setFormError({ ...noError, end: 'End must be greater than start' });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
setFormError({ ...noError });
|
|
287
|
+
const requestData = {
|
|
288
|
+
id: sourceId,
|
|
289
|
+
repository_id: sequenceAccession,
|
|
290
|
+
assembly_accession: assemblyId || null,
|
|
291
|
+
coordinates: formatSequenceLocationString(coords.start, coords.end, coords.strand === 'plus' ? 1 : -1),
|
|
292
|
+
};
|
|
293
|
+
sendPostRequest({ endpoint: 'genome_coordinates', requestData, source });
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const onSequenceAccessionChange = async (userInput, resp) => {
|
|
297
|
+
setFormError({ ...noError });
|
|
298
|
+
setCoords({ start: '', end: '', strand: '' });
|
|
299
|
+
if (resp === null) {
|
|
300
|
+
setSpecies(null);
|
|
301
|
+
setSequenceAccession('');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
setSpecies(resp.species || null);
|
|
305
|
+
setSequenceAccession(resp.sequenceAccessionStandard);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<form onSubmit={onSubmit}>
|
|
310
|
+
{(selectionMode === 'custom_sequence_accession') && (
|
|
311
|
+
(
|
|
312
|
+
<>
|
|
313
|
+
<TextFieldValidate onChange={onSequenceAccessionChange} getterFunction={getInfoFromSequenceAccession} label="Sequence accession" defaultHelperText="Example ID: NC_003424.3" />
|
|
314
|
+
{species && <KnownSpeciesField species={species} />}
|
|
315
|
+
</>
|
|
316
|
+
)
|
|
317
|
+
)}
|
|
318
|
+
{(selectionMode === 'custom_reference') && (
|
|
319
|
+
<>
|
|
320
|
+
{assemblyId && <KnownAssemblyField assemblyId={assemblyId} />}
|
|
321
|
+
<SpeciesPicker {...{ setSpecies, setAssemblyId }} />
|
|
322
|
+
</>
|
|
323
|
+
)}
|
|
324
|
+
{(selectionMode === 'custom_other') && (
|
|
325
|
+
<AssemblyIdSelector {...{ setAssemblyId, onAssemblyIdChange: () => { setFormError({ ...noError }); setSequenceAccession(''); } }} />
|
|
326
|
+
)}
|
|
327
|
+
{assemblyId && ['custom_reference', 'custom_other'].includes(selectionMode) && (
|
|
328
|
+
<SequenceAccessionPicker {...{ assemblyAccession: assemblyId, setSequenceAccession }} />
|
|
329
|
+
)}
|
|
330
|
+
{sequenceAccession && (
|
|
331
|
+
<>
|
|
332
|
+
<Box component="fieldset" sx={{ p: 1, mb: 1 }} style={{ borderRadius: '.5em', boxShadow: null }}>
|
|
333
|
+
<legend><FormLabel>Sequence coordinates</FormLabel></legend>
|
|
334
|
+
<FormControl fullWidth>
|
|
335
|
+
<TextField
|
|
336
|
+
fullWidth
|
|
337
|
+
label="Start"
|
|
338
|
+
value={coords.start}
|
|
339
|
+
onChange={(event) => setCoords((prev) => ({ ...prev, start: event.target.value }))}
|
|
340
|
+
type="number"
|
|
341
|
+
error={formError.start !== null}
|
|
342
|
+
helperText={formError.start}
|
|
343
|
+
/>
|
|
344
|
+
</FormControl>
|
|
345
|
+
<FormControl fullWidth>
|
|
346
|
+
<TextField
|
|
347
|
+
fullWidth
|
|
348
|
+
label="End"
|
|
349
|
+
value={coords.end}
|
|
350
|
+
onChange={(event) => setCoords((prev) => ({ ...prev, end: event.target.value }))}
|
|
351
|
+
type="number"
|
|
352
|
+
error={formError.end !== null}
|
|
353
|
+
helperText={formError.end}
|
|
354
|
+
/>
|
|
355
|
+
</FormControl>
|
|
356
|
+
<FormControl fullWidth>
|
|
357
|
+
<InputLabel error={formError.strand !== null} id={`selection-mode-${sourceId}-strand-label`}>Strand</InputLabel>
|
|
358
|
+
<Select
|
|
359
|
+
labelId={`selection-mode-${sourceId}-strand-label`}
|
|
360
|
+
label="Strand"
|
|
361
|
+
value={coords.strand}
|
|
362
|
+
onChange={(event) => setCoords((prev) => ({ ...prev, strand: event.target.value }))}
|
|
363
|
+
error={formError.strand !== null}
|
|
364
|
+
>
|
|
365
|
+
<MenuItem value="plus">plus</MenuItem>
|
|
366
|
+
<MenuItem value="minus">minus</MenuItem>
|
|
367
|
+
</Select>
|
|
368
|
+
<FormHelperText error={formError.strand !== null}>{formError.strand}</FormHelperText>
|
|
369
|
+
</FormControl>
|
|
370
|
+
</Box>
|
|
371
|
+
<SubmitButtonBackendAPI
|
|
372
|
+
requestStatus={requestStatus}
|
|
373
|
+
{...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": "submit-genome-region-custom-coordinates" })}
|
|
374
|
+
>Submit</SubmitButtonBackendAPI>
|
|
375
|
+
</>
|
|
376
|
+
)}
|
|
377
|
+
</form>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function SourceGenomeRegionSelectGene({ gene, upstreamBasesRef, downstreamBasesRef, setGene, assemblyId }) {
|
|
382
|
+
const [error, setError] = React.useState('');
|
|
383
|
+
const genePostRequestSettings = React.useMemo(() => ({
|
|
384
|
+
setValue: setGene,
|
|
385
|
+
getOptions: async (userInput) => {
|
|
386
|
+
try {
|
|
387
|
+
// We await the response to catch the error
|
|
388
|
+
setError('');
|
|
389
|
+
return await geneSuggest(assemblyId, userInput);
|
|
390
|
+
} catch (e) {
|
|
391
|
+
// Connection error
|
|
392
|
+
if (e.code === 'ERR_NETWORK' || e.response === undefined) {
|
|
393
|
+
setError('Connection error');
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
// Bad request
|
|
397
|
+
if (e.response?.status >= 400 && e.response?.status < 500) {
|
|
398
|
+
setError(e.response.data.message);
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
// Server error
|
|
402
|
+
if (e.response?.status >= 500) {
|
|
403
|
+
setError('NCBI server error');
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
// Here we are assuming that the assemblyId has been validated
|
|
407
|
+
setError('The assembly has no gene annotations');
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
getOptionLabel: ({ annotation }) => (annotation ? `${annotation.symbol} ${annotation.locus_tag === undefined ? '' : annotation.locus_tag} ${annotation.name}` : ''),
|
|
412
|
+
isOptionEqualToValue: (option, value) => option.locus_tag === value.locus_tag,
|
|
413
|
+
textLabel: 'Gene',
|
|
414
|
+
disableFiltering: true,
|
|
415
|
+
}), [setGene, assemblyId]);
|
|
416
|
+
|
|
417
|
+
return (
|
|
418
|
+
<>
|
|
419
|
+
<PostRequestSelect {...genePostRequestSettings} fullWidth />
|
|
420
|
+
{error && (<Alert severity="error">{error}</Alert>)}
|
|
421
|
+
{gene && (
|
|
422
|
+
<>
|
|
423
|
+
<FormControl fullWidth>
|
|
424
|
+
<TextField
|
|
425
|
+
label="Gene coordinates"
|
|
426
|
+
value={formatGeneCoords(gene)}
|
|
427
|
+
disabled
|
|
428
|
+
/>
|
|
429
|
+
</FormControl>
|
|
430
|
+
<FormControl fullWidth>
|
|
431
|
+
<TextField
|
|
432
|
+
fullWidth
|
|
433
|
+
label="Upstream bases"
|
|
434
|
+
inputRef={upstreamBasesRef}
|
|
435
|
+
type="number"
|
|
436
|
+
defaultValue={1000}
|
|
437
|
+
/>
|
|
438
|
+
</FormControl>
|
|
439
|
+
<FormControl fullWidth>
|
|
440
|
+
<TextField
|
|
441
|
+
fullWidth
|
|
442
|
+
label="Downstream bases"
|
|
443
|
+
inputRef={downstreamBasesRef}
|
|
444
|
+
type="number"
|
|
445
|
+
defaultValue={1000}
|
|
446
|
+
/>
|
|
447
|
+
</FormControl>
|
|
448
|
+
</>
|
|
449
|
+
)}
|
|
450
|
+
</>
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function SourceGenomeRegion({ source, requestStatus, sendPostRequest }) {
|
|
455
|
+
const { id: sourceId } = source;
|
|
456
|
+
const [selectionMode, setSelectionMode] = React.useState('');
|
|
457
|
+
const changeSelectionMode = (event) => { setSelectionMode(event.target.value); };
|
|
458
|
+
|
|
459
|
+
return (
|
|
460
|
+
<>
|
|
461
|
+
<form>
|
|
462
|
+
<FormControl fullWidth>
|
|
463
|
+
<InputLabel id={`selection-mode-${sourceId}-label`}>Type of region</InputLabel>
|
|
464
|
+
<Select
|
|
465
|
+
value={selectionMode}
|
|
466
|
+
onChange={changeSelectionMode}
|
|
467
|
+
labelId={`selection-mode-${sourceId}-label`}
|
|
468
|
+
label="Type of region"
|
|
469
|
+
>
|
|
470
|
+
<MenuItem value="reference_genome">Locus in reference genome</MenuItem>
|
|
471
|
+
<MenuItem value="other_assembly">Locus in other assembly</MenuItem>
|
|
472
|
+
<MenuItem value="custom_reference">Custom coordinates in reference genome</MenuItem>
|
|
473
|
+
<MenuItem value="custom_other">Custom coordinates in other assembly</MenuItem>
|
|
474
|
+
<MenuItem value="custom_sequence_accession">Custom coordinates in sequence accession</MenuItem>
|
|
475
|
+
</Select>
|
|
476
|
+
</FormControl>
|
|
477
|
+
</form>
|
|
478
|
+
{selectionMode === 'reference_genome' && (<SourceGenomeRegionLocusOnReference {...{ source, requestStatus, sendPostRequest }} />)}
|
|
479
|
+
{selectionMode === 'other_assembly' && (<SourceGenomeRegionLocusOnOther {...{ source, requestStatus, sendPostRequest }} />)}
|
|
480
|
+
{selectionMode.startsWith('custom') && (<SourceGenomeRegionCustomCoordinates {...{ source, requestStatus, sendPostRequest, selectionMode }} />)}
|
|
481
|
+
|
|
482
|
+
</>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export { SourceGenomeRegion, AssemblyIdSelector, SpeciesPicker, SequenceAccessionPicker };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
3
|
+
import { FormControl, InputAdornment, TextField } from '@mui/material';
|
|
4
|
+
import { isEqual } from 'lodash-es';
|
|
5
|
+
import SingleInputSelector from './SingleInputSelector';
|
|
6
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
7
|
+
import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
|
|
8
|
+
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
9
|
+
import SelectPrimerForm from '../primers/SelectPrimerForm';
|
|
10
|
+
|
|
11
|
+
// A component representing the ligation of several fragments
|
|
12
|
+
function SourceHomologousRecombination({ source, requestStatus, sendPostRequest }) {
|
|
13
|
+
const isCrispr = source.type === 'CRISPRSource';
|
|
14
|
+
const { id: sourceId, input } = source;
|
|
15
|
+
const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, sourceId), isEqual);
|
|
16
|
+
const inputsAreNotTemplates = inputSequences.every((sequence) => sequence.type !== 'TemplateSequence');
|
|
17
|
+
const [template, setTemplate] = React.useState(input.length > 0 ? input[0].sequence : null);
|
|
18
|
+
const [insert, setInsert] = React.useState(input.length > 1 ? input[1].sequence : null);
|
|
19
|
+
const [selectedPrimers, setSelectedPrimers] = React.useState([]);
|
|
20
|
+
const { updateSource, setCurrentTab } = cloningActions;
|
|
21
|
+
const dispatch = useDispatch();
|
|
22
|
+
|
|
23
|
+
const primers = useSelector((state) => state.cloning.primers, isEqual);
|
|
24
|
+
|
|
25
|
+
const allowSubmit = (template !== null && insert !== null) && (isCrispr ? selectedPrimers.length > 0 : true) && inputsAreNotTemplates;
|
|
26
|
+
const minimalHomologyRef = React.useRef(null);
|
|
27
|
+
const onSubmit = (event) => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
const requestData = {
|
|
30
|
+
source: { id: sourceId, input, output_name: source.output_name },
|
|
31
|
+
sequences: inputSequences,
|
|
32
|
+
};
|
|
33
|
+
const config = { params: { minimal_homology: minimalHomologyRef.current.value } };
|
|
34
|
+
if (isCrispr) {
|
|
35
|
+
requestData.guides = selectedPrimers.map((primerId) => primers.find((p) => p.id === primerId));
|
|
36
|
+
sendPostRequest({ endpoint: 'crispr', requestData, config, source });
|
|
37
|
+
} else {
|
|
38
|
+
sendPostRequest({ endpoint: 'homologous_recombination', requestData, config, source });
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const onTemplateChange = (event) => {
|
|
43
|
+
setTemplate(Number(event.target.value));
|
|
44
|
+
const newInput = [Number(event.target.value)];
|
|
45
|
+
if (insert) {
|
|
46
|
+
newInput.push(insert);
|
|
47
|
+
}
|
|
48
|
+
dispatch(updateSource({ id: sourceId, input: newInput.map((id) => ({ sequence: id })) }));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const onInsertChange = (event) => {
|
|
52
|
+
if (event.target.value === '') {
|
|
53
|
+
setInsert(null);
|
|
54
|
+
dispatch(updateSource({ id: sourceId, input: [{ sequence: template }] }));
|
|
55
|
+
} else {
|
|
56
|
+
setInsert(Number(event.target.value));
|
|
57
|
+
dispatch(updateSource({ id: sourceId, input: [{ sequence: template }, { sequence: Number(event.target.value) }] }));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const goToPrimerTab = () => {
|
|
62
|
+
dispatch(setCurrentTab(1));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="homologous-recombination">
|
|
67
|
+
<form onSubmit={onSubmit}>
|
|
68
|
+
<FormControl fullWidth>
|
|
69
|
+
<SingleInputSelector
|
|
70
|
+
label="Template sequence"
|
|
71
|
+
{...{ selectedId: template,
|
|
72
|
+
onChange: onTemplateChange,
|
|
73
|
+
inputSequenceIds: [...new Set(input.map(({sequence}) => sequence))].filter(
|
|
74
|
+
(id) => id !== insert,
|
|
75
|
+
) }}
|
|
76
|
+
/>
|
|
77
|
+
</FormControl>
|
|
78
|
+
<FormControl fullWidth>
|
|
79
|
+
<SingleInputSelector
|
|
80
|
+
label="Insert sequence"
|
|
81
|
+
allowUnset
|
|
82
|
+
{...{ selectedId: insert,
|
|
83
|
+
onChange: onInsertChange,
|
|
84
|
+
inputSequenceIds: [...new Set(input.map(({sequence}) => sequence))].filter(
|
|
85
|
+
(id) => id !== template,
|
|
86
|
+
) }}
|
|
87
|
+
/>
|
|
88
|
+
</FormControl>
|
|
89
|
+
<FormControl fullWidth>
|
|
90
|
+
<TextField
|
|
91
|
+
label="Minimal homology length"
|
|
92
|
+
inputRef={minimalHomologyRef}
|
|
93
|
+
type="number"
|
|
94
|
+
defaultValue={40}
|
|
95
|
+
InputProps={{
|
|
96
|
+
endAdornment: <InputAdornment position="end">bp</InputAdornment>,
|
|
97
|
+
sx: { '& input': { textAlign: 'center' } },
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
</FormControl>
|
|
101
|
+
{isCrispr && (
|
|
102
|
+
<SelectPrimerForm
|
|
103
|
+
primers={primers}
|
|
104
|
+
selected={selectedPrimers}
|
|
105
|
+
onChange={setSelectedPrimers}
|
|
106
|
+
label="Select gRNAs (from primers)"
|
|
107
|
+
goToPrimerTab={goToPrimerTab}
|
|
108
|
+
multiple
|
|
109
|
+
/>
|
|
110
|
+
)}
|
|
111
|
+
{ allowSubmit && (
|
|
112
|
+
<SubmitButtonBackendAPI
|
|
113
|
+
requestStatus={requestStatus}
|
|
114
|
+
color="primary"
|
|
115
|
+
{...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": isCrispr ? "submit-crispr" : "submit-homologous-recombination" })}
|
|
116
|
+
>
|
|
117
|
+
{isCrispr ? 'Perform CRISPR' : 'Recombine'}
|
|
118
|
+
</SubmitButtonBackendAPI>
|
|
119
|
+
)}
|
|
120
|
+
</form>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default SourceHomologousRecombination;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
3
|
+
import { geneSuggest } from '@opencloning/utils/ncbiRequests';
|
|
4
|
+
import { formatBackendPayloadWithGene } from './SourceGenomeRegion';
|
|
5
|
+
import { Box, CircularProgress } from '@mui/material';
|
|
6
|
+
|
|
7
|
+
function SourceKnownGenomeRegion({ source, requestStatus, sendPostRequest }) {
|
|
8
|
+
// A source where we pass a complete GenomeCoordinatesSource, so we just have to make the request
|
|
9
|
+
const [error, setError] = React.useState('');
|
|
10
|
+
const [connectAttempt, setConnectAttempt] = React.useState(0);
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
async function makeRequest() {
|
|
13
|
+
setError(false);
|
|
14
|
+
if (source.locus_tag && source.assembly_accession) {
|
|
15
|
+
let resp;
|
|
16
|
+
try {
|
|
17
|
+
resp = await geneSuggest(source.assembly_accession, source.locus_tag)
|
|
18
|
+
} catch (e) {
|
|
19
|
+
setError(true);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (resp.length === 0) {
|
|
23
|
+
setError(true);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const requestData = formatBackendPayloadWithGene(source.assembly_accession, resp[0], source.padding, source.padding);
|
|
27
|
+
requestData.id = source.id;
|
|
28
|
+
sendPostRequest({ endpoint: 'genome_coordinates', requestData, source });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const requestData = {
|
|
32
|
+
...source,
|
|
33
|
+
type: 'GenomeCoordinatesSource',
|
|
34
|
+
};
|
|
35
|
+
sendPostRequest({ endpoint: 'genome_coordinates', requestData, source });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
makeRequest();
|
|
39
|
+
}, [connectAttempt]);
|
|
40
|
+
|
|
41
|
+
if (requestStatus.status === 'error' || error) {
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div>
|
|
45
|
+
<div style={{ marginBottom: '1em', marginTop: '2em' }}>Could not retrieve genome sequence.</div>
|
|
46
|
+
<SubmitButtonBackendAPI requestStatus={requestStatus} onClick={() => setConnectAttempt(connectAttempt + 1)}>Retry</SubmitButtonBackendAPI>
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
return (
|
|
51
|
+
<div style={{ marginBottom: '1em', marginTop: '2em', textAlign: 'center' }}>
|
|
52
|
+
<div>Loading genome sequence...</div>
|
|
53
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
|
|
54
|
+
<CircularProgress />
|
|
55
|
+
</Box>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default SourceKnownGenomeRegion;
|