@datagrok/sequence-translator 0.0.5 → 0.0.9

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/src/package.ts CHANGED
@@ -5,216 +5,22 @@ import * as DG from 'datagrok-api/dg';
5
5
  import * as OCL from 'openchemlib/full.js';
6
6
  import $ from 'cash-dom';
7
7
  import {defineAxolabsPattern} from './defineAxolabsPattern';
8
- import {saveSenseAntiSense} from './save-sense-antisense';
9
- import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS} from './map';
8
+ import {saveSenseAntiSense} from './structures-works/save-sense-antisense';
9
+ import {sequenceToSmiles, sequenceToMolV3000} from './structures-works/from-monomers';
10
+ import {convertSequence, undefinedInputSequence} from './structures-works/sequence-codes-tools';
11
+ import {map, COL_NAMES, MODIFICATIONS} from './structures-works/map';
12
+ import {SALTS_CSV} from './salts';
13
+ import {USERS_CSV} from './users';
14
+ import {ICDS} from './ICDs';
15
+ import {SOURCES} from './sources';
16
+ import {IDPS} from './IDPs';
10
17
 
11
18
  export const _package = new DG.Package();
12
19
 
13
20
  const defaultInput = 'AGGTCCTCTTGACTTAGGCC';
14
- const undefinedInputSequence = 'Type of input sequence is undefined';
15
- const noTranslationTableAvailable = 'No translation table available';
16
21
  const sequenceWasCopied = 'Copied';
17
22
  const tooltipSequence = 'Copy sequence';
18
23
 
19
- function getAllCodesOfSynthesizer(synthesizer: string): string[] {
20
- let codes: string[] = [];
21
- for (const technology of Object.keys(map[synthesizer]))
22
- codes = codes.concat(Object.keys(map[synthesizer][technology]));
23
- return codes.concat(Object.keys(MODIFICATIONS));
24
- }
25
-
26
- function getListOfPossibleSynthesizersByFirstMatchedCode(sequence: string): string[] {
27
- const synthesizers: string[] = [];
28
- Object.keys(map).forEach((synthesizer: string) => {
29
- const codes = getAllCodesOfSynthesizer(synthesizer);
30
- //TODO: get first non-dropdown code when there are two modifications
31
- let start = 0;
32
- for (let i = 0; i < sequence.length; i++) {
33
- if (sequence[i] == ')') {
34
- start = i + 1;
35
- break;
36
- }
37
- }
38
- if (codes.some((s: string) => s == sequence.slice(start, start + s.length)))
39
- synthesizers.push(synthesizer);
40
- });
41
- return synthesizers;
42
- }
43
-
44
- function getListOfPossibleTechnologiesByFirstMatchedCode(sequence: string, synthesizer: string): string[] {
45
- const technologies: string[] = [];
46
- Object.keys(map[synthesizer]).forEach((technology: string) => {
47
- const codes = Object.keys(map[synthesizer][technology]).concat(Object.keys(MODIFICATIONS));
48
- if (codes.some((s) => s == sequence.slice(0, s.length)))
49
- technologies.push(technology);
50
- });
51
- return technologies;
52
- }
53
-
54
- export function isValidSequence(sequence: string): {
55
- indexOfFirstNotValidCharacter: number,
56
- expectedSynthesizer: string | null,
57
- expectedTechnology: string | null
58
- } {
59
- const possibleSynthesizers = getListOfPossibleSynthesizersByFirstMatchedCode(sequence);
60
- if (possibleSynthesizers.length == 0)
61
- return {indexOfFirstNotValidCharacter: 0, expectedSynthesizer: null, expectedTechnology: null};
62
-
63
- let outputIndices = Array(possibleSynthesizers.length).fill(0);
64
-
65
- const firstUniqueCharacters = ['r', 'd'];
66
- const nucleotides = ['A', 'U', 'T', 'C', 'G'];
67
-
68
- possibleSynthesizers.forEach((synthesizer, synthesizerIndex) => {
69
- const codes = getAllCodesOfSynthesizer(synthesizer);
70
- while (outputIndices[synthesizerIndex] < sequence.length) {
71
- const matchedCode = codes
72
- .find((c) => c == sequence.slice(outputIndices[synthesizerIndex], outputIndices[synthesizerIndex] + c.length));
73
-
74
- if (matchedCode == null)
75
- break;
76
-
77
- if ( // for mistake pattern 'rAA'
78
- outputIndices[synthesizerIndex] > 1 &&
79
- nucleotides.includes(sequence[outputIndices[synthesizerIndex]]) &&
80
- firstUniqueCharacters.includes(sequence[outputIndices[synthesizerIndex] - 2])
81
- ) break;
82
-
83
- if ( // for mistake pattern 'ArA'
84
- firstUniqueCharacters.includes(sequence[outputIndices[synthesizerIndex] + 1]) &&
85
- nucleotides.includes(sequence[outputIndices[synthesizerIndex]])
86
- ) {
87
- outputIndices[synthesizerIndex]++;
88
- break;
89
- }
90
-
91
- outputIndices[synthesizerIndex] += matchedCode.length;
92
- }
93
- });
94
-
95
- const indexOfExpectedSythesizer = Math.max.apply(Math, outputIndices);
96
- const indexOfFirstNotValidCharacter = (indexOfExpectedSythesizer == sequence.length) ? -1 : indexOfExpectedSythesizer;
97
- const expectedSynthesizer = possibleSynthesizers[outputIndices.indexOf(indexOfExpectedSythesizer)];
98
- if (indexOfFirstNotValidCharacter != -1)
99
- return {
100
- indexOfFirstNotValidCharacter: indexOfFirstNotValidCharacter,
101
- expectedSynthesizer: expectedSynthesizer,
102
- expectedTechnology: null
103
- };
104
-
105
- let possibleTechnologies = getListOfPossibleTechnologiesByFirstMatchedCode(sequence, expectedSynthesizer);
106
- if (possibleTechnologies.length == 0)
107
- return { indexOfFirstNotValidCharacter: 0, expectedSynthesizer: null, expectedTechnology: null };
108
-
109
- outputIndices = Array(possibleTechnologies.length).fill(0);
110
-
111
- possibleTechnologies.forEach((technology: string, technologyIndex: number) => {
112
- let codes = Object.keys(map[expectedSynthesizer][technology]);
113
- while (outputIndices[technologyIndex] < sequence.length) {
114
-
115
- let matchedCode = codes
116
- .find((c) => c == sequence.slice(outputIndices[technologyIndex], outputIndices[technologyIndex] + c.length));
117
-
118
- if (matchedCode == null)
119
- break;
120
-
121
- if ( // for mistake pattern 'rAA'
122
- outputIndices[technologyIndex] > 1 &&
123
- nucleotides.includes(sequence[outputIndices[technologyIndex]]) &&
124
- firstUniqueCharacters.includes(sequence[outputIndices[technologyIndex] - 2])
125
- ) break;
126
-
127
- if ( // for mistake pattern 'ArA'
128
- firstUniqueCharacters.includes(sequence[outputIndices[technologyIndex] + 1]) &&
129
- nucleotides.includes(sequence[outputIndices[technologyIndex]])
130
- ) {
131
- outputIndices[technologyIndex]++;
132
- break;
133
- }
134
-
135
- outputIndices[technologyIndex] += matchedCode.length;
136
- }
137
- });
138
-
139
- const indexOfExpectedTechnology = Math.max.apply(Math, outputIndices);
140
- const expectedTechnology = possibleTechnologies[outputIndices.indexOf(indexOfExpectedTechnology)];
141
-
142
- return {
143
- indexOfFirstNotValidCharacter: indexOfFirstNotValidCharacter,
144
- expectedSynthesizer: expectedSynthesizer,
145
- expectedTechnology: expectedTechnology
146
- };
147
- }
148
-
149
- function sortByStringLengthInDescendingOrder(array: string[]): string[] {
150
- return array.sort(function(a: string, b: string) { return b.length - a.length; });
151
- }
152
-
153
- function getObjectWithCodesAndSmiles(sequence: string) {
154
- const obj: { [code: string]: string } = {};
155
- for (const synthesizer of Object.keys(map))
156
- for (const technology of Object.keys(map[synthesizer]))
157
- for (let code of Object.keys(map[synthesizer][technology]))
158
- obj[code] = map[synthesizer][technology][code].SMILES;
159
- // TODO: create object based from synthesizer type to avoid key(codes) duplicates
160
- const output = isValidSequence(sequence);
161
- if (output.expectedSynthesizer == SYNTHESIZERS.MERMADE_12)
162
- obj['g'] = map[SYNTHESIZERS.MERMADE_12][TECHNOLOGIES.SI_RNA]['g'].SMILES;
163
- else if (output.expectedSynthesizer == SYNTHESIZERS.AXOLABS)
164
- obj['g'] = map[SYNTHESIZERS.AXOLABS][TECHNOLOGIES.SI_RNA]['g'].SMILES;
165
- return obj;
166
- }
167
-
168
- export function sequenceToSmiles(sequence: string, inverted: boolean = false): string {
169
- const obj = getObjectWithCodesAndSmiles(sequence);
170
- let codes = sortByStringLengthInDescendingOrder(Object.keys(obj));
171
- let i = 0;
172
- let smiles = '';
173
- const codesList = [];
174
- const links = ['s', 'ps', '*'];
175
- const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
176
- const dropdowns = Object.keys(MODIFICATIONS);
177
- codes = codes.concat(dropdowns);
178
- while (i < sequence.length) {
179
- const code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
180
- i += code.length;
181
- inverted ? codesList.unshift(code) : codesList.push(code);
182
- }
183
- for (let i = 0; i < codesList.length; i++) {
184
- if (dropdowns.includes(codesList[i])) {
185
- if (i == codesList.length -1 || (i < codesList.length - 1 && links.includes(codesList[i + 1]))) {
186
- smiles += (i >= codesList.length / 2) ?
187
- MODIFICATIONS[codesList[i]].right:
188
- MODIFICATIONS[codesList[i]].left;
189
- } else if (i < codesList.length - 1) {
190
- smiles += (i >= codesList.length / 2) ?
191
- MODIFICATIONS[codesList[i]].right + stadardPhosphateLinkSmiles:
192
- MODIFICATIONS[codesList[i]].left + stadardPhosphateLinkSmiles;
193
- }
194
- } else {
195
- if (links.includes(codesList[i]) ||
196
- includesStandardLinkAlready.includes(codesList[i]) ||
197
- (i < codesList.length - 1 && links.includes(codesList[i + 1]))
198
- )
199
- smiles += obj[codesList[i]];
200
- else
201
- smiles += obj[codesList[i]] + stadardPhosphateLinkSmiles;
202
- }
203
- }
204
- smiles = smiles.replace(/OO/g, 'O');
205
- return (
206
- (
207
- links.includes(codesList[codesList.length - 1]) &&
208
- codesList.length > 1 &&
209
- !includesStandardLinkAlready.includes(codesList[codesList.length - 2])
210
- ) ||
211
- dropdowns.includes(codesList[codesList.length - 1]) ||
212
- includesStandardLinkAlready.includes(codesList[codesList.length - 1])
213
- ) ?
214
- smiles :
215
- smiles.slice(0, smiles.length - stadardPhosphateLinkSmiles.length + 1);
216
- }
217
-
218
24
  //name: Sequence Translator
