@datagrok/sequence-translator 0.0.4 → 0.0.8

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
@@ -3,657 +3,354 @@ import * as grok from 'datagrok-api/grok';
3
3
  import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
  import * as OCL from 'openchemlib/full.js';
6
- import $ from "cash-dom";
7
- import {defineAxolabsPattern} from "./defineAxolabsPattern";
8
- import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS} from "./map";
6
+ import $ from 'cash-dom';
7
+ import {defineAxolabsPattern} from './defineAxolabsPattern';
8
+ import {saveSenseAntiSense} from './structures-works/save-sense-antisense';
9
+ import {sequenceToSmiles} from './structures-works/from-monomers';
10
+ import {convertSequence, undefinedInputSequence} from './structures-works/sequence-codes-tools';
11
+ import {SALTS_CSV} from './salts';
9
12
 
10
- export let _package = new DG.Package();
13
+ export const _package = new DG.Package();
11
14
 
12
- const defaultInput = "AGGTCCTCTTGACTTAGGCC";
13
- const undefinedInputSequence = "Type of input sequence is undefined";
14
- const noTranslationTableAvailable = "No translation table available";
15
+ const defaultInput = 'AGGTCCTCTTGACTTAGGCC';
15
16
  const sequenceWasCopied = 'Copied';
16
17
  const tooltipSequence = 'Copy sequence';
17
18
 
18
- function getAllCodesOfSynthesizer(synthesizer: string) {
19
- let codes: string[] = [];
20
- for (let technology of Object.keys(map[synthesizer]))
21
- codes = codes.concat(Object.keys(map[synthesizer][technology]));
22
- return codes.concat(Object.keys(MODIFICATIONS));
23
- }
24
-
25
- function getListOfPossibleSynthesizersByFirstMatchedCode(sequence: string): string[] {
26
- let synthesizers: string[] = [];
27
- Object.keys(map).forEach((synthesizer: string) => {
28
- const codes = getAllCodesOfSynthesizer(synthesizer);
29
- //TODO: get first non-dropdown code when there are two modifications
30
- let start = 0;
31
- for (let i = 0; i < sequence.length; i++)
32
- if (sequence[i] == ')') {
33
- start = i + 1;
34
- break;
35
- }
36
- if (codes.some((s: string) => s == sequence.slice(start, start + s.length)))
37
- synthesizers.push(synthesizer);
38
- });
39
- return synthesizers;
40
- }
41
-
42
- function getListOfPossibleTechnologiesByFirstMatchedCode(sequence: string, synthesizer: string): string[] {
43
- let technologies: string[] = [];
44
- Object.keys(map[synthesizer]).forEach((technology: string) => {
45
- const codes = Object.keys(map[synthesizer][technology]).concat(Object.keys(MODIFICATIONS));
46
- if (codes.some((s) => s == sequence.slice(0, s.length)))
47
- technologies.push(technology);
48
- });
49
- return technologies;
50
- }
51
-
52
- function isValidSequence(sequence: string) {
53
- let possibleSynthesizers = getListOfPossibleSynthesizersByFirstMatchedCode(sequence);
54
- if (possibleSynthesizers.length == 0)
55
- return { indexOfFirstNotValidCharacter: 0, expectedType: null };
56
-
57
- let outputIndices = Array(possibleSynthesizers.length).fill(0);
58
-
59
- const firstUniqueCharacters = ['r', 'd'], nucleotides = ["A", "U", "T", "C", "G"];
60
-
61
- possibleSynthesizers.forEach((synthesizer, synthesizerIndex) => {
62
- let codes = getAllCodesOfSynthesizer(synthesizer);
63
- while (outputIndices[synthesizerIndex] < sequence.length) {
64
-
65
- let matchedCode = codes
66
- .find((c) => c == sequence.slice(outputIndices[synthesizerIndex], outputIndices[synthesizerIndex] + c.length));
67
-
68
- if (matchedCode == null)
69
- break;
70
-
71
- if ( // for mistake pattern 'rAA'
72
- outputIndices[synthesizerIndex] > 1 &&
73
- nucleotides.includes(sequence[outputIndices[synthesizerIndex]]) &&
74
- firstUniqueCharacters.includes(sequence[outputIndices[synthesizerIndex] - 2])
75
- ) break;
76
-
77
- if ( // for mistake pattern 'ArA'
78
- firstUniqueCharacters.includes(sequence[outputIndices[synthesizerIndex] + 1]) &&
79
- nucleotides.includes(sequence[outputIndices[synthesizerIndex]])
80
- ) {
81
- outputIndices[synthesizerIndex]++;
82
- break;
83
- }
84
-
85
- outputIndices[synthesizerIndex] += matchedCode.length;
86
- }
87
- });
88
-
89
- const indexOfExpectedSythesizer = Math.max.apply(Math, outputIndices);
90
- const indexOfFirstNotValidCharacter = (indexOfExpectedSythesizer == sequence.length) ? -1 : indexOfExpectedSythesizer;
91
- const expectedSynthesizer = possibleSynthesizers[outputIndices.indexOf(indexOfExpectedSythesizer)];
92
- if (indexOfFirstNotValidCharacter != -1)
93
- return {
94
- indexOfFirstNotValidCharacter: indexOfFirstNotValidCharacter,
95
- expectedType: expectedSynthesizer
96
- };
97
-
98
- let possibleTechnologies = getListOfPossibleTechnologiesByFirstMatchedCode(sequence, expectedSynthesizer);
99
- if (possibleTechnologies.length == 0)
100
- return { indexOfFirstNotValidCharacter: 0, expectedRepresentation: null };
101
-
102
- outputIndices = Array(possibleTechnologies.length).fill(0);
103
-
104
- possibleTechnologies.forEach((technology, technologyIndex) => {
105
- let codes = Object.keys(map[expectedSynthesizer][technology]);
106
- while (outputIndices[technologyIndex] < sequence.length) {
107
-
108
- let matchedCode = codes
109
- .find((c) => c == sequence.slice(outputIndices[technologyIndex], outputIndices[technologyIndex] + c.length));
110
-
111
- if (matchedCode == null)
112
- break;
113
-
114
- if ( // for mistake pattern 'rAA'
115
- outputIndices[technologyIndex] > 1 &&
116
- nucleotides.includes(sequence[outputIndices[technologyIndex]]) &&
117
- firstUniqueCharacters.includes(sequence[outputIndices[technologyIndex] - 2])
118
- ) break;
119
-
120
- if ( // for mistake pattern 'ArA'
121
- firstUniqueCharacters.includes(sequence[outputIndices[technologyIndex] + 1]) &&
122
- nucleotides.includes(sequence[outputIndices[technologyIndex]])
123
- ) {
124
- outputIndices[technologyIndex]++;
125
- break;
126
- }
127
-
128
- outputIndices[technologyIndex] += matchedCode.length;
129
- }
130
- });
131
-
132
- const indexOfExpectedTechnology = Math.max.apply(Math, outputIndices);
133
- const expectedTechnology = possibleTechnologies[outputIndices.indexOf(indexOfExpectedTechnology)];
134
-
135
- return {
136
- indexOfFirstNotValidCharacter: indexOfFirstNotValidCharacter,
137
- expectedType: expectedSynthesizer + ' ' + expectedTechnology
138
- };
139
- }
140
-
141
- function sortByStringLengthInDescendingOrder(array: string[]): string[] {
142
- return array.sort(function(a: string, b: string) { return b.length - a.length; });
143
- }
144
-
145
- function getObjectWithCodesAndSmiles() {
146
- let obj: {[code: string]: string} = {};
147
- for (let synthesizer of Object.keys(map))
148
- for (let technology of Object.keys(map[synthesizer]))
149
- for (let code of Object.keys(map[synthesizer][technology]))
150
- obj[code] = map[synthesizer][technology][code].SMILES;
151
- return obj;
152
- }
153
-
154
- export function sequenceToSmiles(sequence: string) {
155
- const obj = getObjectWithCodesAndSmiles();
156
- let codes = sortByStringLengthInDescendingOrder(Object.keys(obj));
157
- let i = 0, smiles = '', codesList = [];
158
- const links = ['s', 'ps', '*'];
159
- const includesStandardLinkAlready = ["e", "h", "g", "f", "i", "l", "k", "j"];
160
- const dropdowns = Object.keys(MODIFICATIONS);
161
- codes = codes.concat(dropdowns);
162
- while (i < sequence.length) {
163
- let code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
164
- i += code.length;
165
- codesList.push(code);
166
- }
167
- for (let i = 0; i < codesList.length; i++) {
168
- if (dropdowns.includes(codesList[i])) {
169
- smiles += (i >= codesList.length / 2) ?
170
- MODIFICATIONS[codesList[i]].right :
171
- MODIFICATIONS[codesList[i]].left;
172
- } else {
173
- if (links.includes(codesList[i]) && i > 1 && !includesStandardLinkAlready.includes(codesList[i - 1]))
174
- smiles = smiles.slice(0, smiles.length - stadardPhosphateLinkSmiles.length + 1);
175
- else if (links.includes(codesList[i]) ||
176
- includesStandardLinkAlready.includes(codesList[i]) ||
177
- (i < codesList.length - 1 && (links.includes(codesList[i + 1]) || dropdowns.includes(codesList[i + 1])))
178
- )
179
- smiles += obj[codesList[i]];
180
- else
181
- smiles += obj[codesList[i]] + stadardPhosphateLinkSmiles;
182
- }
183
- }
184
- smiles = smiles.replace(/OO/g, 'O');
185
- return (
186
- (
187
- links.includes(codesList[codesList.length - 1]) &&
188
- codesList.length > 1 &&
189
- !includesStandardLinkAlready.includes(codesList[codesList.length - 2])
190
- ) ||
191
- dropdowns.includes(codesList[codesList.length - 1]) ||
192
- includesStandardLinkAlready.includes(codesList[codesList.length - 1])
193
- ) ?
194
- smiles :
195
- smiles.slice(0, smiles.length - stadardPhosphateLinkSmiles.length + 1);
196
- }
197
-
198
19
  //name: Sequence Translator
