@datagrok/sequence-translator 1.0.15 → 1.0.17

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.
Files changed (38) hide show
  1. package/.eslintrc.json +16 -3
  2. package/detectors.js +0 -28
  3. package/dist/package-test.js +4372 -3957
  4. package/dist/package.js +3875 -3460
  5. package/package.json +4 -2
  6. package/setup-unlink-clean.sh +21 -0
  7. package/src/apps/oligo-sd-file-app.ts +58 -0
  8. package/src/autostart/calculations.ts +11 -8
  9. package/src/autostart/constants.ts +0 -12
  10. package/src/autostart/registration.ts +194 -157
  11. package/src/{axolabs/define-pattern.ts → axolabs-tab/axolabs-tab.ts} +2 -2
  12. package/src/axolabs-tab/define-pattern.ts +874 -0
  13. package/src/{axolabs → axolabs-tab}/draw-svg.ts +1 -1
  14. package/src/{axolabs → axolabs-tab}/helpers.ts +2 -2
  15. package/src/{autostart → hardcode-to-be-eliminated}/ICDs.ts +0 -0
  16. package/src/{autostart → hardcode-to-be-eliminated}/IDPs.ts +0 -0
  17. package/src/{structures-works → hardcode-to-be-eliminated}/const.ts +0 -0
  18. package/src/{axolabs → hardcode-to-be-eliminated}/constants.ts +0 -0
  19. package/src/{structures-works → hardcode-to-be-eliminated}/converters.ts +1 -1
  20. package/src/{structures-works → hardcode-to-be-eliminated}/map.ts +2 -2
  21. package/src/{autostart → hardcode-to-be-eliminated}/salts.ts +0 -0
  22. package/src/{autostart → hardcode-to-be-eliminated}/sources.ts +0 -0
  23. package/src/{autostart → hardcode-to-be-eliminated}/users.ts +0 -0
  24. package/src/{main/main-view.ts → main-tab/main-tab.ts} +28 -80
  25. package/src/package.ts +77 -13
  26. package/src/sdf-tab/sdf-tab.ts +163 -0
  27. package/src/{structures-works → sdf-tab}/sequence-codes-tools.ts +8 -5
  28. package/src/tests/smiles-tests.ts +2 -2
  29. package/src/utils/const.ts +0 -0
  30. package/src/{helpers.ts → utils/helpers.ts} +3 -3
  31. package/src/utils/parse.ts +27 -0
  32. package/src/utils/sdf-add-columns.ts +118 -0
  33. package/src/utils/sdf-save-table.ts +56 -0
  34. package/src/utils/structures-works/draw-molecule.ts +84 -0
  35. package/src/{structures-works → utils/structures-works}/from-monomers.ts +15 -16
  36. package/src/{structures-works → utils/structures-works}/mol-transformations.ts +34 -52
  37. package/{test-SequenceTranslator-91c83d8913ff-f94596bc.html → test-SequenceTranslator-6288c2fbe346-695b7b55.html} +10 -10
  38. package/src/structures-works/save-sense-antisense.ts +0 -91
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.0.15",
4
+ "version": "1.0.17",
5
5
  "author": {
6
6
  "name": "Alexey Choposky",
7
7
  "email": "achopovsky@datagrok.ai"
@@ -16,7 +16,9 @@
16
16
  "@datagrok-libraries/utils": "^1.17.2",
17
17
  "@types/react": "^18.0.15",
18
18
  "@datagrok-libraries/bio": "^5.11.1",
19
- "datagrok-api": "^1.7.2",
19
+ "@deck.gl/core": "8.8.12",
20
+ "@luma.gl/core": "8.5.17",
21
+ "datagrok-api": "^1.8.2",
20
22
  "datagrok-tools": "^4.1.2",
21
23
  "npm": "^8.11.0",
22
24
  "openchemlib": "6.0.1",
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ package_dir=$(pwd)
3
+
4
+ GREEN='\e[0;32m'
5
+ NO_COLOR='\e[0m'
6
+
7
+ dirs=(
8
+ "../../js-api/"
9
+ "../../libraries/utils/"
10
+ "../../libraries/bio/"
11
+ )
12
+
13
+ npm uninstall --location=global datagrok-api @datagrok-libraries/utils @datagrok-libraries/ml @datagrok-libraries/bio
14
+
15
+ for dir in ${dirs[@]}; do
16
+ cd $package_dir
17
+ cd $dir
18
+ echo -e $GREEN Removing node_modules and dist in $(pwd) $NO_COLOR
19
+ rm -rf node_modules dist
20
+ # rm package-lock.json
21
+ done
@@ -0,0 +1,58 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+ import * as DG from 'datagrok-api/dg';
4
+ import {_package} from '../package';
5
+ import {FileSource} from 'datagrok-api/dg';
6
+
7
+ export class OligoSdFileApp {
8
+ df!: DG.DataFrame;
9
+
10
+ constructor() {}
11
+
12
+ async init(srcDf?: DG.DataFrame): Promise<void> {
13
+ let dataDf: DG.DataFrame;
14
+ if (srcDf) {
15
+ dataDf = srcDf;
16
+ } else {
17
+ const dfFn: string = 'System:AppData/SequenceTranslator/test input_Nov28_Duplex_dimer.xlsx';
18
+ dataDf = await this.loadData(dfFn);
19
+ }
20
+
21
+ this.setData(dataDf);
22
+ }
23
+
24
+ async loadData(dfFn: string): Promise<DG.DataFrame> {
25
+ //
26
+ const dataDf: DG.DataFrame = await grok.data.files.openTable(dfFn);
27
+ return dataDf;
28
+ }
29
+
30
+ async setData(df: DG.DataFrame): Promise<void> {
31
+ if (this.viewed) {
32
+ await this.destroyView();
33
+ this.viewed = false;
34
+ }
35
+
36
+ this.df = df;
37
+
38
+ if (!this.viewed) {
39
+ await this.buildView();
40
+ this.viewed = true;
41
+ }
42
+ }
43
+
44
+ private viewed: boolean = false;
45
+ private tView?: DG.TableView;
46
+
47
+ async destroyView(): Promise<void> {
48
+ this.tView!.close();
49
+ delete this.tView;
50
+ }
51
+
52
+ async buildView(): Promise<void> {
53
+ console.debug('SequenceTranslator: OligoSdFileApp.buildView() ');
54
+
55
+ this.tView = grok.shell.addTableView(this.df);
56
+ this.tView.path = this.tView.basePath = 'func/SequenceTranslator.oligoSdFileApp';
57
+ }
58
+ }
@@ -1,21 +1,24 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
- import {sortByStringLengthInDescendingOrder} from '../helpers';
3
- import {MODIFICATIONS} from '../structures-works/map';
2
+
3
+ import {sortByStringLengthInDescendingOrder} from '../utils/helpers';
4
+ import {MODIFICATIONS} from '../hardcode-to-be-eliminated/map';
4
5
 
5
6
  export function saltMass(
6
- saltNames: string[], molWeightCol: DG.Column, equivalentsCol: DG.Column, i: number, saltCol: DG.Column,
7
+ saltNames: string[], saltsMolWeightList: number[], equivalentsCol: DG.Column, i: number, saltCol: DG.Column
7
8
  ): number {
8
9
  const saltRowIndex = saltNames.indexOf(saltCol.get(i));
9
10
  return (
10
- saltRowIndex == -1 || molWeightCol.get(saltRowIndex) == DG.FLOAT_NULL || equivalentsCol.get(i) == DG.INT_NULL
11
+ saltRowIndex == -1 || saltsMolWeightList[saltRowIndex] == DG.FLOAT_NULL || equivalentsCol.get(i) == DG.INT_NULL
11
12
  ) ?
12
13
  DG.FLOAT_NULL :
13
- molWeightCol.get(saltRowIndex) * equivalentsCol.get(i);
14
+ saltsMolWeightList[saltRowIndex] * equivalentsCol.get(i);
14
15
  }
15
16
 
16
- export function saltMolWeigth(saltNamesList: string[], saltCol: DG.Column, molWeightCol: DG.Column, i: number): number {
17
+ export function saltMolWeigth(
18
+ saltNamesList: string[], saltCol: DG.Column, saltsMolWeightList: number[], i: number
19
+ ): number {
17
20
  const saltRowIndex = saltNamesList.indexOf(saltCol.get(i));
18
- return (saltRowIndex == -1) ? DG.FLOAT_NULL : molWeightCol.get(saltRowIndex);
21
+ return (saltRowIndex == -1) ? DG.FLOAT_NULL : saltsMolWeightList[saltRowIndex];
19
22
  }
20
23
 
21
24
  export function batchMolWeight(compoundMolWeightCol: DG.Column, saltMassCol: DG.Column, i: number): number {
@@ -24,7 +27,7 @@ export function batchMolWeight(compoundMolWeightCol: DG.Column, saltMassCol: DG.
24
27
  compoundMolWeightCol.get(i) + saltMassCol.get(i);
25
28
  }
26
29
 
27
- export function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
30
+ export function molecularWeight(sequence: string, weightsObj: { [index: string]: number }): number {
28
31
  const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
29
32
  let weight = 0;
30
33
  let i = 0;
@@ -6,18 +6,6 @@ export const SEQUENCE_TYPES = {
6
6
  DIMER: 'Dimer',
7
7
  };
8
8
 
9
- export const CELL_STRUCTURE = {
10
- DUPLEX: {
11
- BEFORE_SS: 'SS ',
12
- BEFORE_AS: '\r\nAS ',
13
- },
14
- TRIPLEX_OR_DIMER: {
15
- BEFORE_SS: 'SS ',
16
- BEFORE_AS1: '\r\nAS1 ',
17
- BEFORE_AS2: '\r\nAS2 ',
18
- },
19
- };
20
-
21
9
  export const COL_NAMES = {
22
10
  CHEMISTRY: 'Chemistry',
23
11
  NUMBER: 'Number',
@@ -1,125 +1,220 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
- import {siRnaBioSpringToGcrs, siRnaAxolabsToGcrs, gcrsToNucleotides, asoGapmersBioSpringToGcrs, gcrsToMermade12,
5
- siRnaNucleotidesToGcrs} from '../structures-works/converters';
6
- import {weightsObj, SYNTHESIZERS} from '../structures-works/map';
7
- import {SEQUENCE_TYPES, COL_NAMES, GENERATED_COL_NAMES, CELL_STRUCTURE} from './constants';
4
+ import {
5
+ siRnaBioSpringToGcrs, siRnaAxolabsToGcrs, gcrsToNucleotides, asoGapmersBioSpringToGcrs, gcrsToMermade12,
6
+ siRnaNucleotidesToGcrs
7
+ } from '../hardcode-to-be-eliminated/converters';
8
+ import {weightsObj, SYNTHESIZERS} from '../hardcode-to-be-eliminated/map';
9
+ import {SEQUENCE_TYPES, COL_NAMES, GENERATED_COL_NAMES} from './constants';
8
10
  import {saltMass, saltMolWeigth, molecularWeight, batchMolWeight} from './calculations';
9
- import {isValidSequence} from '../structures-works/sequence-codes-tools';
10
- import {sequenceToMolV3000} from '../structures-works/from-monomers';
11
- import {linkStrandsV3000} from '../structures-works/mol-transformations';
12
- import {stringify, download, removeEmptyRows, differenceOfTwoArrays} from '../helpers';
11
+ import {isValidSequence} from '../sdf-tab/sequence-codes-tools';
12
+ import {sequenceToMolV3000} from '../utils/structures-works/from-monomers';
13
+ import {linkStrandsV3000} from '../utils/structures-works/mol-transformations';
14
+ import {stringify, download, removeEmptyRows, differenceOfTwoArrays} from '../utils/helpers';
13
15
 
14
- import {SALTS_CSV} from './salts';
15
- import {USERS_CSV} from './users';
16
- import {ICDS} from './ICDs';
17
- import {SOURCES} from './sources';
18
- import {IDPS} from './IDPs';
16
+ import {SALTS_CSV} from '../hardcode-to-be-eliminated/salts';
17
+ import {USERS_CSV} from '../hardcode-to-be-eliminated/users';
18
+ import {ICDS} from '../hardcode-to-be-eliminated/ICDs';
19
+ import {SOURCES} from '../hardcode-to-be-eliminated/sources';
20
+ import {IDPS} from '../hardcode-to-be-eliminated/IDPs';
19
21
 
22
+ import {sdfAddColumns} from '../utils/sdf-add-columns';
23
+ import {sdfSaveTable} from '../utils/sdf-save-table';
20
24
 
21
- function parseStrandsFromDuplexCell(s: string): {SS: string, AS: string} {
22
- const arr = s
23
- .slice(CELL_STRUCTURE.DUPLEX.BEFORE_SS.length)
24
- .split(CELL_STRUCTURE.DUPLEX.BEFORE_AS);
25
- return {SS: arr[0], AS: arr[1]};
25
+ const enum PREFIXES {
26
+ AS = 'AS',
27
+ SS = 'SS',
28
+ AS1 = 'AS1',
29
+ AS2 = 'AS2'
26
30
  }
27
31
 
28
- function parseStrandsFromTriplexOrDimerCell(s: string): {SS: string, AS1: string, AS2: string} {
29
- const arr1 = s
30
- .slice(CELL_STRUCTURE.TRIPLEX_OR_DIMER.BEFORE_SS.length)
31
- .split(CELL_STRUCTURE.TRIPLEX_OR_DIMER.BEFORE_AS1);
32
- const arr2 = arr1[1]
33
- .split(CELL_STRUCTURE.TRIPLEX_OR_DIMER.BEFORE_AS2);
34
- return {SS: arr1[0], AS1: arr2[0], AS2: arr2[1]};
32
+ const enum SEQ_TYPE {
33
+ AS = 'AS',
34
+ SS = 'SS',
35
+ DUPLEX = 'Duplex',
36
+ DIMER = 'Dimer',
35
37
  }
36
38
 
37
- async function saveTableAsSdFile(table: DG.DataFrame) {
38
- if (GENERATED_COL_NAMES.some((colName) => !table.columns.contains(colName))) {
39
- const absentColNames = differenceOfTwoArrays(GENERATED_COL_NAMES, table.columns.names()).join(`', '`);
40
- grok.shell.warning(`File saved without columns '${absentColNames}'`);
39
+ /** Computable categories of sequence types */
40
+ const enum SEQ_TYPE_CATEGORY {
41
+ AS_OR_SS,
42
+ DUPLEX,
43
+ DIMER,
44
+ }
45
+
46
+ /** Map between types and their categories inferrable from 'Sequence' column */
47
+ const typeCategoryMap = {
48
+ [SEQ_TYPE.AS]: SEQ_TYPE_CATEGORY.AS_OR_SS,
49
+ [SEQ_TYPE.SS]: SEQ_TYPE_CATEGORY.AS_OR_SS,
50
+ [SEQ_TYPE.DIMER]: SEQ_TYPE_CATEGORY.DIMER,
51
+ [SEQ_TYPE.DUPLEX]: SEQ_TYPE_CATEGORY.DUPLEX,
52
+ };
53
+
54
+ /** Style used for cells in 'Type' column */
55
+ const typeColCellStyle = {
56
+ 'display': 'flex',
57
+ 'justify-content': 'center',
58
+ 'align-items': 'center',
59
+ 'text-color': 'var(--grey-5)', // --grey-6 does not match other cells
60
+ 'width': '100%',
61
+ 'height': '100%',
62
+ };
63
+
64
+ const pinkBackground = {
65
+ 'background-color': '#ff8080',
66
+ };
67
+
68
+ /** Style used for a cell with invalid value */
69
+ const typeColErrorStyle = Object.assign({}, pinkBackground, typeColCellStyle);
70
+
71
+ export function sdfHandleErrorUI(msgPrefix: string, df: DG.DataFrame, rowI: number, err: any) {
72
+ const errStr: string = err.toString();
73
+ const errMsg: string = msgPrefix + `row #${rowI + 1}, name: '${df.get('Chemistry Name', rowI)}', ` +
74
+ `type: ${df.get('Type', rowI)} error: ${errStr}.`;
75
+ grok.shell.warning(errMsg);
76
+ }
77
+
78
+ /** Determine the category of the value specified in 'Types' column */
79
+ function getActualTypeClass(actualType: string): SEQ_TYPE_CATEGORY {
80
+ if (Object.keys(typeCategoryMap).includes(actualType))
81
+ return typeCategoryMap[actualType as SEQ_TYPE];
82
+ else
83
+ throw new Error('Some types in \'Types\' column are invalid ');
84
+ }
85
+
86
+ function isASorSS(splittedLines: string[][]): boolean {
87
+ return splittedLines.length === 1 && splittedLines[0].length === 1;
88
+ }
89
+
90
+ /** Check whether the number of lines and prefixes in the 'Sequence' string
91
+ * are valid */
92
+ function verifyPrefixes(splittedLines: string[][], allowedPrefixes: Set<PREFIXES>, allowedLength: number): boolean {
93
+ const lengthCriterion = splittedLines.length === allowedLength;
94
+ let prefixCriterion = true;
95
+ for (const line of splittedLines) {
96
+ const prefix = line[0];
97
+ prefixCriterion &&= (allowedPrefixes.has(prefix as PREFIXES));
41
98
  }
99
+ return lengthCriterion && prefixCriterion;
100
+ }
42
101
 
43
- const sequenceCol = table.getCol(COL_NAMES.SEQUENCE);
44
- const typeCol = table.getCol(COL_NAMES.TYPE);
102
+ function isDuplex(splittedLines: string[][]): boolean {
103
+ const allowedPrefixes = new Set([PREFIXES.SS, PREFIXES.AS]);
104
+ return verifyPrefixes(splittedLines, allowedPrefixes, 2);
105
+ }
45
106
 
46
- let result = '';
47
- for (let i = 0; i < table.rowCount; i++) {
48
- const format = SYNTHESIZERS.GCRS; //getFormat(sequenceCol.get(i))!;
49
- if (typeCol.get(i) == SEQUENCE_TYPES.SENSE_STRAND)
50
- result += `${sequenceToMolV3000(sequenceCol.get(i), false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
51
- else if (typeCol.get(i) == SEQUENCE_TYPES.ANTISENSE_STRAND)
52
- result += `${sequenceToMolV3000(sequenceCol.get(i), true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
53
- else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
54
- const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
55
- const as = `${sequenceToMolV3000(obj.AS, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
56
- const ss = `${sequenceToMolV3000(obj.SS, false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
57
- result += `${linkStrandsV3000({senseStrands: [ss], antiStrands: [as]}, true)}\n\n`;
58
- } else if ([SEQUENCE_TYPES.TRIPLEX, SEQUENCE_TYPES.DIMER].includes(typeCol.get(i))) {
59
- const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
60
- const as1 = `${sequenceToMolV3000(obj.AS1, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
61
- const as2 = `${sequenceToMolV3000(obj.AS2, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
62
- const ss = `${sequenceToMolV3000(obj.SS, false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
63
- result += `${linkStrandsV3000({senseStrands: [ss], antiStrands: [as1, as2]}, true)}\n\n`;
64
- }
107
+ function isDimer(splittedLines: string[][]): boolean {
108
+ const allowedPrefixes = new Set([PREFIXES.SS, PREFIXES.AS1, PREFIXES.AS2]);
109
+ return verifyPrefixes(splittedLines, allowedPrefixes, 3);
110
+ }
65
111
 
66
- for (const col of table.columns) {
67
- if (col.name != COL_NAMES.SEQUENCE)
68
- result += `> <${col.name}>\n${col.get(i)}\n\n`;
112
+ function inferTypeClassFromSequence(seq: string): SEQ_TYPE_CATEGORY {
113
+ const lines = seq.split('\n');
114
+ const splittedLines = [];
115
+ for (const line of lines)
116
+ splittedLines.push(line.split(' '));
117
+ if (isASorSS(splittedLines))
118
+ return SEQ_TYPE_CATEGORY.AS_OR_SS;
119
+ else if (isDuplex(splittedLines))
120
+ return SEQ_TYPE_CATEGORY.DUPLEX;
121
+ else if (isDimer(splittedLines))
122
+ return SEQ_TYPE_CATEGORY.DIMER;
123
+ else
124
+ throw new Error('Some cells in \'Sequence\' column have wrong formatting');
125
+ }
126
+
127
+ /** Compare type specified in 'Type' column to that computed from 'Sequence' column */
128
+ function validateType(actualType: string, seq: string): boolean {
129
+ if (actualType === '' && seq === '')
130
+ return true;
131
+ else
132
+ return getActualTypeClass(actualType) === inferTypeClassFromSequence(seq);
133
+ }
134
+
135
+ function oligoSdFileGrid(view: DG.TableView): void {
136
+ const typeColName = 'Type';
137
+ const seqColName = 'Sequence';
138
+ const grid = view.grid;
139
+ const df = view.dataFrame;
140
+ const typeCol = df.getCol(typeColName);
141
+ grid.columns.byName(typeColName)!.cellType = 'html';
142
+ const seqCol = df.getCol(seqColName);
143
+ grid.onCellPrepare((gridCell: DG.GridCell) => {
144
+ if (gridCell.isTableCell && gridCell.gridColumn.column!.name === typeColName) {
145
+ let isValidType = false;
146
+ let formattingError = false;
147
+ try {
148
+ isValidType = validateType(gridCell.cell.value, seqCol.get(gridCell.tableRow!.idx));
149
+ } catch {
150
+ formattingError = true;
151
+ }
152
+ const el = ui.div(
153
+ gridCell.cell.value, isValidType ? {style: typeColCellStyle} : {style: typeColErrorStyle}
154
+ );
155
+ gridCell.style.element = el;
156
+ const msg = formattingError ? 'Sequence pattern or Type value has wrong formatting' :
157
+ 'Input in Type column doesn\'t match the Sequence pattern';
158
+ if (!isValidType)
159
+ ui.tooltip.bind(el, msg);
69
160
  }
70
- result += '$$$$\n';
71
- }
72
- download(`${table.name}.sdf`, encodeURIComponent(result));
161
+ });
73
162
  }
74
163
 
75
164
  export function autostartOligoSdFileSubscription() {
76
165
  grok.events.onViewAdded.subscribe((v: any) => {
77
- if (v.type == DG.VIEW_TYPE.TABLE_VIEW) {
78
- if (v.dataFrame.columns.contains(COL_NAMES.TYPE))
166
+ if (v.type === DG.VIEW_TYPE.TABLE_VIEW) {
167
+ if (v.dataFrame.columns.contains(COL_NAMES.TYPE)) {
168
+ oligoSdFileGrid(v);
79
169
  oligoSdFile(v.dataFrame);
170
+ }
80
171
 
81
172
  // Should be removed after fixing bug https://github.com/datagrok-ai/public/issues/808
82
173
  grok.events.onContextMenu.subscribe((args) => {
83
- const seqCol = args.args.context.table.currentCol; // /^[fsACGUacgu]{6,}$/
174
+ if (!(args.args.context instanceof DG.Grid)) return;
175
+ const grid: DG.Grid = args.args.context as DG.Grid;
176
+ const menu: DG.Menu = args.args.menu;
177
+
178
+ const seqCol = grid.table.currentCol; // /^[fsACGUacgu]{6,}$/
84
179
  if (DG.Detector.sampleCategories(seqCol,
85
180
  (s) => /(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C){6,}$/.test(s))) {
86
- args.args.menu.item('Convert raw nucleotides to GCRS', () => {
87
- args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
181
+ menu.item('Convert raw nucleotides to GCRS', () => {
182
+ grid.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
88
183
  return siRnaNucleotidesToGcrs(seqCol.get(i));
89
184
  });
90
185
  });
91
186
  } else if (DG.Detector.sampleCategories(seqCol,
92
187
  (s) => /(\(invabasic\)|\(GalNAc-2-JNJ\)|f|s|A|C|G|U|a|c|g|u){6,}$/.test(s))) {
93
- args.args.menu.item('Convert Axolabs to GCRS', () => {
94
- args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
188
+ menu.item('Convert Axolabs to GCRS', () => {
189
+ grid.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
95
190
  return siRnaAxolabsToGcrs(seqCol.get(i));
96
191
  });
97
192
  }); // /^[fmpsACGU]{6,}$/
98
193
  } else if (DG.Detector.sampleCategories(seqCol,
99
194
  (s) => /(\(invabasic\)|\(GalNAc-2-JNJ\)|f|m|ps|A|C|G|U){6,}$/.test(s)) ||
100
- DG.Detector.sampleCategories(seqCol, (s) => /^(?=.*moe)(?=.*5mC)(?=.*ps){6,}/.test(s))) {
101
- args.args.menu.item('Convert GCRS to raw', () => {
102
- args.args.context.table.columns.addNewString(seqCol.name + ' to raw').init((i: number) => {
195
+ DG.Detector.sampleCategories(seqCol, (s) => /^(?=.*moe)(?=.*5mC)(?=.*ps){6,}/.test(s))) {
196
+ menu.item('Convert GCRS to raw', () => {
197
+ grid.table.columns.addNewString(seqCol.name + ' to raw').init((i: number) => {
103
198
  return gcrsToNucleotides(seqCol.get(i));
104
199
  });
105
200
  });
106
- args.args.menu.item('Convert GCRS to MM12', () => {
107
- args.args.context.table.columns.addNewString(seqCol.name + ' to MM12').init((i: number) => {
201
+ menu.item('Convert GCRS to MM12', () => {
202
+ grid.table.columns.addNewString(seqCol.name + ' to MM12').init((i: number) => {
108
203
  return gcrsToMermade12(seqCol.get(i));
109
204
  });
110
205
  }); // /^[*56789ATGC]{6,}$/
111
206
  } else if (DG.Detector.sampleCategories(seqCol,
112
207
  (s) => /(\(invabasic\)|\(GalNAc-2-JNJ\)|\*|5|6|7|8|9|A|T|G|C){6,}$/.test(s))) {
113
- args.args.menu.item('Convert Biospring to GCRS', () => {
114
- const seqCol = args.args.context.table.currentCol;
115
- args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
208
+ menu.item('Convert Biospring to GCRS', () => {
209
+ const seqCol = grid.table.currentCol;
210
+ grid.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
116
211
  return asoGapmersBioSpringToGcrs(seqCol.get(i));
117
212
  });
118
213
  }); // /^[*1-8]{6,}$/
119
214
  } else if (DG.Detector.sampleCategories(seqCol,
120
215
  (s) => /(\(invabasic\)|\(GalNAc-2-JNJ\)|\*|1|2|3|4|5|6|7|8){6,}$/.test(s))) {
121
- args.args.menu.item('Convert Biospring to GCRS', () => {
122
- args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
216
+ menu.item('Convert Biospring to GCRS', () => {
217
+ grid.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
123
218
  return siRnaBioSpringToGcrs(seqCol.get(i));
124
219
  });
125
220
  });
@@ -136,85 +231,13 @@ export function oligoSdFile(table: DG.DataFrame) {
136
231
  const icdsDf = DG.DataFrame.fromCsv(ICDS);
137
232
  const idpsDf = DG.DataFrame.fromCsv(IDPS);
138
233
 
139
- const sequenceCol = table.getCol(COL_NAMES.SEQUENCE);
140
234
  const saltCol = table.getCol(COL_NAMES.SALT);
141
235
  const equivalentsCol = table.getCol(COL_NAMES.EQUIVALENTS);
142
- const typeCol = table.getCol(COL_NAMES.TYPE);
143
- const chemistryNameCol = table.getCol(COL_NAMES.CHEMISTRY_NAME);
144
-
145
- const molWeightCol = saltsDf.getCol('MOLWEIGHT');
146
- const saltNamesList = saltsDf.getCol('DISPLAY').toList();
147
-
148
- let newDf: DG.DataFrame;
149
- let addColumnsPressed = false;
150
-
151
- function addColumns(t: DG.DataFrame) {
152
- if (GENERATED_COL_NAMES.some((colName) => t.columns.contains(colName)))
153
- return grok.shell.error('Columns already exist');
154
-
155
- t = removeEmptyRows(t, sequenceCol);
156
-
157
- t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => {
158
- return ([SEQUENCE_TYPES.DUPLEX, SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) ?
159
- chemistryNameCol.get(i) :
160
- sequenceCol.get(i);
161
- });
162
236
 
163
- t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => {
164
- if ([SEQUENCE_TYPES.SENSE_STRAND, SEQUENCE_TYPES.ANTISENSE_STRAND].includes(typeCol.get(i)))
165
- return sequenceCol.get(i);
166
- else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
167
- const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
168
- return `${chemistryNameCol.get(i)}; duplex of SS: ${obj.SS} and AS: ${obj.AS}`;
169
- } else if ([SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) {
170
- const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
171
- return `${chemistryNameCol.get(i)}; duplex of SS: ${obj.SS} and AS1: ${obj.AS1} and AS2: ${obj.AS2}`;
172
- }
173
- });
174
-
175
- t.columns.addNewFloat(COL_NAMES.COMPOUND_MOL_WEIGHT).init((i: number) => {
176
- if ([SEQUENCE_TYPES.SENSE_STRAND, SEQUENCE_TYPES.ANTISENSE_STRAND].includes(typeCol.get(i))) {
177
- return (isValidSequence(sequenceCol.get(i), null).indexOfFirstNotValidChar == -1) ?
178
- molecularWeight(sequenceCol.get(i), weightsObj) :
179
- DG.FLOAT_NULL;
180
- } else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
181
- const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
182
- return (Object.values(obj).every((seq) => isValidSequence(seq, null).indexOfFirstNotValidChar == -1)) ?
183
- molecularWeight(obj.SS, weightsObj) + molecularWeight(obj.AS, weightsObj) :
184
- DG.FLOAT_NULL;
185
- } else if ([SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) {
186
- const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
187
- return (Object.values(obj).every((seq) => isValidSequence(seq, null).indexOfFirstNotValidChar == -1)) ?
188
- molecularWeight(obj.SS, weightsObj) + molecularWeight(obj.AS1, weightsObj) +
189
- molecularWeight(obj.AS2, weightsObj) :
190
- DG.FLOAT_NULL;
191
- }
192
- });
193
-
194
- t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) =>
195
- saltMass(saltNamesList, molWeightCol, equivalentsCol, i, saltCol));
196
-
197
- t.columns.addNewFloat(COL_NAMES.SALT_MOL_WEIGHT).init((i: number) =>
198
- saltMolWeigth(saltNamesList, saltCol, molWeightCol, i));
237
+ const saltsMolWeightList: number[] = saltsDf.getCol('MOLWEIGHT').toList();
238
+ const saltNamesList: string[] = saltsDf.getCol('DISPLAY').toList();
199
239
 
200
- const compoundMolWeightCol = t.getCol(COL_NAMES.COMPOUND_MOL_WEIGHT);
201
- const saltMassCol = t.getCol(COL_NAMES.SALT_MASS);
202
- t.columns.addNewFloat(COL_NAMES.BATCH_MOL_WEIGHT).init((i: number) =>
203
- batchMolWeight(compoundMolWeightCol, saltMassCol, i));
204
-
205
- grok.shell.getTableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
206
- addColumnsPressed = true;
207
- return newDf = t;
208
- }
209
-
210
- function updateCalculatedColumns(t: DG.DataFrame, i: number): void {
211
- const smValue = saltMass(saltNamesList, molWeightCol, equivalentsCol, i, saltCol);
212
- t.getCol(COL_NAMES.SALT_MASS).set(i, smValue, false);
213
- const smwValue = saltMolWeigth(saltNamesList, saltCol, molWeightCol, i);
214
- t.getCol(COL_NAMES.SALT_MOL_WEIGHT).set(i, smwValue, false);
215
- const bmw = batchMolWeight(t.getCol(COL_NAMES.COMPOUND_MOL_WEIGHT), t.getCol(COL_NAMES.SALT_MASS), i);
216
- t.getCol(COL_NAMES.BATCH_MOL_WEIGHT).set(i, bmw, false);
217
- }
240
+ let newDf: DG.DataFrame | undefined = undefined;
218
241
 
219
242
  const d = ui.div([
220
243
  ui.icons.edit(() => {
@@ -223,9 +246,19 @@ export function oligoSdFile(table: DG.DataFrame) {
223
246
  table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
224
247
  d.append(
225
248
  ui.divH([
226
- ui.icons.add(() => addColumns(table), `Add columns: '${GENERATED_COL_NAMES.join(`', '`)}'`),
227
- ui.icons.save(() => saveTableAsSdFile(addColumnsPressed ? newDf : table), 'Save SD file'),
228
- ]),
249
+ ui.button('Add columns',
250
+ () => {
251
+ newDf = sdfAddColumns(table, saltNamesList, saltsMolWeightList,
252
+ (rowI, err) => { sdfHandleErrorUI('Error on ', table, rowI, err); });
253
+ grok.shell.getTableView(newDf.name).grid.columns.setOrder(Object.values(COL_NAMES));
254
+ },
255
+ `Add columns: '${GENERATED_COL_NAMES.join(`', '`)}'`),
256
+ ui.bigButton('Save SDF', () => {
257
+ const df: DG.DataFrame = newDf ?? table;
258
+ sdfSaveTable(df,
259
+ (rowI, err) => { sdfHandleErrorUI('Skip ', df, rowI, err); });
260
+ }, 'Save SD file'),
261
+ ])
229
262
  );
230
263
 
231
264
  const view = grok.shell.getTableView(table.name);
@@ -238,11 +271,15 @@ export function oligoSdFile(table: DG.DataFrame) {
238
271
  view.dataFrame.getCol(COL_NAMES.IDP).setTag(DG.TAGS.CHOICES, stringify(idpsDf.columns.byIndex(0).toList()));
239
272
 
240
273
  grok.events.onContextMenu.subscribe((args) => {
274
+ if (!(args.args.context instanceof DG.Grid)) return;
275
+ const grid: DG.Grid = args.args.context as DG.Grid;
276
+ const menu: DG.Menu = args.args.menu;
277
+
241
278
  if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
242
- .includes(args.args.context.table.currentCol.name)) {
243
- args.args.menu.item('Fill Column With Value', () => {
244
- const v = args.args.context.table.currentCell.value;
245
- args.args.context.table.currentCell.column.init(v);
279
+ .includes(grid.table.currentCol.name)) {
280
+ menu.item('Fill Column With Value', () => {
281
+ const v = grid.table.currentCell.value;
282
+ grid.table.currentCell.column.init(v);
246
283
  for (let i = 0; i < view.dataFrame.rowCount; i++)
247
284
  updateCalculatedColumns(view.dataFrame, i);
248
285
  });
@@ -256,9 +293,9 @@ export function oligoSdFile(table: DG.DataFrame) {
256
293
  });
257
294
 
258
295
  function updateCalculatedColumns(t: DG.DataFrame, i: number): void {
259
- const smValue = saltMass(saltNamesList, molWeightCol, equivalentsCol, i, saltCol);
296
+ const smValue = saltMass(saltNamesList, saltsMolWeightList, equivalentsCol, i, saltCol);
260
297
  t.getCol(COL_NAMES.SALT_MASS).set(i, smValue, false);
261
- const smwValue = saltMolWeigth(saltNamesList, saltCol, molWeightCol, i);
298
+ const smwValue = saltMolWeigth(saltNamesList, saltCol, saltsMolWeightList, i);
262
299
  t.getCol(COL_NAMES.SALT_MOL_WEIGHT).set(i, smwValue, false);
263
300
  const bmw = batchMolWeight(t.getCol(COL_NAMES.COMPOUND_MOL_WEIGHT), t.getCol(COL_NAMES.SALT_MASS), i);
264
301
  t.getCol(COL_NAMES.BATCH_MOL_WEIGHT).set(i, bmw, false);
@@ -6,7 +6,7 @@ import * as svg from 'save-svg-as-png';
6
6
  import $ from 'cash-dom';
7
7
 
8
8
  import {drawAxolabsPattern} from './draw-svg';
9
- import {AXOLABS_MAP} from './constants';
9
+ import {AXOLABS_MAP} from '../hardcode-to-be-eliminated/constants';
10
10
  import {isOverhang} from './helpers';
11
11
 
12
12
  const baseChoices: string[] = Object.keys(AXOLABS_MAP);
@@ -114,7 +114,7 @@ function addColumnWithTranslatedSequences(
114
114
  });
115
115
  }
116
116
 
117
- export function defineAxolabsPattern() {
117
+ export function getAxolabsTab() {
118
118
  const enumerateModifications = [defaultBase];
119
119
  let maximalSsLength = defaultSequenceLength;
120
120
  let maximalAsLength = defaultSequenceLength;