@opencloning/ui 1.1.1 → 1.2.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 +29 -0
- package/package.json +4 -4
- package/src/components/MainSequenceEditor.jsx +2 -0
- package/src/components/assembler/Assembler.jsx +0 -1
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +110 -91
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +1 -1
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +2 -2
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +1 -1
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +1 -1
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +1 -1
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +5 -22
- package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +6 -4
- package/src/components/sources/SourceAssembly.jsx +1 -2
- package/src/hooks/useStoreEditor.js +4 -0
- package/src/version.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @opencloning/ui
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#595](https://github.com/manulera/OpenCloning_frontend/pull/595) [`1b28cc5`](https://github.com/manulera/OpenCloning_frontend/commit/1b28cc5852460a072982dc529b58fc9607fae21f) Thanks [@manulera](https://github.com/manulera)! - Minor improvements and bug fixes:
|
|
8
|
+
|
|
9
|
+
- include name of tracks in alignment + update ove to display correct Track Properties table
|
|
10
|
+
- fix display main sequence when alignments are present
|
|
11
|
+
- change default minimum hib length to 14 for primer design
|
|
12
|
+
- Gibson primer design: default to circular assembly, force circular for single input assemblies
|
|
13
|
+
- Gibson primer design: make product sequence preview circular when assembly is circular
|
|
14
|
+
- Primer design: in circular assemblies of one fragment only, display the spacer before the fragment in the preview.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies []:
|
|
19
|
+
- @opencloning/store@1.2.0
|
|
20
|
+
- @opencloning/utils@1.2.0
|
|
21
|
+
|
|
22
|
+
## 1.1.2
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- [#592](https://github.com/manulera/OpenCloning_frontend/pull/592) [`57092ae`](https://github.com/manulera/OpenCloning_frontend/commit/57092ae54d96485e84191d6c20ade9e9a6838a65) Thanks [@manulera](https://github.com/manulera)! - Remove partial overlap option from restriction and ligation overlap, related to https://github.com/manulera/OpenCloning_backend/pull/389
|
|
27
|
+
|
|
28
|
+
- Updated dependencies []:
|
|
29
|
+
- @opencloning/store@1.1.2
|
|
30
|
+
- @opencloning/utils@1.1.2
|
|
31
|
+
|
|
3
32
|
## 1.1.1
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencloning/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"@emotion/styled": "^11.14.0",
|
|
25
25
|
"@mui/icons-material": "^5.15.17",
|
|
26
26
|
"@mui/material": "^5.15.17",
|
|
27
|
-
"@opencloning/store": "1.
|
|
28
|
-
"@opencloning/utils": "1.
|
|
27
|
+
"@opencloning/store": "1.2.0",
|
|
28
|
+
"@opencloning/utils": "1.2.0",
|
|
29
29
|
"@teselagen/bio-parsers": "^0.4.32",
|
|
30
|
-
"@teselagen/ove": "^0.8.
|
|
30
|
+
"@teselagen/ove": "^0.8.30",
|
|
31
31
|
"@teselagen/range-utils": "^0.3.13",
|
|
32
32
|
"@teselagen/sequence-utils": "^0.3.35",
|
|
33
33
|
"@zip.js/zip.js": "^2.7.62",
|
|
@@ -65,6 +65,8 @@ function MainSequenceEditor() {
|
|
|
65
65
|
(state) => {
|
|
66
66
|
const history = state.VectorEditor.mainEditor?.sequenceDataHistory;
|
|
67
67
|
if (!history) return false;
|
|
68
|
+
const sequenceId = state.VectorEditor.mainEditor?.sequenceData?.id;
|
|
69
|
+
if (sequenceId === 'opencloning_primer_design_product') return false;
|
|
68
70
|
return state.cloning.mainSequenceId && Object.keys(history).length > 0 && history.future.length === 0;
|
|
69
71
|
}
|
|
70
72
|
);
|
|
@@ -138,7 +138,6 @@ function AssemblerComponent({ data, categories }) {
|
|
|
138
138
|
{index === 0 && item.category !== '' && (
|
|
139
139
|
<AssemblerLink overhang={leftOverhang} />
|
|
140
140
|
)}
|
|
141
|
-
<AssemblerPart />
|
|
142
141
|
<Box sx={{ width: '250px', border: 3, borderColor, borderRadius: 4, p: 2 }}>
|
|
143
142
|
<FormControl fullWidth sx={{ mb: 2 }}>
|
|
144
143
|
<InputLabel>Category</InputLabel>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { batch, useDispatch, useSelector, useStore } from 'react-redux';
|
|
3
3
|
import { updateEditor } from '@teselagen/ove';
|
|
4
4
|
import { isEqual } from 'lodash-es';
|
|
@@ -18,19 +18,28 @@ function changeValueAtIndex(current, index, newValue) {
|
|
|
18
18
|
const PrimerDesignContext = React.createContext();
|
|
19
19
|
|
|
20
20
|
export function PrimerDesignProvider({ children, designType, sequenceIds, primerDesignSettings, steps }) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
|
|
22
|
+
const templateSequenceIds = React.useMemo(() => {
|
|
23
|
+
if (designType === 'homologous_recombination' || designType === 'gateway_bp') {
|
|
24
|
+
return sequenceIds.slice(0, 1);
|
|
25
|
+
}
|
|
26
|
+
return sequenceIds;
|
|
27
|
+
}, [sequenceIds, designType]);
|
|
28
|
+
|
|
29
|
+
// Compute initial values based on design type (props don't change, so compute once)
|
|
30
|
+
const initialFragmentOrientationsLength = templateSequenceIds.length;
|
|
31
|
+
const initialCircularAssembly = designType === 'gibson_assembly';
|
|
32
|
+
const initialSpacersLength = initialCircularAssembly ? initialFragmentOrientationsLength : initialFragmentOrientationsLength + 1;
|
|
25
33
|
|
|
26
34
|
const [primers, setPrimers] = useState([]);
|
|
27
35
|
const [rois, setRois] = useState(Array(sequenceIds.length).fill(null));
|
|
28
36
|
const [error, setError] = useState('');
|
|
29
37
|
const [selectedTab, setSelectedTab] = useState(0);
|
|
30
38
|
const [sequenceProduct, setSequenceProduct] = useState(null);
|
|
31
|
-
const [fragmentOrientations, setFragmentOrientations] = useState(Array(
|
|
32
|
-
const [circularAssembly, setCircularAssembly] = useState(
|
|
33
|
-
const [spacers, setSpacers] = useState(Array(
|
|
39
|
+
const [fragmentOrientations, setFragmentOrientations] = useState(Array(initialFragmentOrientationsLength).fill('forward'));
|
|
40
|
+
const [circularAssembly, setCircularAssembly] = useState(initialCircularAssembly);
|
|
41
|
+
const [spacers, setSpacers] = useState(Array(initialSpacersLength).fill(''));
|
|
42
|
+
const sequenceProductTimeoutRef = React.useRef();
|
|
34
43
|
|
|
35
44
|
const spacersAreValid = React.useMemo(() => spacers.every((spacer) => !stringIsNotDNA(spacer)), [spacers]);
|
|
36
45
|
const sequenceNames = useSelector((state) => sequenceIds.map((id) => state.cloning.teselaJsonCache[id].name), isEqual);
|
|
@@ -44,7 +53,7 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
44
53
|
const { setMainSequenceId, addPrimersToPCRSource, setCurrentTab } = cloningActions;
|
|
45
54
|
const httpClient = useHttpClient();
|
|
46
55
|
|
|
47
|
-
const
|
|
56
|
+
const submissionPreventedMessage = React.useMemo(() => {
|
|
48
57
|
if (rois.some((region) => region === null)) {
|
|
49
58
|
return 'Not all regions have been selected';
|
|
50
59
|
} if (primerDesignSettings.error) {
|
|
@@ -53,71 +62,78 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
53
62
|
return 'Spacer sequences not valid';
|
|
54
63
|
}
|
|
55
64
|
return '';
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const submissionPreventedMessage = getSubmissionPreventedMessage();
|
|
65
|
+
}, [rois, primerDesignSettings.error, spacers]);
|
|
59
66
|
|
|
60
67
|
React.useEffect(() => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
68
|
+
// Clear any existing timeout
|
|
69
|
+
clearTimeout(sequenceProductTimeoutRef.current);
|
|
70
|
+
|
|
71
|
+
// Debounce the heavy calculation
|
|
72
|
+
sequenceProductTimeoutRef.current = setTimeout(() => {
|
|
73
|
+
let newSequenceProduct = null;
|
|
74
|
+
if (submissionPreventedMessage === '') {
|
|
75
|
+
const { teselaJsonCache } = store.getState().cloning;
|
|
76
|
+
const sequences = sequenceIds.map((id) => teselaJsonCache[id]);
|
|
77
|
+
if (designType === 'simple_pair' || designType === 'restriction_ligation') {
|
|
78
|
+
const enzymeSpacers = designType === 'restriction_ligation' ? primerDesignSettings.enzymeSpacers : ['', ''];
|
|
79
|
+
const extendedSpacers = [enzymeSpacers[0] + spacers[0], spacers[1] + enzymeSpacers[1]];
|
|
80
|
+
newSequenceProduct = joinSequencesIntoSingleSequence(sequences, rois.map((s) => s.selectionLayer), fragmentOrientations, extendedSpacers, circularAssembly, 'primer tail');
|
|
81
|
+
newSequenceProduct.name = 'PCR product';
|
|
82
|
+
} else if (designType === 'gibson_assembly') {
|
|
83
|
+
newSequenceProduct = joinSequencesIntoSingleSequence(sequences, rois.map((s) => s.selectionLayer), fragmentOrientations, spacers, circularAssembly);
|
|
84
|
+
newSequenceProduct.name = 'Gibson Assembly product';
|
|
85
|
+
} else if (designType === 'homologous_recombination') {
|
|
86
|
+
newSequenceProduct = simulateHomologousRecombination(sequences[0], sequences[1], rois, fragmentOrientations[0] === 'reverse', spacers);
|
|
87
|
+
newSequenceProduct.name = 'Homologous recombination product';
|
|
88
|
+
} else if (designType === 'gateway_bp') {
|
|
89
|
+
newSequenceProduct = joinSequencesIntoSingleSequence([sequences[0]], [rois[0].selectionLayer], fragmentOrientations, spacers, false, 'primer tail');
|
|
90
|
+
newSequenceProduct.name = 'PCR product';
|
|
91
|
+
const { knownCombination } = primerDesignSettings;
|
|
92
|
+
const leftFeature = {
|
|
93
|
+
start: knownCombination.translationFrame[0],
|
|
94
|
+
end: spacers[0].length - 1,
|
|
95
|
+
type: 'CDS',
|
|
96
|
+
name: 'translation frame',
|
|
97
|
+
strand: 1,
|
|
98
|
+
forward: true,
|
|
99
|
+
};
|
|
100
|
+
const nbAas = Math.floor((spacers[1].length - knownCombination.translationFrame[1]) / 3);
|
|
101
|
+
const rightStart = newSequenceProduct.sequence.length - knownCombination.translationFrame[1] - nbAas * 3;
|
|
102
|
+
const rightFeature = {
|
|
103
|
+
start: rightStart,
|
|
104
|
+
end: newSequenceProduct.sequence.length - knownCombination.translationFrame[1] - 1,
|
|
105
|
+
type: 'CDS',
|
|
106
|
+
name: 'translation frame',
|
|
107
|
+
strand: 1,
|
|
108
|
+
forward: true,
|
|
109
|
+
};
|
|
110
|
+
newSequenceProduct.features.push(leftFeature);
|
|
111
|
+
newSequenceProduct.features.push(rightFeature);
|
|
112
|
+
} else if (designType === 'ebic') {
|
|
113
|
+
newSequenceProduct = ebicTemplateAnnotation(sequences[0], rois[0].selectionLayer, primerDesignSettings);
|
|
114
|
+
}
|
|
104
115
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
setSequenceProduct({...newSequenceProduct, id: 'opencloning_primer_design_product'});
|
|
117
|
+
}, 300);
|
|
118
|
+
|
|
119
|
+
// Cleanup timeout on unmount or when dependencies change
|
|
120
|
+
return () => {
|
|
121
|
+
clearTimeout(sequenceProductTimeoutRef.current);
|
|
122
|
+
};
|
|
123
|
+
}, [rois, spacersAreValid, fragmentOrientations, circularAssembly, designType, spacers, primerDesignSettings, sequenceIds, templateSequenceIds, store, submissionPreventedMessage]);
|
|
108
124
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
// Remove the first spacer
|
|
125
|
+
|
|
126
|
+
React.useEffect(() => {
|
|
127
|
+
if (circularAssembly && spacers.length !== templateSequenceIds.length) {
|
|
113
128
|
setSpacers((current) => current.slice(1));
|
|
114
|
-
}
|
|
115
|
-
|
|
129
|
+
}
|
|
130
|
+
if (!circularAssembly && spacers.length !== templateSequenceIds.length + 1) {
|
|
116
131
|
setSpacers((current) => ['', ...current]);
|
|
117
132
|
}
|
|
118
|
-
};
|
|
133
|
+
}, [circularAssembly, spacers, templateSequenceIds.length]);
|
|
119
134
|
|
|
120
|
-
|
|
135
|
+
|
|
136
|
+
const onSelectRegion = useCallback((index, selectedRegion, allowSinglePosition = false) => {
|
|
121
137
|
const { caretPosition } = selectedRegion;
|
|
122
138
|
if (caretPosition === undefined) {
|
|
123
139
|
setRois((c) => changeValueAtIndex(c, index, null));
|
|
@@ -133,39 +149,40 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
133
149
|
}
|
|
134
150
|
setRois((c) => changeValueAtIndex(c, index, null));
|
|
135
151
|
return 'Select a region (not a single position) to amplify';
|
|
136
|
-
};
|
|
152
|
+
}, [setRois]);
|
|
137
153
|
|
|
138
|
-
const onTabChange = (event, newValue) => {
|
|
154
|
+
const onTabChange = useCallback((event, newValue) => {
|
|
139
155
|
setSelectedTab(newValue);
|
|
140
156
|
if (newValue < sequenceIds.length) {
|
|
141
157
|
updateStoreEditor('mainEditor', sequenceIds[newValue]);
|
|
142
158
|
dispatch(setMainSequenceId(sequenceIds[newValue]));
|
|
143
159
|
} else if (newValue === sequenceIds.length) {
|
|
144
|
-
|
|
160
|
+
// Don't update editor here - let the useEffect handle it when sequenceProduct is ready
|
|
161
|
+
// This avoids using stale data since sequenceProduct is debounced
|
|
145
162
|
} else {
|
|
146
163
|
updateStoreEditor('mainEditor', null);
|
|
147
164
|
}
|
|
148
|
-
};
|
|
165
|
+
}, [sequenceIds, updateStoreEditor, dispatch, setMainSequenceId, setSelectedTab]);
|
|
149
166
|
|
|
150
|
-
const handleNext = () => {
|
|
167
|
+
const handleNext = useCallback(() => {
|
|
151
168
|
onTabChange(null, selectedTab + 1);
|
|
152
|
-
};
|
|
169
|
+
}, [onTabChange, selectedTab]);
|
|
153
170
|
|
|
154
|
-
const handleBack = () => {
|
|
171
|
+
const handleBack = useCallback(() => {
|
|
155
172
|
onTabChange(null, selectedTab - 1);
|
|
156
|
-
};
|
|
173
|
+
}, [onTabChange, selectedTab]);
|
|
157
174
|
|
|
158
|
-
const handleSelectRegion = (index, selectedRegion, allowSinglePosition = false) => {
|
|
175
|
+
const handleSelectRegion = useCallback((index, selectedRegion, allowSinglePosition = false) => {
|
|
159
176
|
const regionError = onSelectRegion(index, selectedRegion, allowSinglePosition);
|
|
160
177
|
if (!regionError) {
|
|
161
178
|
handleNext();
|
|
162
179
|
}
|
|
163
180
|
return regionError;
|
|
164
|
-
};
|
|
181
|
+
}, [onSelectRegion, handleNext]);
|
|
165
182
|
|
|
166
|
-
const handleFragmentOrientationChange = (index, orientation) => {
|
|
183
|
+
const handleFragmentOrientationChange = useCallback((index, orientation) => {
|
|
167
184
|
setFragmentOrientations((current) => changeValueAtIndex(current, index, orientation));
|
|
168
|
-
};
|
|
185
|
+
}, [setFragmentOrientations]);
|
|
169
186
|
|
|
170
187
|
// Focus on the right sequence when changing tabs
|
|
171
188
|
useEffect(() => {
|
|
@@ -178,16 +195,16 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
178
195
|
|
|
179
196
|
// Update the sequence product in the editor if in the last tab
|
|
180
197
|
useEffect(() => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
updateEditor(store, 'mainEditor', { sequenceData: sequenceProduct || {} });
|
|
184
|
-
}
|
|
185
|
-
}, 500);
|
|
198
|
+
if (selectedTab === sequenceIds.length) {
|
|
199
|
+
const timeoutId = setTimeout(() => {
|
|
200
|
+
updateEditor(store, 'mainEditor', { sequenceData: sequenceProduct || {}, selectionLayer: {} });
|
|
201
|
+
}, 100);
|
|
186
202
|
|
|
187
|
-
|
|
188
|
-
|
|
203
|
+
return () => clearTimeout(timeoutId);
|
|
204
|
+
}
|
|
205
|
+
}, [sequenceProduct, selectedTab, sequenceIds.length, store]);
|
|
189
206
|
|
|
190
|
-
const designPrimers = async () => {
|
|
207
|
+
const designPrimers = useCallback(async () => {
|
|
191
208
|
// Validate fragmentOrientations
|
|
192
209
|
fragmentOrientations.forEach((orientation) => {
|
|
193
210
|
if (orientation !== 'forward' && orientation !== 'reverse') {
|
|
@@ -277,9 +294,9 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
277
294
|
setError(errorMessage);
|
|
278
295
|
return true;
|
|
279
296
|
}
|
|
280
|
-
};
|
|
297
|
+
}, [fragmentOrientations, rois, sequenceIds, templateSequenceIds, designType, circularAssembly, primerDesignSettings, spacers, store, httpClient, backendRoute, handleNext]);
|
|
281
298
|
|
|
282
|
-
const addPrimers = () => {
|
|
299
|
+
const addPrimers = useCallback(() => {
|
|
283
300
|
const pcrSources = store.getState().cloning.sources.filter((source) => source.type === 'PCRSource');
|
|
284
301
|
let usedPCRSources;
|
|
285
302
|
if (designType === 'ebic') {
|
|
@@ -303,7 +320,7 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
303
320
|
onTabChange(null, 0);
|
|
304
321
|
document.getElementById(`source-${usedPCRSources[0].id}`)?.scrollIntoView();
|
|
305
322
|
updateStoreEditor('mainEditor', null);
|
|
306
|
-
};
|
|
323
|
+
}, [primers, dispatch, setMainSequenceId, setCurrentTab, onTabChange, updateStoreEditor, designType, templateSequenceIds, addPrimersToPCRSource, store]);
|
|
307
324
|
|
|
308
325
|
const value = React.useMemo(() => ({
|
|
309
326
|
primers,
|
|
@@ -318,7 +335,6 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
318
335
|
handleSelectRegion,
|
|
319
336
|
sequenceIds,
|
|
320
337
|
fragmentOrientations,
|
|
321
|
-
circularAssembly,
|
|
322
338
|
spacers,
|
|
323
339
|
setFragmentOrientations,
|
|
324
340
|
setSpacers,
|
|
@@ -327,7 +343,8 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
327
343
|
primerDesignSettings,
|
|
328
344
|
submissionPreventedMessage,
|
|
329
345
|
addPrimers,
|
|
330
|
-
|
|
346
|
+
circularAssembly,
|
|
347
|
+
setCircularAssembly,
|
|
331
348
|
templateSequenceIds,
|
|
332
349
|
templateSequenceNames,
|
|
333
350
|
designType,
|
|
@@ -354,7 +371,9 @@ export function PrimerDesignProvider({ children, designType, sequenceIds, primer
|
|
|
354
371
|
primerDesignSettings,
|
|
355
372
|
submissionPreventedMessage,
|
|
356
373
|
addPrimers,
|
|
374
|
+
setCircularAssembly,
|
|
357
375
|
templateSequenceIds,
|
|
376
|
+
templateSequenceNames,
|
|
358
377
|
designType,
|
|
359
378
|
steps,
|
|
360
379
|
]);
|
package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx
CHANGED
|
@@ -19,7 +19,7 @@ function PrimerDesignGatewayBP({ donorVectorId, pcrSource }) {
|
|
|
19
19
|
},
|
|
20
20
|
], [templateSequenceId, donorVectorId]);
|
|
21
21
|
|
|
22
|
-
const primerDesignSettings = useGatewayPrimerDesignSettings({ homology_length: null, minimal_hybridization_length:
|
|
22
|
+
const primerDesignSettings = useGatewayPrimerDesignSettings({ homology_length: null, minimal_hybridization_length: 14, target_tm: 55 });
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<PrimerDesignProvider
|
package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx
CHANGED
|
@@ -10,9 +10,9 @@ export default function PrimerDesignGibsonAssembly({ pcrSources }) {
|
|
|
10
10
|
...templateSequencesIds.map((id, index) => (
|
|
11
11
|
{ label: `Seq ${id}`, selectOrientation: true }
|
|
12
12
|
)),
|
|
13
|
-
], [pcrSources]);
|
|
13
|
+
], [pcrSources, templateSequencesIds]);
|
|
14
14
|
|
|
15
|
-
const primerDesignSettings = usePrimerDesignSettings({ homology_length: 35, minimal_hybridization_length:
|
|
15
|
+
const primerDesignSettings = usePrimerDesignSettings({ homology_length: 35, minimal_hybridization_length: 14, target_tm: 55 });
|
|
16
16
|
return (
|
|
17
17
|
<PrimerDesignProvider
|
|
18
18
|
designType="gibson_assembly"
|
|
@@ -18,7 +18,7 @@ export default function PrimerDesignHomologousRecombination({ homologousRecombin
|
|
|
18
18
|
},
|
|
19
19
|
], [templateSequenceId, homologousRecombinationTargetId]);
|
|
20
20
|
|
|
21
|
-
const primerDesignSettings = usePrimerDesignSettings({ homology_length: 80, minimal_hybridization_length:
|
|
21
|
+
const primerDesignSettings = usePrimerDesignSettings({ homology_length: 80, minimal_hybridization_length: 14, target_tm: 55 });
|
|
22
22
|
return (
|
|
23
23
|
<PrimerDesignProvider
|
|
24
24
|
designType="homologous_recombination"
|
package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx
CHANGED
|
@@ -12,7 +12,7 @@ function PrimerDesignRestriction({ pcrSource }) {
|
|
|
12
12
|
{ label: 'Amplified region' },
|
|
13
13
|
], []);
|
|
14
14
|
|
|
15
|
-
const primerDesignSettings = useEnzymePrimerDesignSettings({ homology_length: null, minimal_hybridization_length:
|
|
15
|
+
const primerDesignSettings = useEnzymePrimerDesignSettings({ homology_length: null, minimal_hybridization_length: 14, target_tm: 55 });
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<PrimerDesignProvider designType="restriction_ligation" sequenceIds={sequenceIds} primerDesignSettings={primerDesignSettings} steps={steps}>
|
package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx
CHANGED
|
@@ -12,7 +12,7 @@ function PrimerDesignSimplePair({ pcrSource }) {
|
|
|
12
12
|
{ label: 'Amplified region' },
|
|
13
13
|
], []);
|
|
14
14
|
|
|
15
|
-
const primerDesignSettings = usePrimerDesignSettings({ homology_length: null, minimal_hybridization_length:
|
|
15
|
+
const primerDesignSettings = usePrimerDesignSettings({ homology_length: null, minimal_hybridization_length: 14, target_tm: 55 });
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<PrimerDesignProvider designType="simple_pair" sequenceIds={sequenceIds} primerDesignSettings={primerDesignSettings} steps={steps}>
|
|
@@ -6,33 +6,16 @@ import { usePrimerDesign } from './PrimerDesignContext';
|
|
|
6
6
|
|
|
7
7
|
function PrimerSpacerForm({ open = true }) {
|
|
8
8
|
const { spacers, setSpacers, circularAssembly, templateSequenceNames, templateSequenceIds } = usePrimerDesign();
|
|
9
|
-
const [localSpacers, setLocalSpacers] = React.useState(spacers);
|
|
10
|
-
const timeoutRef = React.useRef();
|
|
11
|
-
|
|
12
|
-
// Debounced upstream updates to avoid heavy re-rendering
|
|
13
|
-
const handleSpacerChange = (index, value) => {
|
|
14
|
-
setLocalSpacers((current) => current.map((spacer, i) => (i === index ? value : spacer)));
|
|
15
|
-
|
|
16
|
-
// Clear any existing timeout
|
|
17
|
-
clearTimeout(timeoutRef.current);
|
|
18
|
-
|
|
19
|
-
// Set new timeout and store its ID in the ref
|
|
20
|
-
timeoutRef.current = setTimeout(() => {
|
|
21
|
-
setSpacers((current) => current.map((spacer, i) => (i === index ? value : spacer)));
|
|
22
|
-
}, 500);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
React.useEffect(() => {
|
|
26
|
-
if (!localSpacers.every((spacer, index) => spacer === spacers[index])) {
|
|
27
|
-
setLocalSpacers(spacers);
|
|
28
|
-
}
|
|
29
|
-
}, [spacers]);
|
|
30
9
|
|
|
31
10
|
const fragmentCount = templateSequenceIds.length;
|
|
32
11
|
|
|
33
12
|
const sequenceNamesWrapped = [...templateSequenceNames, templateSequenceNames[0]];
|
|
34
13
|
const templateSequenceIdsWrapped = [...templateSequenceIds, templateSequenceIds[0]];
|
|
35
14
|
|
|
15
|
+
const handleSpacerChange = (index, value) => {
|
|
16
|
+
setSpacers((current) => current.map((spacer, i) => (i === index ? value : spacer)));
|
|
17
|
+
};
|
|
18
|
+
|
|
36
19
|
const getSequenceName = (seqIndex) => {
|
|
37
20
|
const name = sequenceNamesWrapped[seqIndex];
|
|
38
21
|
const id = templateSequenceIdsWrapped[seqIndex];
|
|
@@ -55,7 +38,7 @@ function PrimerSpacerForm({ open = true }) {
|
|
|
55
38
|
<CollapsableLabel label="Spacer sequences" className="primer-spacer-form" open={open}>
|
|
56
39
|
<Box sx={{ pt: 1, width: '80%', margin: 'auto' }}>
|
|
57
40
|
<Box>
|
|
58
|
-
{
|
|
41
|
+
{spacers.map((spacer, index) => {
|
|
59
42
|
const error = stringIsNotDNA(spacer) ? 'Invalid DNA sequence' : '';
|
|
60
43
|
return (
|
|
61
44
|
<FormControl key={index} fullWidth sx={{ mb: 2 }}>
|
|
@@ -9,7 +9,7 @@ import { usePrimerDesign } from './PrimerDesignContext';
|
|
|
9
9
|
import RestrictionSpacerForm from './RestrictionSpacerForm';
|
|
10
10
|
|
|
11
11
|
function TabPannelSettings() {
|
|
12
|
-
const { error, templateSequenceIds, designType, selectedTab, sequenceIds, circularAssembly,
|
|
12
|
+
const { error, templateSequenceIds, designType, selectedTab, sequenceIds, circularAssembly, setCircularAssembly, designPrimers, primers, primerDesignSettings, submissionPreventedMessage } = usePrimerDesign();
|
|
13
13
|
return (
|
|
14
14
|
<TabPanel value={selectedTab} index={sequenceIds.length}>
|
|
15
15
|
<Box sx={{ width: '80%', margin: 'auto' }}>
|
|
@@ -32,12 +32,14 @@ function TabPannelSettings() {
|
|
|
32
32
|
<FormControlLabel
|
|
33
33
|
control={(
|
|
34
34
|
<Checkbox
|
|
35
|
+
data-test="circular-assembly-checkbox"
|
|
36
|
+
disabled={templateSequenceIds.length === 1}
|
|
35
37
|
checked={circularAssembly}
|
|
36
|
-
onChange={
|
|
38
|
+
onChange={(e) => setCircularAssembly(e.target.checked)}
|
|
37
39
|
name="circular-assembly"
|
|
38
40
|
/>
|
|
39
|
-
|
|
40
|
-
label=
|
|
41
|
+
)}
|
|
42
|
+
label={templateSequenceIds.length === 1 ? 'Circular assembly (only one sequence input)' : 'Circular assembly'}
|
|
41
43
|
/>
|
|
42
44
|
</FormControl>
|
|
43
45
|
</Box>
|
|
@@ -80,7 +80,6 @@ function SourceAssembly({ source, requestStatus, sendPostRequest }) {
|
|
|
80
80
|
if (enzymes.length === 0) { return; }
|
|
81
81
|
requestData.source.restriction_enzymes = enzymes;
|
|
82
82
|
const config = { params: {
|
|
83
|
-
allow_partial_overlap: allowPartialOverlap,
|
|
84
83
|
circular_only: circularOnly,
|
|
85
84
|
} };
|
|
86
85
|
sendPostRequest({ endpoint: 'restriction_and_ligation', requestData, config, source });
|
|
@@ -176,7 +175,7 @@ function SourceAssembly({ source, requestStatus, sendPostRequest }) {
|
|
|
176
175
|
<FormControlLabel control={<Checkbox checked={circularOnly} onChange={() => setCircularOnly(!circularOnly)} />} label="Circular assemblies only" />
|
|
177
176
|
</FormControl>
|
|
178
177
|
)}
|
|
179
|
-
{ ['
|
|
178
|
+
{ ['LigationSource'].includes(assemblyType) && (
|
|
180
179
|
<FormControl fullWidth style={{ textAlign: 'left' }}>
|
|
181
180
|
<FormControlLabel control={<Checkbox checked={allowPartialOverlap} onChange={flipAllowPartialOverlap} />} label="Allow partial overlaps" />
|
|
182
181
|
</FormControl>
|
|
@@ -27,6 +27,8 @@ export default function useStoreEditor() {
|
|
|
27
27
|
const pcrPrimers = getPCRPrimers(cloning, id);
|
|
28
28
|
const alignmentFiles = cloning.files.filter((e) => e.sequence_id === id && e.file_type === 'Sequencing file');
|
|
29
29
|
let { panelsShown } = store.getState().VectorEditor.mainEditor;
|
|
30
|
+
panelsShown = [[...panelsShown[0].filter((p) => p.id !== 'simpleAlignment')]]
|
|
31
|
+
|
|
30
32
|
if (alignmentFiles.length > 0) {
|
|
31
33
|
addAlignment(store, {
|
|
32
34
|
id: 'simpleAlignment',
|
|
@@ -44,6 +46,7 @@ export default function useStoreEditor() {
|
|
|
44
46
|
alignmentData: {
|
|
45
47
|
// the alignmentData just needs the sequence < TODO this has to be changed to be the largest ---
|
|
46
48
|
sequence: alignmentFiles[0].alignment[0],
|
|
49
|
+
name: sequenceData.name
|
|
47
50
|
},
|
|
48
51
|
},
|
|
49
52
|
...await Promise.all(alignmentFiles.map(async (aln) => {
|
|
@@ -75,6 +78,7 @@ export default function useStoreEditor() {
|
|
|
75
78
|
sequenceData: {...alignmentSequenceData, name: aln.file_name},
|
|
76
79
|
alignmentData: {
|
|
77
80
|
sequence: aln.alignment[1],
|
|
81
|
+
name: aln.file_name
|
|
78
82
|
},
|
|
79
83
|
chromatogramData,
|
|
80
84
|
};
|
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.2.0";
|