199
20
  //tags: app
200
- export function sequenceTranslator() {
201
-
202
- let windows = grok.shell.windows;
21
+ export function sequenceTranslator(): void {
22
+ const windows = grok.shell.windows;
203
23
  windows.showProperties = false;
204
24
  windows.showToolbox = false;
205
25
  windows.showHelp = false;
206
26
 
207
- function updateTableAndMolecule(sequence: string) {
208
- moleculeSvgDiv.innerHTML = "";
209
- outputTableDiv.innerHTML = "";
210
- let pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
27
+ function updateTableAndMolecule(sequence: string): void {
28
+ moleculeSvgDiv.innerHTML = '';
29
+ outputTableDiv.innerHTML = '';
30
+ const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
31
+ let errorsExist = false;
211
32
  try {
212
- let outputSequenceObj = convertSequence(sequence);
213
- let tableRows = [];
214
- for (let key of Object.keys(outputSequenceObj).slice(1)) {
33
+ const outputSequenceObj = convertSequence(sequence);
34
+ const tableRows = [];
35
+
36
+ for (const key of Object.keys(outputSequenceObj).slice(1)) {
37
+ const indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
38
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter :
39
+ -1;
40
+ if ('indexOfFirstNotValidCharacter' in outputSequenceObj) {
41
+ const indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
42
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter :
43
+ -1;
44
+ if (indexOfFirstNotValidCharacter != -1)
45
+ errorsExist = true;
46
+ }
47
+
215
48
  tableRows.push({
216
49
  'key': key,
217
- 'value': ("indexOfFirstNotValidCharacter" in outputSequenceObj) ?
50
+ 'value': ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
218
51
  ui.divH([
219
- ui.divText(sequence.slice(0, JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter), {style: {color: "grey"}}),
52
+ ui.divText(sequence.slice(0, indexOfFirstNotValidCharacter), {style: {color: 'grey'}}),
220
53
  ui.tooltip.bind(
221
- ui.divText(sequence.slice(JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter), {style: {color: "red"}}),
222
- "Expected format: " + JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).expectedType + ". Press 'SHOW CODES' button to see tables with valid codes"
223
- )
54
+ ui.divText(sequence.slice(indexOfFirstNotValidCharacter), {style: {color: 'red'}}),
55
+ 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).expectedSynthesizer +
56
+ '. See tables with valid codes on the right',
57
+ ),
224
58
  ]) : //@ts-ignore
225
- ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key]).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')
226
- })
59
+ ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
60
+ .then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
61
+ });
227
62
  }
