@datagrok/sequence-translator 1.0.4 → 1.0.5

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/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@datagrok/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.0.4",
4
+ "version": "1.0.5",
5
+ "author": {
6
+ "name": "Vadym Kovadlo",
7
+ "email": "vkovadlo@datagrok.ai"
8
+ },
5
9
  "description": "SequenceTranslator is a [package](https://datagrok.ai/help/develop/develop#packages) for the [Datagrok](https://datagrok.ai) platform, used to translate [oligonucleotide](https://en.wikipedia.org/wiki/Oligonucleotide) sequences between [different representations](https://github.com/datagrok-ai/public/tree/master/packages/SequenceTranslator#sequence-representations).",
6
10
  "repository": {
7
11
  "type": "git",
@@ -10,14 +14,14 @@
10
14
  },
11
15
  "dependencies": {
12
16
  "@datagrok-libraries/utils": "^0.1.0",
13
- "@types/react": "latest",
17
+ "@types/react": "^18.0.15",
14
18
  "datagrok-api": "^1.1.7",
15
19
  "datagrok-tools": "^4.1.2",
16
20
  "npm": "^8.11.0",
21
+ "openchemlib": "6.0.1",
17
22
  "save-svg-as-png": "^1.4.17",
18
- "ts-loader": "latest",
19
- "typescript": "latest",
20
- "openchemlib": "6.0.1"
23
+ "ts-loader": "^9.3.1",
24
+ "typescript": "^4.7.4"
21
25
  },
22
26
  "scripts": {
23
27
  "link-api": "npm link datagrok-api",
@@ -38,18 +42,19 @@
38
42
  "vendors/openchemlib-full.js"
39
43
  ],
40
44
  "devDependencies": {
45
+ "@types/jest": "^27.0.0",
46
+ "@types/jquery": "^3.5.14",
41
47
  "@typescript-eslint/eslint-plugin": "^4.29.1",
42
48
  "@typescript-eslint/parser": "^4.29.1",
43
49
  "cash-dom": "^8.1.0",
44
50
  "eslint": "^7.32.0",
45
51
  "eslint-config-google": "^0.14.0",
46
- "webpack": "^5.31.0",
47
- "webpack-cli": "^4.6.0",
48
- "jest-html-reporter": "^3.5.0",
49
52
  "jest": "^27.0.0",
50
- "@types/jest": "^27.0.0",
53
+ "jest-html-reporter": "^3.5.0",
54
+ "puppeteer": "^13.7.0",
51
55
  "ts-jest": "^27.0.0",
52
- "puppeteer": "^13.7.0"
56
+ "webpack": "^5.31.0",
57
+ "webpack-cli": "^4.6.0"
53
58
  },
54
59
  "category": "Bioinformatics"
55
60
  }
