@opencloning/ui 1.4.2 → 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 CHANGED
@@ -1,5 +1,25 @@
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
+
13
+ ## 1.4.3
14
+
15
+ ### Patch Changes
16
+
17
+ - [#622](https://github.com/manulera/OpenCloning_frontend/pull/622) [`03cec1c`](https://github.com/manulera/OpenCloning_frontend/commit/03cec1c53e6e8365bb8bf9617ed3128b65ab18cc) Thanks [@manulera](https://github.com/manulera)! - Fix loading external assemblies in syntax builder by extracting config out of ExistingSyntaxDialog component
18
+
19
+ - Updated dependencies []:
20
+ - @opencloning/store@1.4.3
21
+ - @opencloning/utils@1.4.3
22
+
3
23
  ## 1.4.2
4
24
 
5
25
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencloning/ui",
3
- "version": "1.4.2",
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.2",
29
- "@opencloning/utils": "1.4.2",
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",
@@ -17,6 +17,7 @@ import useBackendRoute from '../../hooks/useBackendRoute';
17
17
  import useHttpClient from '../../hooks/useHttpClient';
18
18
  import useAlerts from '../../hooks/useAlerts';
19
19
  import UploadPlasmidsButton from './UploadPlasmidsButton';
20
+ import { useConfig } from '../../providers';
20
21
 
21
22
 
22
23
  const { setState: setCloningState, setCurrentTab: setCurrentTabAction } = cloningActions;
@@ -240,6 +241,7 @@ function categoriesFromSyntaxAndPlasmids(syntax, plasmids) {
240
241
  function LoadSyntaxButton({ setSyntax, addPlasmids }) {
241
242
  const [existingSyntaxDialogOpen, setExistingSyntaxDialogOpen] = React.useState(false)
242
243
  const httpClient = useHttpClient();
244
+ const { staticContentPath } = useConfig();
243
245
  const backendRoute = useBackendRoute();
244
246
  const { addAlert } = useAlerts();
245
247
  const onSyntaxSelect = React.useCallback(async (syntax, plasmids) => {
@@ -257,7 +259,7 @@ function LoadSyntaxButton({ setSyntax, addPlasmids }) {
257
259
  }, [setSyntax, addPlasmids, httpClient, backendRoute, addAlert])
258
260
  return <>
259
261
  <Button color="success" onClick={() => setExistingSyntaxDialogOpen(true)}>Load Syntax</Button>
260
- {existingSyntaxDialogOpen && <ExistingSyntaxDialog onClose={() => setExistingSyntaxDialogOpen(false)} onSyntaxSelect={onSyntaxSelect}/>}
262
+ {existingSyntaxDialogOpen && <ExistingSyntaxDialog staticContentPath={staticContentPath} onClose={() => setExistingSyntaxDialogOpen(false)} onSyntaxSelect={onSyntaxSelect}/>}
261
263
  </>
262
264
  }
263
265
 
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import { ConfigProvider } from '@opencloning/ui/providers/ConfigProvider';
3
2
  import ExistingSyntaxDialog from './ExistingSyntaxDialog';
4
3
 
5
4
  // Test config
@@ -50,12 +49,10 @@ describe('<ExistingSyntaxDialog />', () => {
50
49
  const onSyntaxSelectSpy = cy.spy().as('onSyntaxSelectSpy');
51
50
 
52
51
  cy.mount(
53
- <ConfigProvider config={testConfig}>
54
- <ExistingSyntaxDialog
55
- onClose={onCloseSpy}
56
- onSyntaxSelect={onSyntaxSelectSpy}
57
- />
58
- </ConfigProvider>,
52
+ <ExistingSyntaxDialog
53
+ onClose={onCloseSpy}
54
+ onSyntaxSelect={onSyntaxSelectSpy}
55
+ />,
59
56
  );
60
57
 
61
58
  cy.wait('@getSyntaxes');
@@ -82,12 +79,10 @@ describe('<ExistingSyntaxDialog />', () => {
82
79
  }).as('getPlasmidsData');
83
80
 
84
81
  cy.mount(
85
- <ConfigProvider config={testConfig}>
86
- <ExistingSyntaxDialog
87
- onClose={onCloseSpy}
88
- onSyntaxSelect={onSyntaxSelectSpy}
89
- />
90
- </ConfigProvider>,
82
+ <ExistingSyntaxDialog
83
+ onClose={onCloseSpy}
84
+ onSyntaxSelect={onSyntaxSelectSpy}
85
+ />,
91
86
  );
92
87
 
93
88
  cy.wait('@getSyntaxes');
@@ -111,12 +106,10 @@ describe('<ExistingSyntaxDialog />', () => {
111
106
  }).as('getSyntaxDataError');
112
107
 
113
108
  cy.mount(
114
- <ConfigProvider config={testConfig}>
115
- <ExistingSyntaxDialog
116
- onClose={onCloseSpy}
117
- onSyntaxSelect={onSyntaxSelectSpy}
118
- />
119
- </ConfigProvider>,
109
+ <ExistingSyntaxDialog
110
+ onClose={onCloseSpy}
111
+ onSyntaxSelect={onSyntaxSelectSpy}
112
+ />,
120
113
  );
121
114
 
122
115
  cy.wait('@getSyntaxes');
@@ -146,12 +139,10 @@ describe('<ExistingSyntaxDialog />', () => {
146
139
  }).as('getPlasmidsDataError');
147
140
 
148
141
  cy.mount(
149
- <ConfigProvider config={testConfig}>
150
- <ExistingSyntaxDialog
151
- onClose={onCloseSpy}
152
- onSyntaxSelect={onSyntaxSelectSpy}
153
- />
154
- </ConfigProvider>,
142
+ <ExistingSyntaxDialog
143
+ onClose={onCloseSpy}
144
+ onSyntaxSelect={onSyntaxSelectSpy}
145
+ />,
155
146
  );
156
147
 
157
148
  cy.wait('@getSyntaxes');
@@ -187,12 +178,10 @@ describe('<ExistingSyntaxDialog />', () => {
187
178
  }).as('getPlasmidsData2');
188
179
 
189
180
  cy.mount(
190
- <ConfigProvider config={testConfig}>
191
- <ExistingSyntaxDialog
192
- onClose={onCloseSpy}
193
- onSyntaxSelect={onSyntaxSelectSpy}
194
- />
195
- </ConfigProvider>,
181
+ <ExistingSyntaxDialog
182
+ onClose={onCloseSpy}
183
+ onSyntaxSelect={onSyntaxSelectSpy}
184
+ />,
196
185
  );
197
186
 
198
187
  cy.wait('@getSyntaxes');
@@ -232,12 +221,10 @@ describe('<ExistingSyntaxDialog />', () => {
232
221
  }
233
222
 
234
223
  cy.mount(
235
- <ConfigProvider config={testConfig}>
236
- <ExistingSyntaxDialog
237
- onClose={onCloseSpy}
238
- onSyntaxSelect={onSyntaxSelectSpy}
239
- />
240
- </ConfigProvider>,
224
+ <ExistingSyntaxDialog
225
+ onClose={onCloseSpy}
226
+ onSyntaxSelect={onSyntaxSelectSpy}
227
+ />,
241
228
  );
242
229
 
243
230
  cy.wait('@getSyntaxes');
@@ -262,12 +249,10 @@ describe('<ExistingSyntaxDialog />', () => {
262
249
  }
263
250
 
264
251
  cy.mount(
265
- <ConfigProvider config={testConfig}>
266
- <ExistingSyntaxDialog
267
- onClose={onCloseSpy}
268
- onSyntaxSelect={onSyntaxSelectSpy}
269
- />
270
- </ConfigProvider>,
252
+ <ExistingSyntaxDialog
253
+ onClose={onCloseSpy}
254
+ onSyntaxSelect={onSyntaxSelectSpy}
255
+ />,
271
256
  );
272
257
 
273
258
  cy.wait('@getSyntaxes');
@@ -28,13 +28,12 @@ function LocalSyntaxDialog({ onClose, onSyntaxSelect }) {
28
28
  )
29
29
  }
30
30
 
31
- function ExistingSyntaxDialog({ onClose, onSyntaxSelect }) {
31
+ function ExistingSyntaxDialog({ staticContentPath, onClose, onSyntaxSelect }) {
32
32
  const [syntaxes, setSyntaxes] = React.useState([]);
33
33
  const [connectAttempt, setConnectAttempt] = React.useState(0);
34
34
  const [requestStatus, setRequestStatus] = React.useState({ status: 'loading' });
35
35
  const [loadError, setLoadError] = React.useState(null);
36
36
  const fileInputRef = React.useRef(null);
37
- const { staticContentPath } = useConfig();
38
37
  const [localDialogOpen, setLocalDialogOpen] = React.useState(false);
39
38
 
40
39
  React.useEffect(() => {
@@ -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 paths = allSimplePaths(graph, fragment.left.ovhg, fragment.right.ovhg);
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 { assignSequenceToSyntaxPart, simplifyDigestFragment, reverseComplementSimplifiedDigestFragment, tripletsToTranslation, partDataToDisplayData, arrayCombinations } from "./assembler_utils";
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
- for (const edge of graph.inEdges(cutNode)) {
44
- graph.dropEdge(edge);
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.copy();
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(graph.inDegree('B')).toBe(0);
119
- expect(graph.outDegree('B')).toBe(1);
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(graph.outDegree('A')).toBe(1);
132
- expect(graph.hasEdge('A', 'B')).toBe(true);
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(graph.inDegree('A')).toBe(0);
143
- expect(graph.hasEdge('A', 'B')).toBe(true);
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
 
@@ -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 { isEnzymePalyndromic } from '@opencloning/utils/enzyme_utils';
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 && !isEnzymePalyndromic(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 && !isEnzymePalyndromic(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";
2
+ export const version = "1.4.4";