63
+
64
+ if (errorsExist) {
65
+ const expectedSynthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!)
66
+ .expectedSynthesizer.slice(0, -6);
67
+ asoGapmersGrid.onCellPrepare(function(gc) {
68
+ gc.style.backColor = (gc.gridColumn.name == expectedSynthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
69
+ });
70
+ omeAndFluoroGrid.onCellPrepare(function(gc) {
71
+ gc.style.backColor = (gc.gridColumn.name == expectedSynthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
72
+ });
73
+ switchInput.enabled = true;
74
+ } else {
75
+ asoGapmersGrid.onCellPrepare(function(gc) {
76
+ gc.style.backColor = 0xFFFFFFFF;
77
+ });
78
+ omeAndFluoroGrid.onCellPrepare(function(gc) {
79
+ gc.style.backColor = 0xFFFFFFFF;
80
+ });
81
+ }
82
+
228
83
  outputTableDiv.append(
229
- ui.div([DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) => [item.key, item.value], ['Code', 'Sequence']).root], 'table')
84
+ ui.div([
85
+ DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
86
+ [item.key, item.value], ['Code', 'Sequence']).root,
87
+ ], 'table'),
230
88
  );
231
89
  semTypeOfInputSequence.textContent = 'Detected input type: ' + outputSequenceObj.type;
232
90
 
233
- let width = $(window).width();
234
- const canvas = ui.canvas(width, Math.round(width / 2));
235
- let smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
236
- // @ts-ignore
237
- OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromSmiles(smiles), { suppressChiralText: true });
238
- if (outputSequenceObj.type != undefinedInputSequence)
91
+ if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
92
+ const canvas = ui.canvas(300, 170);
93
+ canvas.addEventListener('click', () => {
94
+ const canv = ui.canvas($(window).width(), $(window).height());
95
+ const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
96
+ // @ts-ignore
97
+ OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromSmiles(smiles), {suppressChiralText: true});
98
+ ui.dialog('Molecule: ' + inputSequenceField.value)
99
+ .add(canv)
100
+ .showModal(true);
101
+ });
102
+ $(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
103
+ $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
104
+ const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
105
+ // @ts-ignore
106
+ OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromSmiles(smiles), {suppressChiralText: true});
239
107
  moleculeSvgDiv.append(canvas);
108
+ } else
109
+ moleculeSvgDiv.innerHTML = '';
240
110
  } finally {
241
111
  pi.close();
242
112
  }
243
113
  }
244
114
 
