@datagrok/sequence-translator 0.0.8 → 1.0.0

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
@@ -6,13 +6,19 @@ import * as OCL from 'openchemlib/full.js';
6
6
  import $ from 'cash-dom';
7
7
  import {defineAxolabsPattern} from './defineAxolabsPattern';
8
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';
9
+ import {sequenceToSmiles, sequenceToMolV3000} from './structures-works/from-monomers';
10
+ import {convertSequence, undefinedInputSequence, isValidSequence, getFormat} from
11
+ './structures-works/sequence-codes-tools';
12
+ import {map, COL_NAMES, MODIFICATIONS} from './structures-works/map';
11
13
  import {SALTS_CSV} from './salts';
14
+ import {USERS_CSV} from './users';
15
+ import {ICDS} from './ICDs';
16
+ import {SOURCES} from './sources';
17
+ import {IDPS} from './IDPs';
12
18
 
13
19
  export const _package = new DG.Package();
14
20
 
15
- const defaultInput = 'AGGTCCTCTTGACTTAGGCC';
21
+ const defaultInput = 'fAmCmGmAmCpsmU';
16
22
  const sequenceWasCopied = 'Copied';
17
23
  const tooltipSequence = 'Copy sequence';
18
24
 
@@ -24,35 +30,40 @@ export function sequenceTranslator(): void {
24
30
  windows.showToolbox = false;
25
31
  windows.showHelp = false;
26
32
 
27
- function updateTableAndMolecule(sequence: string): void {
33
+ function updateTableAndMolecule(sequence: string, inputFormat: string, isSet: boolean): void {
28
34
  moleculeSvgDiv.innerHTML = '';
29
35
  outputTableDiv.innerHTML = '';
30
36
  const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
31
37
  let errorsExist = false;
32
38
  try {
33
- const outputSequenceObj = convertSequence(sequence);
39
+ sequence = sequence.replace(/\s/g, '');
40
+ const output = isValidSequence(sequence, null);
41
+ if (isSet)
42
+ output.synthesizer = [inputFormat];
43
+ inputFormatChoiceInput.value = output.synthesizer![0];
44
+ const outputSequenceObj = convertSequence(sequence, output);
34
45
  const tableRows = [];
35
46
 
36
47
  for (const key of Object.keys(outputSequenceObj).slice(1)) {
37
- const indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
38
- JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter :
48
+ const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
49
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
39
50
  -1;
40
- if ('indexOfFirstNotValidCharacter' in outputSequenceObj) {
41
- const indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
42
- JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter :
51
+ if ('indexOfFirstNotValidChar' in outputSequenceObj) {
52
+ const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
53
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
43
54
  -1;
44
- if (indexOfFirstNotValidCharacter != -1)
55
+ if (indexOfFirstNotValidChar != -1)
45
56
  errorsExist = true;
46
57
  }
47
58
 
48
59
  tableRows.push({
49
60
  'key': key,
50
- 'value': ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
61
+ 'value': ('indexOfFirstNotValidChar' in outputSequenceObj) ?
51
62
  ui.divH([
52
- ui.divText(sequence.slice(0, indexOfFirstNotValidCharacter), {style: {color: 'grey'}}),
63
+ ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
53
64
  ui.tooltip.bind(
54
- ui.divText(sequence.slice(indexOfFirstNotValidCharacter), {style: {color: 'red'}}),
55
- 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).expectedSynthesizer +
65
+ ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
66
+ 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
56
67
  '. See tables with valid codes on the right',
57
68
  ),
58
69
  ]) : //@ts-ignore
@@ -62,48 +73,44 @@ export function sequenceTranslator(): void {
62
73
  }
63
74
 
64
75
  if (errorsExist) {
65
- const expectedSynthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!)
66
- .expectedSynthesizer.slice(0, -6);
76
+ const synthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer.slice(0, -6);
67
77
  asoGapmersGrid.onCellPrepare(function(gc) {
68
- gc.style.backColor = (gc.gridColumn.name == expectedSynthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
78
+ gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
69
79
  });
70
80
  omeAndFluoroGrid.onCellPrepare(function(gc) {
71
- gc.style.backColor = (gc.gridColumn.name == expectedSynthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
81
+ gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
72
82
  });
73
83
  switchInput.enabled = true;
74
84
  } else {
75
- asoGapmersGrid.onCellPrepare(function(gc) {
76
- gc.style.backColor = 0xFFFFFFFF;
77
- });
78
- omeAndFluoroGrid.onCellPrepare(function(gc) {
79
- gc.style.backColor = 0xFFFFFFFF;
80
- });
85
+ asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
86
+ omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
81
87
  }
82
88
 
83
89
  outputTableDiv.append(
84
90
  ui.div([
85
91
  DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
86
92
  [item.key, item.value], ['Code', 'Sequence']).root,
87
- ], 'table'),
93
+ ]),
88
94
  );
89
- semTypeOfInputSequence.textContent = 'Detected input type: ' + outputSequenceObj.type;
90
95
 
91
96
  if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
92
97
  const canvas = ui.canvas(300, 170);
93
98
  canvas.addEventListener('click', () => {
94
99
  const canv = ui.canvas($(window).width(), $(window).height());
95
- const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
100
+ const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
101
+ output.synthesizer![0]);
96
102
  // @ts-ignore
97
- OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromSmiles(smiles), {suppressChiralText: true});
103
+ OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
98
104
  ui.dialog('Molecule: ' + inputSequenceField.value)
99
105
  .add(canv)
100
106
  .showModal(true);
101
107
  });
102
108
  $(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
103
109
  $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
104
- const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
110
+ const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
111
+ output.synthesizer![0]);
105
112
  // @ts-ignore
106
- OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromSmiles(smiles), {suppressChiralText: true});
113
+ OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
107
114
  moleculeSvgDiv.append(canvas);
108
115
  } else
109
116
  moleculeSvgDiv.innerHTML = '';
@@ -112,10 +119,14 @@ export function sequenceTranslator(): void {
112
119
  }
113
120
  }