@@ -0,0 +1,194 @@
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 {siRnaAxolabsToGcrs, gcrsToNucleotides, asoGapmersBioSpringToGcrs, gcrsToMermade12,
5
+ } from '../structures-works/converters';
6
+ import {map, COL_NAMES, MODIFICATIONS} from '../structures-works/map';
7
+ import {getFormat} from '../structures-works/sequence-codes-tools';
8
+ import {sequenceToMolV3000} from '../structures-works/from-monomers';
9
+
10
+ import {SALTS_CSV} from '../salts';
11
+ import {USERS_CSV} from '../users';
12
+ import {ICDS} from '../ICDs';
13
+ import {SOURCES} from '../sources';
14
+ import {IDPS} from '../IDPs';
15
+
16
+ const weightsObj: {[code: string]: number} = {};
17
+ for (const synthesizer of Object.keys(map)) {
18
+ for (const technology of Object.keys(map[synthesizer])) {
19
+ for (const code of Object.keys(map[synthesizer][technology]))
20
+ weightsObj[code] ?? map[synthesizer][technology][code].weight;
21
+ }
22
+ }
23
+ for (const [key, value] of Object.entries(MODIFICATIONS))
24
+ weightsObj[key] = value.molecularWeight;
25
+
26
+
27
+ function sortByStringLengthInDescendingOrder(array: string[]): string[] {
28
+ return array.sort(function(a, b) {return b.length - a.length;});
29
+ }
30
+
31
+ function stringify(items: string[]): string {
32
+ return '["' + items.join('", "') + '"]';
33
+ }
34
+
35
+ function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
36
+ const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
37
+ let weight = 0;
38
+ let i = 0;
39
+ while (i < sequence.length) {
40
+ const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
41
+ weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
42
+ i += matchedCode.length;
43
+ }
44
+ return weight - 61.97;
45
+ }
46
+
47
+ async function saveTableAsSdFile(table: DG.DataFrame) {
48
+ if (!table.columns.contains('Compound Name')) {
49
+ grok.shell.warning(
50
+ 'File saved without columns \'' +
51
+ [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
52
+ COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
53
+ );
54
+ }
55
+ const structureColumn = table.getCol(COL_NAMES.SEQUENCE);
56
+ const typeColumn = table.getCol(COL_NAMES.TYPE);
57
+ let result = '';
58
+ for (let i = 0; i < table.rowCount; i++) {
59
+ const format = getFormat(structureColumn.get(i))!;
60
+ result += (typeColumn.get(i) == 'SS') ?
61
+ sequenceToMolV3000(structureColumn.get(i), false, true, format) + '\n' + `> <Sequence>\nSense Strand\n\n` :
62
+ sequenceToMolV3000(structureColumn.get(i), true, true, format) + '\n' + `> <Sequence>\nAnti Sense\n\n`;
63
+ for (const col of table.columns) {
64
+ if (col.name != COL_NAMES.SEQUENCE)
65
+ result += `> <${col.name}>\n${col.get(i)}\n\n`;
66
+ }
67
+ result += '$$$$\n\n';
68
+ }
69
+ const element = document.createElement('a');
70
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
71
+ element.setAttribute('download', table.name + '.sdf');
72
+ element.click();
73
+ }
74
+
75
+ export function autostartOligoSdFileSubscription() {
76
+ grok.events.onViewAdded.subscribe((v: any) => {
77
+ if (v.type == 'TableView') {
78
+ if (v.dataFrame.columns.contains(COL_NAMES.TYPE))
79
+ oligoSdFile(v.dataFrame);
80
+ grok.events.onContextMenu.subscribe((args) => {
81
+ const seqCol = args.args.context.table.currentCol;
82
+ if (DG.Detector.sampleCategories(seqCol, (s) => /^[fsACGUacgu]{6,}$/.test(s))) {
83
+ args.args.menu.item('Convert Axolabs to GCRS', () => {
84
+ args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
85
+ return siRnaAxolabsToGcrs(seqCol.get(i));
86
+ });
87
+ });
88
+ } else if (DG.Detector.sampleCategories(seqCol, (s) => /^[fmpsACGU]{6,}$/.test(s)) ||
89
+ DG.Detector.sampleCategories(seqCol, (s) => /^(?=.*moe)(?=.*5mC)(?=.*ps){6,}/.test(s))) {
90
+ args.args.menu.item('Convert GCRS to raw', () => {
91
+ args.args.context.table.columns.addNewString(seqCol.name + ' to raw').init((i: number) => {
92
+ return gcrsToNucleotides(seqCol.get(i));
93
+ });
94
+ });
95
+ args.args.menu.item('Convert GCRS to MM12', () => {
96
+ args.args.context.table.columns.addNewString(seqCol.name + ' to MM12').init((i: number) => {
97
+ return gcrsToMermade12(seqCol.get(i));
98
+ });
99
+ });
100
+ } else if (DG.Detector.sampleCategories(seqCol, (s) => /^[*56789ATGC]{6,}$/.test(s))) {
101
+ args.args.menu.item('Convert Biospring to GCRS', () => {
102
+ const seqCol = args.args.context.table.currentCol;
103
+ args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
104
+ return asoGapmersBioSpringToGcrs(seqCol.get(i));
105
+ });
106
+ });
107
+ } else if (DG.Detector.sampleCategories(seqCol, (s) => /^[*1-8]{6,}$/.test(s))) {
108
+ args.args.menu.item('Convert Biospring to GCRS', () => {
109
+ args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
110
+ return siRnaAxolabsToGcrs(seqCol.get(i));
111
+ });
112
+ });
113
+ }
114
+ });
115
+ }
116
+ });
117
+ }
118
+
119
+ export function oligoSdFile(table: DG.DataFrame) {
120
+ const saltsDf = DG.DataFrame.fromCsv(SALTS_CSV);
121
+ const usersDf = DG.DataFrame.fromCsv(USERS_CSV);
122
+ const sourcesDf = DG.DataFrame.fromCsv(SOURCES);
123
+ const icdsDf = DG.DataFrame.fromCsv(ICDS);
124
+ const idpsDf = DG.DataFrame.fromCsv(IDPS);
125
+
126
+ function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
127
+ if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
128
+ return grok.shell.error('Columns already exist');
129
+
130
+ const sequence = t.getCol(COL_NAMES.SEQUENCE);
131
+ const salt = t.getCol(COL_NAMES.SALT);
132
+ const equivalents = t.getCol(COL_NAMES.EQUIVALENTS);
133
+
134
+ t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => sequence.get(i));
135
+ t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => (i > 0 && i % 2 == 0) ?
136
+ sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
137
+ sequence.getString(i),
138
+ );
139
+ const molWeightCol = saltsDf.getCol('MOLWEIGHT');
140
+ const saltNamesList = saltsDf.getCol('DISPLAY').toList();
141
+ t.columns.addNewFloat(COL_NAMES.CPD_MW)
142
+ .init((i: number) => molecularWeight(sequence.get(i), weightsObj));
143
+ t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) => {
144
+ const saltRowIndex = saltNamesList.indexOf(salt.get(i));
145
+ const mw = molWeightCol.get(saltRowIndex);
146
+ return mw * equivalents.get(i);
147
+ });
148
+ t.columns.addNewCalculated(COL_NAMES.BATCH_MW,
149
+ '${' + COL_NAMES.CPD_MW + '} + ${' + COL_NAMES.SALT_MASS + '}', DG.COLUMN_TYPE.FLOAT, false,
150
+ );
151
+
152
+ addColumnsPressed = true;
153
+ return newDf = t;
154
+ }
155
+
156
+ let newDf: DG.DataFrame;
157
+ let addColumnsPressed = false;
158
+
159
+ const d = ui.div([
160
+ ui.icons.edit(() => {
161
+ d.innerHTML = '';
162
+ if (table.getCol(COL_NAMES.IDP).type != DG.COLUMN_TYPE.STRING)
163
+ table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
164
+ d.append(
165
+ ui.link('Add Columns', () => {
166
+ addColumns(table, saltsDf);
167
+ grok.shell.tableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
168
+ }, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
169
+ COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), ''),
170
+ ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
171
+ );
172
+
173
+ const view = grok.shell.getTableView(table.name);
174
+
175
+ view.dataFrame.getCol(COL_NAMES.TYPE).setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
176
+ view.dataFrame.getCol(COL_NAMES.OWNER).setTag(DG.TAGS.CHOICES, stringify(usersDf.columns.byIndex(0).toList()));
177
+ view.dataFrame.getCol(COL_NAMES.SALT).setTag(DG.TAGS.CHOICES, stringify(saltsDf.columns.byIndex(0).toList()));
178
+ view.dataFrame.getCol(COL_NAMES.SOURCE).setTag(DG.TAGS.CHOICES, stringify(sourcesDf.columns.byIndex(0).toList()));
179
+ view.dataFrame.getCol(COL_NAMES.ICD).setTag(DG.TAGS.CHOICES, stringify(icdsDf.columns.byIndex(0).toList()));
180
+ view.dataFrame.getCol(COL_NAMES.IDP).setTag(DG.TAGS.CHOICES, stringify(idpsDf.columns.byIndex(0).toList()));
181
+
182
+ grok.events.onContextMenu.subscribe((args) => {
183
+ if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
184
+ .includes(args.args.context.table.currentCol.name)) {
185
+ args.args.menu.item('Fill Column With Value', () => {
186
+ const v = args.args.context.table.currentCell.value;
187
+ args.args.context.table.currentCell.column.init(v);
188
+ });
189
+ }
190
+ });
191
+ }),
192
+ ]);
193
+ grok.shell.v.setRibbonPanels([[d]]);
194
+ }
@@ -94,7 +94,7 @@ export const axolabsMap:
94
94
  color: invAbasicColor,