245
- let semTypeOfInputSequence = ui.divText('');
246
- let moleculeSvgDiv = ui.block([]);
247
- let outputTableDiv = ui.div([], 'table');
248
- let inputSequenceField = ui.textInput("", defaultInput, (sequence: string) => updateTableAndMolecule(sequence));
249
- updateTableAndMolecule(defaultInput);
115
+ const semTypeOfInputSequence = ui.divText('');
116
+ const moleculeSvgDiv = ui.block([]);
117
+ const outputTableDiv = ui.div([], 'table');
118
+ const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence));
119
+
120
+ const asoDf = DG.DataFrame.fromObjects([
121
+ {'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
122
+ {'Name': '2\'MOE-rA', 'BioSpring': '6', 'Janssen GCRS': 'moeA'},
123
+ {'Name': '2\'MOE-5Me-rC', 'BioSpring': '7', 'Janssen GCRS': 'moe5mC'},
124
+ {'Name': '2\'MOE-rG', 'BioSpring': '8', 'Janssen GCRS': 'moeG'},
125
+ {'Name': '5-Methyl-dC', 'BioSpring': '9', 'Janssen GCRS': '5mC'},
126
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Janssen GCRS': 'ps'},
127
+ {'Name': 'dA', 'BioSpring': 'A', 'Janssen GCRS': 'A, dA'},
128
+ {'Name': 'dC', 'BioSpring': 'C', 'Janssen GCRS': 'C, dC'},
129
+ {'Name': 'dG', 'BioSpring': 'G', 'Janssen GCRS': 'G, dG'},
130
+ {'Name': 'dT', 'BioSpring': 'T', 'Janssen GCRS': 'T, dT'},
131
+ {'Name': 'rA', 'BioSpring': '', 'Janssen GCRS': 'rA'},
132
+ {'Name': 'rC', 'BioSpring': '', 'Janssen GCRS': 'rC'},
133
+ {'Name': 'rG', 'BioSpring': '', 'Janssen GCRS': 'rG'},
134
+ {'Name': 'rU', 'BioSpring': '', 'Janssen GCRS': 'rU'},
135
+ ])!;
136
+ const asoGapmersGrid = DG.Viewer.grid(asoDf, {showRowHeader: false, showCellTooltip: false});
137
+
138
+ asoDf.onCurrentCellChanged.subscribe((_) => {
139
+ navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'));
140
+ });
250
141
 
251
- let tablesWithCodes = ui.divV([
252
- DG.HtmlTable.create(Object.keys(MODIFICATIONS), (item: string) => [item], ['Overhang modification']).root,
253
- ui.div([], {style: {height: '30px'}})
254
- ]);
255
- for (let synthesizer of Object.keys(map)) {
256
- for (let technology of Object.keys(map[synthesizer])) {
257
- let tableRows = [];
258
- for (let [key, value] of Object.entries(map[synthesizer][technology]))
259
- tableRows.push({'name': value.name, 'code': key});
260
- tablesWithCodes.append(
261
- DG.HtmlTable.create(
262
- tableRows,
263
- (item: {name: string; code: string;}) => [item['name'], item['code']],
264
- [synthesizer + ' ' + technology, 'Code']
265
- ).root,
266
- ui.div([], {style: {height: '30px'}})
267
- );
268
- }
269
- }
270
- let showCodesButton = ui.button('SHOW CODES', () => ui.dialog('Codes').add(tablesWithCodes).show());
271
- let copySmiles = ui.button(
272
- 'COPY SMILES',
273
- () => navigator.clipboard.writeText(sequenceToSmiles(inputSequenceField.value.replace(/\s/g, '')))
274
- .then(() => grok.shell.info(sequenceWasCopied))
142
+ const omeAndFluoroGrid = DG.Viewer.grid(
143
+ DG.DataFrame.fromObjects([
144
+ {'Name': '2\'-fluoro-U', 'BioSpring': '1', 'Axolabs': 'Uf', 'Janssen GCRS': 'fU'},
145
+ {'Name': '2\'-fluoro-A', 'BioSpring': '2', 'Axolabs': 'Af', 'Janssen GCRS': 'fA'},
146
+ {'Name': '2\'-fluoro-C', 'BioSpring': '3', 'Axolabs': 'Cf', 'Janssen GCRS': 'fC'},
147
+ {'Name': '2\'-fluoro-G', 'BioSpring': '4', 'Axolabs': 'Gf', 'Janssen GCRS': 'fG'},
148
+ {'Name': '2\'OMe-rU', 'BioSpring': '5', 'Axolabs': 'u', 'Janssen GCRS': 'mU'},
149
+ {'Name': '2\'OMe-rA', 'BioSpring': '6', 'Axolabs': 'a', 'Janssen GCRS': 'mA'},
150
+ {'Name': '2\'OMe-rC', 'BioSpring': '7', 'Axolabs': 'c', 'Janssen GCRS': 'mC'},
151
+ {'Name': '2\'OMe-rG', 'BioSpring': '8', 'Axolabs': 'g', 'Janssen GCRS': 'mG'},
152
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Axolabs': 's', 'Janssen GCRS': 'ps'},
153
+ ])!, {showRowHeader: false, showCellTooltip: false},
275
154
  );
276
- let saveMolFileButton = ui.bigButton('SAVE MOL FILE', () => {
277
- let smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
278
- let result = `${OCL.Molecule.fromSmiles(smiles).toMolfile()}\n`;
279
- let element = document.createElement('a');
280
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
281
- element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
282
- element.click();
283
- });
284
155
 
285
- const appMainDescription = ui.info([
286
- ui.divText('\n How to convert one sequence:',{style:{'font-weight':'bolder'}}),
287
- ui.divText("Paste sequence into the text field below"),
288
- ui.divText('\n How to convert many sequences:',{style:{'font-weight':'bolder'}}),
289
- ui.divText("1. Drag & drop an Excel or CSV file with sequences into Datagrok. The platform will automatically detect columns with sequences"),
290
- ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
291
- ui.divText("This will add the result column to the right of the table"),
292
- ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.'
156
+ const overhangModificationsGrid = DG.Viewer.grid(
157
+ DG.DataFrame.fromObjects([
158
+ {'Name': '(invabasic)'},
159
+ {'Name': '(GalNAc-2-JNJ)'},
160
+ ])!, {showRowHeader: false, showCellTooltip: false},
293
161
  );
162
+ updateTableAndMolecule(defaultInput);
294
163
 
295
- let v = grok.shell.newView('Sequence Translator', [
296
- ui.tabControl({
297
- 'MAIN': ui.div([
298
- appMainDescription,
299
- ui.panel([
300
- ui.div([
301
- ui.h1('Input sequence'),
164
+ const appMainDescription = ui.info([
165
+ ui.divText('How to convert one sequence:', {style: {'font-weight': 'bolder'}}),
166
+ ui.divText('Paste sequence into the text field below'),
167
+ ui.divText('\n How to convert many sequences:', {style: {'font-weight': 'bolder'}}),
168
+ ui.divText('1. Drag & drop an Excel or CSV file with sequences into Datagrok'),
169
+ ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
170
+ ui.divText('This will add the result column to the right of the table'),
171
+ ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.');
172
+
173
+ const codesTablesDiv = ui.splitV([
174
+ ui.box(ui.h2('ASO Gapmers'), {style: {maxHeight: '40px'}}),
175
+ asoGapmersGrid.root,
176
+ ui.box(ui.h2('2\'-OMe and 2\'-F siRNA'), {style: {maxHeight: '40px'}}),
177
+ omeAndFluoroGrid.root,
178
+ ui.box(ui.h2('Overhang modifications'), {style: {maxHeight: '40px'}}),
179
+ overhangModificationsGrid.root,
180
+ ], {style: {maxWidth: '350px'}});
181
+
182
+ const tabControl = ui.tabControl({
183
+ 'MAIN': ui.box(
184
+ ui.splitH([
185
+ ui.splitV([
186
+ ui.panel([
187
+ appMainDescription,
302
188
  ui.div([
303
- inputSequenceField.root
304
- ],'input-base')
305
- ], 'sequenceInput'),
306
- semTypeOfInputSequence,
307
- ui.block([
308
- ui.h1('Output'),
309
- outputTableDiv
310
- ]),
311
- moleculeSvgDiv,
312
- ui.divH([saveMolFileButton, showCodesButton, copySmiles])
313
- ], 'sequence')
314
- ]),
315
- 'AXOLABS': defineAxolabsPattern()
316
- })
317
- ]);
189
+ ui.h1('Input sequence'),
190
+ ui.div([
191
+ inputSequenceField.root,
192
+ ], 'input-base'),
193
+ ], 'sequenceInput'),
194
+ semTypeOfInputSequence,
195
+ ui.block([
196
+ ui.h1('Output'),
197
+ outputTableDiv,
198
+ ]),
199
+ moleculeSvgDiv,
200
+ ], 'sequence'),
201
+ ]),
202
+ codesTablesDiv,
203
+ ], {style: {height: '100%', width: '100%'}}),
204
+ ),
205
+ 'AXOLABS': defineAxolabsPattern(),
206
+ 'SDF': saveSenseAntiSense(),
207
+ });
208
+
209
+ const v = grok.shell.newView('Sequence Translator', [tabControl]);
318
210
  v.box = true;
319
211
 
212
+ const switchInput = ui.switchInput('Codes', true, (v: boolean) => (v) ?
213
+ $(codesTablesDiv).show() :
214
+ $(codesTablesDiv).hide(),
215
+ );
216
+
217
+ const topPanel = [
218
+ ui.iconFA('download', () => {
219
+ const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
220
+ const result = `${OCL.Molecule.fromSmiles(smiles).toMolfile()}\n`;
221
+ const element = document.createElement('a');
222
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
223
+ element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
224
+ element.click();
225
+ }, 'Save .mol file'),
226
+ ui.iconFA('copy', () => {
227
+ navigator.clipboard.writeText(sequenceToSmiles(inputSequenceField.value.replace(/\s/g, '')))
228
+ .then(() => grok.shell.info(sequenceWasCopied));
229
+ }, 'Copy SMILES'),
230
+ switchInput.root,
231
+ ];
232
+
233
+ tabControl.onTabChanged.subscribe((_) =>
234
+ v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
235
+ v.setRibbonPanels([topPanel]);
236
+
320
237
  $('.sequence')
321
- .children().css('padding','5px 0');
238
+ .children().css('padding', '5px 0');
322
239
  $('.sequenceInput .input-base')
323
- .css('margin','0');
240
+ .css('margin', '0');
324
241
  $('.sequenceInput textarea')
325
- .css('resize','none')
326
- .css('min-height','50px')
327
- .css('width','100%')
328
- .attr("spellcheck", "false");
242
+ .css('resize', 'none')
243
+ .css('min-height', '50px')
244
+ .css('width', '100%')
245
+ .attr('spellcheck', 'false');
329
246
  $('.sequenceInput select')
330
- .css('width','100%');
247
+ .css('width', '100%');
331
248
  }
332
249
 
333
- function convertSequence(text: string) {
334
- text = text.replace(/\s/g, '');
335
- let seq = text;
336
- let output = isValidSequence(seq);
337
- if (output.indexOfFirstNotValidCharacter != -1)
338
- return {
339
- indexOfFirstNotValidCharacter: JSON.stringify(output),
340
- Error: undefinedInputSequence
341
- };
342
- if (output.expectedType == SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.DNA)
343
- return {
344
- type: SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.DNA,
345
- Nucleotides: seq,
346
- BioSpring: asoGapmersNucleotidesToBioSpring(seq),
347
- GCRS: asoGapmersNucleotidesToGcrs(seq)
348
- };
349
- if (output.expectedType == SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.ASO_GAPMERS)
350
- return {
351
- type: SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.ASO_GAPMERS,
352
- Nucleotides: asoGapmersBioSpringToNucleotides(seq),
353
- BioSpring: seq,
354
- GCRS: asoGapmersBioSpringToGcrs(seq)
355
- };
356
- if (output.expectedType == SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.ASO_GAPMERS)
357
- return {
358
- type: SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.ASO_GAPMERS,
359
- Nucleotides: asoGapmersGcrsToNucleotides(seq),
360
- BioSpring: asoGapmersGcrsToBioSpring(seq),
361
- Mermade12: gcrsToMermade12(seq),
362
- GCRS: seq
363
- };
364
- if (output.expectedType == SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.RNA)
365
- return {
366
- type: SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.RNA,
367
- Nucleotides: seq,
368
- BioSpring: siRnaNucleotideToBioSpringSenseStrand(seq),
369
- Axolabs: siRnaNucleotideToAxolabsSenseStrand(seq),
370
- GCRS: siRnaNucleotidesToGcrs(seq)
371
- };
372
- if (output.expectedType == SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.SI_RNA)
373
- return {
374
- type: SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.SI_RNA,
375
- Nucleotides: siRnaBioSpringToNucleotides(seq),
376
- BioSpring: seq,
377
- Axolabs: siRnaBioSpringToAxolabs(seq),
378
- GCRS: siRnaBioSpringToGcrs(seq)
379
- };
380
- if (output.expectedType == SYNTHESIZERS.AXOLABS + ' ' + TECHNOLOGIES.SI_RNA)
381
- return {
382
- type: SYNTHESIZERS.AXOLABS + ' ' + TECHNOLOGIES.SI_RNA,
383
- Nucleotides: siRnaAxolabsToNucleotides(seq),
384
- BioSpring: siRnaAxolabsToBioSpring(seq),
385
- Axolabs: seq,
386
- GCRS: siRnaAxolabsToGcrs(seq)
387
- };
388
- if (output.expectedType == SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.SI_RNA)
389
- return {
390
- type: SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.SI_RNA,
391
- Nucleotides: siRnaGcrsToNucleotides(seq),
392
- BioSpring: siRnaGcrsToBioSpring(seq),
393
- Axolabs: siRnaGcrsToAxolabs(seq),
394
- MM12: gcrsToMermade12(seq),
395
- GCRS: seq
396
- };
397
- if (output.expectedType == SYNTHESIZERS.GCRS)
398
- return {
399
- type: SYNTHESIZERS.GCRS,
400
- Nucleotides: gcrsToNucleotides(seq),
401
- GCRS: seq,
402
- Mermade12: gcrsToMermade12(seq)
250
+ async function saveTableAsSdFile(table: DG.DataFrame) {
251
+ if (!table.columns.contains('Compound Name')) {
252
+ grok.shell.warning(
253
+ 'File saved without columns \'Compound Name\', \'Compound Components\', \'Cpd MW\', \'Salt mass\', \'Batch MW\'');
254
+ }
255
+ const structureColumn = table.columns.byName('Sequence');
256
+ let result = '';
257
+ for (let i = 0; i < table.rowCount; i++) {
258
+ try {
259
+ const smiles = sequenceToSmiles(structureColumn.get(i));
260
+ const mol = OCL.Molecule.fromSmiles(smiles);
261
+ result += `\n${mol.toMolfile()}\n`;
262
+ for (const col of table.columns)
263
+ result += `> <${col.name}>\n${col.get(i)}\n\n`;
264
+ result += '$$$$';
265
+ } catch (error) {
266
+ console.error(error);
403
267
  }
404
- if (output.expectedType == SYNTHESIZERS.MERMADE_12)
405
- return {
406
- type: SYNTHESIZERS.MERMADE_12,
407
- Nucleotides: noTranslationTableAvailable,
408
- GCRS: noTranslationTableAvailable,
409
- Mermade12: seq
410
- };
411
- return {
412
- type: undefinedInputSequence,
413
- Nucleotides: undefinedInputSequence
414
- };
415
- }
416
-
417
- //name: asoGapmersNucleotidesToBioSpring
418
- //input: string nucleotides {semType: DNA nucleotides}
419
- //output: string result {semType: BioSpring / Gapmers}
420
- export function asoGapmersNucleotidesToBioSpring(nucleotides: string) {
421
- let count: number = -1;
422
- const objForEdges: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "T": "5*", "A": "6*", "C": "7*", "G": "8*"};
423
- const objForCenter: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "T": "T*", "A": "A*", "C": "9*", "G": "G*"};
424
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|T|C|G)/g, function (x: string) {
425
- count++;
426
- return (count > 4 && count < 15) ? objForCenter[x] : objForEdges[x];
427
- }).slice(0, (nucleotides.endsWith("(invabasic)") || nucleotides.endsWith("(GalNAc-2-JNJ)")) ? nucleotides.length : 2 * count + 1);
428
- }
429
-
430
- //name: asoGapmersNucleotidesToGcrs
431
- //input: string nucleotides {semType: DNA nucleotides}
432
- //output: string result {semType: GCRS / Gapmers}
433
- export function asoGapmersNucleotidesToGcrs(nucleotides: string) {
434
- let count: number = -1;
435
- const objForEdges: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "T": "moeUnps", "A": "moeAnps", "C": "moe5mCnps", "G": "moeGnps"};
436
- const objForCenter: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "C": "5mCps", "A": "Aps", "T": "Tps", "G": "Gps"};
437
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|T|C|G)/g, function (x: string) {
438
- count++;
439
- if (count < 5) return (count == 4) ? objForEdges[x].slice(0, -3) + 'ps' : objForEdges[x];
440
- if (count < 15) return (count == 14) ? objForCenter[x].slice(0, -2) + 'nps' : objForCenter[x];
441
- return objForEdges[x];
442
- }).slice(0, (nucleotides.endsWith("(invabasic)") || nucleotides.endsWith("(GalNAc-2-JNJ)")) ? nucleotides.length : -3);
443
- }
444
-
445
- //name: asoGapmersBioSpringToNucleotides
446
- //input: string nucleotides {semType: BioSpring / Gapmers}
447
- //output: string result {semType: DNA nucleotides}
448
- export function asoGapmersBioSpringToNucleotides(nucleotides: string) {
449
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "*": "", "5": "T", "6": "A", "7": "C", "8": "G", "9": "C"};
450
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|\*|5|6|7|8|9)/g, function (x: string) {return obj[x];});
451
- }
452
-
453
- //name: asoGapmersBioSpringToGcrs
454
- //input: string nucleotides {semType: BioSpring / Gapmers}
455
- //output: string result {semType: GCRS / Gapmers}
456
- export function asoGapmersBioSpringToGcrs(nucleotides: string) {
457
- let count: number = -1;
458
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
459
- "5*": "moeUnps", "6*": "moeAnps", "7*": "moe5mCnps", "8*": "moeGnps", "9*": "5mCps", "A*": "Aps", "T*": "Tps",
460
- "G*": "Gps", "C*": "Cps", "5": "moeU", "6": "moeA", "7": "moe5mC", "8": "moeG"
461
- };
462
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|5\*|6\*|7\*|8\*|9\*|A\*|T\*|G\*|C\*|5|6|7|8)/g, function (x: string) {
463
- count++;
464
- return (count == 4) ? obj[x].slice(0, -3) + 'ps' : (count == 14) ? obj[x].slice(0, -2) + 'nps' : obj[x];
465
- });
466
- }
467
-
468
- //name: asoGapmersGcrsToBioSpring
469
- //input: string nucleotides {semType: GCRS / Gapmers}
470
- //output: string result {semType: BioSpring / Gapmers}
471
- export function asoGapmersGcrsToBioSpring(nucleotides: string) {
472
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
473
- "moeT": "5", "moeA": "6", "moe5mC": "7", "moeG": "8", "moeU": "5", "5mC": "9", "nps": "*", "ps": "*", "U": "T"
474
- };
475
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|moeT|moeA|moe5mC|moeG|moeU|5mC|nps|ps|U)/g, function (x: string) {return obj[x];});
476
- }
477
-
478
- //name: asoGapmersGcrsToNucleotides
479
- //input: string nucleotides {semType: GCRS / Gapmers}
480
- //output: string result {semType: DNA nucleotides}
481
- export function asoGapmersGcrsToNucleotides(nucleotides: string) {
482
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "moe": "", "5m": "", "n": "", "ps": "", "U": "T"};
483
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|moe|5m|n|ps|U)/g, function (x: string) {return obj[x];});
484
- }
485
-
486
- //name: siRnaBioSpringToNucleotides
487
- //input: string nucleotides {semType: BioSpring / siRNA}
488
- //output: string result {semType: RNA nucleotides}
489
- export function siRnaBioSpringToNucleotides(nucleotides: string) {
490
- 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", "*": ""};
491
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
492
- }
493
-
494
- //name: siRnaBioSpringToAxolabs
495
- //input: string nucleotides {semType: BioSpring / siRNA}
496
- //output: string result {semType: Axolabs / siRNA}
497
- export function siRnaBioSpringToAxolabs(nucleotides: string) {
498
- 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"};
499
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
500
- }
501
-
502
- //name: siRnaBioSpringToGcrs
503
- //input: string nucleotides {semType: BioSpring / siRNA}
504
- //output: string result {semType: GCRS}
505
- export function siRnaBioSpringToGcrs(nucleotides: string) {
506
- 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"};
507
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
508
- }
509
-
510
- //name: siRnaAxolabsToGcrs
511
- //input: string nucleotides {semType: Axolabs / siRNA}
512
- //output: string result {semType: GCRS}
513
- export function siRnaAxolabsToGcrs(nucleotides: string) {
514
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
515
- "Uf": "fU", "Af": "fA", "Cf": "fC", "Gf": "fG", "u": "mU", "a": "mA", "c": "mC", "g": "mG", "s": "ps"
516
- };
517
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
518
- }
519
-
520
- //name: siRnaAxolabsToBioSpring
521
- //input: string nucleotides {semType: Axolabs / siRNA}
522
- //output: string result {semType: BioSpring / siRNA}
523
- export function siRnaAxolabsToBioSpring(nucleotides: string) {
524
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
525
- "Uf": "1", "Af": "2", "Cf": "3", "Gf": "4", "u": "5", "a": "6", "c": "7", "g": "8", "s": "*"
526
- };
527
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
528
- }
529
-
530
- //name: siRnaAxolabsToNucleotides
531
- //input: string nucleotides {semType: Axolabs / siRNA}
532
- //output: string result {semType: RNA nucleotides}
533
- export function siRnaAxolabsToNucleotides(nucleotides: string) {
534
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
535
- "Uf": "U", "Af": "A", "Cf": "C", "Gf": "G", "u": "U", "a": "A", "c": "C", "g": "G", "s": ""
536
- };
537
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
538
- }
539
-
540
- //name: siRnaGcrsToNucleotides
541
- //input: string nucleotides {semType: GCRS}
542
- //output: string result {semType: RNA nucleotides}
543
- export function siRnaGcrsToNucleotides(nucleotides: string) {
544
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
545
- "fU": "U", "fA": "A", "fC": "C", "fG": "G", "mU": "U", "mA": "A", "mC": "C", "mG": "G", "ps": ""
546
- };
547
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
548
- }
549
-
550
- //name: siRnaGcrsToBioSpring
551
- //input: string nucleotides {semType: GCRS}
552
- //output: string result {semType: BioSpring / siRNA}
553
- export function siRnaGcrsToBioSpring(nucleotides: string) {
554
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
555
- "fU": "1", "fA": "2", "fC": "3", "fG": "4", "mU": "5", "mA": "6", "mC": "7", "mG": "8", "ps": "*"
556
- };
557
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
558
- }
559
-
560
- //name: siRnaGcrsToAxolabs
561
- //input: string nucleotides {semType: GCRS}
562
- //output: string result {semType: Axolabs / siRNA}
563
- export function siRnaGcrsToAxolabs(nucleotides: string) {
564
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
565
- "fU": "Uf", "fA": "Af", "fC": "Cf", "fG": "Gf", "mU": "u", "mA": "a", "mC": "c", "mG": "g", "ps": "s"
566
- };
567
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
568
- }
569
-
570
- //name: siRnaNucleotideToBioSpringSenseStrand
571
- //input: string nucleotides {semType: RNA nucleotides}
572
- //output: string result {semType: BioSpring / siRNA}
573
- export function siRnaNucleotideToBioSpringSenseStrand(nucleotides: string) {
574
- let count: number = -1;
575
- const objForLeftEdge: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "6*", "U": "5*", "G": "8*", "C": "7*"};
576
- const objForRightEdge: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "*6", "U": "*5", "G": "*8", "C": "*7"};
577
- const objForOddIndices: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "6", "U": "5", "G": "8", "C": "7"};
578
- const objForEvenIndices: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "2", "U": "1", "G": "4", "C": "3"};
579
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
580
- count++;
581
- if (count < 2) return objForLeftEdge[x];
582
- if (count > nucleotides.length - 3) return objForRightEdge[x];
583
- return (count % 2 == 0) ? objForEvenIndices[x] : objForOddIndices[x];
584
- });
585
- }
586
-
587
- //name: siRnaNucleotidesToGcrs
588
- //input: string nucleotides {semType: RNA nucleotides}
589
- //output: string result {semType: GCRS}
590
- export function siRnaNucleotidesToGcrs(nucleotides: string) {
591
- let count: number = -1;
592
- const objForLeftEdge: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "mAps", "U": "mUps", "G": "mGps", "C": "mCps"};
593
- const objForRightEdge: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "psmA", "U": "psmU", "G": "psmG", "C": "psmC"};
594
- const objForEvenIndices: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "fA", "U": "fU", "G": "fG", "C": "fC"};
595
- const objForOddIndices: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "mA", "U": "mU", "G": "mG", "C": "mC"};
596
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
597
- count++;
598
- if (count < 2) return objForLeftEdge[x];
599
- if (count > nucleotides.length - 3) return objForRightEdge[x];
600
- return (count % 2 == 0) ? objForEvenIndices[x] : objForOddIndices[x];
601
- });
602
- }
603
-
604
- //name: siRnaNucleotideToAxolabsSenseStrand
605
- //input: string nucleotides {semType: RNA nucleotides}
606
- //output: string result {semType: Axolabs}
607
- export function siRnaNucleotideToAxolabsSenseStrand(nucleotides: string) {
608
- let count: number = -1;
609
- const objForLeftEdge: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "as", "U": "us", "G": "gs", "C": "cs"};
610
- const objForSomeIndices: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "Af", "U": "Uf", "G": "Gf", "C": "Cf"};
611
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "a", "U": "u", "G": "g", "C": "c"};
612
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
613
- count++;
614
- if (count < 2) return objForLeftEdge[x];
615
- if (count == 6 || (count > 7 && count < 11)) return objForSomeIndices[x]
616
- if (count == nucleotides.length - 1) return 'a';
617
- return obj[x];
618
- });
268
+ }
269
+ const element = document.createElement('a');
270
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
271
+ element.setAttribute('download', table.name + '.sdf');
272
+ element.click();
619
273
  }
