@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,216 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Alert, Box, FormControl, FormLabel, InputAdornment, TextField } from '@mui/material';
|
|
3
|
+
import TabPanel from '../../../navigation/TabPanel';
|
|
4
|
+
import { usePrimerDesign } from './PrimerDesignContext';
|
|
5
|
+
import StepNavigation from './StepNavigation';
|
|
6
|
+
import { useSelector } from 'react-redux';
|
|
7
|
+
import EnzymeMultiSelect from '../../../form/EnzymeMultiSelect';
|
|
8
|
+
import { isEqual } from 'lodash-es';
|
|
9
|
+
import { getSequenceWithinRange } from '@teselagen/range-utils';
|
|
10
|
+
import { aliasedEnzymesByName, cutSequenceByRestrictionEnzyme } from '@teselagen/sequence-utils';
|
|
11
|
+
|
|
12
|
+
function trimPadding({ templateSequence, padding_left, padding_right, restrictionSitesToAvoid, roi, max_inside, max_outside }) {
|
|
13
|
+
const { start, end } = roi.selectionLayer;
|
|
14
|
+
const leftAnnotationRange = { start: start - padding_left, end: start - 1 };
|
|
15
|
+
const leftArm = getSequenceWithinRange(leftAnnotationRange, templateSequence.sequence);
|
|
16
|
+
const rightAnnotationRange = { start: end + 1, end: end + padding_right };
|
|
17
|
+
const rightArm = getSequenceWithinRange(rightAnnotationRange, templateSequence.sequence);
|
|
18
|
+
|
|
19
|
+
const leftMargin = { start: start - max_outside, end: start + max_inside - 1 };
|
|
20
|
+
const rightMargin = { start: end - max_inside, end: end + max_outside - 1 };
|
|
21
|
+
const leftMarginArm = getSequenceWithinRange(leftMargin, templateSequence.sequence);
|
|
22
|
+
const rightMarginArm = getSequenceWithinRange(rightMargin, templateSequence.sequence);
|
|
23
|
+
|
|
24
|
+
const enzymes = restrictionSitesToAvoid.map((enzyme) => aliasedEnzymesByName[enzyme.toLowerCase()]);
|
|
25
|
+
if (enzymes.length === 0) {
|
|
26
|
+
return { padding_left, padding_right, cutsitesInMargins: false };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cutsInLeftMargin = enzymes.flatMap((enzyme) => cutSequenceByRestrictionEnzyme(
|
|
30
|
+
leftMarginArm,
|
|
31
|
+
true,
|
|
32
|
+
enzyme
|
|
33
|
+
));
|
|
34
|
+
const cutsInRightMargin = enzymes.flatMap((enzyme) => cutSequenceByRestrictionEnzyme(
|
|
35
|
+
rightMarginArm,
|
|
36
|
+
false,
|
|
37
|
+
enzyme
|
|
38
|
+
));
|
|
39
|
+
|
|
40
|
+
const cutsitesInMargins = cutsInLeftMargin.length > 0 || cutsInRightMargin.length > 0;
|
|
41
|
+
|
|
42
|
+
const leftCutsites = enzymes.flatMap((enzyme) => cutSequenceByRestrictionEnzyme(
|
|
43
|
+
leftArm,
|
|
44
|
+
true,
|
|
45
|
+
enzyme
|
|
46
|
+
));
|
|
47
|
+
const rightCutsites = enzymes.flatMap((enzyme) => cutSequenceByRestrictionEnzyme(
|
|
48
|
+
rightArm,
|
|
49
|
+
false,
|
|
50
|
+
enzyme
|
|
51
|
+
));
|
|
52
|
+
|
|
53
|
+
let paddingLeft = padding_left;
|
|
54
|
+
let paddingRight = padding_right;
|
|
55
|
+
if (leftCutsites.length > 0) {
|
|
56
|
+
paddingLeft = leftArm.length - 1 - Math.max(...leftCutsites.map((cutsite) => cutsite.recognitionSiteRange.end));
|
|
57
|
+
}
|
|
58
|
+
if (rightCutsites.length > 0) {
|
|
59
|
+
paddingRight = Math.min(...rightCutsites.map((cutsite) => cutsite.recognitionSiteRange.start));
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
padding_left: paddingLeft,
|
|
63
|
+
padding_right: paddingRight,
|
|
64
|
+
cutsitesInMargins,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function TabPanelEBICSettings() {
|
|
70
|
+
const { error, selectedTab, sequenceIds, primers, submissionPreventedMessage, designPrimers, primerDesignSettings, rois } = usePrimerDesign();
|
|
71
|
+
const { max_inside, max_outside, target_tm, target_tm_tolerance, updateSettings, restrictionSitesToAvoid, padding_left, padding_right } = primerDesignSettings;
|
|
72
|
+
const [cutsitesInMarginsError, setCutsitesInMarginsError] = React.useState(false);
|
|
73
|
+
|
|
74
|
+
const templateSequence = useSelector((state) => state.cloning.teselaJsonCache[sequenceIds[0]], isEqual);
|
|
75
|
+
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
if (rois.length > 0 && rois[0] !== null) {
|
|
78
|
+
const { padding_left: newPaddingLeft, padding_right: newPaddingRight, cutsitesInMargins } = trimPadding({
|
|
79
|
+
templateSequence,
|
|
80
|
+
padding_left,
|
|
81
|
+
padding_right,
|
|
82
|
+
restrictionSitesToAvoid,
|
|
83
|
+
roi: rois[0],
|
|
84
|
+
max_inside,
|
|
85
|
+
max_outside,
|
|
86
|
+
});
|
|
87
|
+
updateSettings({ padding_left: newPaddingLeft, padding_right: newPaddingRight });
|
|
88
|
+
setCutsitesInMarginsError(cutsitesInMargins);
|
|
89
|
+
}
|
|
90
|
+
}, [templateSequence, restrictionSitesToAvoid, rois, max_inside, max_outside, padding_left, padding_right]);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<TabPanel value={selectedTab} index={sequenceIds.length}>
|
|
94
|
+
<Box sx={{ width: '80%', margin: 'auto' }}>
|
|
95
|
+
<Box sx={{ pt: 1 }}>
|
|
96
|
+
<FormLabel>Primer settings</FormLabel>
|
|
97
|
+
<Box sx={{ pt: 1.5 }}>
|
|
98
|
+
|
|
99
|
+
<Box>
|
|
100
|
+
<FormControl sx={{ mr: 2 }}>
|
|
101
|
+
<TextField
|
|
102
|
+
label="Max inside"
|
|
103
|
+
value={max_inside}
|
|
104
|
+
onChange={(e) => { updateSettings({ max_inside: Number(e.target.value) }); }}
|
|
105
|
+
type="number"
|
|
106
|
+
InputProps={{
|
|
107
|
+
endAdornment: <InputAdornment position="end">bp</InputAdornment>,
|
|
108
|
+
sx: { width: '10em' },
|
|
109
|
+
}}
|
|
110
|
+
error={max_inside < 0}
|
|
111
|
+
helperText={max_inside < 0 ? 'Max inside must be greater than 0' : ''}
|
|
112
|
+
/>
|
|
113
|
+
</FormControl>
|
|
114
|
+
|
|
115
|
+
<FormControl sx={{ mr: 2 }}>
|
|
116
|
+
<TextField
|
|
117
|
+
label="Max outside"
|
|
118
|
+
value={max_outside}
|
|
119
|
+
onChange={(e) => { updateSettings({ max_outside: Number(e.target.value) }); }}
|
|
120
|
+
type="number"
|
|
121
|
+
InputProps={{
|
|
122
|
+
endAdornment: <InputAdornment position="end">bp</InputAdornment>,
|
|
123
|
+
sx: { width: '10em' },
|
|
124
|
+
}}
|
|
125
|
+
error={max_outside < 0}
|
|
126
|
+
helperText={max_outside < 0 ? 'Max outside must be greater than 0' : ''}
|
|
127
|
+
/>
|
|
128
|
+
</FormControl>
|
|
129
|
+
</Box>
|
|
130
|
+
|
|
131
|
+
<Box sx={{ mt: 2 }}>
|
|
132
|
+
<FormControl sx={{ mr: 2 }}>
|
|
133
|
+
<TextField
|
|
134
|
+
label="Target Tm"
|
|
135
|
+
value={target_tm}
|
|
136
|
+
onChange={(e) => { updateSettings({ target_tm: Number(e.target.value) }); }}
|
|
137
|
+
type="number"
|
|
138
|
+
InputProps={{
|
|
139
|
+
endAdornment: <InputAdornment position="end">°C</InputAdornment>,
|
|
140
|
+
sx: { width: '10em' },
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
</FormControl>
|
|
144
|
+
|
|
145
|
+
<FormControl sx={{ mr: 2 }}>
|
|
146
|
+
<TextField
|
|
147
|
+
label="Tm tolerance"
|
|
148
|
+
value={target_tm_tolerance}
|
|
149
|
+
onChange={(e) => { updateSettings({ target_tm_tolerance: Number(e.target.value) }); }}
|
|
150
|
+
type="number"
|
|
151
|
+
InputProps={{
|
|
152
|
+
endAdornment: <InputAdornment position="end">°C</InputAdornment>,
|
|
153
|
+
sx: { width: '10em' },
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
</FormControl>
|
|
157
|
+
|
|
158
|
+
</Box>
|
|
159
|
+
<Box sx={{ mt: 2 }}>
|
|
160
|
+
<FormControl sx={{ mr: 2 }}>
|
|
161
|
+
<TextField
|
|
162
|
+
label="Padding left"
|
|
163
|
+
value={padding_left}
|
|
164
|
+
onChange={(e) => { updateSettings({ padding_left: Number(e.target.value) }); }}
|
|
165
|
+
type="number"
|
|
166
|
+
InputProps={{
|
|
167
|
+
endAdornment: <InputAdornment position="end">bp</InputAdornment>,
|
|
168
|
+
sx: { width: '10em' },
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
</FormControl>
|
|
172
|
+
<FormControl sx={{ mr: 2 }}>
|
|
173
|
+
<TextField
|
|
174
|
+
label="Padding right"
|
|
175
|
+
value={padding_right}
|
|
176
|
+
onChange={(e) => { updateSettings({ padding_right: Number(e.target.value) }); }}
|
|
177
|
+
type="number"
|
|
178
|
+
InputProps={{
|
|
179
|
+
endAdornment: <InputAdornment position="end">bp</InputAdornment>,
|
|
180
|
+
sx: { width: '10em' },
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
</FormControl>
|
|
184
|
+
</Box>
|
|
185
|
+
<Box sx={{ mt: 2 }}>
|
|
186
|
+
<FormControl sx={{ mr: 2, width: '15em' }}>
|
|
187
|
+
<EnzymeMultiSelect
|
|
188
|
+
value={restrictionSitesToAvoid}
|
|
189
|
+
setEnzymes={(v) => updateSettings({ restrictionSitesToAvoid: v })}
|
|
190
|
+
label="Sites to avoid"
|
|
191
|
+
multiple={true}
|
|
192
|
+
/>
|
|
193
|
+
</FormControl>
|
|
194
|
+
|
|
195
|
+
</Box>
|
|
196
|
+
</Box>
|
|
197
|
+
</Box>
|
|
198
|
+
</Box>
|
|
199
|
+
{error && <Alert severity="error" sx={{ width: 'fit-content', margin: 'auto', mt: 2 }}>{error}</Alert>}
|
|
200
|
+
{cutsitesInMarginsError && (
|
|
201
|
+
<Alert severity="error" sx={{ width: 'fit-content', margin: 'auto', mt: 2 }}>
|
|
202
|
+
Restriction enzyme cut sites were detected in the margin regions. Please adjust the margin size or select different restriction sites to avoid this issue.
|
|
203
|
+
</Alert>
|
|
204
|
+
)}
|
|
205
|
+
<StepNavigation
|
|
206
|
+
onStepCompletion={designPrimers}
|
|
207
|
+
stepCompletionText="Design primers"
|
|
208
|
+
nextDisabled={primers.length === 0}
|
|
209
|
+
stepCompletionToolTip={submissionPreventedMessage}
|
|
210
|
+
allowStepCompletion={submissionPreventedMessage === ''}
|
|
211
|
+
/>
|
|
212
|
+
</TabPanel>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export default TabPanelEBICSettings;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallowEqual, useSelector } from 'react-redux';
|
|
3
|
+
import { Box } from '@mui/material';
|
|
4
|
+
import TabPanel from '../../../navigation/TabPanel';
|
|
5
|
+
import { usePrimerDesign } from './PrimerDesignContext';
|
|
6
|
+
import StepNavigation from './StepNavigation';
|
|
7
|
+
import PrimerResultForm from './PrimerResultForm';
|
|
8
|
+
|
|
9
|
+
function TabPanelResults() {
|
|
10
|
+
const { selectedTab, primers, addPrimers, setPrimers, handleBack, sequenceIds } = usePrimerDesign();
|
|
11
|
+
const existingPrimerNames = useSelector((state) => state.cloning.primers.map((p) => p.name), shallowEqual);
|
|
12
|
+
const primersAreValid = primers.length && primers.every((primer) => primer.name && !existingPrimerNames.includes(primer.name));
|
|
13
|
+
return (
|
|
14
|
+
<TabPanel value={selectedTab} index={sequenceIds.length + 1}>
|
|
15
|
+
<Box sx={{ marginTop: 3 }}>
|
|
16
|
+
{primers.map((primer, index) => (
|
|
17
|
+
<PrimerResultForm
|
|
18
|
+
key={index}
|
|
19
|
+
updatePrimerName={(newName) => {
|
|
20
|
+
const updatedPrimers = [...primers];
|
|
21
|
+
updatedPrimers[index] = { ...primer, name: newName };
|
|
22
|
+
setPrimers(updatedPrimers);
|
|
23
|
+
}}
|
|
24
|
+
primer={primer}
|
|
25
|
+
existingPrimerNames={existingPrimerNames}
|
|
26
|
+
/>
|
|
27
|
+
))}
|
|
28
|
+
<StepNavigation
|
|
29
|
+
handleBack={handleBack}
|
|
30
|
+
handleNext={null}
|
|
31
|
+
onStepCompletion={addPrimers}
|
|
32
|
+
stepCompletionText="Save primers"
|
|
33
|
+
nextDisabled
|
|
34
|
+
stepCompletionToolTip="Primers are not valid"
|
|
35
|
+
allowStepCompletion={primersAreValid}
|
|
36
|
+
/>
|
|
37
|
+
</Box>
|
|
38
|
+
</TabPanel>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default TabPanelResults;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Alert, FormControl, TextField } from '@mui/material';
|
|
3
|
+
import { useSelector, useStore } from 'react-redux';
|
|
4
|
+
|
|
5
|
+
import StepNavigation from './StepNavigation';
|
|
6
|
+
import { selectedRegion2String } from '@opencloning/utils/selectedRegionUtils';
|
|
7
|
+
import GatewayRoiSelect from './GatewayRoiSelect';
|
|
8
|
+
import TabPanel from '../../../navigation/TabPanel';
|
|
9
|
+
import { usePrimerDesign } from './PrimerDesignContext';
|
|
10
|
+
|
|
11
|
+
function TabPanelSelectRoi({ step, index }) {
|
|
12
|
+
const { selectedTab, rois, handleSelectRegion, sequenceIds, primerDesignSettings, designType } = usePrimerDesign();
|
|
13
|
+
const [error, setError] = React.useState('');
|
|
14
|
+
const editorHasSelection = useSelector((state) => state.cloning.mainSequenceSelection.caretPosition !== undefined);
|
|
15
|
+
const store = useStore();
|
|
16
|
+
const id = sequenceIds[index];
|
|
17
|
+
const {
|
|
18
|
+
description = `Select the fragment of sequence ${id} to be amplified in the editor and click "Choose region"`,
|
|
19
|
+
inputLabel = `Amplified region (sequence ${id})`,
|
|
20
|
+
allowSinglePosition = false,
|
|
21
|
+
stepCompletionToolTip = 'Select a region in the editor',
|
|
22
|
+
} = step;
|
|
23
|
+
|
|
24
|
+
const mode = designType === 'gateway_bp' && index === 1 ? 'gateway_bp' : 'editor';
|
|
25
|
+
const allowStepCompletion = (mode === 'editor' && editorHasSelection) || (mode === 'gateway_bp' && primerDesignSettings.knownCombination);
|
|
26
|
+
const onStepCompletion = () => {
|
|
27
|
+
const selectedRegion = store.getState().cloning.mainSequenceSelection;
|
|
28
|
+
setError(handleSelectRegion(index, selectedRegion, allowSinglePosition));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<TabPanel value={selectedTab} index={index} className={`select-roi-tab-${index}`}>
|
|
33
|
+
<Alert severity="info">{description}</Alert>
|
|
34
|
+
{error && (<Alert severity="error">{error}</Alert>)}
|
|
35
|
+
{mode === 'editor' && (
|
|
36
|
+
<FormControl sx={{ py: 2 }}>
|
|
37
|
+
<TextField
|
|
38
|
+
label={inputLabel}
|
|
39
|
+
value={selectedRegion2String(rois[index])}
|
|
40
|
+
disabled
|
|
41
|
+
/>
|
|
42
|
+
</FormControl>
|
|
43
|
+
)}
|
|
44
|
+
{mode === 'gateway_bp' && (
|
|
45
|
+
<GatewayRoiSelect id={id} />
|
|
46
|
+
)}
|
|
47
|
+
<StepNavigation
|
|
48
|
+
isFirstStep={index === 0}
|
|
49
|
+
nextDisabled={(index === sequenceIds.length - 1) && rois.some((region) => region === null)}
|
|
50
|
+
nextToolTip="You must select all regions before proceeding"
|
|
51
|
+
allowStepCompletion={allowStepCompletion}
|
|
52
|
+
stepCompletionText="Choose region"
|
|
53
|
+
stepCompletionToolTip={stepCompletionToolTip}
|
|
54
|
+
onStepCompletion={onStepCompletion}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
</TabPanel>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default TabPanelSelectRoi;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Alert, Box, Checkbox, FormControl, FormControlLabel, FormLabel } from '@mui/material';
|
|
3
|
+
import StepNavigation from './StepNavigation';
|
|
4
|
+
import TabPanel from '../../../navigation/TabPanel';
|
|
5
|
+
import PrimerSettingsForm from './PrimerSettingsForm';
|
|
6
|
+
import PrimerSpacerForm from './PrimerSpacerForm';
|
|
7
|
+
import OrientationPicker from './OrientationPicker';
|
|
8
|
+
import { usePrimerDesign } from './PrimerDesignContext';
|
|
9
|
+
import RestrictionSpacerForm from './RestrictionSpacerForm';
|
|
10
|
+
|
|
11
|
+
function TabPannelSettings() {
|
|
12
|
+
const { error, templateSequenceIds, designType, selectedTab, sequenceIds, circularAssembly, onCircularAssemblyChange, designPrimers, primers, primerDesignSettings, submissionPreventedMessage } = usePrimerDesign();
|
|
13
|
+
return (
|
|
14
|
+
<TabPanel value={selectedTab} index={sequenceIds.length}>
|
|
15
|
+
<Box sx={{ width: '80%', margin: 'auto' }}>
|
|
16
|
+
<PrimerSettingsForm {...primerDesignSettings} />
|
|
17
|
+
<Box sx={{ mt: 2 }}>
|
|
18
|
+
{designType === 'gibson_assembly' && <FormLabel>Fragment orientation</FormLabel>}
|
|
19
|
+
{templateSequenceIds.map((id, index) => (
|
|
20
|
+
<OrientationPicker
|
|
21
|
+
key={id}
|
|
22
|
+
id={id}
|
|
23
|
+
index={index}
|
|
24
|
+
/>
|
|
25
|
+
))}
|
|
26
|
+
</Box>
|
|
27
|
+
{designType === 'restriction_ligation' && <RestrictionSpacerForm />}
|
|
28
|
+
<PrimerSpacerForm />
|
|
29
|
+
{designType === 'gibson_assembly' && (
|
|
30
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', width: '100%' }}>
|
|
31
|
+
<FormControl>
|
|
32
|
+
<FormControlLabel
|
|
33
|
+
control={(
|
|
34
|
+
<Checkbox
|
|
35
|
+
checked={circularAssembly}
|
|
36
|
+
onChange={onCircularAssemblyChange}
|
|
37
|
+
name="circular-assembly"
|
|
38
|
+
/>
|
|
39
|
+
)}
|
|
40
|
+
label="Circular assembly"
|
|
41
|
+
/>
|
|
42
|
+
</FormControl>
|
|
43
|
+
</Box>
|
|
44
|
+
)}
|
|
45
|
+
|
|
46
|
+
</Box>
|
|
47
|
+
{error && (<Alert severity="error" sx={{ width: 'fit-content', margin: 'auto', mb: 2 }}>{error}</Alert>)}
|
|
48
|
+
<StepNavigation
|
|
49
|
+
onStepCompletion={designPrimers}
|
|
50
|
+
stepCompletionText="Design primers"
|
|
51
|
+
nextDisabled={primers.length === 0}
|
|
52
|
+
stepCompletionToolTip={submissionPreventedMessage}
|
|
53
|
+
allowStepCompletion={submissionPreventedMessage === ''}
|
|
54
|
+
/>
|
|
55
|
+
</TabPanel>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default TabPannelSettings;
|
package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const getError = (s) => {
|
|
4
|
+
if (s.max_inside < 0) {
|
|
5
|
+
return 'Max inside must be greater than 0';
|
|
6
|
+
}
|
|
7
|
+
if (s.max_outside < 0) {
|
|
8
|
+
return 'Max outside must be greater than 0';
|
|
9
|
+
}
|
|
10
|
+
return '';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default function useEBICPrimerDesignSettings() {
|
|
14
|
+
const [settings, setSettings] = React.useState({
|
|
15
|
+
max_inside: 50,
|
|
16
|
+
max_outside: 20,
|
|
17
|
+
target_tm: 61,
|
|
18
|
+
target_tm_tolerance: 3,
|
|
19
|
+
restrictionSitesToAvoid: [],
|
|
20
|
+
padding_left: 1000,
|
|
21
|
+
padding_right: 1000,
|
|
22
|
+
});
|
|
23
|
+
const [error, setError] = React.useState(getError(settings));
|
|
24
|
+
const updateSettings = (newSettings) => {
|
|
25
|
+
setSettings((prev) => ({ ...prev, ...newSettings }));
|
|
26
|
+
};
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
setError(getError(settings));
|
|
29
|
+
}, [settings]);
|
|
30
|
+
return { ...settings, error, updateSettings };
|
|
31
|
+
}
|
package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { getReverseComplementSequenceString as reverseComplement } from '@teselagen/sequence-utils';
|
|
3
|
+
import { getEnzymeRecognitionSequence } from '@opencloning/utils/enzyme_utils';
|
|
4
|
+
import { stringIsNotDNA } from '@opencloning/store/cloning_utils';
|
|
5
|
+
import usePrimerDesignSettings from './usePrimerDesignSettings';
|
|
6
|
+
|
|
7
|
+
function getError(enzymePrimerDesignSettings) {
|
|
8
|
+
const { left_enzyme: leftEnzyme, right_enzyme: rightEnzyme, filler_bases: fillerBases } = enzymePrimerDesignSettings;
|
|
9
|
+
if (!leftEnzyme && !rightEnzyme) {
|
|
10
|
+
return 'You must select and enzyme';
|
|
11
|
+
}
|
|
12
|
+
if (stringIsNotDNA(fillerBases)) {
|
|
13
|
+
return 'Filler bases not valid';
|
|
14
|
+
}
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function useEnzymePrimerDesignSettings(defaultSettings) {
|
|
19
|
+
const primerDesignSettings = usePrimerDesignSettings(defaultSettings);
|
|
20
|
+
const [enzymePrimerDesignSettings, setEnzymePrimerDesignSettings] = useState({
|
|
21
|
+
left_enzyme: null,
|
|
22
|
+
right_enzyme: null,
|
|
23
|
+
left_enzyme_inverted: false,
|
|
24
|
+
right_enzyme_inverted: false,
|
|
25
|
+
filler_bases: 'TTT',
|
|
26
|
+
enzymeSpacers: ['', ''],
|
|
27
|
+
});
|
|
28
|
+
const [enzymeError, setEnzymeError] = useState(getError(enzymePrimerDesignSettings));
|
|
29
|
+
|
|
30
|
+
const updateEnzymeSettings = (newSettings) => {
|
|
31
|
+
setEnzymePrimerDesignSettings((prev) => ({ ...prev, ...newSettings }));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const { left_enzyme: leftEnzyme, right_enzyme: rightEnzyme, filler_bases: fillerBases, left_enzyme_inverted: leftEnzymeInverted, right_enzyme_inverted: rightEnzymeInverted } = enzymePrimerDesignSettings;
|
|
36
|
+
if ((leftEnzyme || rightEnzyme) && !stringIsNotDNA(fillerBases)) {
|
|
37
|
+
const leftEnzymeSeq = leftEnzymeInverted ? reverseComplement(getEnzymeRecognitionSequence(leftEnzyme)) : getEnzymeRecognitionSequence(leftEnzyme);
|
|
38
|
+
const rightEnzymeSeq = rightEnzymeInverted ? getEnzymeRecognitionSequence(rightEnzyme) : reverseComplement(getEnzymeRecognitionSequence(rightEnzyme));
|
|
39
|
+
const leftSpacerStartingSeq = (leftEnzyme ? fillerBases : '') + leftEnzymeSeq;
|
|
40
|
+
const rightSpacerEndingSeq = rightEnzymeSeq + reverseComplement((rightEnzyme ? fillerBases : ''));
|
|
41
|
+
setEnzymePrimerDesignSettings((prev) => ({ ...prev, enzymeSpacers: [leftSpacerStartingSeq, rightSpacerEndingSeq] }));
|
|
42
|
+
} else {
|
|
43
|
+
setEnzymePrimerDesignSettings((prev) => ({ ...prev, enzymeSpacers: ['', ''] }));
|
|
44
|
+
}
|
|
45
|
+
setEnzymeError(getError(enzymePrimerDesignSettings));
|
|
46
|
+
}, [
|
|
47
|
+
enzymePrimerDesignSettings.left_enzyme,
|
|
48
|
+
enzymePrimerDesignSettings.right_enzyme,
|
|
49
|
+
enzymePrimerDesignSettings.filler_bases,
|
|
50
|
+
enzymePrimerDesignSettings.left_enzyme_inverted,
|
|
51
|
+
enzymePrimerDesignSettings.right_enzyme_inverted,
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const error = primerDesignSettings.error || enzymeError;
|
|
55
|
+
|
|
56
|
+
return { ...primerDesignSettings, ...enzymePrimerDesignSettings, updateEnzymeSettings, error };
|
|
57
|
+
}
|
package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import usePrimerDesignSettings from './usePrimerDesignSettings';
|
|
3
|
+
|
|
4
|
+
export default function useGatewayPrimerDesignSettings(initialSettings) {
|
|
5
|
+
const primerDesignSettings = usePrimerDesignSettings(initialSettings);
|
|
6
|
+
|
|
7
|
+
// Gateway BP
|
|
8
|
+
const [knownCombination, setKnownCombination] = useState(null);
|
|
9
|
+
|
|
10
|
+
const error = primerDesignSettings.error || (!knownCombination && 'No valid combination of attP sites selected');
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
...primerDesignSettings,
|
|
14
|
+
knownCombination,
|
|
15
|
+
setKnownCombination,
|
|
16
|
+
error,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import primerDesignMinimalValues from './primerDesignMinimalValues.json';
|
|
3
|
+
|
|
4
|
+
const getError = (s) => {
|
|
5
|
+
const valid = (s.homology_length === null || s.homology_length >= primerDesignMinimalValues.homology_length)
|
|
6
|
+
&& s.minimal_hybridization_length >= primerDesignMinimalValues.hybridization_length
|
|
7
|
+
&& s.target_tm >= primerDesignMinimalValues.target_tm;
|
|
8
|
+
if (!valid) {
|
|
9
|
+
return 'Invalid settings';
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function usePrimerDesignSettings(defaultSettings) {
|
|
15
|
+
const [settings, setSettings] = React.useState(defaultSettings);
|
|
16
|
+
const [error, setError] = React.useState(getError(defaultSettings));
|
|
17
|
+
|
|
18
|
+
const updateSettings = (newSettings) => {
|
|
19
|
+
setSettings((prev) => ({ ...prev, ...newSettings }));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
setError(getError(settings));
|
|
24
|
+
}, [settings]);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...settings,
|
|
28
|
+
error,
|
|
29
|
+
updateSettings,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Button, Checkbox, FormControl, FormControlLabel } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { batch, useDispatch } from 'react-redux';
|
|
4
|
+
import SingleInputSelector from '../../../sources/SingleInputSelector';
|
|
5
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
6
|
+
import useStoreEditor from '../../../../hooks/useStoreEditor';
|
|
7
|
+
import LabelWithTooltip from '../../../form/LabelWithTooltip';
|
|
8
|
+
import useGatewaySites from '../../../../hooks/useGatewaySites';
|
|
9
|
+
import NoAttPSitesError from '../common/NoAttPSitesError';
|
|
10
|
+
import RetryAlert from '../../../form/RetryAlert';
|
|
11
|
+
import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
|
|
12
|
+
|
|
13
|
+
const { addTemplateChildAndSubsequentSource, setCurrentTab, setMainSequenceId } = cloningActions;
|
|
14
|
+
|
|
15
|
+
function PrimerDesignGatewayBP({ source }) {
|
|
16
|
+
const [target, setTarget] = React.useState('');
|
|
17
|
+
const [greedy, setGreedy] = React.useState(false);
|
|
18
|
+
|
|
19
|
+
const { updateStoreEditor } = useStoreEditor();
|
|
20
|
+
const { requestStatus, sites: sitesInTarget, attemptAgain } = useGatewaySites({ target, greedy });
|
|
21
|
+
const nbOfAttPSites = sitesInTarget.filter((site) => site.siteName.startsWith('attP')).length;
|
|
22
|
+
const inputSequenceId = getPcrTemplateSequenceId(source);
|
|
23
|
+
|
|
24
|
+
const dispatch = useDispatch();
|
|
25
|
+
const onSubmit = (event) => {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
const newSource = {
|
|
28
|
+
input: [{ sequence: Number(target) }],
|
|
29
|
+
type: 'GatewaySource',
|
|
30
|
+
reaction_type: 'BP',
|
|
31
|
+
greedy,
|
|
32
|
+
};
|
|
33
|
+
const newSequence = {
|
|
34
|
+
type: 'TemplateSequence',
|
|
35
|
+
primer_design: 'gateway_bp',
|
|
36
|
+
circular: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
batch(() => {
|
|
40
|
+
dispatch(addTemplateChildAndSubsequentSource({ newSource, newSequence, sourceId: source.id }));
|
|
41
|
+
dispatch(setMainSequenceId(inputSequenceId));
|
|
42
|
+
updateStoreEditor('mainEditor', inputSequenceId);
|
|
43
|
+
dispatch(setCurrentTab(3));
|
|
44
|
+
// Scroll to the top of the page after 300ms
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
document.querySelector('.tab-panels-container')?.scrollTo({ top: 0, behavior: 'instant' });
|
|
47
|
+
}, 300);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
return (
|
|
51
|
+
<form onSubmit={onSubmit}>
|
|
52
|
+
|
|
53
|
+
<FormControl fullWidth>
|
|
54
|
+
<SingleInputSelector
|
|
55
|
+
label="Donor vector"
|
|
56
|
+
selectedId={target}
|
|
57
|
+
onChange={(e) => setTarget(e.target.value)}
|
|
58
|
+
inputSequenceIds={[]}
|
|
59
|
+
helperText={requestStatus.status === 'loading' ? 'Validating...' : ''}
|
|
60
|
+
/>
|
|
61
|
+
</FormControl>
|
|
62
|
+
<FormControl fullWidth>
|
|
63
|
+
<FormControlLabel
|
|
64
|
+
control={<Checkbox checked={greedy} onChange={() => setGreedy(!greedy)} />}
|
|
65
|
+
label={(
|
|
66
|
+
<LabelWithTooltip label="Greedy attP finder" tooltip="Use a more greedy consensus site to find attP sites (might give false positives)" />
|
|
67
|
+
)}
|
|
68
|
+
/>
|
|
69
|
+
</FormControl>
|
|
70
|
+
|
|
71
|
+
{requestStatus.status === 'success' && target && nbOfAttPSites < 2 && (
|
|
72
|
+
<NoAttPSitesError sites={sitesInTarget} />
|
|
73
|
+
)}
|
|
74
|
+
{requestStatus.status === 'error' && (
|
|
75
|
+
<RetryAlert onRetry={attemptAgain}>
|
|
76
|
+
{requestStatus.message}
|
|
77
|
+
</RetryAlert>
|
|
78
|
+
)}
|
|
79
|
+
{target && requestStatus.status === 'success' && nbOfAttPSites > 1 && (
|
|
80
|
+
<Button type="submit" variant="contained" color="success">
|
|
81
|
+
Design primers
|
|
82
|
+
</Button>
|
|
83
|
+
)}
|
|
84
|
+
</form>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default React.memo(PrimerDesignGatewayBP);
|