219
25
  //tags: app
220
26
  export function sequenceTranslator(): void {
@@ -233,11 +39,11 @@ export function sequenceTranslator(): void {
233
39
  const tableRows = [];
234
40
 
235
41
  for (const key of Object.keys(outputSequenceObj).slice(1)) {
236
- let indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
42
+ const indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
237
43
  JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter :
238
44
  -1;
239
45
  if ('indexOfFirstNotValidCharacter' in outputSequenceObj) {
240
- let indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
46
+ const indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
241
47
  JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter :
242
48
  -1;
243
49
  if (indexOfFirstNotValidCharacter != -1)
@@ -251,54 +57,54 @@ export function sequenceTranslator(): void {
251
57
  ui.divText(sequence.slice(0, indexOfFirstNotValidCharacter), {style: {color: 'grey'}}),
252
58
  ui.tooltip.bind(
253
59
  ui.divText(sequence.slice(indexOfFirstNotValidCharacter), {style: {color: 'red'}}),
254
- 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).expectedSynthesizer + '. See tables with valid codes on the right'
60
+ 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).expectedSynthesizer +
61
+ '. See tables with valid codes on the right',
255
62
  ),
256
63
  ]) : //@ts-ignore
257
- ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key]).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')
64
+ ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
65
+ .then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
258
66
  });
259
67
  }
260
68
 
261
69
  if (errorsExist) {
262
- const expectedSynthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).expectedSynthesizer.slice(0, -6);
263
- asoGapmersGrid.onCellPrepare(function (gc) {
70
+ const expectedSynthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!)
71
+ .expectedSynthesizer.slice(0, -6);
72
+ asoGapmersGrid.onCellPrepare(function(gc) {
264
73
  gc.style.backColor = (gc.gridColumn.name == expectedSynthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
265
74
  });
266
- omeAndFluoroGrid.onCellPrepare(function (gc) {
75
+ omeAndFluoroGrid.onCellPrepare(function(gc) {
267
76
  gc.style.backColor = (gc.gridColumn.name == expectedSynthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
268
77
  });
269
78
  switchInput.enabled = true;
270
79
  } else {
271
- asoGapmersGrid.onCellPrepare(function (gc) {
272
- gc.style.backColor = 0xFFFFFFFF;
273
- });
274
- omeAndFluoroGrid.onCellPrepare(function (gc) {
275
- gc.style.backColor = 0xFFFFFFFF;
276
- });
80
+ asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
81
+ omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
277
82
  }
278
83
 
279
84
  outputTableDiv.append(
280
85
  ui.div([
281
- DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) => [item.key, item.value], ['Code', 'Sequence']).root
282
- ], 'table')
86
+ DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
87
+ [item.key, item.value], ['Code', 'Sequence']).root,
88
+ ], 'table'),
283
89
  );