620
274
 
621
- //name: siRnaNucleotideToAxolabsAntisenseStrand
622
- //input: string nucleotides {semType: RNA nucleotides}
623
- //output: string result {semType: Axolabs}
624
- export function siRnaNucleotideToAxolabsAntisenseStrand(nucleotides: string) {
625
- let count: number = -1;
626
- const objForSmallLinkages: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "as", "U": "us", "G": "gs", "C": "cs"};
627
- const objForBigLinkages: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "Afs", "U": "Ufs", "G": "Gfs", "C": "Cfs"};
628
- const objForSomeIndices: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "Af", "U": "Uf", "G": "Gf", "C": "Cf"};
629
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)", "A": "a", "U": "u", "G": "g", "C": "c"};
630
- return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
631
- count++;
632
- if (count > 19 && count < 22) return objForSmallLinkages[x];
633
- if (count == 0) return 'us';
634
- if (count == 1) return objForBigLinkages[x];
635
- return (count == 5 || count == 7 || count == 8 || count == 13 || count == 15) ? objForSomeIndices[x] : obj[x];
275
+ //tags: autostart
276
+ export function autostartOligoSdFileSubscription() {
277
+ grok.events.onViewAdded.subscribe((v: any) => {
278
+ if (v.type == 'TableView' && v.dataFrame.columns.contains('Type'))
279
+ oligoSdFile(v.dataFrame);
636
280
  });
637
281
  }