114
121
 
115
- const semTypeOfInputSequence = ui.divText('');
122
+ const inputFormatChoiceInput = ui.choiceInput(
123
+ 'Input format: ', 'Janssen GCRS Codes', Object.keys(map), (format: string) => {
124
+ updateTableAndMolecule(inputSequenceField.value.replace(/\s/g, ''), format, true);
125
+ });
116
126
  const moleculeSvgDiv = ui.block([]);
117
- const outputTableDiv = ui.div([], 'table');
118
- const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence));
127
+ const outputTableDiv = ui.div([]);
128
+ const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence,
129
+ inputFormatChoiceInput.value!, false));
119
130
 
120
131
  const asoDf = DG.DataFrame.fromObjects([
121
132
  {'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
@@ -154,12 +165,11 @@ export function sequenceTranslator(): void {
154
165
  );
155
166
 
156
167
  const overhangModificationsGrid = DG.Viewer.grid(
157
- DG.DataFrame.fromObjects([
158
- {'Name': '(invabasic)'},
159
- {'Name': '(GalNAc-2-JNJ)'},
168
+ DG.DataFrame.fromColumns([
169
+ DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
160
170
  ])!, {showRowHeader: false, showCellTooltip: false},
161
171
  );
162
- updateTableAndMolecule(defaultInput);
172
+ updateTableAndMolecule(defaultInput, inputFormatChoiceInput.value!, true);
163
173
 
164
174
  const appMainDescription = ui.info([
165
175
  ui.divText('How to convert one sequence:', {style: {'font-weight': 'bolder'}}),
@@ -190,8 +200,8 @@ export function sequenceTranslator(): void {
190
200
  ui.div([
191
201
  inputSequenceField.root,
192
202
  ], 'input-base'),
193
- ], 'sequenceInput'),
194
- semTypeOfInputSequence,
203
+ ], 'inputSequence'),
204
+ ui.div([inputFormatChoiceInput], {style: {padding: '5px 0'}}),
195
205
  ui.block([
196
206
  ui.h1('Output'),
197
207
  outputTableDiv,
@@ -216,15 +226,16 @@ export function sequenceTranslator(): void {
216
226
 
217
227
  const topPanel = [
218
228
  ui.iconFA('download', () => {
219
- const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
220
- const result = `${OCL.Molecule.fromSmiles(smiles).toMolfile()}\n`;
229
+ const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
230
+ inputFormatChoiceInput.value!);
221
231
  const element = document.createElement('a');
222
232
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
223
233
  element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
224
234
  element.click();
225
235
  }, 'Save .mol file'),
226
236
  ui.iconFA('copy', () => {
227
- navigator.clipboard.writeText(sequenceToSmiles(inputSequenceField.value.replace(/\s/g, '')))
237
+ navigator.clipboard.writeText(
238
+ sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''), false, inputFormatChoiceInput.value!))
228
239
  .then(() => grok.shell.info(sequenceWasCopied));
229
240
  }, 'Copy SMILES'),
230
241
  switchInput.root,
@@ -233,38 +244,29 @@ export function sequenceTranslator(): void {
233
244
  tabControl.onTabChanged.subscribe((_) =>
234
245
  v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
235
246
  v.setRibbonPanels([topPanel]);
236
-
237
- $('.sequence')
238
- .children().css('padding', '5px 0');
239
- $('.sequenceInput .input-base')
240
- .css('margin', '0');
241
- $('.sequenceInput textarea')
242
- .css('resize', 'none')
243
- .css('min-height', '50px')
244
- .css('width', '100%')
245
- .attr('spellcheck', 'false');
246
- $('.sequenceInput select')
247
- .css('width', '100%');
248
247
  }
249
248
 
250
249
  async function saveTableAsSdFile(table: DG.DataFrame) {
251
250
  if (!table.columns.contains('Compound Name')) {
252
251
  grok.shell.warning(
253
- 'File saved without columns \'Compound Name\', \'Compound Components\', \'Cpd MW\', \'Salt mass\', \'Batch MW\'');
252
+ 'File saved without columns \'' +
253
+ [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
254
+ COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
255
+ );
254
256
  }
255
- const structureColumn = table.columns.byName('Sequence');
257
+ const structureColumn = table.col(COL_NAMES.SEQUENCE)!;
258
+ const typeColumn = table.col(COL_NAMES.TYPE)!;
256
259
  let result = '';
257
260
  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)
261
+ const format = getFormat(structureColumn.get(i));
262
+ result += (typeColumn.get(i) == 'SS') ?
263
+ sequenceToMolV3000(structureColumn.get(i), false, true, format!) + '\n' + `> <Sequence>\nSense Strand\n\n` :
264
+ sequenceToMolV3000(structureColumn.get(i), true, true, format!) + '\n' + `> <Sequence>\nAnti Sense\n\n`;
265
+ for (const col of table.columns) {
266
+ if (col.name != COL_NAMES.SEQUENCE)
263
267
  result += `> <${col.name}>\n${col.get(i)}\n\n`;
264
- result += '$$$$';
265
- } catch (error) {
266
- console.error(error);
267
268
  }
269
+ result += '$$$$\n\n';
268
270
  }
269
271
  const element = document.createElement('a');
270
272
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
@@ -272,83 +274,115 @@ async function saveTableAsSdFile(table: DG.DataFrame) {
272
274
  element.click();
273
275
  }
274
276
 
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
+ }
283
+ }
284
+ for (const [key, value] of Object.entries(MODIFICATIONS))
285
+ weightsObj[key] = value.molecularWeight;
286
+
287
+ function sortByStringLengthInDescendingOrder(array: string[]): string[] {
288
+ return array.sort(function(a, b) {return b.length - a.length;});
289
+ }
290
+
291
+ function stringifyItems(items: string[]): string {
292
+ return '["' + items.join('", "') + '"]';
293
+ }
294
+
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;
299
+ while (i < sequence.length) {
300
+ const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
301
+ weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
302
+ i += matchedCode!.length;
303
+ }
304
+ return weight - 61.97;
305
+ }
306
+
275
307
  //tags: autostart
276
308
  export function autostartOligoSdFileSubscription() {
277
309
  grok.events.onViewAdded.subscribe((v: any) => {
278
- if (v.type == 'TableView' && v.dataFrame.columns.contains('Type'))
310
+ if (v.type == 'TableView' && v.dataFrame.columns.contains(COL_NAMES.TYPE))
279
311
  oligoSdFile(v.dataFrame);
280
312
  });
281
313
  }
282
314
 
283
315
  export function oligoSdFile(table: DG.DataFrame) {
284
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
+
285
322
  function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
286
- if (t.columns.contains('Compound Name'))
323
+ if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
287
324
  return grok.shell.error('Columns already exist!');
288
325
 
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')!;
326
+ const sequence = t.col(COL_NAMES.SEQUENCE)!;
327
+ const salt = t.col(COL_NAMES.SALT)!;
328
+ const equivalents = t.col(COL_NAMES.EQUIVALENTS)!;
295
329
 
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) ?
330
+ t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => sequence.get(i));
331
+ t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => (i > 0 && i % 2 == 0) ?
298
332
  sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
299
333
  sequence.getString(i),
300
334
  );
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);
335
+ const molWeightCol = saltsDf.col('MOLWEIGHT')!;
336
+ const saltNamesList = saltsDf.col('DISPLAY')!.toList();
337
+ t.columns.addNewFloat(COL_NAMES.CPD_MW)
338
+ .init((i: number) => molecularWeight(sequence.get(i), weightsObj));
339
+ t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) => {
340
+ const saltRowIndex = saltNamesList.indexOf(salt.get(i));
341
+ const mw = molWeightCol.get(saltRowIndex);
342
+ return mw * equivalents.get(i);
309
343
  });