284
90
  semTypeOfInputSequence.textContent = 'Detected input type: ' + outputSequenceObj.type;
285
91
 
286
92
  if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
287
- let canvas = ui.canvas(300, 170);
288
- canvas.addEventListener("click", () => {
289
- let canv = ui.canvas($(window).width(), $(window).height());
290
- const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
93
+ const canvas = ui.canvas(300, 170);
94
+ canvas.addEventListener('click', () => {
95
+ const canv = ui.canvas($(window).width(), $(window).height());
96
+ const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true);
291
97
  // @ts-ignore
292
- OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromSmiles(smiles), { suppressChiralText: true });
293
- ui.dialog('Molecule')
98
+ OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
99
+ ui.dialog('Molecule: ' + inputSequenceField.value)
294
100
  .add(canv)
295
101
  .showModal(true);
296
102
  });
297
103
  $(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
298
104
  $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
299
- const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
105
+ const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true);
300
106
  // @ts-ignore
301
- OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromSmiles(smiles), { suppressChiralText: true });
107
+ OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
302
108
  moleculeSvgDiv.append(canvas);
303
109
  } else
304
110
  moleculeSvgDiv.innerHTML = '';
@@ -313,69 +119,66 @@ export function sequenceTranslator(): void {
313
119
  const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence));
314
120
 
315
121
  const asoDf = DG.DataFrame.fromObjects([
316
- { Name: "2'MOE-5Me-rU", BioSpring: "5", "Janssen GCRS": "moeT" },
317
- { Name: "2'MOE-rA", BioSpring: "6", "Janssen GCRS": "moeA" },
318
- { Name: "2'MOE-5Me-rC", BioSpring: "7", "Janssen GCRS": "moe5mC" },
319
- { Name: "2'MOE-rG", BioSpring: "8", "Janssen GCRS": "moeG" },
320
- { Name: "5-Methyl-dC", BioSpring: "9", "Janssen GCRS": "5mC" },
321
- { Name: "ps linkage", BioSpring: "*", "Janssen GCRS": "ps" },
322
- { Name: "dA", BioSpring: "A", "Janssen GCRS": "A, dA" },
323
- { Name: "dC", BioSpring: "C", "Janssen GCRS": "C, dC" },
324
- { Name: "dG", BioSpring: "G", "Janssen GCRS": "G, dG" },
325
- { Name: "dT", BioSpring: "T", "Janssen GCRS": "T, dT" },
326
- { Name: "rA", BioSpring: "", "Janssen GCRS": "rA" },
327
- { Name: "rC", BioSpring: "", "Janssen GCRS": "rC" },
328
- { Name: "rG", BioSpring: "", "Janssen GCRS": "rG" },
329
- { Name: "rU", BioSpring: "", "Janssen GCRS": "rU" }
122
+ {'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
123
+ {'Name': '2\'MOE-rA', 'BioSpring': '6', 'Janssen GCRS': 'moeA'},
124
+ {'Name': '2\'MOE-5Me-rC', 'BioSpring': '7', 'Janssen GCRS': 'moe5mC'},
125
+ {'Name': '2\'MOE-rG', 'BioSpring': '8', 'Janssen GCRS': 'moeG'},
126
+ {'Name': '5-Methyl-dC', 'BioSpring': '9', 'Janssen GCRS': '5mC'},
127
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Janssen GCRS': 'ps'},
128
+ {'Name': 'dA', 'BioSpring': 'A', 'Janssen GCRS': 'A, dA'},
129
+ {'Name': 'dC', 'BioSpring': 'C', 'Janssen GCRS': 'C, dC'},
130
+ {'Name': 'dG', 'BioSpring': 'G', 'Janssen GCRS': 'G, dG'},
131
+ {'Name': 'dT', 'BioSpring': 'T', 'Janssen GCRS': 'T, dT'},
132
+ {'Name': 'rA', 'BioSpring': '', 'Janssen GCRS': 'rA'},
133
+ {'Name': 'rC', 'BioSpring': '', 'Janssen GCRS': 'rC'},
134
+ {'Name': 'rG', 'BioSpring': '', 'Janssen GCRS': 'rG'},
135
+ {'Name': 'rU', 'BioSpring': '', 'Janssen GCRS': 'rU'},
330
136
  ])!;
331
- const asoGapmersGrid = DG.Viewer.grid(
332
- asoDf, { showRowHeader: false, showCellTooltip: false }
333
- );
137
+ const asoGapmersGrid = DG.Viewer.grid(asoDf, {showRowHeader: false, showCellTooltip: false});
334
138
 
335
139
  asoDf.onCurrentCellChanged.subscribe((_) => {
336
- navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'))
140
+ navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'));
337
141
  });
338
142
 
339
- let omeAndFluoroGrid = DG.Viewer.grid(
143
+ const omeAndFluoroGrid = DG.Viewer.grid(
340
144
  DG.DataFrame.fromObjects([
341
- { Name: "2'-fluoro-U", BioSpring: "1", Axolabs: "Uf", "Janssen GCRS": "fU" },
342
- { Name: "2'-fluoro-A", BioSpring: "2", Axolabs: "Af", "Janssen GCRS": "fA" },
343
- { Name: "2'-fluoro-C", BioSpring: "3", Axolabs: "Cf", "Janssen GCRS": "fC" },
344
- { Name: "2'-fluoro-G", BioSpring: "4", Axolabs: "Gf", "Janssen GCRS": "fG" },
345
- { Name: "2'OMe-rU", BioSpring: "5", Axolabs: "u", "Janssen GCRS": "mU" },
346
- { Name: "2'OMe-rA", BioSpring: "6", Axolabs: "a", "Janssen GCRS": "mA" },
347
- { Name: "2'OMe-rC", BioSpring: "7", Axolabs: "c", "Janssen GCRS": "mC" },
348
- { Name: "2'OMe-rG", BioSpring: "8", Axolabs: "g", "Janssen GCRS": "mG" },
349
- { Name: "ps linkage", BioSpring: "*", Axolabs: "s", "Janssen GCRS": "ps" }
350
- ])!, { showRowHeader: false, showCellTooltip: false }
145
+ {'Name': '2\'-fluoro-U', 'BioSpring': '1', 'Axolabs': 'Uf', 'Janssen GCRS': 'fU'},
146
+ {'Name': '2\'-fluoro-A', 'BioSpring': '2', 'Axolabs': 'Af', 'Janssen GCRS': 'fA'},
147
+ {'Name': '2\'-fluoro-C', 'BioSpring': '3', 'Axolabs': 'Cf', 'Janssen GCRS': 'fC'},
148
+ {'Name': '2\'-fluoro-G', 'BioSpring': '4', 'Axolabs': 'Gf', 'Janssen GCRS': 'fG'},
149
+ {'Name': '2\'OMe-rU', 'BioSpring': '5', 'Axolabs': 'u', 'Janssen GCRS': 'mU'},
150
+ {'Name': '2\'OMe-rA', 'BioSpring': '6', 'Axolabs': 'a', 'Janssen GCRS': 'mA'},
151
+ {'Name': '2\'OMe-rC', 'BioSpring': '7', 'Axolabs': 'c', 'Janssen GCRS': 'mC'},
152
+ {'Name': '2\'OMe-rG', 'BioSpring': '8', 'Axolabs': 'g', 'Janssen GCRS': 'mG'},
153
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Axolabs': 's', 'Janssen GCRS': 'ps'},
154
+ ])!, {showRowHeader: false, showCellTooltip: false},
351
155
  );
