@opencloning/ui 1.4.11 → 1.5.1
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 +24 -0
- package/package.json +3 -3
- package/src/components/assembler/Assembler.cy.jsx +1 -1
- package/src/components/assembler/Assembler.jsx +4 -4
- package/src/components/assembler/ExistingSyntaxDialog.cy.jsx +2 -9
- package/src/components/assembler/ExistingSyntaxDialog.jsx +4 -5
- package/src/components/assembler/UploadPlasmidsButton.cy.jsx +32 -2
- package/src/components/assembler/assembler_utils.js +21 -10
- package/src/components/assembler/assembler_utils.test.js +29 -0
- package/src/components/assembler/useAssembler.js +2 -2
- package/src/components/assembler/usePlasmidsLogic.js +8 -8
- package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +4 -30
- package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +2 -1
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +55 -313
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +2 -2
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +14 -5
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +12 -17
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +8 -4
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +1 -59
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +2 -3
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +18 -8
- package/src/components/primers/primer_design/SequenceTabComponents/{TabPannelSettings.jsx → TabPanelSettings.jsx} +4 -22
- package/src/components/primers/primer_design/SequenceTabComponents/designTypeStrategies.js +178 -0
- package/src/components/primers/primer_design/SequenceTabComponents/hooks/useDesignPrimers.js +89 -0
- package/src/components/primers/primer_design/SequenceTabComponents/hooks/useRegionSelection.js +37 -0
- package/src/components/primers/primer_design/SequenceTabComponents/hooks/useSequenceProduct.js +35 -0
- package/src/components/primers/primer_design/SequenceTabComponents/hooks/useSpacers.js +13 -0
- package/src/components/primers/primer_design/SequenceTabComponents/hooks/useTabNavigation.js +40 -0
- package/src/components/primers/primer_design/SequenceTabComponents/utils/getSequenceLabel.js +7 -0
- package/src/components/primers/primer_design/SequenceTabComponents/utils/knownGatewayCombinations.js +27 -0
- package/src/components/primers/primer_design/SequenceTabComponents/utils/trimPadding.js +58 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +11 -17
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.cy.jsx +131 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +143 -24
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +11 -15
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +10 -16
- package/src/components/primers/primer_design/SourceComponents/useNavigateAfterPrimerDesign.js +23 -0
- package/src/version.js +1 -1
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { Button, Checkbox, FormControl, FormControlLabel } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { useDispatch } from 'react-redux';
|
|
4
4
|
import SingleInputSelector from '../../../sources/SingleInputSelector';
|
|
5
5
|
import { cloningActions } from '@opencloning/store/cloning';
|
|
6
|
-
import useStoreEditor from '../../../../hooks/useStoreEditor';
|
|
7
6
|
import LabelWithTooltip from '../../../form/LabelWithTooltip';
|
|
8
7
|
import useGatewaySites from '../../../../hooks/useGatewaySites';
|
|
9
8
|
import NoAttPSitesError from '../common/NoAttPSitesError';
|
|
10
9
|
import RetryAlert from '../../../form/RetryAlert';
|
|
11
10
|
import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
|
|
11
|
+
import useNavigateAfterPrimerDesign from './useNavigateAfterPrimerDesign';
|
|
12
12
|
|
|
13
|
-
const { addTemplateChildAndSubsequentSource
|
|
13
|
+
const { addTemplateChildAndSubsequentSource } = cloningActions;
|
|
14
14
|
|
|
15
15
|
function PrimerDesignGatewayBP({ source }) {
|
|
16
16
|
const [target, setTarget] = React.useState('');
|
|
17
17
|
const [greedy, setGreedy] = React.useState(false);
|
|
18
|
+
const dispatch = useDispatch();
|
|
19
|
+
const inputSequenceId = getPcrTemplateSequenceId(source);
|
|
20
|
+
const navigateAfterDesign = useNavigateAfterPrimerDesign();
|
|
18
21
|
|
|
19
|
-
const { updateStoreEditor } = useStoreEditor();
|
|
20
22
|
const { requestStatus, sites: sitesInTarget, attemptAgain } = useGatewaySites({ target, greedy });
|
|
21
23
|
const nbOfAttPSites = sitesInTarget.filter((site) => site.siteName.startsWith('attP')).length;
|
|
22
|
-
const inputSequenceId = getPcrTemplateSequenceId(source);
|
|
23
24
|
|
|
24
|
-
const dispatch = useDispatch();
|
|
25
25
|
const onSubmit = (event) => {
|
|
26
26
|
event.preventDefault();
|
|
27
27
|
const newSource = {
|
|
@@ -36,20 +36,14 @@ function PrimerDesignGatewayBP({ source }) {
|
|
|
36
36
|
circular: false,
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
dispatch(addTemplateChildAndSubsequentSource({ newSource, newSequence, sourceId: source.id }))
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
});
|
|
39
|
+
navigateAfterDesign(
|
|
40
|
+
() => dispatch(addTemplateChildAndSubsequentSource({ newSource, newSequence, sourceId: source.id })),
|
|
41
|
+
inputSequenceId,
|
|
42
|
+
);
|
|
49
43
|
};
|
|
44
|
+
|
|
50
45
|
return (
|
|
51
46
|
<form onSubmit={onSubmit}>
|
|
52
|
-
|
|
53
47
|
<FormControl fullWidth>
|
|
54
48
|
<SingleInputSelector
|
|
55
49
|
label="Donor vector"
|
package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.cy.jsx
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { GibsonAmplifyAndCircularControls } from './PrimerDesignGibsonAssembly';
|
|
3
|
+
|
|
4
|
+
const baseTargets = [1, 2, 3];
|
|
5
|
+
const baseSequenceNames = [
|
|
6
|
+
{ id: 1, name: 'seq1' },
|
|
7
|
+
{ id: 2, name: 'seq2' },
|
|
8
|
+
{ id: 3, name: 'seq3' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function TestComponent() {
|
|
12
|
+
const [circularAssembly, setCircularAssembly] = React.useState(false);
|
|
13
|
+
const [amplified, setAmplified] = React.useState([false, true, false]);
|
|
14
|
+
return <GibsonAmplifyAndCircularControls
|
|
15
|
+
targets={baseTargets}
|
|
16
|
+
sequenceNames={baseSequenceNames}
|
|
17
|
+
amplified={amplified}
|
|
18
|
+
setAmplified={setAmplified}
|
|
19
|
+
circularAssembly={circularAssembly}
|
|
20
|
+
setCircularAssembly={setCircularAssembly}
|
|
21
|
+
/>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('<GibsonAmplifyAndCircularControls />', () => {
|
|
25
|
+
|
|
26
|
+
it('disables turning off a fragment when it would create two adjacent unamplified fragments in linear assemblies', () => {
|
|
27
|
+
cy.mount(
|
|
28
|
+
<GibsonAmplifyAndCircularControls
|
|
29
|
+
targets={baseTargets}
|
|
30
|
+
sequenceNames={baseSequenceNames}
|
|
31
|
+
amplified={[true, false, false]}
|
|
32
|
+
setAmplified={() => {}}
|
|
33
|
+
circularAssembly={false}
|
|
34
|
+
setCircularAssembly={() => {}}
|
|
35
|
+
/>,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// First checkbox corresponds to Seq. 1
|
|
40
|
+
cy.get('input[type="checkbox"]').eq(0).should('be.disabled');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('enforces circular adjacency rule when circularAssembly is true', () => {
|
|
44
|
+
// Configuration where turning off the last fragment is allowed
|
|
45
|
+
cy.mount(
|
|
46
|
+
<GibsonAmplifyAndCircularControls
|
|
47
|
+
targets={baseTargets}
|
|
48
|
+
sequenceNames={baseSequenceNames}
|
|
49
|
+
amplified={[false, true, true]}
|
|
50
|
+
setAmplified={() => {}}
|
|
51
|
+
circularAssembly
|
|
52
|
+
setCircularAssembly={() => {}}
|
|
53
|
+
/>,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Third checkbox corresponds to Seq. 3
|
|
57
|
+
cy.get('input[type="checkbox"]').eq(1).should('be.disabled');
|
|
58
|
+
cy.get('input[type="checkbox"]').eq(2).should('be.disabled');
|
|
59
|
+
|
|
60
|
+
// Configuration where turning off the last fragment would leave both ends unamplified
|
|
61
|
+
cy.mount(
|
|
62
|
+
<GibsonAmplifyAndCircularControls
|
|
63
|
+
targets={baseTargets}
|
|
64
|
+
sequenceNames={baseSequenceNames}
|
|
65
|
+
amplified={[false, true, false]}
|
|
66
|
+
setAmplified={() => {}}
|
|
67
|
+
circularAssembly
|
|
68
|
+
setCircularAssembly={() => {}}
|
|
69
|
+
/>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
cy.get('input[type="checkbox"]').eq(1).should('be.disabled');
|
|
73
|
+
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('enforces linear rules when circularAssembly is false', () => {
|
|
77
|
+
cy.mount(
|
|
78
|
+
<GibsonAmplifyAndCircularControls
|
|
79
|
+
targets={baseTargets}
|
|
80
|
+
sequenceNames={baseSequenceNames}
|
|
81
|
+
amplified={[false, true, true]}
|
|
82
|
+
setAmplified={() => {}}
|
|
83
|
+
circularAssembly={false}
|
|
84
|
+
setCircularAssembly={() => {}}
|
|
85
|
+
/>,
|
|
86
|
+
);
|
|
87
|
+
cy.get('input[type="checkbox"]').eq(1).should('be.disabled');
|
|
88
|
+
cy.get('input[type="checkbox"]').eq(2).should('not.be.disabled');
|
|
89
|
+
|
|
90
|
+
cy.mount(
|
|
91
|
+
<GibsonAmplifyAndCircularControls
|
|
92
|
+
targets={baseTargets}
|
|
93
|
+
sequenceNames={baseSequenceNames}
|
|
94
|
+
amplified={[true, true, false]}
|
|
95
|
+
setAmplified={() => {}}
|
|
96
|
+
circularAssembly={false}
|
|
97
|
+
setCircularAssembly={() => {}}
|
|
98
|
+
/>,
|
|
99
|
+
);
|
|
100
|
+
cy.get('input[type="checkbox"]').eq(0).should('not.be.disabled');
|
|
101
|
+
cy.get('input[type="checkbox"]').eq(1).should('be.disabled');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('disables circular assembly checkbox when there is only one target', () => {
|
|
105
|
+
cy.mount(
|
|
106
|
+
<GibsonAmplifyAndCircularControls
|
|
107
|
+
targets={[1]}
|
|
108
|
+
sequenceNames={[{ id: 1, name: 'single' }]}
|
|
109
|
+
amplified={[true]}
|
|
110
|
+
setAmplified={() => {}}
|
|
111
|
+
circularAssembly
|
|
112
|
+
setCircularAssembly={() => {}}
|
|
113
|
+
/>,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
cy.get('input[type="checkbox"]').eq(1).should('not.exist');
|
|
117
|
+
cy.get('input[name="circular-assembly"]').should('be.disabled').and('be.checked');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('calls setAmplified with all true values when targets changeor circularAssembly changes', () => {
|
|
121
|
+
cy.mount(<TestComponent />);
|
|
122
|
+
|
|
123
|
+
cy.get('input[type="checkbox"]').eq(0).should('not.be.checked');
|
|
124
|
+
cy.get('input[type="checkbox"]').eq(2).should('not.be.checked');
|
|
125
|
+
cy.get('input[type="checkbox"]').last().click();
|
|
126
|
+
cy.get('input[type="checkbox"]').eq(0).should('be.checked');
|
|
127
|
+
cy.get('input[type="checkbox"]').eq(1).should('be.checked');
|
|
128
|
+
cy.get('input[type="checkbox"]').eq(2).should('be.checked');
|
|
129
|
+
|
|
130
|
+
})
|
|
131
|
+
});
|
package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx
CHANGED
|
@@ -1,27 +1,137 @@
|
|
|
1
|
-
import { Button, FormControl } from '@mui/material';
|
|
1
|
+
import { Box, Button, Checkbox, FormControl, FormControlLabel, Tooltip, Typography } from '@mui/material';
|
|
2
|
+
import { Info as InfoIcon } from '@mui/icons-material';
|
|
2
3
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
5
|
+
import { isEqual } from 'lodash-es';
|
|
4
6
|
import MultipleInputsSelector from '../../../sources/MultipleInputsSelector';
|
|
5
7
|
import { cloningActions } from '@opencloning/store/cloning';
|
|
6
|
-
import useStoreEditor from '../../../../hooks/useStoreEditor';
|
|
7
8
|
import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
|
|
9
|
+
import useNavigateAfterPrimerDesign from './useNavigateAfterPrimerDesign';
|
|
10
|
+
import { getSequenceLabel } from '../SequenceTabComponents/utils/getSequenceLabel';
|
|
11
|
+
|
|
12
|
+
const { addPCRsAndSubsequentSourcesForAssembly } = cloningActions;
|
|
13
|
+
|
|
14
|
+
function AmplifySectionTitle() {
|
|
15
|
+
return (
|
|
16
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, mb: 0.5, alignSelf: 'stretch' }}>
|
|
17
|
+
<Typography variant="caption" sx={{ fontSize: '1rem', color: 'text.secondary' }}>
|
|
18
|
+
Amplify
|
|
19
|
+
</Typography>
|
|
20
|
+
<Tooltip
|
|
21
|
+
arrow
|
|
22
|
+
placement="top"
|
|
23
|
+
title="Checked sequences will be amplified by PCR with designed primers. Unchecked sequences are used directly in the assembly without amplification."
|
|
24
|
+
>
|
|
25
|
+
<InfoIcon sx={{ fontSize: '1rem', color: 'text.secondary' }} />
|
|
26
|
+
</Tooltip>
|
|
27
|
+
</Box>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function GibsonAmplifyAndCircularControls({
|
|
32
|
+
targets,
|
|
33
|
+
sequenceNames,
|
|
34
|
+
amplified,
|
|
35
|
+
setAmplified,
|
|
36
|
+
circularAssembly,
|
|
37
|
+
setCircularAssembly,
|
|
38
|
+
}) {
|
|
39
|
+
const isValidAmplifiedConfig = (config) => {
|
|
40
|
+
for (let i = 0; i < config.length - 1; i += 1) {
|
|
41
|
+
if (!config[i] && !config[i + 1]) return false;
|
|
42
|
+
}
|
|
43
|
+
if (circularAssembly && config.length > 1 && !config[0] && !config[config.length - 1]) return false;
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const canToggleOff = (index) => {
|
|
48
|
+
if (!amplified[index]) return true;
|
|
49
|
+
const next = [...amplified];
|
|
50
|
+
next[index] = false;
|
|
51
|
+
return isValidAmplifiedConfig(next);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleAmplifiedToggle = (index) => {
|
|
55
|
+
setAmplified((prev) => {
|
|
56
|
+
const next = [...prev];
|
|
57
|
+
next[index] = !next[index];
|
|
58
|
+
return next;
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
setAmplified(Array(targets.length).fill(true));
|
|
64
|
+
}, [targets, circularAssembly]);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
{targets.length > 1 && (
|
|
69
|
+
<Box data-testid="amplify-section" sx={{ mt: 1, display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
|
70
|
+
<AmplifySectionTitle />
|
|
71
|
+
{targets.map((id, index) => {
|
|
72
|
+
const name = sequenceNames.find((s) => s.id === id)?.name || 'template';
|
|
73
|
+
const label = getSequenceLabel(id, name);
|
|
74
|
+
return (
|
|
75
|
+
<FormControl data-testid={`amplify-section-${index}`} key={id} sx={{ alignItems: 'flex-start', mb: 0.5 }}>
|
|
76
|
+
<FormControlLabel
|
|
77
|
+
control={(
|
|
78
|
+
<Checkbox
|
|
79
|
+
checked={amplified[index]}
|
|
80
|
+
onChange={() => handleAmplifiedToggle(index)}
|
|
81
|
+
disabled={amplified[index] && !canToggleOff(index)}
|
|
82
|
+
size="small"
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
85
|
+
label={(
|
|
86
|
+
<Typography variant="body2" noWrap sx={{ minWidth: 0 }}>
|
|
87
|
+
{label}
|
|
88
|
+
</Typography>
|
|
89
|
+
)}
|
|
90
|
+
sx={{ mr: 0 }}
|
|
91
|
+
/>
|
|
92
|
+
</FormControl>
|
|
93
|
+
);
|
|
94
|
+
})}
|
|
95
|
+
</Box>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
<FormControl>
|
|
99
|
+
<FormControlLabel
|
|
100
|
+
control={(
|
|
101
|
+
<Checkbox
|
|
102
|
+
data-test="circular-assembly-checkbox"
|
|
103
|
+
disabled={targets.length === 1}
|
|
104
|
+
checked={circularAssembly}
|
|
105
|
+
onChange={(e) => setCircularAssembly(e.target.checked)}
|
|
106
|
+
name="circular-assembly"
|
|
107
|
+
/>
|
|
108
|
+
)}
|
|
109
|
+
label={targets.length === 1 ? 'Circular assembly (only one sequence input)' : 'Circular assembly'}
|
|
110
|
+
/>
|
|
111
|
+
</FormControl>
|
|
112
|
+
</>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
8
115
|
|
|
9
116
|
function PrimerDesignGibsonAssembly({ source, assemblyType }) {
|
|
10
117
|
const [targets, setTargets] = React.useState(source.input.map(({ sequence }) => sequence));
|
|
118
|
+
const [amplified, setAmplified] = React.useState(() => source.input.map(() => true));
|
|
119
|
+
const [circularAssembly, setCircularAssembly] = React.useState(true);
|
|
11
120
|
const inputSequenceId = getPcrTemplateSequenceId(source);
|
|
121
|
+
const dispatch = useDispatch();
|
|
122
|
+
const navigateAfterDesign = useNavigateAfterPrimerDesign();
|
|
123
|
+
|
|
124
|
+
const sequenceNames = useSelector(
|
|
125
|
+
({ cloning }) => targets.map((id) => ({ id, name: cloning.teselaJsonCache[id]?.name || 'template' })),
|
|
126
|
+
isEqual,
|
|
127
|
+
);
|
|
12
128
|
|
|
13
129
|
const onInputChange = (newInputSequenceIds) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} else {
|
|
18
|
-
setTargets(newInputSequenceIds);
|
|
19
|
-
}
|
|
130
|
+
const newValue = newInputSequenceIds.includes(inputSequenceId) ? newInputSequenceIds : [...newInputSequenceIds, inputSequenceId];
|
|
131
|
+
setTargets(newValue);
|
|
132
|
+
setAmplified(Array(newValue.length).fill(true));
|
|
20
133
|
};
|
|
21
134
|
|
|
22
|
-
const { updateStoreEditor } = useStoreEditor();
|
|
23
|
-
const { addPCRsAndSubsequentSourcesForAssembly, setCurrentTab, setMainSequenceId } = cloningActions;
|
|
24
|
-
const dispatch = useDispatch();
|
|
25
135
|
const onSubmit = (event) => {
|
|
26
136
|
event.preventDefault();
|
|
27
137
|
const newSequence = {
|
|
@@ -30,18 +140,18 @@ function PrimerDesignGibsonAssembly({ source, assemblyType }) {
|
|
|
30
140
|
circular: false,
|
|
31
141
|
};
|
|
32
142
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
143
|
+
const firstAmplifiedId = targets.find((_, i) => amplified[i]) ?? inputSequenceId;
|
|
144
|
+
navigateAfterDesign(
|
|
145
|
+
() => dispatch(addPCRsAndSubsequentSourcesForAssembly({
|
|
146
|
+
sourceId: source.id,
|
|
147
|
+
newSequence,
|
|
148
|
+
templateIds: targets,
|
|
149
|
+
sourceType: assemblyType,
|
|
150
|
+
amplified,
|
|
151
|
+
circularAssembly,
|
|
152
|
+
})),
|
|
153
|
+
firstAmplifiedId,
|
|
154
|
+
);
|
|
45
155
|
};
|
|
46
156
|
|
|
47
157
|
return (
|
|
@@ -54,6 +164,15 @@ function PrimerDesignGibsonAssembly({ source, assemblyType }) {
|
|
|
54
164
|
/>
|
|
55
165
|
</FormControl>
|
|
56
166
|
|
|
167
|
+
<GibsonAmplifyAndCircularControls
|
|
168
|
+
targets={targets}
|
|
169
|
+
sequenceNames={sequenceNames}
|
|
170
|
+
amplified={amplified}
|
|
171
|
+
setAmplified={setAmplified}
|
|
172
|
+
circularAssembly={circularAssembly}
|
|
173
|
+
setCircularAssembly={setCircularAssembly}
|
|
174
|
+
/>
|
|
175
|
+
|
|
57
176
|
<Button type="submit" variant="contained" color="success">
|
|
58
177
|
Design primers
|
|
59
178
|
</Button>
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { Alert, Button, FormControl } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { useDispatch } from 'react-redux';
|
|
4
4
|
import SingleInputSelector from '../../../sources/SingleInputSelector';
|
|
5
5
|
import { cloningActions } from '@opencloning/store/cloning';
|
|
6
|
-
import useStoreEditor from '../../../../hooks/useStoreEditor';
|
|
7
6
|
import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
|
|
7
|
+
import useNavigateAfterPrimerDesign from './useNavigateAfterPrimerDesign';
|
|
8
|
+
|
|
9
|
+
const { addTemplateChildAndSubsequentSource } = cloningActions;
|
|
8
10
|
|
|
9
11
|
function PrimerDesignHomologousRecombination({ source, primerDesignType }) {
|
|
10
12
|
const [target, setTarget] = React.useState('');
|
|
11
|
-
|
|
12
|
-
const { updateStoreEditor } = useStoreEditor();
|
|
13
|
-
const { addTemplateChildAndSubsequentSource, setCurrentTab, setMainSequenceId } = cloningActions;
|
|
14
13
|
const dispatch = useDispatch();
|
|
15
14
|
const inputSequenceId = getPcrTemplateSequenceId(source);
|
|
15
|
+
const navigateAfterDesign = useNavigateAfterPrimerDesign();
|
|
16
|
+
|
|
16
17
|
const onSubmit = (event) => {
|
|
17
18
|
event.preventDefault();
|
|
18
19
|
const newSource = {
|
|
@@ -25,17 +26,12 @@ function PrimerDesignHomologousRecombination({ source, primerDesignType }) {
|
|
|
25
26
|
circular: false,
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
dispatch(addTemplateChildAndSubsequentSource({ newSource, newSequence, sourceId: source.id }))
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
dispatch(setCurrentTab(3));
|
|
33
|
-
// Scroll to the top of the page after 300ms
|
|
34
|
-
setTimeout(() => {
|
|
35
|
-
document.querySelector('.tab-panels-container')?.scrollTo({ top: 0, behavior: 'instant' });
|
|
36
|
-
}, 300);
|
|
37
|
-
});
|
|
29
|
+
navigateAfterDesign(
|
|
30
|
+
() => dispatch(addTemplateChildAndSubsequentSource({ newSource, newSequence, sourceId: source.id })),
|
|
31
|
+
inputSequenceId,
|
|
32
|
+
);
|
|
38
33
|
};
|
|
34
|
+
|
|
39
35
|
return (
|
|
40
36
|
<form onSubmit={onSubmit}>
|
|
41
37
|
<Alert severity="info" icon={false} sx={{ textAlign: 'left' }}>
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { FormControl, InputLabel, MenuItem, Select } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
|
|
4
|
-
import { batch, useDispatch } from 'react-redux';
|
|
3
|
+
import { useDispatch } from 'react-redux';
|
|
5
4
|
import PrimerDesignHomologousRecombination from './PrimerDesignHomologousRecombination';
|
|
6
5
|
import PrimerDesignGibsonAssembly from './PrimerDesignGibsonAssembly';
|
|
7
6
|
import { cloningActions } from '@opencloning/store/cloning';
|
|
8
|
-
import useStoreEditor from '../../../../hooks/useStoreEditor';
|
|
9
7
|
import PrimerDesignGatewayBP from './PrimerDesignGatewayBP';
|
|
10
8
|
import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
|
|
9
|
+
import useNavigateAfterPrimerDesign from './useNavigateAfterPrimerDesign';
|
|
10
|
+
|
|
11
|
+
const { addPCRsAndSubsequentSourcesForAssembly } = cloningActions;
|
|
11
12
|
|
|
12
13
|
function PrimerDesignSourceForm({ source }) {
|
|
13
14
|
const [primerDesignType, setPrimerDesignType] = React.useState('');
|
|
14
|
-
const { updateStoreEditor } = useStoreEditor();
|
|
15
|
-
const { addPCRsAndSubsequentSourcesForAssembly, setMainSequenceId, setCurrentTab } = cloningActions;
|
|
16
15
|
const dispatch = useDispatch();
|
|
17
16
|
const inputSequenceId = getPcrTemplateSequenceId(source);
|
|
17
|
+
const navigateAfterDesign = useNavigateAfterPrimerDesign();
|
|
18
|
+
|
|
18
19
|
React.useEffect(() => {
|
|
19
|
-
// Here the user does not have to select anything else
|
|
20
20
|
if (primerDesignType === 'restriction_ligation' || primerDesignType === 'simple_pair') {
|
|
21
21
|
const newSequence = {
|
|
22
22
|
type: 'TemplateSequence',
|
|
@@ -24,16 +24,10 @@ function PrimerDesignSourceForm({ source }) {
|
|
|
24
24
|
circular: false,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
dispatch(addPCRsAndSubsequentSourcesForAssembly({ sourceId: source.id, newSequence, templateIds: [], sourceType: null }))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
dispatch(setCurrentTab(3));
|
|
32
|
-
// Scroll to the top of the page after 300ms
|
|
33
|
-
setTimeout(() => {
|
|
34
|
-
document.querySelector('.tab-panels-container')?.scrollTo({ top: 0, behavior: 'instant' });
|
|
35
|
-
}, 300);
|
|
36
|
-
});
|
|
27
|
+
navigateAfterDesign(
|
|
28
|
+
() => dispatch(addPCRsAndSubsequentSourcesForAssembly({ sourceId: source.id, newSequence, templateIds: [inputSequenceId], sourceType: null })),
|
|
29
|
+
inputSequenceId,
|
|
30
|
+
);
|
|
37
31
|
}
|
|
38
32
|
}, [primerDesignType]);
|
|
39
33
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { batch, useDispatch } from 'react-redux';
|
|
3
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
4
|
+
import useStoreEditor from '../../../../hooks/useStoreEditor';
|
|
5
|
+
|
|
6
|
+
const { setMainSequenceId, setCurrentTab } = cloningActions;
|
|
7
|
+
|
|
8
|
+
export default function useNavigateAfterPrimerDesign() {
|
|
9
|
+
const dispatch = useDispatch();
|
|
10
|
+
const { updateStoreEditor } = useStoreEditor();
|
|
11
|
+
|
|
12
|
+
return useCallback((primaryAction, inputSequenceId) => {
|
|
13
|
+
batch(() => {
|
|
14
|
+
primaryAction();
|
|
15
|
+
dispatch(setMainSequenceId(inputSequenceId));
|
|
16
|
+
updateStoreEditor('mainEditor', inputSequenceId);
|
|
17
|
+
dispatch(setCurrentTab(3));
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
document.querySelector('.tab-panels-container')?.scrollTo({ top: 0, behavior: 'instant' });
|
|
20
|
+
}, 300);
|
|
21
|
+
});
|
|
22
|
+
}, [dispatch, updateStoreEditor]);
|
|
23
|
+
}
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Version placeholder - replaced at publish time via prepack script
|
|
2
|
-
export const version = "1.
|
|
2
|
+
export const version = "1.5.1";
|