95
95
  },
96
96
  '2\'-OMe-U(o)': {
97
- fullName: 'Nucleotide Uridine with 2O-Methyl protection (overhang)',
97
+ fullName: 'Nucleotide Uridine with 2\'O-Methyl protection (overhang)',
98
98
  symbols: ['mU', 'mU', 'mU', 'mU'],
99
99
  color: 'rgb(65,233,80)',
100
100
  },
@@ -1,4 +1,3 @@
1
- /* Do not change these import lines. Datagrok will import API library in exactly the same manner */
2
1
  import * as grok from 'datagrok-api/grok';
3
2
  import * as ui from 'datagrok-api/ui';
4
3
  import * as DG from 'datagrok-api/dg';
@@ -6,8 +5,8 @@ import * as DG from 'datagrok-api/dg';
6
5
  import * as svg from 'save-svg-as-png';
7
6
  import $ from 'cash-dom';
8
7
 
9
- import {drawAxolabsPattern} from './drawAxolabsPattern';
10
- import {axolabsMap} from './axolabsMap';
8
+ import {drawAxolabsPattern} from './draw-svg';
9
+ import {axolabsMap} from './constants';
11
10
 
12
11
  const baseChoices: string[] = Object.keys(axolabsMap);
13
12
  const defaultBase: string = baseChoices[0];