638
282
 
639
- //name: gcrsToNucleotides
640
- //input: string nucleotides {semType: GCRS}
641
- //output: string result {semType: RNA nucleotides}
642
- export function gcrsToNucleotides(nucleotides: string) {
643
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
644
- "mAps": "A", "mUps": "U", "mGps": "G", "mCps": "C", "fAps": "A", "fUps": "U", "fGps": "G", "fCps": "C",
645
- "fU": "U", "fA": "A", "fC": "C", "fG": "G", "mU": "U", "mA": "A", "mC": "C", "mG": "G"
646
- };
647
- 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];});
648
- }
283
+ export function oligoSdFile(table: DG.DataFrame) {
284
+ const saltsDf = DG.DataFrame.fromCsv(SALTS_CSV);
285
+ function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
286
+ if (t.columns.contains('Compound Name'))
287
+ return grok.shell.error('Columns already exist!');
288
+
289
+ table.col('Source')?.init('Johnson and Johnson Pharma');
290
+ table.col('ICD')?.init('No Contract');
291
+
292
+ const sequence = t.col('Sequence')!;
293
+ const salt = t.col('Salt')!;
294
+ const equivalents = t.col('Equivalents')!;
295
+
296
+ t.columns.addNewString('Compound Name').init((i: number) => sequence.get(i));
297
+ t.columns.addNewString('Compound Comments').init((i: number) => (i > 0 && i % 2 == 0) ?
298
+ sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
299
+ sequence.getString(i),
300
+ );
301
+ const chargeCol = saltsDf.col('CHARGE')!.toList();
302
+ const saltNames = saltsDf.col('DISPLAY')!.toList();
303
+ const molWeight = saltsDf.col('MOLWEIGHT')!.toList();
304
+ t.columns.addNewFloat('Cpd MW').init((i: number) => ((i + 1) % 3 == 0) ? DG.FLOAT_NULL : molWeight[i]);
305
+ t.columns.addNewFloat('Salt mass').init((i: number) => {
306
+ const v = chargeCol[saltNames.indexOf(salt.get(i))];
307
+ const n = (v == null) ? 0 : chargeCol[saltNames.indexOf(salt.get(i))];
308
+ return n * equivalents.get(i);
309
+ });
310
+ t.columns.addNewCalculated('Batch MW', '${Cpd MW} + ${Salt mass}', DG.COLUMN_TYPE.FLOAT, false);
311
+
312
+ addColumnsPressed = true;
313
+ return newDf = t;
314
+ }
649
315
 