310
- t.columns.addNewCalculated('Batch MW', '${Cpd MW} + ${Salt mass}', DG.COLUMN_TYPE.FLOAT, false);
344
+ t.columns.addNewCalculated(COL_NAMES.BATCH_MW,
345
+ '${' + COL_NAMES.CPD_MW + '} + ${' + COL_NAMES.SALT_MASS + '}', DG.COLUMN_TYPE.FLOAT, false,
346
+ );
311
347
 
312
348
  addColumnsPressed = true;
313
349
  return newDf = t;
314
350
  }
315
351
 
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
352
  let newDf: DG.DataFrame;
320
353
  let addColumnsPressed = false;
321
354
 
322
355
  const d = ui.div([
323
356
  ui.icons.edit(() => {
324
357
  d.innerHTML = '';
358
+ if (table.col(COL_NAMES.IDP)!.type != DG.COLUMN_TYPE.STRING)
359
+ table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
325
360
  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', ''),
361
+ ui.link('Add Columns', () => {
362
+ addColumns(table, saltsDf);
363
+ grok.shell.tableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
364
+ }, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
365
+ COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), ''),
330
366
  ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
331
367
  );
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
368
 
349
- table.onDataChanged.subscribe((_) => {
350
- if (table.currentCol.name == 'IDP' && typeof table.currentCell.value != 'number')
351
- grok.shell.error('Value should be numeric');
369
+ const view = grok.shell.getTableView(table.name)!;
370
+
371
+ view.table!.col(COL_NAMES.TYPE)!.setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
372
+ view.table!.col(COL_NAMES.OWNER)!.setTag(DG.TAGS.CHOICES, stringifyItems(usersDf.columns.byIndex(0).toList()));
373
+ view.table!.col(COL_NAMES.SALT)!.setTag(DG.TAGS.CHOICES, stringifyItems(saltsDf.columns.byIndex(0).toList()));
374
+ view.table!.col(COL_NAMES.SOURCE)!.setTag(DG.TAGS.CHOICES, stringifyItems(sourcesDf.columns.byIndex(0).toList()));
375
+ view.table!.col(COL_NAMES.ICD)!.setTag(DG.TAGS.CHOICES, stringifyItems(icdsDf.columns.byIndex(0).toList()));
376
+ view.table!.col(COL_NAMES.IDP)!.setTag(DG.TAGS.CHOICES, stringifyItems(idpsDf.columns.byIndex(0).toList()));
377
+
378
+ grok.events.onContextMenu.subscribe((args) => {
379
+ if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
380
+ .includes(args.args.context.table.currentCol.name)) {
381
+ args.args.menu.item('Fill Column With Value', () => {
382
+ const v = args.args.context.table.currentCell.value;
383
+ args.args.context.table.currentCell.column.init(v);
384
+ });
385
+ }
352
386
  });
353
387
  }),
354
388
  ]);