@@ -57,17 +56,23 @@ function getUserName(patternName: string): string[] {
57
56
 
58
57
  function translateSequence(
59
58
  sequence: string,
60
- bases: any,
61
- ptoLinkages: any,
62
- startModification: any,
63
- endModification: any,
59
+ bases: DG.InputBase[],
60
+ ptoLinkages: DG.InputBase[],
61
+ startModification: DG.InputBase,
62
+ endModification: DG.InputBase,
64
63
  firstPtoExist: boolean): string {
65
- let counter: number = -1;
64
+ let i: number = -1;
66
65
  let mainSequence = sequence.replace(/[AUGC]/g, function(x: string) {
67
- counter++;
66
+ i++;
68
67
  const indexOfSymbol = axolabsMap['RNA']['symbols'].indexOf(x);
69
- const symbol = axolabsMap[bases[counter].value]['symbols'][indexOfSymbol];
70
- return (ptoLinkages[counter].value) ? symbol + 's' : symbol;
68
+ let symbol = axolabsMap[bases[i].value]['symbols'][indexOfSymbol];
69
+ if (bases[i].value.slice(-3) == '(o)') {
70
+ if (i < sequence.length / 2 && bases[i + 1].value.slice(-3) != '(o)')
71
+ symbol = symbol + x + 'f';
72
+ else if (i > sequence.length / 2 && bases[i - 1].value.slice(-3) != '(o)')
73
+ symbol = x + 'f' + symbol;
74
+ }
75
+ return (ptoLinkages[i].value) ? symbol + 's' : symbol;
71
76
  });
72
77
  if (mainSequence.slice(0, 5).split('mU').length == 3)
73
78
  mainSequence = '(uu)' + mainSequence.slice(4);
@@ -90,10 +95,10 @@ function addColumnWithIds(tableName: string, columnName: string, patternName: st
90
95
  function addColumnWithTranslatedSequences(
91
96
  tableName: string,
92
97
  columnName: string,
93
- bases: any,
94
- ptoLinkages: any,
95
- startModification: any,
96
- endModification: any,
98
+ bases: DG.InputBase[],
99
+ ptoLinkages: DG.InputBase[],
100
+ startModification: DG.InputBase,
101
+ endModification: DG.InputBase,
97
102
  firstPtoExist: boolean) {
98
103
  const nameOfNewColumn = 'Axolabs ' + columnName;
99
104
  const columns = grok.shell.table(tableName).columns;
@@ -240,8 +245,9 @@ export function defineAxolabsPattern() {
240
245
  }
241
246
 
242
247
  function updateInputExamples() {
243
- ssInputExample.value = generateExample(ssLength.value!, sequenceBase.value!);
244
- if (createAsStrand.value)
248
+ if (inputSsColumn.value == '')
249
+ ssInputExample.value = generateExample(ssLength.value!, sequenceBase.value!);
250
+ if (createAsStrand.value && inputAsColumn.value == '')
245
251
  asInputExample.value = generateExample(asLength.value!, sequenceBase.value!);
246
252
  }
247
253
 
@@ -333,7 +339,7 @@ export function defineAxolabsPattern() {
333
339
  }
334
340
 
335
341
  function checkWhetherAllValuesInColumnHaveTheSameLength(colName: string): boolean {
336
- const col = tables.value!.columns.byName(colName);
342
+ const col = tables.value!.getCol(colName);
337
343
  let allLengthsAreTheSame = true;
338
344
  for (let i = 1; i < col.length; i++) {
339
345
  if (col.get(i - 1).length != col.get(i).length && col.get(i).length != 0) {
@@ -510,26 +516,24 @@ export function defineAxolabsPattern() {
510
516
  const asLength = ui.intInput('AS Length', defaultSequenceLength, () => updateUiForNewSequenceLength());
511
517
  const asLengthDiv = ui.div([asLength.root]);
512
518
 
513
- function validateSsColumn(colName: string) {
519
+ function validateSsColumn(colName: string): void {
514
520
  const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
515
- const firstSequence = tables.value!.columns.byName(colName).get(0);
521
+ const firstSequence = tables.value!.getCol(colName).get(0);
516
522
  if (allLengthsAreTheSame && firstSequence.length != ssLength.value)
517
- ssLength.value = tables.value!.columns.byName(colName).get(0).length;
523
+ ssLength.value = tables.value!.getCol(colName).get(0).length;
518
524
  ssInputExample.value = firstSequence;
519
525
  }
520
526
 
521
- function validateAsColumn(colName: string) {
527
+ function validateAsColumn(colName: string): void {
522
528
  const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
523
- const firstSequence = tables.value!.columns.byName(colName).get(0);
529
+ const firstSequence = tables.value!.getCol(colName).get(0);
524
530
  if (allLengthsAreTheSame && firstSequence.length != asLength.value)
525
- asLength.value = tables.value!.columns.byName(colName).get(0).length;
526
- asLengthDiv.innerHTML = '';
527
- asLengthDiv.append(asLength.root);
531
+ asLength.value = tables.value!.getCol(colName).get(0).length;
528
532
  asInputExample.value = firstSequence;
529
533
  }
530
534
 
531
535
  function validateIdsColumn(colName: string) {
532
- const col = tables.value!.columns.byName(colName);
536
+ const col = tables.value!.getCol(colName);
533
537
  if (col.type != DG.TYPE.INT)
534
538
  grok.shell.error('Column should contain integers only');
535
539
  else if (col.categories.filter((e) => e != '').length < col.toList().filter((e) => e != '').length) {
@@ -670,8 +674,8 @@ export function defineAxolabsPattern() {
670
674
  dialog
671
675
  .add(ui.divText('Length of sequences in columns doesn\'t match entered length. Update length value?'))
672
676
  .addButton('YES', () => {
673
- ssLength.value = tables.value!.columns.byName(inputSsColumn.value!).getString(0).length;
674
- asLength.value = tables.value!.columns.byName(inputAsColumn.value!).getString(0).length;
677
+ ssLength.value = tables.value!.getCol(inputSsColumn.value!).getString(0).length;
678
+ asLength.value = tables.value!.getCol(inputAsColumn.value!).getString(0).length;
675
679
  dialog.close();
676
680
  })
677
681
  .show();
@@ -692,10 +696,7 @@ export function defineAxolabsPattern() {
692
696
  }
693
697
  });
694
698
 
695
- const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!), () => {
696
- ssOutputExample.value = translateSequence(ssInputExample.value, ssBases, ssPtoLinkages,
697
- ssFiveModification, ssThreeModification, firstSsPto.value!);
698
- });
699
+ const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!));
699
700
  const ssOutputExample = ui.textInput(' ', translateSequence(
700
701
  ssInputExample.value, ssBases, ssPtoLinkages, ssThreeModification, ssFiveModification, firstSsPto.value!));
701
702
  (ssInputExample.input as HTMLElement).style.resize = 'none';
@@ -713,10 +714,7 @@ export function defineAxolabsPattern() {
713
714
  ], 'ui-input-options'),
714
715
  );
715
716
 
716
- const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!), () => {
717
- asOutputExample.value = translateSequence(
718
- asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!);
719
- });
717
+ const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!));
720
718
  const asOutputExample = ui.textInput(' ', translateSequence(
721
719
  asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!));
722
720
  (asInputExample.input as HTMLElement).style.resize = 'none';
@@ -760,11 +758,14 @@ export function defineAxolabsPattern() {
760
758
  ]),