650
- //name: gcrsToMermade12
651
- //input: string nucleotides {semType: GCRS}
652
- //output: string result {semType: Mermade 12 / siRNA}
653
- export function gcrsToMermade12(nucleotides: string) {
654
- const obj: {[index: string]: string} = {"(invabasic)": "(invabasic)", "(GalNAc-2-JNJ)": "(GalNAc-2-JNJ)",
655
- "mAps": "e", "mUps": "h", "mGps": "g", "mCps": "f", "fAps": "i", "fUps": "l", "fGps": "k", "fCps": "j", "fU": "L",
656
- "fA": "I", "fC": "J", "fG": "K", "mU": "H", "mA": "E", "mC": "F", "mG": "G"
657
- };
658
- 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]});
316
+ const columnsOrder = ['Chemistry', 'Number', 'Type', 'Chemistry Name', 'Internal compound ID',
317
+ 'IDP', 'Sequence', 'Compound Name', 'Compound Comments', 'Salt', 'Equivalents', 'Purity', 'Cpd MW', 'Salt mass',
318
+ 'Batch MW', 'Source', 'ICD', 'Owner'];
319
+ let newDf: DG.DataFrame;
320
+ let addColumnsPressed = false;
321
+
322
+ const d = ui.div([
323
+ ui.icons.edit(() => {
324
+ d.innerHTML = '';
325
+ d.append(
326
+ ui.link('Add Columns', async () => {
327
+ await addColumns(table, saltsDf);
328
+ grok.shell.tableView(table.name).grid.columns.setOrder(columnsOrder);
329
+ }, 'Add columns: Compound Name, Compound Components, Cpd MW, Salt mass, Batch MW', ''),
330
+ ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
331
+ );
332
+ const view = grok.shell.getTableView(table.name);
333
+ const typeCol = view.grid.col('Type')!;
334
+ const saltCol = view.grid.col('Salt')!;
335
+ saltCol.cellType = 'html';
336
+ typeCol.cellType = 'html';
337
+ view.grid.onCellPrepare(function(gc: DG.GridCell) {
338
+ if (gc.isTableCell) {
339
+ if (gc.gridColumn.name == 'Type')
340
+ gc.style.element = ui.choiceInput('', gc.cell.value, ['AS', 'SS', 'Duplex']).root;
341
+ else if (gc.gridColumn.name == 'Salt') {
342
+ gc.style.element = ui.choiceInput('', gc.cell.value, saltsDf.columns.byIndex(1).toList(), () => {
343
+ view.dataFrame.col('Salt')!.set(gc.gridRow, '');
344
+ }).root;
345
+ }
346
+ }
347
+ });
348
+
349
+ table.onDataChanged.subscribe((_) => {
350
+ if (table.currentCol.name == 'IDP' && typeof table.currentCell.value != 'number')
351
+ grok.shell.error('Value should be numeric');
352
+ });
353
+ }),
354
+ ]);
355
+ grok.shell.v.setRibbonPanels([[d]]);
659
356
  }