352
156
 
353
157
  const overhangModificationsGrid = DG.Viewer.grid(
354
158
  DG.DataFrame.fromObjects([
355
- { Name: "(invabasic)" },
356
- { Name: "(GalNAc-2-JNJ)" }
357
- ])!, { showRowHeader: false, showCellTooltip: false }
159
+ {'Name': '(invabasic)'},
160
+ {'Name': '(GalNAc-2-JNJ)'},
161
+ ])!, {showRowHeader: false, showCellTooltip: false},
358
162
  );
359
163
  updateTableAndMolecule(defaultInput);
360
164
 
361
165
  const appMainDescription = ui.info([
362
- ui.divText('How to convert one sequence:',{style:{'font-weight':'bolder'}}),
363
- ui.divText('Paste sequence into the text field below'),
364
- ui.divText('\n How to convert many sequences:',{style:{'font-weight':'bolder'}}),
365
- ui.divText('1. Drag & drop an Excel or CSV file with sequences into Datagrok. The platform will automatically detect columns with sequences'),
366
- ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
367
- ui.divText('This will add the result column to the right of the table'),
368
- ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.'
369
- );
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.');
370
173
 
371
174
  const codesTablesDiv = ui.splitV([
372
- ui.box(ui.h2('ASO Gapmers'), { style: {maxHeight: '40px'} }),
175
+ ui.box(ui.h2('ASO Gapmers'), {style: {maxHeight: '40px'}}),
373
176
  asoGapmersGrid.root,
374
- ui.box(ui.h2("2'-OMe and 2'-F siRNA"), { style: {maxHeight: '40px'} }),
177
+ ui.box(ui.h2('2\'-OMe and 2\'-F siRNA'), {style: {maxHeight: '40px'}}),
375
178
  omeAndFluoroGrid.root,
376
- ui.box(ui.h2('Overhang modifications'), { style: {maxHeight: '40px'} }),
377
- overhangModificationsGrid.root
378
- ], { style: { maxWidth: '350px' } });
179
+ ui.box(ui.h2('Overhang modifications'), {style: {maxHeight: '40px'}}),
180
+ overhangModificationsGrid.root,
181
+ ], {style: {maxWidth: '350px'}});
379
182
 