761
759
  ], 'ui-form');
762
760
 
761
+ const downloadButton = ui.button('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value,
762
+ {backgroundColor: 'white'}));
763
+
763
764
  const mainSection = ui.panel([
764
765
  ui.block([
765
766
  svgDiv,
766
767
  ], {style: {overflowX: 'scroll'}}),
767
- ui.button('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value)),
768
+ downloadButton,
768
769
  isEnumerateModificationsDiv,
769
770
  ui.div([
770
771
  ui.div([
@@ -1,4 +1,4 @@
1
- import {axolabsMap} from './axolabsMap';
1
+ import {axolabsMap} from './constants';
2
2
 
3
3
  // https://uxdesign.cc/star-rating-make-svg-great-again-d4ce4731347e
4
4
  function getPointsToDrawStar(centerX: number, centerY: number): string {
@@ -0,0 +1,225 @@
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 {convertSequence, undefinedInputSequence, isValidSequence} from '../structures-works/sequence-codes-tools';
5
+ import {map, MODIFICATIONS} from '../structures-works/map';
6
+ import {sequenceToSmiles, sequenceToMolV3000} from '../structures-works/from-monomers';
7
+
8
+ import $ from 'cash-dom';
9
+
10
+ const defaultInput = 'fAmCmGmAmCpsmU';
11
+ const sequenceWasCopied = 'Copied';
12
+ const tooltipSequence = 'Copy sequence';
13
+
14
+ export function mainView() {
15
+ function updateTableAndMolecule(sequence: string, inputFormat: string, isSet: boolean): void {
16
+ moleculeSvgDiv.innerHTML = '';
17
+ outputTableDiv.innerHTML = '';
18
+ const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
19
+ let errorsExist = false;
20
+ try {
21
+ sequence = sequence.replace(/\s/g, '');
22
+ const output = isValidSequence(sequence, null);
23
+ if (isSet)
24
+ output.synthesizer = [inputFormat];
25
+ inputFormatChoiceInput.value = output.synthesizer![0];
26
+ const outputSequenceObj = convertSequence(sequence, output);
27
+ const tableRows = [];
28
+
29
+ for (const key of Object.keys(outputSequenceObj).slice(1)) {
30
+ const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
31
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
32
+ -1;
33
+ if ('indexOfFirstNotValidChar' in outputSequenceObj) {
34
+ const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
35
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
36
+ -1;
37
+ if (indexOfFirstNotValidChar != -1)
38
+ errorsExist = true;
39
+ }
40
+
41
+ tableRows.push({
42
+ 'key': key,
43
+ 'value': ('indexOfFirstNotValidChar' in outputSequenceObj) ?
44
+ ui.divH([
45
+ ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
46
+ ui.tooltip.bind(
47
+ ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
48
+ 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
49
+ '. See tables with valid codes on the right',
50
+ ),
51
+ ]) : //@ts-ignore
52
+ ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
53
+ .then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
54
+ });
55
+ }
56
+
57
+ if (errorsExist) {
58
+ const synthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer.slice(0, -6);
59
+ asoGapmersGrid.onCellPrepare(function(gc) {
60
+ gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
61
+ });
62
+ omeAndFluoroGrid.onCellPrepare(function(gc) {
63
+ gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
64
+ });
65
+ switchInput.enabled = true;
66
+ } else {
67
+ asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
68
+ omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
69
+ }
70
+
71
+ outputTableDiv.append(
72
+ ui.div([
73
+ DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
74
+ [item.key, item.value], ['Code', 'Sequence']).root,
75
+ ]),
76
+ );
77
+
78
+ if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
79
+ const canvas = ui.canvas(300, 170);
80
+ canvas.addEventListener('click', () => {
81
+ const canv = ui.canvas($(window).width(), $(window).height());
82
+ const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
83
+ output.synthesizer![0]);
84
+ // @ts-ignore
85
+ OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
86
+ ui.dialog('Molecule: ' + inputSequenceField.value)
87
+ .add(canv)
88
+ .showModal(true);
89
+ });
90
+ $(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
91
+ $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
92
+ const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
93
+ output.synthesizer![0]);
94
+ // @ts-ignore
95
+ OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
96
+ moleculeSvgDiv.append(canvas);
97
+ } else
98
+ moleculeSvgDiv.innerHTML = '';
99
+ } finally {
100
+ pi.close();
101
+ }
102
+ }
103
+
104
+ const inputFormatChoiceInput = ui.choiceInput(
105
+ 'Input format: ', 'Janssen GCRS Codes', Object.keys(map), (format: string) => {
106
+ updateTableAndMolecule(inputSequenceField.value.replace(/\s/g, ''), format, true);
107
+ });
108
+ const moleculeSvgDiv = ui.block([]);
109
+ const outputTableDiv = ui.div([]);
110
+ const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence,
111
+ inputFormatChoiceInput.value!, false));
112
+
113
+ const asoDf = DG.DataFrame.fromObjects([
114
+ {'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
115
+ {'Name': '2\'MOE-rA', 'BioSpring': '6', 'Janssen GCRS': 'moeA'},
116
+ {'Name': '2\'MOE-5Me-rC', 'BioSpring': '7', 'Janssen GCRS': 'moe5mC'},
117
+ {'Name': '2\'MOE-rG', 'BioSpring': '8', 'Janssen GCRS': 'moeG'},
118
+ {'Name': '5-Methyl-dC', 'BioSpring': '9', 'Janssen GCRS': '5mC'},
119
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Janssen GCRS': 'ps'},
120
+ {'Name': 'dA', 'BioSpring': 'A', 'Janssen GCRS': 'A, dA'},
121
+ {'Name': 'dC', 'BioSpring': 'C', 'Janssen GCRS': 'C, dC'},
122
+ {'Name': 'dG', 'BioSpring': 'G', 'Janssen GCRS': 'G, dG'},
123
+ {'Name': 'dT', 'BioSpring': 'T', 'Janssen GCRS': 'T, dT'},
124
+ {'Name': 'rA', 'BioSpring': '', 'Janssen GCRS': 'rA'},
125
+ {'Name': 'rC', 'BioSpring': '', 'Janssen GCRS': 'rC'},
126
+ {'Name': 'rG', 'BioSpring': '', 'Janssen GCRS': 'rG'},
127
+ {'Name': 'rU', 'BioSpring': '', 'Janssen GCRS': 'rU'},
128
+ ])!;
129
+ const asoGapmersGrid = DG.Viewer.grid(asoDf, {showRowHeader: false, showCellTooltip: false});
130
+
131
+ asoDf.onCurrentCellChanged.subscribe((_) => {
132
+ navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'));
133
+ });
134
+
135
+ const omeAndFluoroGrid = DG.Viewer.grid(
136
+ DG.DataFrame.fromObjects([
137
+ {'Name': '2\'-fluoro-U', 'BioSpring': '1', 'Axolabs': 'Uf', 'Janssen GCRS': 'fU'},
138
+ {'Name': '2\'-fluoro-A', 'BioSpring': '2', 'Axolabs': 'Af', 'Janssen GCRS': 'fA'},
139
+ {'Name': '2\'-fluoro-C', 'BioSpring': '3', 'Axolabs': 'Cf', 'Janssen GCRS': 'fC'},
140
+ {'Name': '2\'-fluoro-G', 'BioSpring': '4', 'Axolabs': 'Gf', 'Janssen GCRS': 'fG'},
141
+ {'Name': '2\'OMe-rU', 'BioSpring': '5', 'Axolabs': 'u', 'Janssen GCRS': 'mU'},
142
+ {'Name': '2\'OMe-rA', 'BioSpring': '6', 'Axolabs': 'a', 'Janssen GCRS': 'mA'},
143
+ {'Name': '2\'OMe-rC', 'BioSpring': '7', 'Axolabs': 'c', 'Janssen GCRS': 'mC'},
144
+ {'Name': '2\'OMe-rG', 'BioSpring': '8', 'Axolabs': 'g', 'Janssen GCRS': 'mG'},
145
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Axolabs': 's', 'Janssen GCRS': 'ps'},
146
+ ])!, {showRowHeader: false, showCellTooltip: false},
147
+ );
148
+
149
+ const overhangModificationsGrid = DG.Viewer.grid(
150
+ DG.DataFrame.fromColumns([
151
+ DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
152
+ ])!, {showRowHeader: false, showCellTooltip: false},
153
+ );
154
+ updateTableAndMolecule(defaultInput, inputFormatChoiceInput.value!, true);
155
+
156
+ const codesTablesDiv = ui.splitV([
157
+ ui.box(ui.h2('ASO Gapmers'), {style: {maxHeight: '40px'}}),
158
+ asoGapmersGrid.root,
159
+ ui.box(ui.h2('2\'-OMe and 2\'-F siRNA'), {style: {maxHeight: '40px'}}),
160
+ omeAndFluoroGrid.root,
161
+ ui.box(ui.h2('Overhang modifications'), {style: {maxHeight: '40px'}}),
162
+ overhangModificationsGrid.root,
163
+ ], {style: {maxWidth: '350px'}});
164
+
165
+ const appMainDescription = ui.info([
166
+ ui.divText('How to convert one sequence:', {style: {'font-weight': 'bolder'}}),
167
+ ui.divText('Paste sequence into the text field below'),
168
+ ui.divText('\n How to convert many sequences:', {style: {'font-weight': 'bolder'}}),
169
+ ui.divText('1. Drag & drop an Excel or CSV file with sequences into Datagrok'),
170
+ ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
171
+ ui.divText('This will add the result column to the right of the table'),
172
+ ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.');
173
+
174
+ $(codesTablesDiv).hide();
175
+ const switchInput = ui.switchInput('Codes', false, (v: boolean) => (v) ?
176
+ $(codesTablesDiv).show() :
177
+ $(codesTablesDiv).hide(),
178
+ );
179
+
180
+ const topPanel = [
181
+ ui.iconFA('download', () => {
182
+ const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
183
+ inputFormatChoiceInput.value!);
184
+ const element = document.createElement('a');
185
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
186
+ element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
187
+ element.click();
188
+ }, 'Save .mol file'),
189
+ ui.iconFA('copy', () => {
190
+ navigator.clipboard.writeText(
191
+ sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''), false, inputFormatChoiceInput.value!))
192
+ .then(() => grok.shell.info(sequenceWasCopied));
193
+ }, 'Copy SMILES'),
194
+ switchInput.root,
195
+ ];
196
+
197
+ const v = grok.shell.v;
198
+ const tabControl = grok.shell.sidebar;
199
+ tabControl.onTabChanged.subscribe((_) =>
200
+ v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
201
+ v.setRibbonPanels([topPanel]);
202
+
203
+ return ui.box(
204
+ ui.splitH([
205
+ ui.splitV([
206
+ ui.panel([
207
+ appMainDescription,
208
+ ui.div([
209
+ ui.h1('Input sequence'),
210
+ ui.div([
211
+ inputSequenceField.root,
212
+ ], 'input-base'),
213
+ ], 'inputSequence'),
214
+ ui.div([inputFormatChoiceInput], {style: {padding: '5px 0'}}),
215
+ ui.block([
216
+ ui.h1('Output'),
217
+ outputTableDiv,
218
+ ]),
219
+ moleculeSvgDiv,
220
+ ], 'sequence'),
221
+ ]),
222
+ codesTablesDiv,
223
+ ], {style: {height: '100%', width: '100%'}}),
224
+ );
225
+ }