package/src/salts.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const SALTS_CSV = `CHARGE,DISPLAY,MOLWEIGHT
2
- ,no Data Added,100`;
1
+ export const SALTS_CSV = `DISPLAY,MOLWEIGHT
2
+ no Data Added,100`;
package/src/sources.ts ADDED
@@ -0,0 +1,3 @@
1
+ export const SOURCES = `DISPLAY
2
+ NO DATA
3
+ `;
@@ -1,11 +1,13 @@
1
1
  import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS} from './map';
2
2
  import {isValidSequence} from './sequence-codes-tools';
3
+ import {getNucleotidesMol} from './mol-transformations';
3
4
 
4
- export function sequenceToSmiles(sequence: string, inverted: boolean = false): string {
5
- const obj = getObjectWithCodesAndSmiles(sequence);
5
+ export function sequenceToMolV3000(sequence: string, inverted: boolean = false, oclRender: boolean = false,
6
+ format: string): string {
7
+ const obj = getObjectWithCodesAndSmiles(sequence, format);
6
8
  let codes = sortByStringLengthInDescendingOrder(Object.keys(obj));
7
9
  let i = 0;
8
- let smiles = '';
10
+ const smilesCodes:string[] = [];
9
11
  const codesList = [];
10
12
  const links = ['s', 'ps', '*'];
11
13
  const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
@@ -18,15 +20,45 @@ export function sequenceToSmiles(sequence: string, inverted: boolean = false): s
18
20
  }
19
21
  for (let i = 0; i < codesList.length; i++) {
20
22
  if (dropdowns.includes(codesList[i])) {
21
- if (i == codesList.length -1 || (i < codesList.length - 1 && links.includes(codesList[i + 1]))) {
22
- smiles += (i >= codesList.length / 2) ?
23
- MODIFICATIONS[codesList[i]].right:
24
- MODIFICATIONS[codesList[i]].left;
25
- } else if (i < codesList.length - 1) {
26
- smiles += (i >= codesList.length / 2) ?
27
- MODIFICATIONS[codesList[i]].right + stadardPhosphateLinkSmiles:
28
- MODIFICATIONS[codesList[i]].left + stadardPhosphateLinkSmiles;
23
+ smilesCodes.push((i >= codesList.length / 2) ?
24
+ MODIFICATIONS[codesList[i]].right : MODIFICATIONS[codesList[i]].left);
25
+ smilesCodes.push(stadardPhosphateLinkSmiles);
26
+ } else {
27
+ if (links.includes(codesList[i]) ||
28
+ includesStandardLinkAlready.includes(codesList[i]) ||
29
+ (i < codesList.length - 1 && links.includes(codesList[i + 1]))
30
+ )
31
+ smilesCodes.push(obj[codesList[i]]);
32
+ else {
33
+ smilesCodes.push(obj[codesList[i]]);
34
+ smilesCodes.push(stadardPhosphateLinkSmiles);
29
35
  }
36
+ }
37
+ }
38
+
39
+ return getNucleotidesMol(smilesCodes, oclRender);
40
+ }
41
+
42
+ export function sequenceToSmiles(sequence: string, inverted: boolean = false, format: string): string {
43
+ const obj = getObjectWithCodesAndSmiles(sequence, format);
44
+ let codes = sortByStringLengthInDescendingOrder(Object.keys(obj));
45
+ let i = 0;
46
+ let smiles = '';
47
+ const codesList = [];
48
+ const links = ['s', 'ps', '*'];
49
+ const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
50
+ const dropdowns = Object.keys(MODIFICATIONS);
51
+ codes = codes.concat(dropdowns);
52
+ while (i < sequence.length) {
53
+ const code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
54
+ i += code.length;
55
+ inverted ? codesList.unshift(code) : codesList.push(code);
56
+ }
57
+ for (let i = 0; i < codesList.length; i++) {
58
+ if (dropdowns.includes(codesList[i])) {
59
+ smiles += (i >= codesList.length / 2) ?
60
+ MODIFICATIONS[codesList[i]].right + stadardPhosphateLinkSmiles:
61
+ MODIFICATIONS[codesList[i]].left + stadardPhosphateLinkSmiles;
30
62
  } else {
31
63
  if (links.includes(codesList[i]) ||
32
64
  includesStandardLinkAlready.includes(codesList[i]) ||
@@ -51,19 +83,26 @@ export function sequenceToSmiles(sequence: string, inverted: boolean = false): s
51
83
  smiles.slice(0, smiles.length - stadardPhosphateLinkSmiles.length + 1);
52
84
  }
53
85
 
54
- function getObjectWithCodesAndSmiles(sequence: string) {
86
+ function getObjectWithCodesAndSmiles(sequence: string, format: string) {
55
87
  const obj: { [code: string]: string } = {};
56
- for (const synthesizer of Object.keys(map)) {
57
- for (const technology of Object.keys(map[synthesizer])) {
58
- for (const code of Object.keys(map[synthesizer][technology]))
59
- obj[code] = map[synthesizer][technology][code].SMILES;
88
+ if (format == null) {
89
+ for (const synthesizer of Object.keys(map)) {
90
+ for (const technology of Object.keys(map[synthesizer])) {
91
+ for (const code of Object.keys(map[synthesizer][technology]))
92
+ obj[code] = map[synthesizer][technology][code].SMILES;
93
+ }
94
+ }
95
+ } else {
96
+ for (const technology of Object.keys(map[format])) {
97
+ for (const code of Object.keys(map[format][technology]))
98
+ obj[code] = map[format][technology][code].SMILES;
60
99
  }
61
100
  }
62
101
  // TODO: create object based from synthesizer type to avoid key(codes) duplicates
63
- const output = isValidSequence(sequence);
64
- if (output.expectedSynthesizer == SYNTHESIZERS.MERMADE_12)
102
+ const output = isValidSequence(sequence, format);
103
+ if (output.synthesizer!.includes(SYNTHESIZERS.MERMADE_12))
65
104
  obj['g'] = map[SYNTHESIZERS.MERMADE_12][TECHNOLOGIES.SI_RNA]['g'].SMILES;
66
- else if (output.expectedSynthesizer == SYNTHESIZERS.AXOLABS)
105
+ else if (output.synthesizer!.includes(SYNTHESIZERS.AXOLABS))
67
106
  obj['g'] = map[SYNTHESIZERS.AXOLABS][TECHNOLOGIES.SI_RNA]['g'].SMILES;
68
107
  return obj;
69
108
  }
@@ -11,14 +11,36 @@ export const TECHNOLOGIES = {
11
11
  ASO_GAPMERS: 'For ASO Gapmers',
12
12
  SI_RNA: 'For 2\'-OMe and 2\'-F modified siRNA',
13
13
  };
14
+ export const COL_NAMES = {
15
+ CHEMISTRY: 'Chemistry',
16
+ NUMBER: 'Number',
17
+ TYPE: 'Type',
18
+ CHEMISTRY_NAME: 'Chemistry Name',
19
+ INTERNAL_COMPOUND_ID: 'Internal compound ID',
20
+ IDP: 'IDP',
21
+ SEQUENCE: 'Sequence',
22
+ COMPOUND_NAME: 'Compound Name',
23
+ COMPOUND_COMMENTS: 'Compound Comments',
24
+ SALT: 'Salt',
25
+ EQUIVALENTS: 'Equivalents',
26
+ PURITY: 'Purity',
27
+ CPD_MW: 'Cpd MW',
28
+ SALT_MASS: 'Salt mass',
29
+ BATCH_MW: 'Batch MW',
30
+ SOURCE: 'Source',
31
+ ICD: 'ICD',
32
+ OWNER: 'Owner',
33
+ };
14
34
  // interface CODES {
15
35
  // }
16
- export const MODIFICATIONS: {[index: string]: {left: string, right: string}} = {
36
+ export const MODIFICATIONS: {[index: string]: {molecularWeight: number, left: string, right: string}} = {
17
37
  '(invabasic)': {
38
+ molecularWeight: 118.13,
18
39
  left: 'O[C@@H]1C[C@@H]O[C@H]1CO',
19
40
  right: 'O[C@@H]1C[C@@H]O[C@H]1CO',
20
41
  },
21
42
  '(GalNAc-2-JNJ)': {
43
+ molecularWeight: 1273.3,
22
44
  left: 'C(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
23
45
  '(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
24
46
  '(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)NC(=O)CCCC(=O)NCC(O)CO',
@@ -49,7 +71,7 @@ export const map: {[synthesizer: string]:
49
71
  'name': 'Guanine',
50
72
  'weight': 329.21,
51
73
  'normalized': 'dG',
52
- 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C)[C@@H]1O',
74
+ 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
53
75
  },
54
76
  'C': {
55
77
  'name': 'Cytosine',
@@ -75,7 +97,7 @@ export const map: {[synthesizer: string]:
75
97
  'name': 'Guanine',
76
98
  'weight': 329.21,
77
99
  'normalized': 'dG',
78
- 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C)[C@@H]1O',
100
+ 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
79
101
  },
80
102
  'C': {
81
103
  'name': 'Cytosine',
@@ -139,7 +161,7 @@ export const map: {[synthesizer: string]:
139
161
  'name': 'Guanine',
140
162
  'weight': 329.21,
141
163
  'normalized': 'dG',
142
- 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C)[C@@H]1O',
164
+ 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
143
165
  },
144
166
  'T': {
145
167
  'name': 'Tyrosine',
@@ -341,13 +363,13 @@ export const map: {[synthesizer: string]:
341
363
  'name': 'Guanine',
342
364
  'weight': 329.21,
343
365
  'normalized': 'dG',
344
- 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C)[C@@H]1O',
366
+ 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
345
367
  },
346
368
  'dG': {
347
369
  'name': 'Guanine',
348
370
  'weight': 329.21,
349
371
  'normalized': 'dG',
350
- 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C)[C@@H]1O',
372
+ 'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
351
373
  },
352
374
  'T': {
353
375
  'name': 'Tyrosine',