380
183
  const tabControl = ui.tabControl({
381
184
  'MAIN': ui.box(
@@ -404,18 +207,17 @@ export function sequenceTranslator(): void {
404
207
  'SDF': saveSenseAntiSense(),
405
208
  });
406
209
 
407
- let v = grok.shell.newView('Sequence Translator', [tabControl]);
210
+ const v = grok.shell.newView('Sequence Translator', [tabControl]);
408
211
  v.box = true;
409
212
 
410
213
  const switchInput = ui.switchInput('Codes', true, (v: boolean) => (v) ?
411
214
  $(codesTablesDiv).show() :
412
- $(codesTablesDiv).hide()
215
+ $(codesTablesDiv).hide(),
413
216
  );
414
217
 
415
218
  const topPanel = [
416
219
  ui.iconFA('download', () => {
417
- const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
418
- const result = `${OCL.Molecule.fromSmiles(smiles).toMolfile()}\n`;
220
+ const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''));
419
221
  const element = document.createElement('a');
420
222
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
421
223
  element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
@@ -433,464 +235,153 @@ export function sequenceTranslator(): void {
433
235
  v.setRibbonPanels([topPanel]);
434
236
 
435
237
  $('.sequence')
436
- .children().css('padding','5px 0');
238
+ .children().css('padding', '5px 0');
437
239
  $('.sequenceInput .input-base')
438
- .css('margin','0');
240
+ .css('margin', '0');
439
241
  $('.sequenceInput textarea')
440
- .css('resize','none')
441
- .css('min-height','50px')
442
- .css('width','100%')
242
+ .css('resize', 'none')
243
+ .css('min-height', '50px')
244
+ .css('width', '100%')
443
245
  .attr('spellcheck', 'false');
444
246
  $('.sequenceInput select')
445
- .css('width','100%');
446
- }
447
-
448
- function convertSequence(text: string) {
449
- text = text.replace(/\s/g, '');
450
- let seq = text;
451
- let output = isValidSequence(seq);
452
- if (output.indexOfFirstNotValidCharacter != -1)
453
- return {
454
- // type: '',
455
- indexOfFirstNotValidCharacter: JSON.stringify(output),
456
- Error: undefinedInputSequence
457
- };
458
- if (output.expectedSynthesizer == SYNTHESIZERS.RAW_NUCLEOTIDES && output.expectedTechnology == TECHNOLOGIES.DNA)
459
- return {
460
- type: SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.DNA,
461
- Nucleotides: seq,
462
- BioSpring: asoGapmersNucleotidesToBioSpring(seq),
463
- GCRS: asoGapmersNucleotidesToGcrs(seq)
464
- };
465
- if (output.expectedSynthesizer == SYNTHESIZERS.BIOSPRING && output.expectedTechnology == TECHNOLOGIES.ASO_GAPMERS)
466
- return {
467
- type: SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.ASO_GAPMERS,
468
- Nucleotides: asoGapmersBioSpringToNucleotides(seq),
469
- BioSpring: seq,
470
- GCRS: asoGapmersBioSpringToGcrs(seq)
471
- };
472
- if (output.expectedSynthesizer == SYNTHESIZERS.GCRS && output.expectedTechnology == TECHNOLOGIES.ASO_GAPMERS)
473
- return {
474
- type: SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.ASO_GAPMERS,
475
- Nucleotides: asoGapmersGcrsToNucleotides(seq),
476
- BioSpring: asoGapmersGcrsToBioSpring(seq),
477
- Mermade12: gcrsToMermade12(seq),
478
- GCRS: seq
479
- };
480
- if (output.expectedSynthesizer == SYNTHESIZERS.RAW_NUCLEOTIDES && output.expectedTechnology == TECHNOLOGIES.RNA)
481
- return {
482
- type: SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.RNA,
483
- Nucleotides: seq,
484
- BioSpring: siRnaNucleotideToBioSpringSenseStrand(seq),
485
- Axolabs: siRnaNucleotideToAxolabsSenseStrand(seq),
486
- GCRS: siRnaNucleotidesToGcrs(seq)
487
- };
488
- if (output.expectedSynthesizer == SYNTHESIZERS.BIOSPRING && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
489
- return {
490
- type: SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.SI_RNA,
491
- Nucleotides: siRnaBioSpringToNucleotides(seq),
492
- BioSpring: seq,
493
- Axolabs: siRnaBioSpringToAxolabs(seq),
494
- GCRS: siRnaBioSpringToGcrs(seq)
495
- };
496
- if (output.expectedSynthesizer == SYNTHESIZERS.AXOLABS && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
497
- return {
498
- type: SYNTHESIZERS.AXOLABS + ' ' + TECHNOLOGIES.SI_RNA,
499
- Nucleotides: siRnaAxolabsToNucleotides(seq),
500
- BioSpring: siRnaAxolabsToBioSpring(seq),
501
- Axolabs: seq,
502
- GCRS: siRnaAxolabsToGcrs(seq)
503
- };
504
- if (output.expectedSynthesizer == SYNTHESIZERS.GCRS && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
505
- return {
506
- type: SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.SI_RNA,
507
- Nucleotides: siRnaGcrsToNucleotides(seq),
508
- BioSpring: siRnaGcrsToBioSpring(seq),
509
- Axolabs: siRnaGcrsToAxolabs(seq),
510
- MM12: gcrsToMermade12(seq),
511
- GCRS: seq
512
- };
513
- if (output.expectedSynthesizer == SYNTHESIZERS.GCRS)
514
- return {
515
- type: SYNTHESIZERS.GCRS,
516
- Nucleotides: gcrsToNucleotides(seq),
517
- GCRS: seq,
518
- Mermade12: gcrsToMermade12(seq)
519
- }
520
- if (output.expectedSynthesizer == SYNTHESIZERS.MERMADE_12)
521
- return {
522
- type: SYNTHESIZERS.MERMADE_12,
523
- Nucleotides: noTranslationTableAvailable,
524
- GCRS: noTranslationTableAvailable,
525
- Mermade12: seq
526
- };
527
- return {
528
- type: undefinedInputSequence,
529
- Nucleotides: undefinedInputSequence
530
- };
531
- }
532
-
533
- //name: asoGapmersNucleotidesToBioSpring
534
- //input: string nucleotides {semType: DNA nucleotides}
535
- //output: string result {semType: BioSpring / Gapmers}
536
- export function asoGapmersNucleotidesToBioSpring(nucleotides: string): string {
537
- let count: number = -1;
538
- const objForEdges: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': '5*', 'A': '6*', 'C': '7*', 'G': '8*'};
539
- const objForCenter: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': 'T*', 'A': 'A*', 'C': '9*', 'G': 'G*'};
540
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|T|C|G)/g, function (x: string) {
541
- count++;
542
- return (count > 4 && count < 15) ? objForCenter[x] : objForEdges[x];
543
- }).slice(0, (nucleotides.endsWith('(invabasic)') || nucleotides.endsWith('(GalNAc-2-JNJ)')) ? nucleotides.length : 2 * count + 1);
544
- }
545
-
546
- //name: asoGapmersNucleotidesToGcrs
547
- //input: string nucleotides {semType: DNA nucleotides}
548
- //output: string result {semType: GCRS / Gapmers}
549
- export function asoGapmersNucleotidesToGcrs(nucleotides: string): string {
550
- let count: number = -1;
551
- const objForEdges: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': 'moeUnps', 'A': 'moeAnps', 'C': 'moe5mCnps', 'G': 'moeGnps'};
552
- const objForCenter: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'C': '5mCps', 'A': 'Aps', 'T': 'Tps', 'G': 'Gps'};
553
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|T|C|G)/g, function (x: string) {
554
- count++;
555
- if (count < 5) return (count == 4) ? objForEdges[x].slice(0, -3) + 'ps' : objForEdges[x];
556
- if (count < 15) return (count == 14) ? objForCenter[x].slice(0, -2) + 'nps' : objForCenter[x];
557
- return objForEdges[x];
558
- }).slice(0, (nucleotides.endsWith('(invabasic)') || nucleotides.endsWith('(GalNAc-2-JNJ)')) ? nucleotides.length : -3);
559
- }
560
-
561
- //name: asoGapmersBioSpringToNucleotides
562
- //input: string nucleotides {semType: BioSpring / Gapmers}
563
- //output: string result {semType: DNA nucleotides}
564
- export function asoGapmersBioSpringToNucleotides(nucleotides: string): string {
565
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', '*': '', '5': 'T', '6': 'A', '7': 'C', '8': 'G', '9': 'C'};
566
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|\*|5|6|7|8|9)/g, function (x: string) {return obj[x];});
567
- }
568
-
569
- //name: asoGapmersBioSpringToGcrs
570
- //input: string nucleotides {semType: BioSpring / Gapmers}
571
- //output: string result {semType: GCRS / Gapmers}
572
- export function asoGapmersBioSpringToGcrs(nucleotides: string): string {
573
- let count: number = -1;
574
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
575
- '5*': 'moeUnps', '6*': 'moeAnps', '7*': 'moe5mCnps', '8*': 'moeGnps', '9*': '5mCps', 'A*': 'Aps', 'T*': 'Tps',
576
- 'G*': 'Gps', 'C*': 'Cps', '5': 'moeU', '6': 'moeA', '7': 'moe5mC', '8': 'moeG'
577
- };
578
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|5\*|6\*|7\*|8\*|9\*|A\*|T\*|G\*|C\*|5|6|7|8)/g, function (x: string) {
579
- count++;
580
- return (count == 4) ? obj[x].slice(0, -3) + 'ps' : (count == 14) ? obj[x].slice(0, -2) + 'nps' : obj[x];
581
- });
582
- }
583
-
584
- //name: asoGapmersGcrsToBioSpring
585
- //input: string nucleotides {semType: GCRS / Gapmers}
586
- //output: string result {semType: BioSpring / Gapmers}
587
- export function asoGapmersGcrsToBioSpring(nucleotides: string): string {
588
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
589
- 'moeT': '5', 'moeA': '6', 'moe5mC': '7', 'moeG': '8', 'moeU': '5', '5mC': '9', 'nps': '*', 'ps': '*', 'U': 'T'
590
- };
591
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|moeT|moeA|moe5mC|moeG|moeU|5mC|nps|ps|U)/g, function (x: string) {return obj[x];});
592
- }
593
-
594
- //name: asoGapmersGcrsToNucleotides
595
- //input: string nucleotides {semType: GCRS / Gapmers}
596
- //output: string result {semType: DNA nucleotides}
597
- export function asoGapmersGcrsToNucleotides(nucleotides: string) {
598
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'moe': '', '5m': '', 'n': '', 'ps': '', 'U': 'T'};
599
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|moe|5m|n|ps|U)/g, function (x: string) {return obj[x];});
600
- }
601
-
602
- //name: siRnaBioSpringToNucleotides
603
- //input: string nucleotides {semType: BioSpring / siRNA}
604
- //output: string result {semType: RNA nucleotides}
605
- export function siRnaBioSpringToNucleotides(nucleotides: string) {
606
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', '1': 'U', '2': 'A', '3': 'C', '4': 'G', '5': 'U', '6': 'A', '7': 'C', '8': 'G', '*': ''};
607
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
608
- }
609
-
610
- //name: siRnaBioSpringToAxolabs
611
- //input: string nucleotides {semType: BioSpring / siRNA}
612
- //output: string result {semType: Axolabs / siRNA}
613
- export function siRnaBioSpringToAxolabs(nucleotides: string) {
614
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', '1': 'Uf', '2': 'Af', '3': 'Cf', '4': 'Gf', '5': 'u', '6': 'a', '7': 'c', '8': 'g', '*': 's'};
615
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
616
- }
617
-
618
- //name: siRnaBioSpringToGcrs
619
- //input: string nucleotides {semType: BioSpring / siRNA}
620
- //output: string result {semType: GCRS}
621
- export function siRnaBioSpringToGcrs(nucleotides: string) {
622
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', '1': 'fU', '2': 'fA', '3': 'fC', '4': 'fG', '5': 'mU', '6': 'mA', '7': 'mC', '8': 'mG', '*': 'ps'};
623
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
624
- }
625
-
626
- //name: siRnaAxolabsToGcrs
627
- //input: string nucleotides {semType: Axolabs / siRNA}
628
- //output: string result {semType: GCRS}
629
- export function siRnaAxolabsToGcrs(nucleotides: string) {
630
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
631
- 'Uf': 'fU', 'Af': 'fA', 'Cf': 'fC', 'Gf': 'fG', 'u': 'mU', 'a': 'mA', 'c': 'mC', 'g': 'mG', 's': 'ps'
632
- };
633
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
634
- }
635
-
636
- //name: siRnaAxolabsToBioSpring
637
- //input: string nucleotides {semType: Axolabs / siRNA}
638
- //output: string result {semType: BioSpring / siRNA}
639
- export function siRnaAxolabsToBioSpring(nucleotides: string) {
640
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
641
- 'Uf': '1', 'Af': '2', 'Cf': '3', 'Gf': '4', 'u': '5', 'a': '6', 'c': '7', 'g': '8', 's': '*'
642
- };
643
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
644
- }
645
-
646
- //name: siRnaAxolabsToNucleotides
647
- //input: string nucleotides {semType: Axolabs / siRNA}
648
- //output: string result {semType: RNA nucleotides}
649
- export function siRnaAxolabsToNucleotides(nucleotides: string) {
650
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
651
- 'Uf': 'U', 'Af': 'A', 'Cf': 'C', 'Gf': 'G', 'u': 'U', 'a': 'A', 'c': 'C', 'g': 'G', 's': ''
652
- };
653
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
654
- }
655
-
656
- //name: siRnaGcrsToNucleotides
657
- //input: string nucleotides {semType: GCRS}
658
- //output: string result {semType: RNA nucleotides}
659
- export function siRnaGcrsToNucleotides(nucleotides: string) {
660
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
661
- 'fU': 'U', 'fA': 'A', 'fC': 'C', 'fG': 'G', 'mU': 'U', 'mA': 'A', 'mC': 'C', 'mG': 'G', 'ps': ''
662
- };
663
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
664
- }
665
-
666
- //name: siRnaGcrsToBioSpring
667
- //input: string nucleotides {semType: GCRS}
668
- //output: string result {semType: BioSpring / siRNA}
669
- export function siRnaGcrsToBioSpring(nucleotides: string) {
670
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
671
- 'fU': '1', 'fA': '2', 'fC': '3', 'fG': '4', 'mU': '5', 'mA': '6', 'mC': '7', 'mG': '8', 'ps': '*'
672
- };
673
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
674
- }
675
-
676
- //name: siRnaGcrsToAxolabs
677
- //input: string nucleotides {semType: GCRS}
678
- //output: string result {semType: Axolabs / siRNA}
679
- export function siRnaGcrsToAxolabs(nucleotides: string) {
680
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
681
- 'fU': 'Uf', 'fA': 'Af', 'fC': 'Cf', 'fG': 'Gf', 'mU': 'u', 'mA': 'a', 'mC': 'c', 'mG': 'g', 'ps': 's'
682
- };
683
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
684
- }
685
-
686
- //name: siRnaNucleotideToBioSpringSenseStrand
687
- //input: string nucleotides {semType: RNA nucleotides}
688
- //output: string result {semType: BioSpring / siRNA}
689
- export function siRnaNucleotideToBioSpringSenseStrand(nucleotides: string) {
690
- let count: number = -1;
691
- const objForLeftEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': '6*', 'U': '5*', 'G': '8*', 'C': '7*'};
692
- const objForRightEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': '*6', 'U': '*5', 'G': '*8', 'C': '*7'};
693
- const objForOddIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': '6', 'U': '5', 'G': '8', 'C': '7'};
694
- const objForEvenIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': '2', 'U': '1', 'G': '4', 'C': '3'};
695
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
696
- count++;
697
- if (count < 2) return objForLeftEdge[x];
698
- if (count > nucleotides.length - 3) return objForRightEdge[x];
699
- return (count % 2 == 0) ? objForEvenIndices[x] : objForOddIndices[x];
700
- });
701
- }
702
-
703
- //name: siRnaNucleotidesToGcrs
704
- //input: string nucleotides {semType: RNA nucleotides}
705
- //output: string result {semType: GCRS}
706
- export function siRnaNucleotidesToGcrs(nucleotides: string) {
707
- let count: number = -1;
708
- const objForLeftEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'mAps', 'U': 'mUps', 'G': 'mGps', 'C': 'mCps'};
709
- const objForRightEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'psmA', 'U': 'psmU', 'G': 'psmG', 'C': 'psmC'};
710
- const objForEvenIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'fA', 'U': 'fU', 'G': 'fG', 'C': 'fC'};
711
- const objForOddIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'mA', 'U': 'mU', 'G': 'mG', 'C': 'mC'};
712
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
713
- count++;
714
- if (count < 2) return objForLeftEdge[x];
715
- if (count > nucleotides.length - 3) return objForRightEdge[x];
716
- return (count % 2 == 0) ? objForEvenIndices[x] : objForOddIndices[x];
717
- });
718
- }
719
-
720
- //name: siRnaNucleotideToAxolabsSenseStrand
721
- //input: string nucleotides {semType: RNA nucleotides}
722
- //output: string result {semType: Axolabs}
723
- export function siRnaNucleotideToAxolabsSenseStrand(nucleotides: string) {
724
- let count: number = -1;
725
- const objForLeftEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'as', 'U': 'us', 'G': 'gs', 'C': 'cs'};
726
- const objForSomeIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'Af', 'U': 'Uf', 'G': 'Gf', 'C': 'Cf'};
727
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'a', 'U': 'u', 'G': 'g', 'C': 'c'};
728
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
729
- count++;
730
- if (count < 2) return objForLeftEdge[x];
731
- if (count == 6 || (count > 7 && count < 11)) return objForSomeIndices[x]
732
- if (count == nucleotides.length - 1) return 'a';
733
- return obj[x];
734
- });
735
- }
736
-
737
- //name: siRnaNucleotideToAxolabsAntisenseStrand
738
- //input: string nucleotides {semType: RNA nucleotides}
739
- //output: string result {semType: Axolabs}
740
- export function siRnaNucleotideToAxolabsAntisenseStrand(nucleotides: string) {
741
- let count: number = -1;
742
- const objForSmallLinkages: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'as', 'U': 'us', 'G': 'gs', 'C': 'cs'};
743
- const objForBigLinkages: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'Afs', 'U': 'Ufs', 'G': 'Gfs', 'C': 'Cfs'};
744
- const objForSomeIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'Af', 'U': 'Uf', 'G': 'Gf', 'C': 'Cf'};
745
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'a', 'U': 'u', 'G': 'g', 'C': 'c'};
746
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
747
- count++;
748
- if (count > 19 && count < 22) return objForSmallLinkages[x];
749
- if (count == 0) return 'us';
750
- if (count == 1) return objForBigLinkages[x];
751
- return (count == 5 || count == 7 || count == 8 || count == 13 || count == 15) ? objForSomeIndices[x] : obj[x];
752
- });
753
- }
754
-
755
- //name: gcrsToNucleotides
756
- //input: string nucleotides {semType: GCRS}
757
- //output: string result {semType: RNA nucleotides}
758
- export function gcrsToNucleotides(nucleotides: string) {
759
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
760
- 'mAps': 'A', 'mUps': 'U', 'mGps': 'G', 'mCps': 'C', 'fAps': 'A', 'fUps': 'U', 'fGps': 'G', 'fCps': 'C',
761
- 'fU': 'U', 'fA': 'A', 'fC': 'C', 'fG': 'G', 'mU': 'U', 'mA': 'A', 'mC': 'C', 'mG': 'G'
762
- };
763
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|mAps|mUps|mGps|mCps|fAps|fUps|fGps|fCps|fU|fA|fC|fG|mU|mA|mC|mG)/g, function (x: string) {return obj[x];});
764
- }
765
-
766
- //name: gcrsToMermade12
767
- //input: string nucleotides {semType: GCRS}
768
- //output: string result {semType: Mermade 12 / siRNA}
769
- export function gcrsToMermade12(nucleotides: string) {
770
- const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
771
- 'mAps': 'e', 'mUps': 'h', 'mGps': 'g', 'mCps': 'f', 'fAps': 'i', 'fUps': 'l', 'fGps': 'k', 'fCps': 'j', 'fU': 'L',
772
- 'fA': 'I', 'fC': 'J', 'fG': 'K', 'mU': 'H', 'mA': 'E', 'mC': 'F', 'mG': 'G'
773
- };
774
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|mAps|mUps|mGps|mCps|fAps|fUps|fGps|fCps|fU|fA|fC|fG|mU|mA|mC|mG)/g, function (x: string) {return obj[x]});
247
+ .css('width', '100%');
775
248
  }
