@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,164 @@
|
|
|
1
|
+
import { Alert, Box, FormControl, InputLabel, MenuItem, Select } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { getReverseComplementSequenceString as reverseComplement } from '@teselagen/sequence-utils';
|
|
4
|
+
import { isEqual } from 'lodash-es';
|
|
5
|
+
import { parseFeatureLocation } from '@teselagen/bio-parsers';
|
|
6
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
7
|
+
import useGatewaySites from '../../../../hooks/useGatewaySites';
|
|
8
|
+
import useStoreEditor from '../../../../hooks/useStoreEditor';
|
|
9
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
10
|
+
import { usePrimerDesign } from './PrimerDesignContext';
|
|
11
|
+
import RequestStatusWrapper from '../../../form/RequestStatusWrapper';
|
|
12
|
+
|
|
13
|
+
const knownCombinations = [
|
|
14
|
+
{
|
|
15
|
+
siteNames: ['attP4', 'attP1'],
|
|
16
|
+
spacers: ['GGGGACAACTTTGTATAGAAAAGTTGNN', reverseComplement('GGGGACTGCTTTTTTGTACAAACTTGN')],
|
|
17
|
+
orientation: [true, true],
|
|
18
|
+
message: 'Primers tails designed based on pDONR™ P4-P1R',
|
|
19
|
+
translationFrame: [4, 6],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
siteNames: ['attP1', 'attP2'],
|
|
23
|
+
spacers: ['GGGGACAAGTTTGTACAAAAAAGCAGGCTNN', reverseComplement('GGGGACCACTTTGTACAAGAAAGCTGGGTN')],
|
|
24
|
+
orientation: [true, false],
|
|
25
|
+
message: 'Primers tails designed based on pDONR™ 221',
|
|
26
|
+
translationFrame: [4, 6],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
siteNames: ['attP2', 'attP3'],
|
|
30
|
+
spacers: ['GGGGACAGCTTTCTTGTACAAAGTGGNN', reverseComplement('GGGGACAACTTTGTATAATAAAGTTGN')],
|
|
31
|
+
orientation: [false, false],
|
|
32
|
+
message: 'Primers tails designed based on pDONR™ P2R-P3',
|
|
33
|
+
translationFrame: [4, 6],
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
function SiteSelect({ donorSites, site, setSite, label }) {
|
|
38
|
+
return (
|
|
39
|
+
<FormControl sx={{ minWidth: '10em' }}>
|
|
40
|
+
<InputLabel>{label}</InputLabel>
|
|
41
|
+
<Select
|
|
42
|
+
value={site ? `${site.siteName}-${site.location}` : ''}
|
|
43
|
+
onChange={(e) => setSite(
|
|
44
|
+
donorSites.find(({ siteName, location }) => `${siteName}-${location}` === e.target.value),
|
|
45
|
+
)}
|
|
46
|
+
label={label}
|
|
47
|
+
>
|
|
48
|
+
{donorSites.map(({ siteName, location }) => (
|
|
49
|
+
<MenuItem key={`${siteName}-${location}`} value={`${siteName}-${location}`}>
|
|
50
|
+
{siteName}
|
|
51
|
+
{' '}
|
|
52
|
+
{location}
|
|
53
|
+
</MenuItem>
|
|
54
|
+
))}
|
|
55
|
+
</Select>
|
|
56
|
+
</FormControl>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { setMainSequenceSelection } = cloningActions;
|
|
61
|
+
|
|
62
|
+
function GatewayRoiSelect({ id, greedy = false }) {
|
|
63
|
+
const [leftSite, setLeftSite] = React.useState(null);
|
|
64
|
+
const [rightSite, setRightSite] = React.useState(null);
|
|
65
|
+
const [donorSites, setDonorSites] = React.useState([]);
|
|
66
|
+
const { requestStatus, attemptAgain, sites } = useGatewaySites({ target: id, greedy });
|
|
67
|
+
const { updateStoreEditor } = useStoreEditor();
|
|
68
|
+
const donorVectorSequenceLength = useSelector((state) => state.cloning.teselaJsonCache[id].sequence.length);
|
|
69
|
+
const dispatch = useDispatch();
|
|
70
|
+
const { primerDesignSettings, setSpacers } = usePrimerDesign();
|
|
71
|
+
const [gatewaySelection, setGatewaySelection] = React.useState(null);
|
|
72
|
+
const editorSelection = useSelector((state) => state.cloning.mainSequenceSelection, isEqual);
|
|
73
|
+
|
|
74
|
+
const { knownCombination, setKnownCombination } = primerDesignSettings;
|
|
75
|
+
|
|
76
|
+
const handleKnownCombinationChange = (newKnownCombination, selection) => {
|
|
77
|
+
setGatewaySelection(selection);
|
|
78
|
+
if (newKnownCombination) {
|
|
79
|
+
setKnownCombination(newKnownCombination);
|
|
80
|
+
setSpacers(newKnownCombination.spacers);
|
|
81
|
+
} else {
|
|
82
|
+
setKnownCombination(null);
|
|
83
|
+
setSpacers(['', '']);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
if (requestStatus.status === 'success') {
|
|
88
|
+
setDonorSites(sites.filter(({ siteName }) => siteName.startsWith('attP')));
|
|
89
|
+
}
|
|
90
|
+
}, [sites]);
|
|
91
|
+
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (gatewaySelection && !isEqual(editorSelection, gatewaySelection)) {
|
|
94
|
+
updateStoreEditor('mainEditor', null, gatewaySelection.selectionLayer);
|
|
95
|
+
dispatch(setMainSequenceSelection(gatewaySelection));
|
|
96
|
+
}
|
|
97
|
+
}, [editorSelection, gatewaySelection]);
|
|
98
|
+
|
|
99
|
+
const checkKnownCombination = React.useCallback((newLeftSite, newRightSite) => {
|
|
100
|
+
if (newLeftSite && newRightSite) {
|
|
101
|
+
const leftSiteLocation = parseFeatureLocation(newLeftSite.location, 0, 0, 0, 1, donorVectorSequenceLength)[0];
|
|
102
|
+
const rightSiteLocation = parseFeatureLocation(newRightSite.location, 0, 0, 0, 1, donorVectorSequenceLength)[0];
|
|
103
|
+
const selectionLayer = { start: leftSiteLocation.start, end: rightSiteLocation.end };
|
|
104
|
+
const selection = { selectionLayer, caretPosition: -1 };
|
|
105
|
+
const siteNames = [newLeftSite.siteName, newRightSite.siteName];
|
|
106
|
+
const orientation = [newLeftSite.location.includes('(+)'), newRightSite.location.includes('(+)')];
|
|
107
|
+
const knownCombinationForward = knownCombinations.find(({ siteNames: knownSites, orientation: knownOrientation }) => isEqual(knownSites, siteNames) && isEqual(knownOrientation, orientation));
|
|
108
|
+
if (knownCombinationForward) {
|
|
109
|
+
handleKnownCombinationChange(knownCombinationForward, selection);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const siteNamesReverse = [newRightSite.siteName, newLeftSite.siteName];
|
|
113
|
+
const orientationReverse = [!newRightSite.location.includes('(+)'), !newLeftSite.location.includes('(+)')];
|
|
114
|
+
const knownCombinationReverse = knownCombinations.find(({ siteNames: knownSites, orientation: knownOrientation }) => isEqual(knownSites, siteNamesReverse) && isEqual(knownOrientation, orientationReverse));
|
|
115
|
+
if (knownCombinationReverse) {
|
|
116
|
+
handleKnownCombinationChange(knownCombinationReverse, selection);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
handleKnownCombinationChange(null, selection);
|
|
120
|
+
}
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
const onSiteSelectLeft = React.useCallback((site) => {
|
|
124
|
+
setLeftSite(site);
|
|
125
|
+
if (rightSite === null || isEqual(rightSite, site)) {
|
|
126
|
+
// Find the first different one
|
|
127
|
+
const differentSite = donorSites.find(({ location }) => location !== site.location);
|
|
128
|
+
setRightSite(differentSite);
|
|
129
|
+
checkKnownCombination(site, differentSite);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
checkKnownCombination(site, rightSite);
|
|
133
|
+
}, [rightSite, donorSites]);
|
|
134
|
+
|
|
135
|
+
const onSiteSelectRight = React.useCallback((site) => {
|
|
136
|
+
setRightSite(site);
|
|
137
|
+
if (leftSite === null || isEqual(leftSite, site)) {
|
|
138
|
+
// Find the first different one
|
|
139
|
+
const differentSite = donorSites.find(({ location }) => location !== site.location);
|
|
140
|
+
setLeftSite(differentSite);
|
|
141
|
+
checkKnownCombination(differentSite, site);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
checkKnownCombination(leftSite, site);
|
|
146
|
+
}, [leftSite, donorSites]);
|
|
147
|
+
return (
|
|
148
|
+
<RequestStatusWrapper requestStatus={requestStatus} retry={attemptAgain}>
|
|
149
|
+
{donorSites.length < 2 && (<Alert severity="error">The sequence must have at least two AttP sites</Alert>)}
|
|
150
|
+
{donorSites.length >= 2 && (
|
|
151
|
+
<>
|
|
152
|
+
<Box sx={{ my: 2, '& > div': { mx: 1 } }}>
|
|
153
|
+
<SiteSelect donorSites={donorSites} site={leftSite} setSite={onSiteSelectLeft} label="Left attP site" />
|
|
154
|
+
<SiteSelect donorSites={donorSites} site={rightSite} setSite={onSiteSelectRight} label="Right attP site" />
|
|
155
|
+
</Box>
|
|
156
|
+
{knownCombination && (<Alert sx={{ width: '80%', margin: 'auto', mb: 2 }} severity="info">{knownCombination.message}</Alert>)}
|
|
157
|
+
{knownCombination === null && (leftSite && rightSite) && (<Alert sx={{ width: '80%', margin: 'auto', mb: 2 }} severity="error">No recommended primer tails found</Alert>)}
|
|
158
|
+
</>
|
|
159
|
+
)}
|
|
160
|
+
</RequestStatusWrapper>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default GatewayRoiSelect;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { usePrimerDesign } from './PrimerDesignContext';
|
|
4
|
+
|
|
5
|
+
function OrientationPicker({ id, index }) {
|
|
6
|
+
const { designType, fragmentOrientations, handleFragmentOrientationChange, templateSequenceNames } = usePrimerDesign();
|
|
7
|
+
const sequenceName = templateSequenceNames[index];
|
|
8
|
+
let label = sequenceName && sequenceName !== 'name' ? `Seq. ${id} (${sequenceName})` : `Seq. ${id}`;
|
|
9
|
+
if (designType === 'homologous_recombination') {
|
|
10
|
+
label = 'Orientation of insert';
|
|
11
|
+
}
|
|
12
|
+
return (
|
|
13
|
+
<table style={{ width: '100%', tableLayout: 'fixed', borderCollapse: 'collapse', margin: '0', padding: '0' }}>
|
|
14
|
+
<tbody>
|
|
15
|
+
<tr style={{ border: 'none' }}>
|
|
16
|
+
<td style={{ width: '40%', verticalAlign: 'middle', textAlign: 'right', paddingRight: '8px', border: 'none', padding: '2px 8px 2px 0' }}>
|
|
17
|
+
<FormLabel id={`fragment-orientation-label-${index}`}>{label}</FormLabel>
|
|
18
|
+
</td>
|
|
19
|
+
<td style={{ width: '60%', verticalAlign: 'middle', textAlign: 'left', border: 'none', padding: '2px 0' }}>
|
|
20
|
+
<RadioGroup
|
|
21
|
+
row
|
|
22
|
+
aria-labelledby={`fragment-orientation-label-${index}`}
|
|
23
|
+
name={`fragment-orientation-${index}`}
|
|
24
|
+
value={fragmentOrientations[index]}
|
|
25
|
+
onChange={(e) => handleFragmentOrientationChange(index, e.target.value)}
|
|
26
|
+
>
|
|
27
|
+
<FormControlLabel value="forward" control={<Radio />} label="Forward" />
|
|
28
|
+
<FormControlLabel value="reverse" control={<Radio />} label="Reverse" />
|
|
29
|
+
</RadioGroup>
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default OrientationPicker;
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { batch, useDispatch, useSelector, useStore } from 'react-redux';
|
|
3
|
+
import { updateEditor } from '@teselagen/ove';
|
|
4
|
+
import { isEqual } from 'lodash-es';
|
|
5
|
+
import useBackendRoute from '../../../../hooks/useBackendRoute';
|
|
6
|
+
import { selectedRegion2SequenceLocation } from '@opencloning/utils/selectedRegionUtils';
|
|
7
|
+
import error2String from '@opencloning/utils/error2String';
|
|
8
|
+
import useStoreEditor from '../../../../hooks/useStoreEditor';
|
|
9
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
10
|
+
import { stringIsNotDNA } from '@opencloning/store/cloning_utils';
|
|
11
|
+
import { ebicTemplateAnnotation, joinSequencesIntoSingleSequence, simulateHomologousRecombination } from '@opencloning/utils/sequenceManipulation';
|
|
12
|
+
import useHttpClient from '../../../../hooks/useHttpClient';
|
|
13
|
+
|
|
14
|
+
function changeValueAtIndex(current, index, newValue) {
|
|
15
|
+
return current.map((_, i) => (i === index ? newValue : current[i]));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const PrimerDesignContext = React.createContext();
|
|
19
|
+
|
|
20
|
+
export function PrimerDesignProvider({ children, designType, sequenceIds, primerDesignSettings, steps }) {
|
|
21
|
+
let templateSequenceIds = sequenceIds;
|
|
22
|
+
if (designType === 'homologous_recombination' || designType === 'gateway_bp') {
|
|
23
|
+
templateSequenceIds = sequenceIds.slice(0, 1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const [primers, setPrimers] = useState([]);
|
|
27
|
+
const [rois, setRois] = useState(Array(sequenceIds.length).fill(null));
|
|
28
|
+
const [error, setError] = useState('');
|
|
29
|
+
const [selectedTab, setSelectedTab] = useState(0);
|
|
30
|
+
const [sequenceProduct, setSequenceProduct] = useState(null);
|
|
31
|
+
const [fragmentOrientations, setFragmentOrientations] = useState(Array(templateSequenceIds.length).fill('forward'));
|
|
32
|
+
const [circularAssembly, setCircularAssembly] = useState(false);
|
|
33
|
+
const [spacers, setSpacers] = useState(Array(templateSequenceIds.length + 1).fill(''));
|
|
34
|
+
|
|
35
|
+
const spacersAreValid = React.useMemo(() => spacers.every((spacer) => !stringIsNotDNA(spacer)), [spacers]);
|
|
36
|
+
const sequenceNames = useSelector((state) => sequenceIds.map((id) => state.cloning.teselaJsonCache[id].name), isEqual);
|
|
37
|
+
const templateSequenceNames = useSelector((state) => templateSequenceIds.map((id) => state.cloning.teselaJsonCache[id].name), isEqual);
|
|
38
|
+
const mainSequenceId = useSelector((state) => state.cloning.mainSequenceId);
|
|
39
|
+
|
|
40
|
+
const store = useStore();
|
|
41
|
+
const backendRoute = useBackendRoute();
|
|
42
|
+
const dispatch = useDispatch();
|
|
43
|
+
const { updateStoreEditor } = useStoreEditor();
|
|
44
|
+
const { setMainSequenceId, addPrimersToPCRSource, setCurrentTab } = cloningActions;
|
|
45
|
+
const httpClient = useHttpClient();
|
|
46
|
+
|
|
47
|
+
const getSubmissionPreventedMessage = () => {
|
|
48
|
+
if (rois.some((region) => region === null)) {
|
|
49
|
+
return 'Not all regions have been selected';
|
|
50
|
+
} if (primerDesignSettings.error) {
|
|
51
|
+
return primerDesignSettings.error;
|
|
52
|
+
} if (spacers.some((spacer) => stringIsNotDNA(spacer))) {
|
|
53
|
+
return 'Spacer sequences not valid';
|
|
54
|
+
}
|
|
55
|
+
return '';
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const submissionPreventedMessage = getSubmissionPreventedMessage();
|
|
59
|
+
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
let newSequenceProduct = null;
|
|
62
|
+
if (submissionPreventedMessage === '') {
|
|
63
|
+
const { teselaJsonCache } = store.getState().cloning;
|
|
64
|
+
const sequences = sequenceIds.map((id) => teselaJsonCache[id]);
|
|
65
|
+
if (designType === 'simple_pair' || designType === 'restriction_ligation') {
|
|
66
|
+
const enzymeSpacers = designType === 'restriction_ligation' ? primerDesignSettings.enzymeSpacers : ['', ''];
|
|
67
|
+
const extendedSpacers = [enzymeSpacers[0] + spacers[0], spacers[1] + enzymeSpacers[1]];
|
|
68
|
+
newSequenceProduct = joinSequencesIntoSingleSequence(sequences, rois.map((s) => s.selectionLayer), fragmentOrientations, extendedSpacers, circularAssembly, 'primer tail');
|
|
69
|
+
newSequenceProduct.name = 'PCR product';
|
|
70
|
+
} else if (designType === 'gibson_assembly') {
|
|
71
|
+
newSequenceProduct = joinSequencesIntoSingleSequence(sequences, rois.map((s) => s.selectionLayer), fragmentOrientations, spacers, circularAssembly);
|
|
72
|
+
newSequenceProduct.name = 'Gibson Assembly product';
|
|
73
|
+
} else if (designType === 'homologous_recombination') {
|
|
74
|
+
newSequenceProduct = simulateHomologousRecombination(sequences[0], sequences[1], rois, fragmentOrientations[0] === 'reverse', spacers);
|
|
75
|
+
newSequenceProduct.name = 'Homologous recombination product';
|
|
76
|
+
} else if (designType === 'gateway_bp') {
|
|
77
|
+
newSequenceProduct = joinSequencesIntoSingleSequence([sequences[0]], [rois[0].selectionLayer], fragmentOrientations, spacers, false, 'primer tail');
|
|
78
|
+
newSequenceProduct.name = 'PCR product';
|
|
79
|
+
const { knownCombination } = primerDesignSettings;
|
|
80
|
+
const leftFeature = {
|
|
81
|
+
start: knownCombination.translationFrame[0],
|
|
82
|
+
end: spacers[0].length - 1,
|
|
83
|
+
type: 'CDS',
|
|
84
|
+
name: 'translation frame',
|
|
85
|
+
strand: 1,
|
|
86
|
+
forward: true,
|
|
87
|
+
};
|
|
88
|
+
const nbAas = Math.floor((spacers[1].length - knownCombination.translationFrame[1]) / 3);
|
|
89
|
+
const rightStart = newSequenceProduct.sequence.length - knownCombination.translationFrame[1] - nbAas * 3;
|
|
90
|
+
const rightFeature = {
|
|
91
|
+
start: rightStart,
|
|
92
|
+
end: newSequenceProduct.sequence.length - knownCombination.translationFrame[1] - 1,
|
|
93
|
+
type: 'CDS',
|
|
94
|
+
name: 'translation frame',
|
|
95
|
+
strand: 1,
|
|
96
|
+
forward: true,
|
|
97
|
+
};
|
|
98
|
+
newSequenceProduct.features.push(leftFeature);
|
|
99
|
+
newSequenceProduct.features.push(rightFeature);
|
|
100
|
+
setSequenceProduct(newSequenceProduct);
|
|
101
|
+
} else if (designType === 'ebic') {
|
|
102
|
+
newSequenceProduct = ebicTemplateAnnotation(sequences[0], rois[0].selectionLayer, primerDesignSettings);
|
|
103
|
+
setSequenceProduct(newSequenceProduct);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
setSequenceProduct(newSequenceProduct);
|
|
107
|
+
}, [rois, spacersAreValid, fragmentOrientations, circularAssembly, designType, spacers, primerDesignSettings]);
|
|
108
|
+
|
|
109
|
+
const onCircularAssemblyChange = (event) => {
|
|
110
|
+
setCircularAssembly(event.target.checked);
|
|
111
|
+
if (event.target.checked) {
|
|
112
|
+
// Remove the first spacer
|
|
113
|
+
setSpacers((current) => current.slice(1));
|
|
114
|
+
} else {
|
|
115
|
+
// Add it again
|
|
116
|
+
setSpacers((current) => ['', ...current]);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const onSelectRegion = (index, selectedRegion, allowSinglePosition = false) => {
|
|
121
|
+
const { caretPosition } = selectedRegion;
|
|
122
|
+
if (caretPosition === undefined) {
|
|
123
|
+
setRois((c) => changeValueAtIndex(c, index, null));
|
|
124
|
+
return 'You have to select a region in the sequence editor!';
|
|
125
|
+
}
|
|
126
|
+
if (caretPosition === -1) {
|
|
127
|
+
setRois((c) => changeValueAtIndex(c, index, selectedRegion));
|
|
128
|
+
return '';
|
|
129
|
+
}
|
|
130
|
+
if (allowSinglePosition) {
|
|
131
|
+
setRois((c) => changeValueAtIndex(c, index, selectedRegion));
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
setRois((c) => changeValueAtIndex(c, index, null));
|
|
135
|
+
return 'Select a region (not a single position) to amplify';
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const onTabChange = (event, newValue) => {
|
|
139
|
+
setSelectedTab(newValue);
|
|
140
|
+
if (newValue < sequenceIds.length) {
|
|
141
|
+
updateStoreEditor('mainEditor', sequenceIds[newValue]);
|
|
142
|
+
dispatch(setMainSequenceId(sequenceIds[newValue]));
|
|
143
|
+
} else if (newValue === sequenceIds.length) {
|
|
144
|
+
updateEditor(store, 'mainEditor', { sequenceData: sequenceProduct || '', selectionLayer: {} });
|
|
145
|
+
} else {
|
|
146
|
+
updateStoreEditor('mainEditor', null);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const handleNext = () => {
|
|
151
|
+
onTabChange(null, selectedTab + 1);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const handleBack = () => {
|
|
155
|
+
onTabChange(null, selectedTab - 1);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const handleSelectRegion = (index, selectedRegion, allowSinglePosition = false) => {
|
|
159
|
+
const regionError = onSelectRegion(index, selectedRegion, allowSinglePosition);
|
|
160
|
+
if (!regionError) {
|
|
161
|
+
handleNext();
|
|
162
|
+
}
|
|
163
|
+
return regionError;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const handleFragmentOrientationChange = (index, orientation) => {
|
|
167
|
+
setFragmentOrientations((current) => changeValueAtIndex(current, index, orientation));
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Focus on the right sequence when changing tabs
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
// Focus on the correct sequence
|
|
173
|
+
const mainSequenceIndex = sequenceIds.indexOf(mainSequenceId);
|
|
174
|
+
if (mainSequenceIndex !== -1) {
|
|
175
|
+
setSelectedTab(mainSequenceIndex);
|
|
176
|
+
}
|
|
177
|
+
}, [sequenceIds, mainSequenceId]);
|
|
178
|
+
|
|
179
|
+
// Update the sequence product in the editor if in the last tab
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const timeoutId = setTimeout(() => {
|
|
182
|
+
if (selectedTab === sequenceIds.length) {
|
|
183
|
+
updateEditor(store, 'mainEditor', { sequenceData: sequenceProduct || {} });
|
|
184
|
+
}
|
|
185
|
+
}, 500);
|
|
186
|
+
|
|
187
|
+
return () => clearTimeout(timeoutId);
|
|
188
|
+
}, [sequenceProduct, store]);
|
|
189
|
+
|
|
190
|
+
const designPrimers = async () => {
|
|
191
|
+
// Validate fragmentOrientations
|
|
192
|
+
fragmentOrientations.forEach((orientation) => {
|
|
193
|
+
if (orientation !== 'forward' && orientation !== 'reverse') {
|
|
194
|
+
throw new Error('Invalid fragment orientation');
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
const { cloning: { sequences, teselaJsonCache, globalPrimerSettings } } = store.getState();
|
|
198
|
+
let requestData;
|
|
199
|
+
let params;
|
|
200
|
+
let endpoint;
|
|
201
|
+
const paramsForRequest = Object.fromEntries(
|
|
202
|
+
Object.entries(primerDesignSettings)
|
|
203
|
+
.filter(([_, value]) => typeof value !== 'function'),
|
|
204
|
+
);
|
|
205
|
+
if (designType === 'gibson_assembly') {
|
|
206
|
+
params = {
|
|
207
|
+
...paramsForRequest,
|
|
208
|
+
circular: circularAssembly,
|
|
209
|
+
};
|
|
210
|
+
requestData = {
|
|
211
|
+
pcr_templates: sequenceIds.map((id, index) => ({
|
|
212
|
+
sequence: sequences.find((e) => e.id === id),
|
|
213
|
+
location: selectedRegion2SequenceLocation(rois[index], teselaJsonCache[id].size),
|
|
214
|
+
forward_orientation: fragmentOrientations[index] === 'forward',
|
|
215
|
+
})),
|
|
216
|
+
spacers,
|
|
217
|
+
};
|
|
218
|
+
endpoint = 'gibson_assembly';
|
|
219
|
+
} else if (designType === 'homologous_recombination') {
|
|
220
|
+
const [pcrTemplateId, homologousRecombinationTargetId] = sequenceIds;
|
|
221
|
+
params = {
|
|
222
|
+
...paramsForRequest,
|
|
223
|
+
};
|
|
224
|
+
requestData = {
|
|
225
|
+
pcr_template: {
|
|
226
|
+
sequence: sequences.find((e) => e.id === pcrTemplateId),
|
|
227
|
+
location: selectedRegion2SequenceLocation(rois[0], teselaJsonCache[pcrTemplateId].size),
|
|
228
|
+
forward_orientation: fragmentOrientations[0] === 'forward',
|
|
229
|
+
},
|
|
230
|
+
homologous_recombination_target: {
|
|
231
|
+
sequence: sequences.find((e) => e.id === homologousRecombinationTargetId),
|
|
232
|
+
location: selectedRegion2SequenceLocation(rois[1], teselaJsonCache[homologousRecombinationTargetId].size),
|
|
233
|
+
},
|
|
234
|
+
spacers,
|
|
235
|
+
};
|
|
236
|
+
endpoint = 'homologous_recombination';
|
|
237
|
+
} else if (designType === 'simple_pair' || designType === 'gateway_bp' || designType === 'restriction_ligation') {
|
|
238
|
+
const pcrTemplateId = sequenceIds[0];
|
|
239
|
+
params = {
|
|
240
|
+
...paramsForRequest,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
requestData = {
|
|
244
|
+
pcr_template: {
|
|
245
|
+
sequence: sequences.find((e) => e.id === pcrTemplateId),
|
|
246
|
+
location: selectedRegion2SequenceLocation(rois[0], teselaJsonCache[pcrTemplateId].size),
|
|
247
|
+
forward_orientation: fragmentOrientations[0] === 'forward',
|
|
248
|
+
},
|
|
249
|
+
spacers,
|
|
250
|
+
};
|
|
251
|
+
endpoint = 'simple_pair';
|
|
252
|
+
} else if (designType === 'ebic') {
|
|
253
|
+
endpoint = 'ebic';
|
|
254
|
+
requestData = {
|
|
255
|
+
template: {
|
|
256
|
+
sequence: sequences.find((e) => e.id === templateSequenceIds[0]),
|
|
257
|
+
location: selectedRegion2SequenceLocation(rois[0], teselaJsonCache[templateSequenceIds[0]].size),
|
|
258
|
+
// forward_orientation: fragmentOrientations[0] === 'forward',
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
params = {
|
|
262
|
+
...paramsForRequest,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
requestData.settings = globalPrimerSettings;
|
|
266
|
+
const url = backendRoute(`primer_design/${endpoint}`);
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const resp = await httpClient.post(url, requestData, { params });
|
|
270
|
+
setError('');
|
|
271
|
+
const newPrimers = resp.data.primers;
|
|
272
|
+
setPrimers(newPrimers);
|
|
273
|
+
handleNext();
|
|
274
|
+
return false;
|
|
275
|
+
} catch (thrownError) {
|
|
276
|
+
const errorMessage = error2String(thrownError);
|
|
277
|
+
setError(errorMessage);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const addPrimers = () => {
|
|
283
|
+
const pcrSources = store.getState().cloning.sources.filter((source) => source.type === 'PCRSource');
|
|
284
|
+
let usedPCRSources;
|
|
285
|
+
if (designType === 'ebic') {
|
|
286
|
+
usedPCRSources = pcrSources.filter((source) => source.input.some((i) => i.sequence === templateSequenceIds[0]));
|
|
287
|
+
} else {
|
|
288
|
+
usedPCRSources = templateSequenceIds.map((id) => pcrSources.find((source) => source.input.some((i) => i.sequence === id)));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
batch(() => {
|
|
292
|
+
usedPCRSources.forEach((pcrSource, index) => {
|
|
293
|
+
dispatch(addPrimersToPCRSource({
|
|
294
|
+
fwdPrimer: primers[index * 2],
|
|
295
|
+
revPrimer: primers[index * 2 + 1],
|
|
296
|
+
sourceId: pcrSource.id,
|
|
297
|
+
}));
|
|
298
|
+
});
|
|
299
|
+
dispatch(setMainSequenceId(null));
|
|
300
|
+
dispatch(setCurrentTab(0));
|
|
301
|
+
});
|
|
302
|
+
setPrimers([]);
|
|
303
|
+
onTabChange(null, 0);
|
|
304
|
+
document.getElementById(`source-${usedPCRSources[0].id}`)?.scrollIntoView();
|
|
305
|
+
updateStoreEditor('mainEditor', null);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const value = React.useMemo(() => ({
|
|
309
|
+
primers,
|
|
310
|
+
error,
|
|
311
|
+
rois,
|
|
312
|
+
designPrimers,
|
|
313
|
+
setPrimers,
|
|
314
|
+
selectedTab,
|
|
315
|
+
onTabChange,
|
|
316
|
+
handleNext,
|
|
317
|
+
handleBack,
|
|
318
|
+
handleSelectRegion,
|
|
319
|
+
sequenceIds,
|
|
320
|
+
fragmentOrientations,
|
|
321
|
+
circularAssembly,
|
|
322
|
+
spacers,
|
|
323
|
+
setFragmentOrientations,
|
|
324
|
+
setSpacers,
|
|
325
|
+
handleFragmentOrientationChange,
|
|
326
|
+
sequenceNames,
|
|
327
|
+
primerDesignSettings,
|
|
328
|
+
submissionPreventedMessage,
|
|
329
|
+
addPrimers,
|
|
330
|
+
onCircularAssemblyChange,
|
|
331
|
+
templateSequenceIds,
|
|
332
|
+
templateSequenceNames,
|
|
333
|
+
designType,
|
|
334
|
+
steps,
|
|
335
|
+
}), [
|
|
336
|
+
primers,
|
|
337
|
+
error,
|
|
338
|
+
rois,
|
|
339
|
+
designPrimers,
|
|
340
|
+
setPrimers,
|
|
341
|
+
selectedTab,
|
|
342
|
+
onTabChange,
|
|
343
|
+
handleNext,
|
|
344
|
+
handleBack,
|
|
345
|
+
handleSelectRegion,
|
|
346
|
+
sequenceIds,
|
|
347
|
+
fragmentOrientations,
|
|
348
|
+
circularAssembly,
|
|
349
|
+
spacers,
|
|
350
|
+
setFragmentOrientations,
|
|
351
|
+
setSpacers,
|
|
352
|
+
handleFragmentOrientationChange,
|
|
353
|
+
sequenceNames,
|
|
354
|
+
primerDesignSettings,
|
|
355
|
+
submissionPreventedMessage,
|
|
356
|
+
addPrimers,
|
|
357
|
+
templateSequenceIds,
|
|
358
|
+
designType,
|
|
359
|
+
steps,
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<PrimerDesignContext.Provider value={value}>
|
|
364
|
+
{children}
|
|
365
|
+
</PrimerDesignContext.Provider>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export const usePrimerDesign = () => React.useContext(PrimerDesignContext);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PrimerDesignProvider } from './PrimerDesignContext';
|
|
3
|
+
import PrimerDesignForm from './PrimerDesignForm';
|
|
4
|
+
import useEBICPrimerDesignSettings from './useEBICPrimerDesignSettings';
|
|
5
|
+
import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
|
|
6
|
+
|
|
7
|
+
function PrimerDesignEBIC({ pcrSources }) {
|
|
8
|
+
const templateSequenceId = getPcrTemplateSequenceId(pcrSources[0]);
|
|
9
|
+
const sequenceIds = React.useMemo(() => [templateSequenceId], [templateSequenceId]);
|
|
10
|
+
const primerDesignSettings = useEBICPrimerDesignSettings();
|
|
11
|
+
|
|
12
|
+
const steps = React.useMemo(() => [
|
|
13
|
+
{ label: 'Region of interest', description: 'Select in the editor the region to be replaced' },
|
|
14
|
+
], []);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<PrimerDesignProvider designType="ebic" sequenceIds={sequenceIds} primerDesignSettings={primerDesignSettings} steps={steps}>
|
|
18
|
+
<PrimerDesignForm />
|
|
19
|
+
</PrimerDesignProvider>
|
|
20
|
+
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default PrimerDesignEBIC;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Box from '@mui/material/Box';
|
|
3
|
+
import PrimerDesignStepper from './PrimerDesignStepper';
|
|
4
|
+
import TabPanelSelectRoi from './TabPanelSelectRoi';
|
|
5
|
+
import TabPannelSettings from './TabPannelSettings';
|
|
6
|
+
import TabPanelResults from './TabPanelResults';
|
|
7
|
+
import { usePrimerDesign } from './PrimerDesignContext';
|
|
8
|
+
import TabPanelEBICSettings from './TabPanelEBICSettings';
|
|
9
|
+
|
|
10
|
+
function PrimerDesignForm() {
|
|
11
|
+
const { steps, sequenceIds, designType } = usePrimerDesign();
|
|
12
|
+
return (
|
|
13
|
+
<Box>
|
|
14
|
+
<PrimerDesignStepper />
|
|
15
|
+
{steps.slice(0, sequenceIds.length).map((step, index) => (
|
|
16
|
+
<TabPanelSelectRoi
|
|
17
|
+
key={step.label}
|
|
18
|
+
step={step}
|
|
19
|
+
index={index}
|
|
20
|
+
/>
|
|
21
|
+
))}
|
|
22
|
+
{designType !== 'ebic' && <TabPannelSettings />}
|
|
23
|
+
{designType === 'ebic' && <TabPanelEBICSettings />}
|
|
24
|
+
<TabPanelResults />
|
|
25
|
+
</Box>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default PrimerDesignForm;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PrimerDesignProvider } from './PrimerDesignContext';
|
|
3
|
+
import PrimerDesignForm from './PrimerDesignForm';
|
|
4
|
+
|
|
5
|
+
import useGatewayPrimerDesignSettings from './useGatewayPrimerDesignSettings';
|
|
6
|
+
import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
|
|
7
|
+
|
|
8
|
+
function PrimerDesignGatewayBP({ donorVectorId, pcrSource }) {
|
|
9
|
+
const templateSequenceId = getPcrTemplateSequenceId(pcrSource);
|
|
10
|
+
const sequenceIds = React.useMemo(() => [templateSequenceId, donorVectorId], [templateSequenceId, donorVectorId]);
|
|
11
|
+
const steps = React.useMemo(() => [
|
|
12
|
+
{ label: 'Amplified region',
|
|
13
|
+
description: `Select the fragment of sequence ${templateSequenceId} to be amplified in the editor and click "Choose region"`,
|
|
14
|
+
inputLabel: `Amplified region (sequence ${templateSequenceId})` },
|
|
15
|
+
{ label: 'Replaced region',
|
|
16
|
+
description: 'Select attP sites between which the PCR product will be inserted',
|
|
17
|
+
inputLabel: `Replaced region (sequence ${donorVectorId})`,
|
|
18
|
+
stepCompletionToolTip: 'Select a valid combination of attP sites',
|
|
19
|
+
},
|
|
20
|
+
], [templateSequenceId, donorVectorId]);
|
|
21
|
+
|
|
22
|
+
const primerDesignSettings = useGatewayPrimerDesignSettings({ homology_length: null, minimal_hybridization_length: 20, target_tm: 55 });
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<PrimerDesignProvider
|
|
26
|
+
designType="gateway_bp"
|
|
27
|
+
sequenceIds={sequenceIds}
|
|
28
|
+
primerDesignSettings={primerDesignSettings}
|
|
29
|
+
steps={steps}
|
|
30
|
+
>
|
|
31
|
+
<PrimerDesignForm />
|
|
32
|
+
</PrimerDesignProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default PrimerDesignGatewayBP;
|