@datagrok/sequence-translator 1.0.3 → 1.0.6

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.
@@ -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);
@@ -82,16 +87,18 @@ function addColumnWithIds(tableName: string, columnName: string, patternName: st
82
87
  if (columns.contains(nameOfNewColumn))
83
88
  columns.remove(nameOfNewColumn);
84
89
  const columnWithIds = columns.byName(columnName);
85
- return columns.addNewString(nameOfNewColumn).init((i: number) => columnWithIds.get(i) + '_' + patternName);
90
+ return columns.addNewString(nameOfNewColumn).init((i: number) => {
91
+ return (columnWithIds.getString(i) == '') ? '' : columnWithIds.get(i) + '_' + patternName;
92
+ });
86
93
  }
87
94
 
88
95
  function addColumnWithTranslatedSequences(
89
96
  tableName: string,
90
97
  columnName: string,
91
- bases: any,
92
- ptoLinkages: any,
93
- startModification: any,
94
- endModification: any,
98
+ bases: DG.InputBase[],
99
+ ptoLinkages: DG.InputBase[],
100
+ startModification: DG.InputBase,
101
+ endModification: DG.InputBase,
95
102
  firstPtoExist: boolean) {
96
103
  const nameOfNewColumn = 'Axolabs ' + columnName;
97
104
  const columns = grok.shell.table(tableName).columns;
@@ -99,8 +106,10 @@ function addColumnWithTranslatedSequences(
99
106
  columns.remove(nameOfNewColumn);
100
107
  const columnWithInputSequences = columns.byName(columnName);
101
108
  return columns.addNewString(nameOfNewColumn).init((i: number) => {
102
- return translateSequence(columnWithInputSequences.getString(i),
103
- bases, ptoLinkages, startModification, endModification, firstPtoExist);
109
+ return columnWithInputSequences.getString(i) == '' ?
110
+ '' :
111
+ translateSequence(columnWithInputSequences.getString(i), bases, ptoLinkages, startModification, endModification,
112
+ firstPtoExist);
104
113
  });
105
114
  }
106
115
 
@@ -236,8 +245,9 @@ export function defineAxolabsPattern() {
236
245
  }
237
246
 
238
247
  function updateInputExamples() {
239
- ssInputExample.value = generateExample(ssLength.value!, sequenceBase.value!);
240
- if (createAsStrand.value)
248
+ if (inputSsColumn.value == '')
249
+ ssInputExample.value = generateExample(ssLength.value!, sequenceBase.value!);
250
+ if (createAsStrand.value && inputAsColumn.value == '')
241
251
  asInputExample.value = generateExample(asLength.value!, sequenceBase.value!);
242
252
  }
243
253
 
@@ -329,10 +339,10 @@ export function defineAxolabsPattern() {
329
339
  }
330
340
 
331
341
  function checkWhetherAllValuesInColumnHaveTheSameLength(colName: string): boolean {
332
- const col = tables.value!.columns.byName(colName);
342
+ const col = tables.value!.getCol(colName);
333
343
  let allLengthsAreTheSame = true;
334
344
  for (let i = 1; i < col.length; i++) {
335
- if (col.get(i - 1).length != col.get(i).length) {
345
+ if (col.get(i - 1).length != col.get(i).length && col.get(i).length != 0) {
336
346
  allLengthsAreTheSame = false;
337
347
  break;
338
348
  }
@@ -361,12 +371,13 @@ export function defineAxolabsPattern() {
361
371
  }
362
372
 
363
373
  async function postPatternToUserStorage() {
364
- const author = await getCurrentUserName();
365
- if (!saveAs.stringValue.includes('(created by '))
366
- saveAs.value = saveAs.stringValue + author;
374
+ const currUserName = await getCurrentUserName();
375
+ saveAs.value = (saveAs.stringValue.includes('(created by ')) ?
376
+ getShortName(saveAs.value) + currUserName :
377
+ saveAs.stringValue + currUserName;
367
378
  return grok.dapi.userDataStorage.postValue(
368
379
  userStorageKey,
369
- saveAs.stringValue,
380
+ saveAs.value,
370
381
  JSON.stringify({
371
382
  'ssBases': ssBases.slice(0, ssLength.value!).map((e) => e.value),
372
383
  'asBases': asBases.slice(0, asLength.value!).map((e) => e.value),
@@ -387,11 +398,12 @@ export function defineAxolabsPattern() {
387
398
  const lstMy: string[] = [];
388
399
  const lstOthers: string[] = [];
389
400
 
401
+ // TODO: display short name, but use long for querying userdataStorage
390
402
  for (const ent of Object.keys(entities)) {
391
403
  if (await isCurrentUserCreatedThisPattern(ent))
392
404
  lstOthers.push(ent);
393
405
  else
394
- lstMy.push(getShortName(ent));
406
+ lstMy.push(ent);//getShortName(ent));
395
407
  }
396
408
 
397
409
  let loadPattern = ui.choiceInput('Load Pattern', '', lstMy, (v: string) => parsePatternAndUpdateUi(v));
@@ -504,29 +516,27 @@ export function defineAxolabsPattern() {
504
516
  const asLength = ui.intInput('AS Length', defaultSequenceLength, () => updateUiForNewSequenceLength());
505
517
  const asLengthDiv = ui.div([asLength.root]);
506
518
 
507
- function validateSsColumn(colName: string) {
519
+ function validateSsColumn(colName: string): void {
508
520
  const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
509
- const firstSequence = tables.value!.columns.byName(colName).get(0);
521
+ const firstSequence = tables.value!.getCol(colName).get(0);
510
522
  if (allLengthsAreTheSame && firstSequence.length != ssLength.value)
511
- ssLength.value = tables.value!.columns.byName(colName).get(0).length;
523
+ ssLength.value = tables.value!.getCol(colName).get(0).length;
512
524
  ssInputExample.value = firstSequence;
513
525
  }
514
526
 
515
- function validateAsColumn(colName: string) {
527
+ function validateAsColumn(colName: string): void {
516
528
  const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
517
- const firstSequence = tables.value!.columns.byName(colName).get(0);
529
+ const firstSequence = tables.value!.getCol(colName).get(0);
518
530
  if (allLengthsAreTheSame && firstSequence.length != asLength.value)
519
- asLength.value = tables.value!.columns.byName(colName).get(0).length;
520
- asLengthDiv.innerHTML = '';
521
- asLengthDiv.append(asLength.root);
531
+ asLength.value = tables.value!.getCol(colName).get(0).length;
522
532
  asInputExample.value = firstSequence;
523
533
  }
524
534
 
525
535
  function validateIdsColumn(colName: string) {
526
- const col = tables.value!.columns.byName(colName);
536
+ const col = tables.value!.getCol(colName);
527
537
  if (col.type != DG.TYPE.INT)
528
538
  grok.shell.error('Column should contain integers only');
529
- else if (col.categories.length < col.length) {
539
+ else if (col.categories.filter((e) => e != '').length < col.toList().filter((e) => e != '').length) {
530
540
  const duplicates = findDuplicates(col.getRawData());
531
541
  ui.dialog('Non-unique IDs')
532
542
  .add(ui.divText('Press \'OK\' to select rows with non-unique values'))
@@ -541,25 +551,43 @@ export function defineAxolabsPattern() {
541
551
  }
542
552
 
543
553
  const tables = ui.tableInput('Tables', grok.shell.tables[0], grok.shell.tables, (t: DG.DataFrame) => {
544
- const inputSsColumn =
545
- ui.choiceInput('SS Column', '', t.columns.names(), (colName: string) => validateSsColumn(colName));
554
+ const inputSsColumn = ui.choiceInput('SS Column', '', t.columns.names(), (colName: string) => {
555
+ validateSsColumn(colName);
556
+ ssVar = colName;
557
+ });
546
558
  inputSsColumnDiv.innerHTML = '';
547
559
  inputSsColumnDiv.append(inputSsColumn.root);
548
- const inputAsColumn =
549
- ui.choiceInput('AS Column', '', t.columns.names(), (colName: string) => validateAsColumn(colName));
560
+ const inputAsColumn = ui.choiceInput('AS Column', '', t.columns.names(), (colName: string) => {
561
+ validateAsColumn(colName);
562
+ asVar = colName;
563
+ });
550
564
  inputAsColumnDiv.innerHTML = '';
551
565
  inputAsColumnDiv.append(inputAsColumn.root);
552
- const inputIdColumn =
553
- ui.choiceInput('ID Column', '', t.columns.names(), (colName: string) => validateIdsColumn(colName));
566
+ const inputIdColumn = ui.choiceInput('ID Column', '', t.columns.names(), (colName: string) => {
567
+ validateIdsColumn(colName);
568
+ idVar = colName;
569
+ });
554
570
  inputIdColumnDiv.innerHTML = '';
555
571
  inputIdColumnDiv.append(inputIdColumn.root);
556
572
  });
557
573
 
558
- const inputSsColumn = ui.choiceInput('SS Column', '', []);
574
+ let ssVar = '';
575
+ const inputSsColumn = ui.choiceInput('SS Column', '', [], (colName: string) => {
576
+ validateSsColumn(colName);
577
+ ssVar = colName;
578
+ });
559
579
  inputSsColumnDiv.append(inputSsColumn.root);
560
- const inputAsColumn = ui.choiceInput('AS Column', '', []);
580
+ let asVar = '';
581
+ const inputAsColumn = ui.choiceInput('AS Column', '', [], (colName: string) => {
582
+ validateAsColumn(colName);
583
+ asVar = colName;
584
+ });
561
585
  inputAsColumnDiv.append(inputAsColumn.root);
562
- const inputIdColumn = ui.choiceInput('ID Column', '', []);
586
+ let idVar = '';
587
+ const inputIdColumn = ui.choiceInput('ID Column', '', [], (colName: string) => {
588
+ validateIdsColumn(colName);
589
+ idVar = colName;
590
+ });
563
591
  inputIdColumnDiv.append(inputIdColumn.root);
564
592
 
565
593
  updatePatternsList();
@@ -638,7 +666,7 @@ export function defineAxolabsPattern() {
638
666
  });
639
667
 
640
668
  const convertSequenceButton = ui.button('Convert Sequences', () => {
641
- if (inputSsColumn.value == null || (createAsStrand.value && inputAsColumn.value == null))
669
+ if (ssVar == '' || (createAsStrand.value && asVar == ''))
642
670
  grok.shell.info('Please select table and columns on which to apply pattern');
643
671
  else if (ssLength.value != ssInputExample.value.length || asLength.value != asInputExample.value.length) {
644
672
  const dialog = ui.dialog('Length Mismatch');
@@ -646,20 +674,20 @@ export function defineAxolabsPattern() {
646
674
  dialog
647
675
  .add(ui.divText('Length of sequences in columns doesn\'t match entered length. Update length value?'))
648
676
  .addButton('YES', () => {
649
- ssLength.value = tables.value!.columns.byName(inputSsColumn.value!).getString(0).length;
650
- 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;
651
679
  dialog.close();
652
680
  })
653
681
  .show();
654
682
  } else {
655
- if (inputIdColumn.value != null)
656
- addColumnWithIds(tables.value!.name, inputIdColumn.value, getShortName(saveAs.value));
683
+ if (idVar != '')
684
+ addColumnWithIds(tables.value!.name, idVar, getShortName(saveAs.value));
657
685
  addColumnWithTranslatedSequences(
658
- tables.value!.name, inputSsColumn.value, ssBases, ssPtoLinkages,
686
+ tables.value!.name, ssVar, ssBases, ssPtoLinkages,
659
687
  ssFiveModification, ssThreeModification, firstSsPto.value!);
660
688
  if (createAsStrand.value) {
661
689
  addColumnWithTranslatedSequences(
662
- tables.value!.name, inputAsColumn.value!, asBases, asPtoLinkages,
690
+ tables.value!.name, asVar, asBases, asPtoLinkages,
663
691
  asFiveModification, asThreeModification, firstAsPto.value!);
664
692
  }
665
693
  grok.shell.v = grok.shell.getTableView(tables.value!.name);
@@ -668,10 +696,7 @@ export function defineAxolabsPattern() {
668
696
  }
669
697
  });
670
698
 
671
- const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!), () => {
672
- ssOutputExample.value = translateSequence(ssInputExample.value, ssBases, ssPtoLinkages,
673
- ssFiveModification, ssThreeModification, firstSsPto.value!);
674
- });
699
+ const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!));
675
700
  const ssOutputExample = ui.textInput(' ', translateSequence(
676
701
  ssInputExample.value, ssBases, ssPtoLinkages, ssThreeModification, ssFiveModification, firstSsPto.value!));
677
702
  (ssInputExample.input as HTMLElement).style.resize = 'none';
@@ -689,10 +714,7 @@ export function defineAxolabsPattern() {
689
714
  ], 'ui-input-options'),
690
715
  );
691
716
 
692
- const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!), () => {
693
- asOutputExample.value = translateSequence(
694
- asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!);
695
- });
717
+ const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!));
696
718
  const asOutputExample = ui.textInput(' ', translateSequence(
697
719
  asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!));
698
720
  (asInputExample.input as HTMLElement).style.resize = 'none';
@@ -736,11 +758,14 @@ export function defineAxolabsPattern() {
736
758
  ]),
737
759
  ], 'ui-form');
738
760
 
761
+ const downloadButton = ui.button('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value,
762
+ {backgroundColor: 'white'}));
763
+
739
764
  const mainSection = ui.panel([
740
765
  ui.block([
741
766
  svgDiv,
742
767
  ], {style: {overflowX: 'scroll'}}),
743
- ui.button('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value)),
768
+ downloadButton,
744
769
  isEnumerateModificationsDiv,
745
770
  ui.div([
746
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
+ }