@opencloning/ui 1.4.3 → 1.4.4
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 +10 -0
- package/package.json +3 -3
- package/src/components/assembler/assembler_utils.js +10 -1
- package/src/components/assembler/assembler_utils.test.js +28 -2
- package/src/components/assembler/graph_utils.js +5 -4
- package/src/components/assembler/graph_utils.test.js +9 -9
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +3 -3
- package/src/version.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @opencloning/ui
|
|
2
2
|
|
|
3
|
+
## 1.4.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#624](https://github.com/manulera/OpenCloning_frontend/pull/624) [`c333aa3`](https://github.com/manulera/OpenCloning_frontend/commit/c333aa3f5eda8ab7a65799d589ba6e9b376e2d24) Thanks [@manulera](https://github.com/manulera)! - Assigning plasmids to categories handles palindromic parts
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @opencloning/store@1.4.4
|
|
11
|
+
- @opencloning/utils@1.4.4
|
|
12
|
+
|
|
3
13
|
## 1.4.3
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencloning/ui",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"@emotion/styled": "^11.14.0",
|
|
26
26
|
"@mui/icons-material": "^5.15.17",
|
|
27
27
|
"@mui/material": "^5.15.17",
|
|
28
|
-
"@opencloning/store": "1.4.
|
|
29
|
-
"@opencloning/utils": "1.4.
|
|
28
|
+
"@opencloning/store": "1.4.4",
|
|
29
|
+
"@opencloning/utils": "1.4.4",
|
|
30
30
|
"@teselagen/bio-parsers": "^0.4.34",
|
|
31
31
|
"@teselagen/ove": "^0.8.34",
|
|
32
32
|
"@teselagen/range-utils": "^0.3.20",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isRangeWithinRange } from '@teselagen/range-utils';
|
|
2
2
|
import { getComplementSequenceString, getAminoAcidFromSequenceTriplet, getDigestFragmentsForRestrictionEnzymes, getReverseComplementSequenceString } from '@teselagen/sequence-utils';
|
|
3
3
|
import { allSimplePaths } from 'graphology-simple-path';
|
|
4
|
+
import { openCycleAtNode } from './graph_utils';
|
|
4
5
|
|
|
5
6
|
export function tripletsToTranslation(triplets) {
|
|
6
7
|
if (!triplets) return ''
|
|
@@ -100,6 +101,13 @@ export function getSimplifiedDigestFragments(sequenceData, enzymes) {
|
|
|
100
101
|
return simplifiedDigestFragments.concat(simplifiedDigestFragmentsRc);
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
export function isFragmentPalindromic(fragment) {
|
|
105
|
+
return (
|
|
106
|
+
(fragment.left.ovhg === getReverseComplementSequenceString(fragment.left.ovhg)) &&
|
|
107
|
+
(fragment.right.ovhg === getReverseComplementSequenceString(fragment.right.ovhg))
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
export function assignSequenceToSyntaxPart(sequenceData, enzymes, graph) {
|
|
104
112
|
// Something that is important to understand here is the meaning of forward and reverse.
|
|
105
113
|
// It does not mean whether the overhang is 5' or 3', the value on the top strand is always
|
|
@@ -111,7 +119,8 @@ export function assignSequenceToSyntaxPart(sequenceData, enzymes, graph) {
|
|
|
111
119
|
simplifiedDigestFragments
|
|
112
120
|
.filter(f => f.left.forward && !f.right.forward && graph.hasNode(f.left.ovhg) && graph.hasNode(f.right.ovhg))
|
|
113
121
|
.forEach(fragment => {
|
|
114
|
-
const
|
|
122
|
+
const graphForPaths = isFragmentPalindromic(fragment) ? openCycleAtNode(graph, graph.nodes()[0]) : graph;
|
|
123
|
+
const paths = allSimplePaths(graphForPaths, fragment.left.ovhg, fragment.right.ovhg);
|
|
115
124
|
if (paths.length > 0) {
|
|
116
125
|
foundParts.push({left_overhang: fragment.left.ovhg, right_overhang: fragment.right.ovhg, longestFeature: fragment.longestFeature});
|
|
117
126
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { aliasedEnzymesByName, getDigestFragmentsForRestrictionEnzymes, getReverseComplementSequenceString, getComplementSequenceString } from "@teselagen/sequence-utils";
|
|
2
|
-
import
|
|
1
|
+
import { aliasedEnzymesByName, getDigestFragmentsForRestrictionEnzymes, getReverseComplementSequenceString, getComplementSequenceString, getReverseComplementSequenceAndAnnotations } from "@teselagen/sequence-utils";
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { assignSequenceToSyntaxPart, simplifyDigestFragment, reverseComplementSimplifiedDigestFragment, tripletsToTranslation, partDataToDisplayData, arrayCombinations, getSimplifiedDigestFragments } from "./assembler_utils";
|
|
3
4
|
import { partsToEdgesGraph } from "./graph_utils";
|
|
5
|
+
import { genbankToJson } from '@teselagen/bio-parsers';
|
|
4
6
|
|
|
5
7
|
const sequenceBsaI = 'tgggtctcaTACTagagtcacacaggactactaAATGagagacctac';
|
|
6
8
|
const sequenceBsaI2 = 'tgggtctcaAATGagagtcacacaggactactaAGGTagagacctac'
|
|
@@ -28,6 +30,30 @@ describe('reverseComplementSimplifiedDigestFragment', () => {
|
|
|
28
30
|
});
|
|
29
31
|
});
|
|
30
32
|
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
it('handles palindromic fragments', () => {
|
|
36
|
+
|
|
37
|
+
const parts = [
|
|
38
|
+
{left_overhang: 'AATT', right_overhang: 'AGCT'}, // This part is palindromic, should only be picked up in AATT-AGCT, not AGCT-AATT
|
|
39
|
+
{left_overhang: 'AGCT', right_overhang: 'GGAG'},
|
|
40
|
+
{left_overhang: 'GGAG', right_overhang: 'AATT'},
|
|
41
|
+
]
|
|
42
|
+
// Read file
|
|
43
|
+
const sequence = 'tgggtctcaAATTagagtcacacaggactactaAGCTagagacctac'
|
|
44
|
+
const seqData = { sequence, circular: true };
|
|
45
|
+
const result = assignSequenceToSyntaxPart(seqData, [aliasedEnzymesByName["bsai"]], partsToEdgesGraph(parts));
|
|
46
|
+
|
|
47
|
+
expect(result).toEqual([{left_overhang: 'AATT', right_overhang: 'AGCT', longestFeature: null}]);
|
|
48
|
+
|
|
49
|
+
const seqDataRc = getReverseComplementSequenceAndAnnotations(seqData);
|
|
50
|
+
const resultRc = assignSequenceToSyntaxPart(seqDataRc, [aliasedEnzymesByName["bsai"]], partsToEdgesGraph(parts));
|
|
51
|
+
|
|
52
|
+
expect(resultRc).toEqual([{left_overhang: 'AATT', right_overhang: 'AGCT', longestFeature: null}]);
|
|
53
|
+
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
|
|
31
57
|
it('shows the meaning of forward and reverse', () => {
|
|
32
58
|
const sequence = 'aaGGTCTCaTACTaaa'
|
|
33
59
|
const digestFragments = getDigestFragmentsForRestrictionEnzymes(
|
|
@@ -40,9 +40,11 @@ export function partsToEdgesGraph(parts) {
|
|
|
40
40
|
|
|
41
41
|
// Break cycles by removing incoming edges to a node
|
|
42
42
|
export function openCycleAtNode(graph, cutNode) {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const newGraph = graph.copy();
|
|
44
|
+
for (const edge of newGraph.inEdges(cutNode)) {
|
|
45
|
+
newGraph.dropEdge(edge);
|
|
45
46
|
}
|
|
47
|
+
return newGraph;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
// Convert DAG to MSA-like matrix (rows = paths, columns = topological generations)
|
|
@@ -142,8 +144,7 @@ function minimumCoveringRows(msa) {
|
|
|
142
144
|
|
|
143
145
|
export function graphToMSA(graph) {
|
|
144
146
|
if (graph.nodes().length === 0) return [];
|
|
145
|
-
const newGraph = graph.
|
|
146
|
-
openCycleAtNode(newGraph, newGraph.nodes()[0]);
|
|
147
|
+
const newGraph = openCycleAtNode(graph, graph.nodes()[0]);
|
|
147
148
|
return minimumCoveringRows(dagToMSA(newGraph));
|
|
148
149
|
}
|
|
149
150
|
|
|
@@ -114,9 +114,9 @@ describe('openCycleAtNode', () => {
|
|
|
114
114
|
graph.addEdge('C', 'B'); // Creates cycle
|
|
115
115
|
|
|
116
116
|
expect(graph.inDegree('B')).toBe(2);
|
|
117
|
-
openCycleAtNode(graph, 'B');
|
|
118
|
-
expect(
|
|
119
|
-
expect(
|
|
117
|
+
const newGraph = openCycleAtNode(graph, 'B');
|
|
118
|
+
expect(newGraph.inDegree('B')).toBe(0);
|
|
119
|
+
expect(newGraph.outDegree('B')).toBe(1);
|
|
120
120
|
|
|
121
121
|
});
|
|
122
122
|
|
|
@@ -127,9 +127,9 @@ describe('openCycleAtNode', () => {
|
|
|
127
127
|
graph.addEdge('A', 'B');
|
|
128
128
|
graph.addEdge('B', 'A'); // Cycle
|
|
129
129
|
|
|
130
|
-
openCycleAtNode(graph, 'A');
|
|
131
|
-
expect(
|
|
132
|
-
expect(
|
|
130
|
+
const newGraph = openCycleAtNode(graph, 'A');
|
|
131
|
+
expect(newGraph.outDegree('A')).toBe(1);
|
|
132
|
+
expect(newGraph.hasEdge('A', 'B')).toBe(true);
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
it('handles node with no incoming edges', () => {
|
|
@@ -138,9 +138,9 @@ describe('openCycleAtNode', () => {
|
|
|
138
138
|
graph.addNode('B');
|
|
139
139
|
graph.addEdge('A', 'B');
|
|
140
140
|
|
|
141
|
-
openCycleAtNode(graph, 'A');
|
|
142
|
-
expect(
|
|
143
|
-
expect(
|
|
141
|
+
const newGraph = openCycleAtNode(graph, 'A');
|
|
142
|
+
expect(newGraph.inDegree('A')).toBe(0);
|
|
143
|
+
expect(newGraph.hasEdge('A', 'B')).toBe(true);
|
|
144
144
|
});
|
|
145
145
|
});
|
|
146
146
|
|
package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx
CHANGED
|
@@ -6,7 +6,7 @@ import { updateEditor } from '@teselagen/ove';
|
|
|
6
6
|
import EnzymeMultiSelect from '../../../form/EnzymeMultiSelect';
|
|
7
7
|
import { stringIsNotDNA } from '@opencloning/store/cloning_utils';
|
|
8
8
|
import { usePrimerDesign } from './PrimerDesignContext';
|
|
9
|
-
import {
|
|
9
|
+
import { isEnzymePalindromic } from '@opencloning/utils/enzyme_utils';
|
|
10
10
|
|
|
11
11
|
function RestrictionSpacerForm() {
|
|
12
12
|
const { primerDesignSettings } = usePrimerDesign();
|
|
@@ -39,7 +39,7 @@ function RestrictionSpacerForm() {
|
|
|
39
39
|
<FormControl sx={{ width: '10em', mt: 1.5, mr: 2 }}>
|
|
40
40
|
<EnzymeMultiSelect value={leftEnzyme} setEnzymes={(v) => updateEnzymeSettings({ left_enzyme: v })} label="Left enzyme" multiple={false} />
|
|
41
41
|
</FormControl>
|
|
42
|
-
{leftEnzyme && !
|
|
42
|
+
{leftEnzyme && !isEnzymePalindromic(leftEnzyme) && (
|
|
43
43
|
<FormControlLabel
|
|
44
44
|
control={<Checkbox checked={leftEnzymeInverted} onChange={(e) => updateEnzymeSettings({ left_enzyme_inverted: e.target.checked })} />}
|
|
45
45
|
label="Invert site"
|
|
@@ -50,7 +50,7 @@ function RestrictionSpacerForm() {
|
|
|
50
50
|
<FormControl sx={{ width: '10em', mt: 1.5, mr: 2 }}>
|
|
51
51
|
<EnzymeMultiSelect value={rightEnzyme} setEnzymes={(v) => updateEnzymeSettings({ right_enzyme: v })} label="Right enzyme" multiple={false} />
|
|
52
52
|
</FormControl>
|
|
53
|
-
{rightEnzyme && !
|
|
53
|
+
{rightEnzyme && !isEnzymePalindromic(rightEnzyme) && (
|
|
54
54
|
<FormControlLabel
|
|
55
55
|
control={<Checkbox checked={rightEnzymeInverted} onChange={(e) => updateEnzymeSettings({ right_enzyme_inverted: e.target.checked })} />}
|
|
56
56
|
label="Invert site"
|
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.4.
|
|
2
|
+
export const version = "1.4.4";
|