776
249
 
777
250
  async function saveTableAsSdFile(table: DG.DataFrame) {
778
- if (!table.columns.contains('Compound Name'))
779
- grok.shell.warning("File was saved without columns 'Compound Name', 'Compound Components', 'Cpd MW', 'Salt mass', 'Batch MW'!");
780
- let structureColumn = table.columns.byName('Sequence');
251
+ if (!table.columns.contains('Compound Name')) {
252
+ grok.shell.warning(
253
+ 'File saved without columns \'' +
254
+ [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
255
+ COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
256
+ );
257
+ }
258
+ const structureColumn = table.col(COL_NAMES.SEQUENCE)!;
259
+ const typeColumn = table.col(COL_NAMES.TYPE)!;
781
260
  let result = '';
782
261
  for (let i = 0; i < table.rowCount; i++) {
783
- try {
784
- let smiles = sequenceToSmiles(structureColumn.get(i));
785
- //@ts-ignore
786
- let mol = new OCL.Molecule.fromSmiles(smiles);
787
- result += `\n${mol.toMolfile()}\n`;
788
- for (let col of table.columns)
262
+ result += (typeColumn.get(i) == 'SS') ?
263
+ sequenceToMolV3000(structureColumn.get(i), false, true) + '\n' + `> <Sequence>\nSense Strand\n\n` :
264
+ sequenceToMolV3000(structureColumn.get(i), true, true) + '\n' + `> <Sequence>\nAnti Sense\n\n`;
265
+ for (const col of table.columns) {
266
+ if (col.name != COL_NAMES.SEQUENCE)
789
267
  result += `> <${col.name}>\n${col.get(i)}\n\n`;
790
- result += '$$$$';
791
- }
792
- catch (error) {
793
- console.error(error);
794
268
  }
269
+ result += '$$$$\n\n';
795
270
  }
796
- let element = document.createElement('a');
271
+ const element = document.createElement('a');
797
272
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
798
273
  element.setAttribute('download', table.name + '.sdf');
799
274
  element.click();
800
275
  }
801
276
 
802
- function parseNumber(saltName: string) {
803
- let i = saltName.length;
804
- while (saltName.length > -1 && saltName[i] != '(')
805
- i--;
806
- return parseInt(saltName.slice(i + 2));
277
+ const weightsObj: {[code: string]: number} = {};
278
+ for (const synthesizer of Object.keys(map)) {
279
+ for (const technology of Object.keys(map[synthesizer])) {
280
+ for (const code of Object.keys(map[synthesizer][technology]))
281
+ weightsObj[code] = map[synthesizer][technology][code].weight;
282
+ }
807
283
  }
284
+ for (const [key, value] of Object.entries(MODIFICATIONS))
285
+ weightsObj[key] = value.molecularWeight;
808
286
 
809
- //tags: autostart
810
- export function autostartOligoSdFileSubscription() {
811
- grok.events.onViewAdded.subscribe((v: any) => {
812
- if (v.type == 'TableView' && v.dataFrame.columns.contains('Type'))
813
- oligoSdFile(v.dataFrame);
814
- });
287
+ function sortByStringLengthInDescendingOrder(array: string[]): string[] {
288
+ return array.sort(function(a, b) {return b.length - a.length;});
815
289
  }
816
290
 
817
- let weightsObj: {[code: string]: number} = {};
818
- for (let synthesizer of Object.keys(map))
819
- for (let technology of Object.keys(map[synthesizer]))
820
- for (let code of Object.keys(map[synthesizer][technology]))
821
- weightsObj[code] = map[synthesizer][technology][code].weight;
291
+ function stringifyItems(items: string[]): string {
292
+ return '["' + items.join('", "') + '"]';
293
+ }
822
294
 
823
- function molecularWeight(sequence: string): number {
824
- const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj));
825
- let weight = 0, i = 0;
295
+ function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
296
+ const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
297
+ let weight = 0;
298
+ let i = 0;
826
299
  while (i < sequence.length) {
827
- let matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
300
+ const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
828
301
  weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
829
302
  i += matchedCode!.length;
830
303
  }
831
304
  return weight - 61.97;
832
305
  }
833
306
 
834
- export function oligoSdFile(table: DG.DataFrame) {
307
+ //tags: autostart
308
+ export function autostartOligoSdFileSubscription() {
309
+ grok.events.onViewAdded.subscribe((v: any) => {
310
+ if (v.type == 'TableView' && v.dataFrame.columns.contains(COL_NAMES.TYPE))
311
+ oligoSdFile(v.dataFrame);
312
+ });
313
+ }
835
314
 
836
- function addColumns(t: DG.DataFrame) {
837
- if (t.columns.contains('Compound Name'))
315
+ export function oligoSdFile(table: DG.DataFrame) {
316
+ const saltsDf = DG.DataFrame.fromCsv(SALTS_CSV);
317
+ const usersDf = DG.DataFrame.fromCsv(USERS_CSV);
318
+ const sourcesDf = DG.DataFrame.fromCsv(SOURCES);
319
+ const icdsDf = DG.DataFrame.fromCsv(ICDS);
320
+ const idpsDf = DG.DataFrame.fromCsv(IDPS);
321
+
322
+ function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
323
+ if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
838
324
  return grok.shell.error('Columns already exist!');
839
325
 
840
- table.col('Source')?.init('Johnson and Johnson Pharma');
841
- table.col('ICD')?.init('No Contract');
326
+ const sequence = t.col(COL_NAMES.SEQUENCE)!;
327
+ const equivalents = t.col(COL_NAMES.EQUIVALENTS)!;
842
328
 
843
- let sequence = t.col('Sequence')!;
844
- let salt = t.col('Salt')!;
845
- let equivalents = t.col('Equivalents')!;
846
-
847
- t.columns.addNewString('Compound Name').init((i: number) => sequence.get(i));
848
- t.columns.addNewString('Compound Comments').init((i: number) => (i > 0 && i % 2 == 0) ?
329
+ t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => sequence.get(i));
330
+ t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => (i > 0 && i % 2 == 0) ?
849
331
  sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
850
- sequence.getString(i)
332
+ sequence.getString(i),
333
+ );
334
+ t.columns.addNewFloat(COL_NAMES.CPD_MW)
335
+ .init((i: number) => molecularWeight(sequence.get(i), weightsObj));
336
+ const mwCol = t.col(COL_NAMES.CPD_MW)!;
337
+ t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) => {
338
+ const mw = mwCol.get(i);
339
+ return mw * equivalents.get(i);
340
+ });
341
+ t.columns.addNewCalculated(COL_NAMES.BATCH_MW,
342
+ '${' + COL_NAMES.CPD_MW + '} + ${' + COL_NAMES.SALT_MASS + '}', DG.COLUMN_TYPE.FLOAT, false,
851
343
  );
852
- t.columns.addNewFloat('Cpd MW').init((i: number) => ((i + 1) % 3 == 0) ? DG.FLOAT_NULL : molecularWeight(sequence.get(i)));
853
- t.columns.addNewFloat('Salt mass').init((i: number) => parseNumber(salt.get(i)) * equivalents.get(i));
854
- t.columns.addNewCalculated('Batch MW', '${Cpd MW} + ${Salt mass}', DG.COLUMN_TYPE.FLOAT, false);
855
344
 
856
345
  addColumnsPressed = true;
857
346
  return newDf = t;
858
347
  }
859
348
 
860
- const columnsOrder = ["Chemistry", "Number", "Type", "Chemistry Name", "Internal compound ID", "IDP", "Sequence", "Compound Name",
861
- "Compound Comments", "Salt", "Equivalents", "Purity", "Cpd MW", "Salt mass", "Batch MW", "Source", "ICD", "Owner"];
862
349
  let newDf: DG.DataFrame;
863
350
  let addColumnsPressed = false;
864
351
 
865
- let d = ui.div([
352
+ const d = ui.div([
866
353
  ui.icons.edit(() => {
867
354
  d.innerHTML = '';
355
+ if (table.col(COL_NAMES.IDP)!.type != DG.COLUMN_TYPE.STRING)
356
+ table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
868
357
  d.append(
869
- ui.link('Add Columns', async () => {
870
- await addColumns(table);
871
- grok.shell.tableView(table.name).grid.columns.setOrder(columnsOrder);
872
- }, 'Add columns: Compound Name, Compound Components, Cpd MW, Salt mass, Batch MW', ''),
873
- ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table))
358
+ ui.link('Add Columns', () => {
359
+ addColumns(table, saltsDf);
360
+ grok.shell.tableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
361
+ }, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
362
+ COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), ''),
363
+ ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
874
364
  );
875
- let view = grok.shell.getTableView(table.name);
876
- let typeCol = view.grid.col('Type')!;
877
- let saltCol = view.grid.col('Salt')!;
878
- saltCol.cellType = 'html';
879
- typeCol.cellType = 'html';
880
- view.grid.onCellPrepare(function (gc: DG.GridCell) {
881
- if (gc.isTableCell) {
882
- if (gc.gridColumn.name == 'Type')
883
- gc.style.element = ui.choiceInput('', gc.cell.value, ['AS', 'SS', 'Duplex']).root;
884
- else if (gc.gridColumn.name == 'Salt')
885
- gc.style.element = ui.choiceInput('', gc.cell.value, ['Sodium (+1)', 'Sodium (+2)']).root;
886
- }
887
- });
888
365
 
889
- table.onDataChanged.subscribe((_) => {
890
- if (table.currentCol.name == 'IDP' && typeof table.currentCell.value != 'number')
891
- grok.shell.error('Value should be numeric');
366
+ const view = grok.shell.getTableView(table.name)!;
367
+
368
+ view.table!.col(COL_NAMES.TYPE)!.setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
369
+ view.table!.col(COL_NAMES.OWNER)!.setTag(DG.TAGS.CHOICES, stringifyItems(usersDf.columns.byIndex(0).toList()));
370
+ view.table!.col(COL_NAMES.SALT)!.setTag(DG.TAGS.CHOICES, stringifyItems(saltsDf.columns.byIndex(1).toList()));
371
+ view.table!.col(COL_NAMES.SOURCE)!.setTag(DG.TAGS.CHOICES, stringifyItems(sourcesDf.columns.byIndex(0).toList()));
372
+ view.table!.col(COL_NAMES.ICD)!.setTag(DG.TAGS.CHOICES, stringifyItems(icdsDf.columns.byIndex(0).toList()));
373
+ view.table!.col(COL_NAMES.IDP)!.setTag(DG.TAGS.CHOICES, stringifyItems(idpsDf.columns.byIndex(0).toList()));
374
+
375
+ grok.events.onContextMenu.subscribe((args) => {
376
+ if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
377
+ .includes(args.args.context.table.currentCol.name)) {
378
+ args.args.menu.item('Fill Column With Value', () => {
379
+ const v = args.args.context.table.currentCell.value;
380
+ args.args.context.table.currentCell.column.init(v);
381
+ });
382
+ }
892
383
  });
893
- })
384
+ }),
894
385
  ]);
895
386
  grok.shell.v.setRibbonPanels([[d]]);
896
387
  }