@opencloning/utils 1.7.2 → 1.8.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 CHANGED
@@ -1,5 +1,69 @@
1
1
  # @opencloning/utils
2
2
 
3
+ ## 1.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies []:
8
+ - @opencloning/store@1.8.1
9
+
10
+ ## 1.8.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#694](https://github.com/OpenCloning/OpenCloning_frontend/pull/694) [`2ff9010`](https://github.com/OpenCloning/OpenCloning_frontend/commit/2ff90103c1d9e8e984533e51ecf4dc7978756a57) Thanks [@manulera](https://github.com/manulera)! - \* Add alignment functionality functions.
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [[`2ff9010`](https://github.com/OpenCloning/OpenCloning_frontend/commit/2ff90103c1d9e8e984533e51ecf4dc7978756a57)]:
19
+ - @opencloning/store@1.8.0
20
+
21
+ ## 1.7.8
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies []:
26
+ - @opencloning/store@1.7.8
27
+
28
+ ## 1.7.7
29
+
30
+ ### Patch Changes
31
+
32
+ - [#689](https://github.com/manulera/OpenCloning_frontend/pull/689) [`c5d9309`](https://github.com/manulera/OpenCloning_frontend/commit/c5d93099583fdfafafd40e9924d2216b596e0cec) Thanks [@manulera](https://github.com/manulera)! - Added experimental feature for domestication that relies on API calls to goldenbraidpro
33
+
34
+ - Updated dependencies []:
35
+ - @opencloning/store@1.7.7
36
+
37
+ ## 1.7.6
38
+
39
+ ### Patch Changes
40
+
41
+ - Updated dependencies []:
42
+ - @opencloning/store@1.7.6
43
+
44
+ ## 1.7.5
45
+
46
+ ### Patch Changes
47
+
48
+ - Updated dependencies [[`034c738`](https://github.com/manulera/OpenCloning_frontend/commit/034c73840dc183b6b3584365c1db899d2f0c548b)]:
49
+ - @opencloning/store@1.7.5
50
+
51
+ ## 1.7.4
52
+
53
+ ### Patch Changes
54
+
55
+ - [#682](https://github.com/manulera/OpenCloning_frontend/pull/682) [`04703a9`](https://github.com/manulera/OpenCloning_frontend/commit/04703a9269a5d4cc4bdde478c1ec61e5c73b0d2a) Thanks [@manulera](https://github.com/manulera)! - Fix bug when copying pcr products
56
+
57
+ - Updated dependencies []:
58
+ - @opencloning/store@1.7.4
59
+
60
+ ## 1.7.3
61
+
62
+ ### Patch Changes
63
+
64
+ - Updated dependencies []:
65
+ - @opencloning/store@1.7.3
66
+
3
67
  ## 1.7.2
4
68
 
5
69
  ### Patch Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "license": "MIT",
3
3
  "name": "@opencloning/utils",
4
- "version": "1.7.2",
4
+ "version": "1.8.1",
5
5
  "type": "module",
6
6
  "main": "./src/utils/index.js",
7
7
  "exports": {
@@ -21,20 +21,21 @@
21
21
  "./other": "./src/utils/other.js",
22
22
  "./transformCoords": "./src/utils/transformCoords.js",
23
23
  "./selectedRegionUtils": "./src/utils/selectedRegionUtils.js",
24
- "./normalizeSyntax": "./src/utils/normalizeSyntax.js"
24
+ "./normalizeSyntax": "./src/utils/normalizeSyntax.js",
25
+ "./alignmentUtils": "./src/utils/alignmentUtils.js"
25
26
  },
26
27
  "repository": {
27
28
  "type": "git",
28
- "url": "https://github.com/manulera/OpenCloning_frontend.git",
29
+ "url": "https://github.com/OpenCloning/OpenCloning_frontend.git",
29
30
  "directory": "packages/utils"
30
31
  },
31
32
  "dependencies": {
32
- "@opencloning/store": "1.7.2",
33
- "@teselagen/bio-parsers": "^0.4.34",
33
+ "@opencloning/store": "1.8.1",
34
+ "@teselagen/bio-parsers": "^0.4.38",
34
35
  "@teselagen/sequence-utils": "^0.3.42",
35
- "@zip.js/zip.js": "^2.7.62",
36
+ "@zip.js/zip.js": "^2.8.26",
36
37
  "dom-to-svg": "^0.12.2",
37
- "lodash-es": "^4.17.23"
38
+ "lodash-es": "^4.18.1"
38
39
  },
39
40
  "peerDependencies": {
40
41
  "react": "^18.3.1",
@@ -0,0 +1,120 @@
1
+ import { getReverseComplementSequenceAndAnnotations, getReverseComplementSequenceString, rotateSequenceDataToPosition } from '@teselagen/sequence-utils';
2
+ import { findRotation, syncChromatogramDataWithAlignment } from './sequenceManipulation.js';
3
+
4
+ /**
5
+ * Process alignment sequence data by handling rotation and reverse complement.
6
+ * If fileContent has features, it aligns the sequence data to match the alignment
7
+ * by computing rotation and optionally reverse-complementing.
8
+ */
9
+ export function processAlignmentSequenceData(fileContent, alignmentSequence) {
10
+ if (!(fileContent.features && fileContent.features.length > 0)) {
11
+ return {
12
+ name: fileContent.name || '',
13
+ sequence: alignmentSequence,
14
+ };
15
+ }
16
+
17
+ let alignmentSequenceData = fileContent;
18
+ let rotation = findRotation(fileContent.sequence, alignmentSequence);
19
+ const reverseComplemented = rotation === -1;
20
+ if (reverseComplemented) {
21
+ rotation = findRotation(fileContent.sequence, getReverseComplementSequenceString(alignmentSequence));
22
+ alignmentSequenceData = getReverseComplementSequenceAndAnnotations(fileContent);
23
+ }
24
+ if (rotation !== -1 && rotation !== 0) {
25
+ rotation = reverseComplemented ? fileContent.sequence.length - rotation : rotation;
26
+ alignmentSequenceData = rotateSequenceDataToPosition(alignmentSequenceData, rotation);
27
+ }
28
+ return alignmentSequenceData;
29
+ }
30
+
31
+ /**
32
+ * Build a single alignment track from parsed file content and an alignment file object.
33
+ * @param {Object} fileContent - Parsed sequence data (output of getTeselaJsonFromBase64)
34
+ * @param {Object} alignmentFile - Alignment file object with { file_name, alignment }
35
+ * @returns {{ sequenceData: Object, alignmentData: Object, chromatogramData: Object|undefined }}
36
+ */
37
+ export function buildAlignmentTrack(fileContent, alignmentFile) {
38
+ const alignmentSequence = alignmentFile.alignment[1].replaceAll('-', '');
39
+ let chromatogramData = fileContent.chromatogramData;
40
+
41
+ if (chromatogramData) {
42
+ chromatogramData = syncChromatogramDataWithAlignment(chromatogramData, alignmentFile.alignment[1]);
43
+ }
44
+
45
+ const alignmentSequenceData = processAlignmentSequenceData(fileContent, alignmentSequence);
46
+
47
+ return {
48
+ sequenceData: { ...alignmentSequenceData, name: alignmentFile.file_name },
49
+ alignmentData: {
50
+ sequence: alignmentFile.alignment[1],
51
+ name: alignmentFile.file_name,
52
+ },
53
+ chromatogramData,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Build the reference (first) alignment track.
59
+ * @param {Object} sequenceData - The reference sequence data
60
+ * @param {string} firstAlignmentSequence - The alignment string for the reference
61
+ * @returns {{ sequenceData: Object, alignmentData: Object }}
62
+ */
63
+ export function buildReferenceTrack(sequenceData, firstAlignmentSequence) {
64
+ return {
65
+ sequenceData,
66
+ alignmentData: {
67
+ sequence: firstAlignmentSequence,
68
+ name: sequenceData.name,
69
+ },
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Build the full alignment configuration object for addAlignment.
75
+ * @param {number} id - Sequence id
76
+ * @param {Object} sequenceData - Reference sequence data
77
+ * @param {Array} alignmentTracks - Array of alignment track objects (reference + others)
78
+ * @returns {Object} Alignment config for addAlignment
79
+ */
80
+ export function buildAlignmentConfig(id, sequenceData, alignmentTracks) {
81
+ return {
82
+ id: 'simpleAlignment',
83
+ alignmentType: 'Sequencing alignment',
84
+ name: `Seq. ${id}`,
85
+ alignmentAnnotationVisibility: {
86
+ features: true,
87
+ parts: true,
88
+ translations: true,
89
+ },
90
+ alignmentTracks,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Remove a panel from panelsShown by id.
96
+ * @param {Array} panelsShown - Current panelsShown array (array of arrays)
97
+ * @param {string} panelId - The panel id to remove
98
+ * @returns {Array} New panelsShown with the panel filtered out
99
+ */
100
+ export function removePanelFromShown(panelsShown, panelId) {
101
+ return [[...panelsShown[0].filter((p) => p.id !== panelId)]];
102
+ }
103
+
104
+ /**
105
+ * Return new panelsShown with the simpleAlignment panel appended.
106
+ * @param {Array} panelsShown - Current panelsShown (already with simpleAlignment removed)
107
+ * @returns {Array} New panelsShown with simpleAlignment panel added
108
+ */
109
+ export function updatePanelsShownWithAlignment(panelsShown) {
110
+ return [[
111
+ ...panelsShown[0].filter((p) => p.id !== 'simpleAlignment'),
112
+ {
113
+ id: 'simpleAlignment',
114
+ type: 'alignment',
115
+ name: 'Alignments',
116
+ active: true,
117
+ isFullscreen: false,
118
+ },
119
+ ]];
120
+ }
@@ -0,0 +1,22 @@
1
+
2
+ export function featureToGenbankLocationString(feat) {
3
+ const [ inclusive1BasedStart, inclusive1BasedEnd ] = [false, false];
4
+ let locStr = "";
5
+
6
+ if (feat.locations && feat.locations.length > 1) {
7
+ feat.locations.forEach((loc, i) => {
8
+ locStr +=`${parseInt(loc.start, 10) + (inclusive1BasedStart ? 0 : 1)}..${parseInt(loc.end, 10) + (inclusive1BasedEnd ? 0 : 1)}`;
9
+ if (i !== feat.locations.length - 1) {
10
+ locStr += ",";
11
+ }
12
+ });
13
+ locStr = "join(" + locStr + ")";
14
+ } else {
15
+ locStr +=`${parseInt(feat.start, 10) + (inclusive1BasedStart ? 0 : 1)}..${parseInt(feat.end, 10) + (inclusive1BasedEnd ? 0 : 1)}`;
16
+ }
17
+
18
+ if (feat.strand === -1) {
19
+ locStr = "complement(" + locStr + ")";
20
+ }
21
+ return locStr;
22
+ }
@@ -17,3 +17,4 @@ export * from './other.js';
17
17
  export { default as getTransformCoords } from './transformCoords.js';
18
18
  export * from './selectedRegionUtils.js';
19
19
  export * from './normalizeSyntax.js';
20
+ export * from './dnaUtils.js';
@@ -100,11 +100,19 @@ export const shiftState = (newState, oldState, skipPrimers = false) => {
100
100
  return shiftStateIds(newState, oldState, skipPrimers);
101
101
  };
102
102
 
103
+ export function getSequenceIdsThatAreInput(sources) {
104
+ return sources.reduce((result, source) => result.concat(source.input.map(({sequence}) => sequence)), []);
105
+ }
106
+
107
+ export function getSequenceIdsThatAreNotInput(sequences, sources) {
108
+ const sequenceIdsThatAreInput = getSequenceIdsThatAreInput(sources);
109
+ return sequences.filter((sequence) => !sequenceIdsThatAreInput.includes(sequence.id)).map((sequence) => sequence.id);
110
+ }
111
+
103
112
  export function getGraftSequenceId({ sources, sequences }) {
104
- const sequenceIdsThatAreInput = sources.reduce((result, source) => result.concat(source.input.map(({sequence}) => sequence)), []);
105
- const allSequenceIds = sequences.map((seq) => seq.id);
106
- const sequenceIdsThatAreNotInput = allSequenceIds.filter((sequenceId) => !sequenceIdsThatAreInput.includes(sequenceId));
107
- const sourcesWithoutOutput = sources.filter((source) => !allSequenceIds.includes(source.id));
113
+ const allSequenceIds = new Set(sequences.map((seq) => seq.id));
114
+ const sequenceIdsThatAreNotInput = getSequenceIdsThatAreNotInput(sequences, sources);
115
+ const sourcesWithoutOutput = sources.filter((source) => !allSequenceIds.has(source.id));
108
116
  if (sourcesWithoutOutput.length === 0 && sequenceIdsThatAreNotInput.length === 1) {
109
117
  return sequenceIdsThatAreNotInput[0];
110
118
  }
@@ -182,3 +190,18 @@ export const mergeStates = (newState, oldState, skipPrimers = false) => {
182
190
  }
183
191
  return { mergedState, idShift };
184
192
  };
193
+
194
+ export function getParentSequencesFromSource(source, sequences) {
195
+ const parentSequenceIds = source.input.map(({sequence}) => sequence);
196
+ return sequences.filter((sequence) => parentSequenceIds.includes(sequence.id));
197
+ }
198
+
199
+ export function getParentSourcesFromSource(source, sources) {
200
+ const parentSourceIds = source.input.map(({sequence}) => sequence);
201
+ return sources.filter((source) => parentSourceIds.includes(source.id));
202
+ }
203
+
204
+ export function getSequencesNotInDatabase(sources, sequences) {
205
+ const sequenceIds = sources.filter((s) => !s.database_id).map((s) => s.id);
206
+ return sequences.filter((sequence) => sequenceIds.includes(sequence.id));
207
+ }
@@ -43,6 +43,14 @@ export const file2base64 = (file) => new Promise((resolve) => {
43
43
  reader.readAsDataURL(file);
44
44
  });
45
45
 
46
+ export const file2string = (file) => new Promise((resolve) => {
47
+ const reader = new FileReader();
48
+ reader.onload = () => {
49
+ resolve(reader.result);
50
+ };
51
+ reader.readAsText(file);
52
+ });
53
+
46
54
  export const downloadTextFile = (text, fileName, type = 'text/plain') => {
47
55
  const blob = new Blob([text], { type });
48
56
  downloadBlob(blob, fileName);
@@ -3,6 +3,7 @@ import { base64ToBlob, downloadStateAsJson, downloadStateAsZip, loadFilesToSessi
3
3
  import { cloningActions } from '@opencloning/store/cloning';
4
4
  import { collectParentSequencesAndSources, getSubState, mergeStates } from './network';
5
5
  import { getVerificationFileName } from './readNwrite';
6
+ import { getUsedPrimerIds } from '@opencloning/store/cloning_utils';
6
7
 
7
8
  const { setState: setCloningState } = cloningActions;
8
9
 
@@ -27,12 +28,14 @@ export const CopySequenceThunk = (sequenceId) => async (dispatch, getState) => {
27
28
  const { parentSequences, parentSources } = collectParentSequencesAndSources(sourcesToCopy[0], sources, sequences);
28
29
  sequencesToCopy.push(...parentSequences);
29
30
  sourcesToCopy.push(...parentSources);
31
+ const primerIdsToCopy = getUsedPrimerIds(sourcesToCopy);
32
+ const primersToCopy = state.cloning.primers.filter((p) => primerIdsToCopy.includes(p.id));
30
33
  const sequenceIds = sequencesToCopy.map((e) => e.id);
31
34
  const filesToCopy = state.cloning.files.filter((f) => sequenceIds.includes(f.sequence_id));
32
35
  const newState = cloneDeep({
33
36
  sequences: sequencesToCopy,
34
37
  sources: sourcesToCopy,
35
- primers: [],
38
+ primers: primersToCopy,
36
39
  files: filesToCopy,
37
40
  });
38
41
  const files = filesToCopy.map((f) => new File(
@@ -40,7 +43,7 @@ export const CopySequenceThunk = (sequenceId) => async (dispatch, getState) => {
40
43
  getVerificationFileName(f),
41
44
  { type: 'application/octet-stream' },
42
45
  ));
43
- const { mergedState, idShift } = mergeStates(newState, state.cloning, true);
46
+ const { mergedState, idShift } = mergeStates(newState, state.cloning, false);
44
47
  dispatch(setCloningState(mergedState));
45
48
  await loadFilesToSessionStorage(files, idShift);
46
49
  };