@datagrok/sequence-translator 1.0.12 → 1.0.14

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.
@@ -3,90 +3,64 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
  import {siRnaBioSpringToGcrs, siRnaAxolabsToGcrs, gcrsToNucleotides, asoGapmersBioSpringToGcrs, gcrsToMermade12,
5
5
  siRnaNucleotidesToGcrs} from '../structures-works/converters';
6
- import {map, COL_NAMES, MODIFICATIONS} from '../structures-works/map';
6
+ import {weightsObj, SYNTHESIZERS} from '../structures-works/map';
7
+ import {SEQUENCE_TYPES, COL_NAMES, GENERATED_COL_NAMES, CELL_STRUCTURE} from './constants';
8
+ import {saltMass, saltMolWeigth, molecularWeight, batchMolWeight} from './calculations';
7
9
  import {isValidSequence} from '../structures-works/sequence-codes-tools';
8
10
  import {sequenceToMolV3000} from '../structures-works/from-monomers';
9
- import {linkV3000} from '../structures-works/mol-transformations';
11
+ import {linkStrandsV3000} from '../structures-works/mol-transformations';
12
+ import {stringify, download, removeEmptyRows, differenceOfTwoArrays} from '../helpers';
10
13
 
11
- import {SALTS_CSV} from '../salts';
12
- import {USERS_CSV} from '../users';
13
- import {ICDS} from '../ICDs';
14
- import {SOURCES} from '../sources';
15
- import {IDPS} from '../IDPs';
14
+ import {SALTS_CSV} from './salts';
15
+ import {USERS_CSV} from './users';
16
+ import {ICDS} from './ICDs';
17
+ import {SOURCES} from './sources';
18
+ import {IDPS} from './IDPs';
16
19
 
17
20
 
18
- function sortByStringLengthInDescendingOrder(array: string[]): string[] {
19
- return array.sort(function(a, b) {return b.length - a.length;});
21
+ function parseStrandsFromDuplexCell(s: string): {SS: string, AS: string} {
22
+ const arr = s
23
+ .slice(CELL_STRUCTURE.DUPLEX.BEFORE_SS.length)
24
+ .split(CELL_STRUCTURE.DUPLEX.BEFORE_AS);
25
+ return {SS: arr[0], AS: arr[1]};
20
26
  }
21
27
 
22
- function stringify(items: string[]): string {
23
- return '["' + items.join('", "') + '"]';
28
+ function parseStrandsFromTriplexOrDimerCell(s: string): {SS: string, AS1: string, AS2: string} {
29
+ const arr1 = s
30
+ .slice(CELL_STRUCTURE.TRIPLEX_OR_DIMER.BEFORE_SS.length)
31
+ .split(CELL_STRUCTURE.TRIPLEX_OR_DIMER.BEFORE_AS1);
32
+ const arr2 = arr1[1]
33
+ .split(CELL_STRUCTURE.TRIPLEX_OR_DIMER.BEFORE_AS2);
34
+ return {SS: arr1[0], AS1: arr2[0], AS2: arr2[1]};
24
35
  }
25
36
 
26
- function saltMass(saltNames: string[], molWeightCol: DG.Column, equivalentsCol: DG.Column, i: number,
27
- saltCol: DG.Column) {
28
- const saltRowIndex = saltNames.indexOf(saltCol.get(i));
29
- return (
30
- saltRowIndex == -1 || molWeightCol.get(saltRowIndex) == DG.FLOAT_NULL || equivalentsCol.get(i) == DG.INT_NULL) ?
31
- DG.FLOAT_NULL :
32
- molWeightCol.get(saltRowIndex) * equivalentsCol.get(i);
33
- }
34
-
35
- function saltMolWeigth(saltNamesList: string[], saltCol: DG.Column, molWeightCol: DG.Column, i: number) {
36
- const saltRowIndex = saltNamesList.indexOf(saltCol.get(i));
37
- return (saltRowIndex == -1) ? DG.FLOAT_NULL : molWeightCol.get(saltRowIndex);
38
- }
39
-
40
- function batchMolWeight(compoundMolWeightCol: DG.Column, saltMassCol: DG.Column, i: number) {
41
- return (compoundMolWeightCol.getString(i) == '' || saltMassCol.getString(i) == '') ?
42
- DG.FLOAT_NULL :
43
- compoundMolWeightCol.get(i) + saltMassCol.get(i);
44
- }
45
-
46
- function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
47
- const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
48
- let weight = 0;
49
- let i = 0;
50
- while (i < sequence.length) {
51
- const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
52
- weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
53
- i += matchedCode.length;
37
+ async function saveTableAsSdFile(table: DG.DataFrame) {
38
+ if (GENERATED_COL_NAMES.some((colName) => !table.columns.contains(colName))) {
39
+ const absentColNames = differenceOfTwoArrays(GENERATED_COL_NAMES, table.columns.names()).join(`', '`);
40
+ grok.shell.warning(`File saved without columns '${absentColNames}'`);
54
41
  }
55
- return weight - 61.97;
56
- }
57
42
 
58
- function parseStrandsFromDuplexCell(s: string): string[] {
59
- return s.slice(3).split('\r\nAS ');
60
- }
43
+ const sequenceCol = table.getCol(COL_NAMES.SEQUENCE);
44
+ const typeCol = table.getCol(COL_NAMES.TYPE);
61
45
 
62
- async function saveTableAsSdFile(table: DG.DataFrame) {
63
- if (!table.columns.contains('Compound Name')) {
64
- grok.shell.warning(
65
- 'File saved without columns \'' +
66
- [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
67
- COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
68
- );
69
- }
70
- const structureColumn = table.getCol(COL_NAMES.SEQUENCE);
71
- const typeColumn = table.getCol(COL_NAMES.TYPE);
72
46
  let result = '';
73
47
  for (let i = 0; i < table.rowCount; i++) {
74
- const format = 'Janssen GCRS Codes'; //getFormat(structureColumn.get(i))!;
75
- if (typeColumn.get(i) == 'Duplex') {
76
- const array = parseStrandsFromDuplexCell(structureColumn.get(i));
77
- const as = sequenceToMolV3000(array[1], true, true, format) +
78
- '\n' + `> <Sequence>\nAnti Sense\n\n`;
79
- const ss = sequenceToMolV3000(array[0], false, true, format) +
80
- '\n' + `> <Sequence>\nSense Strand\n\n`;
81
- result += linkV3000([ss, as], true, true) + '\n\n';
82
- } else if (typeColumn.get(i) == 'SS') {
83
- const molSS = sequenceToMolV3000(structureColumn.get(i), false, true, format) +
84
- '\n' + `> <Sequence>\nSense Strand\n\n`;
85
- result += molSS;
86
- } else if (typeColumn.get(i) == 'AS') {
87
- const molAS = sequenceToMolV3000(structureColumn.get(i), true, true, format) +
88
- '\n' + `> <Sequence>\nAnti Sense\n\n`;
89
- result += molAS;
48
+ const format = SYNTHESIZERS.GCRS; //getFormat(sequenceCol.get(i))!;
49
+ if (typeCol.get(i) == SEQUENCE_TYPES.SENSE_STRAND)
50
+ result += `${sequenceToMolV3000(sequenceCol.get(i), false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
51
+ else if (typeCol.get(i) == SEQUENCE_TYPES.ANTISENSE_STRAND)
52
+ result += `${sequenceToMolV3000(sequenceCol.get(i), true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
53
+ else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
54
+ const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
55
+ const as = `${sequenceToMolV3000(obj.AS, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
56
+ const ss = `${sequenceToMolV3000(obj.SS, false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
57
+ result += `${linkStrandsV3000({senseStrands: [ss], antiStrands: [as]}, true)}\n\n`;
58
+ } else if ([SEQUENCE_TYPES.TRIPLEX, SEQUENCE_TYPES.DIMER].includes(typeCol.get(i))) {
59
+ const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
60
+ const as1 = `${sequenceToMolV3000(obj.AS1, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
61
+ const as2 = `${sequenceToMolV3000(obj.AS2, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
62
+ const ss = `${sequenceToMolV3000(obj.SS, false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
63
+ result += `${linkStrandsV3000({senseStrands: [ss], antiStrands: [as1, as2]}, true)}\n\n`;
90
64
  }
91
65
 
92
66
  for (const col of table.columns) {
@@ -95,17 +69,16 @@ async function saveTableAsSdFile(table: DG.DataFrame) {
95
69
  }
96
70
  result += '$$$$\n';
97
71
  }
98
- const element = document.createElement('a');
99
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
100
- element.setAttribute('download', table.name + '.sdf');
101
- element.click();
72
+ download(`${table.name}.sdf`, encodeURIComponent(result));
102
73
  }
103
74
 
104
75
  export function autostartOligoSdFileSubscription() {
105
76
  grok.events.onViewAdded.subscribe((v: any) => {
106
- if (v.type == 'TableView') {
77
+ if (v.type == DG.VIEW_TYPE.TABLE_VIEW) {
107
78
  if (v.dataFrame.columns.contains(COL_NAMES.TYPE))
108
79
  oligoSdFile(v.dataFrame);
80
+
81
+ // Should be removed after fixing bug https://github.com/datagrok-ai/public/issues/808
109
82
  grok.events.onContextMenu.subscribe((args) => {
110
83
  const seqCol = args.args.context.table.currentCol; // /^[fsACGUacgu]{6,}$/
111
84
  if (DG.Detector.sampleCategories(seqCol,
@@ -166,56 +139,56 @@ export function oligoSdFile(table: DG.DataFrame) {
166
139
  const sequenceCol = table.getCol(COL_NAMES.SEQUENCE);
167
140
  const saltCol = table.getCol(COL_NAMES.SALT);
168
141
  const equivalentsCol = table.getCol(COL_NAMES.EQUIVALENTS);
169
- const typeColumn = table.getCol(COL_NAMES.TYPE);
142
+ const typeCol = table.getCol(COL_NAMES.TYPE);
170
143
  const chemistryNameCol = table.getCol(COL_NAMES.CHEMISTRY_NAME);
171
144
 
172
145
  const molWeightCol = saltsDf.getCol('MOLWEIGHT');
173
146
  const saltNamesList = saltsDf.getCol('DISPLAY').toList();
174
147
 
148
+ let newDf: DG.DataFrame;
149
+ let addColumnsPressed = false;
150
+
175
151
  function addColumns(t: DG.DataFrame) {
176
- if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
152
+ if (GENERATED_COL_NAMES.some((colName) => t.columns.contains(colName)))
177
153
  return grok.shell.error('Columns already exist');
178
154
 
179
- for (let i = t.rowCount - 1; i > -1; i--) {
180
- if (sequenceCol.get(i) == '')
181
- t.rows.removeAt(i, 1, false);
182
- }
155
+ t = removeEmptyRows(t, sequenceCol);
183
156
 
184
157
  t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => {
185
- return (typeColumn.get(i) == 'Duplex') ? chemistryNameCol.get(i) : sequenceCol.get(i);
158
+ return ([SEQUENCE_TYPES.DUPLEX, SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) ?
159
+ chemistryNameCol.get(i) :
160
+ sequenceCol.get(i);
186
161
  });
187
162
 
188
163
  t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => {
189
- if (typeColumn.get(i) == 'Duplex') {
190
- const arr = parseStrandsFromDuplexCell(sequenceCol.get(i));
191
- return chemistryNameCol.get(i) + '; duplex of SS: ' + arr[0] + ' and AS: ' + arr[1];
164
+ if ([SEQUENCE_TYPES.SENSE_STRAND, SEQUENCE_TYPES.ANTISENSE_STRAND].includes(typeCol.get(i)))
165
+ return sequenceCol.get(i);
166
+ else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
167
+ const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
168
+ return `${chemistryNameCol.get(i)}; duplex of SS: ${obj.SS} and AS: ${obj.AS}`;
169
+ } else if ([SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) {
170
+ const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
171
+ return `${chemistryNameCol.get(i)}; duplex of SS: ${obj.SS} and AS1: ${obj.AS1} and AS2: ${obj.AS2}`;
192
172
  }
193
- return sequenceCol.get(i);
194
173
  });
195
174
 
196
- const weightsObj: {[code: string]: number} = {};
197
- for (const synthesizer of Object.keys(map)) {
198
- for (const technology of Object.keys(map[synthesizer])) {
199
- for (const code of Object.keys(map[synthesizer][technology]))
200
- weightsObj[code] = map[synthesizer][technology][code].weight!;
201
- }
202
- }
203
- for (const [key, value] of Object.entries(MODIFICATIONS))
204
- weightsObj[key] = value.molecularWeight;
205
-
206
- t.columns.addNewFloat(COL_NAMES.CPD_MW).init((i: number) => {
207
- if (typeColumn.get(i) == 'Duplex') {
208
- const arr = parseStrandsFromDuplexCell(sequenceCol.get(i));
209
- return (
210
- isValidSequence(arr[0], null).indexOfFirstNotValidChar == -1 &&
211
- isValidSequence(arr[1], null).indexOfFirstNotValidChar == -1
212
- ) ?
213
- molecularWeight(arr[0], weightsObj) + molecularWeight(arr[1], weightsObj) :
175
+ t.columns.addNewFloat(COL_NAMES.COMPOUND_MOL_WEIGHT).init((i: number) => {
176
+ if ([SEQUENCE_TYPES.SENSE_STRAND, SEQUENCE_TYPES.ANTISENSE_STRAND].includes(typeCol.get(i))) {
177
+ return (isValidSequence(sequenceCol.get(i), null).indexOfFirstNotValidChar == -1) ?
178
+ molecularWeight(sequenceCol.get(i), weightsObj) :
179
+ DG.FLOAT_NULL;
180
+ } else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
181
+ const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
182
+ return (Object.values(obj).every((seq) => isValidSequence(seq, null).indexOfFirstNotValidChar == -1)) ?
183
+ molecularWeight(obj.SS, weightsObj) + molecularWeight(obj.AS, weightsObj) :
184
+ DG.FLOAT_NULL;
185
+ } else if ([SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) {
186
+ const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
187
+ return (Object.values(obj).every((seq) => isValidSequence(seq, null).indexOfFirstNotValidChar == -1)) ?
188
+ molecularWeight(obj.SS, weightsObj) + molecularWeight(obj.AS1, weightsObj) +
189
+ molecularWeight(obj.AS2, weightsObj) :
214
190
  DG.FLOAT_NULL;
215
191
  }
216
- return (isValidSequence(sequenceCol.get(i), null).indexOfFirstNotValidChar == -1) ?
217
- molecularWeight(sequenceCol.get(i), weightsObj) :
218
- DG.FLOAT_NULL;
219
192
  });
220
193
 
221
194
  t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) =>
@@ -224,15 +197,24 @@ export function oligoSdFile(table: DG.DataFrame) {
224
197
  t.columns.addNewFloat(COL_NAMES.SALT_MOL_WEIGHT).init((i: number) =>
225
198
  saltMolWeigth(saltNamesList, saltCol, molWeightCol, i));
226
199
 
227
- t.columns.addNewFloat(COL_NAMES.BATCH_MW).init((i: number) =>
228
- batchMolWeight(t.getCol(COL_NAMES.CPD_MW), t.getCol(COL_NAMES.SALT_MASS), i));
200
+ const compoundMolWeightCol = t.getCol(COL_NAMES.COMPOUND_MOL_WEIGHT);
201
+ const saltMassCol = t.getCol(COL_NAMES.SALT_MASS);
202
+ t.columns.addNewFloat(COL_NAMES.BATCH_MOL_WEIGHT).init((i: number) =>
203
+ batchMolWeight(compoundMolWeightCol, saltMassCol, i));
229
204
 
205
+ grok.shell.getTableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
230
206
  addColumnsPressed = true;
231
207
  return newDf = t;
232
208
  }
233
209
 
234
- let newDf: DG.DataFrame;
235
- let addColumnsPressed = false;
210
+ function updateCalculatedColumns(t: DG.DataFrame, i: number): void {
211
+ const smValue = saltMass(saltNamesList, molWeightCol, equivalentsCol, i, saltCol);
212
+ t.getCol(COL_NAMES.SALT_MASS).set(i, smValue, false);
213
+ const smwValue = saltMolWeigth(saltNamesList, saltCol, molWeightCol, i);
214
+ t.getCol(COL_NAMES.SALT_MOL_WEIGHT).set(i, smwValue, false);
215
+ const bmw = batchMolWeight(t.getCol(COL_NAMES.COMPOUND_MOL_WEIGHT), t.getCol(COL_NAMES.SALT_MASS), i);
216
+ t.getCol(COL_NAMES.BATCH_MOL_WEIGHT).set(i, bmw, false);
217
+ }
236
218
 
237
219
  const d = ui.div([
238
220
  ui.icons.edit(() => {
@@ -240,18 +222,15 @@ export function oligoSdFile(table: DG.DataFrame) {
240
222
  if (table.getCol(COL_NAMES.IDP).type != DG.COLUMN_TYPE.STRING)
241
223
  table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
242
224
  d.append(
243
- ui.link('Add Columns', () => {
244
- addColumns(table);
245
- view.grid.columns.setOrder(Object.values(COL_NAMES));
246
- }, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
247
- COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), '',
248
- ),
249
- ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
225
+ ui.divH([
226
+ ui.icons.add(() => addColumns(table), `Add columns: '${GENERATED_COL_NAMES.join(`', '`)}'`),
227
+ ui.icons.save(() => saveTableAsSdFile(addColumnsPressed ? newDf : table), 'Save SD file'),
228
+ ]),
250
229
  );
251
230
 
252
231
  const view = grok.shell.getTableView(table.name);
253
-
254
- view.dataFrame.getCol(COL_NAMES.TYPE).setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
232
+ view.grid.setOptions({rowHeight: 45});
233
+ view.dataFrame.getCol(COL_NAMES.TYPE).setTag(DG.TAGS.CHOICES, stringify(Object.values(SEQUENCE_TYPES)));
255
234
  view.dataFrame.getCol(COL_NAMES.OWNER).setTag(DG.TAGS.CHOICES, stringify(usersDf.columns.byIndex(0).toList()));
256
235
  view.dataFrame.getCol(COL_NAMES.SALT).setTag(DG.TAGS.CHOICES, stringify(saltsDf.columns.byIndex(0).toList()));
257
236
  view.dataFrame.getCol(COL_NAMES.SOURCE).setTag(DG.TAGS.CHOICES, stringify(sourcesDf.columns.byIndex(0).toList()));
@@ -281,8 +260,8 @@ export function oligoSdFile(table: DG.DataFrame) {
281
260
  t.getCol(COL_NAMES.SALT_MASS).set(i, smValue, false);
282
261
  const smwValue = saltMolWeigth(saltNamesList, saltCol, molWeightCol, i);
283
262
  t.getCol(COL_NAMES.SALT_MOL_WEIGHT).set(i, smwValue, false);
284
- const bmw = batchMolWeight(t.getCol(COL_NAMES.CPD_MW), t.getCol(COL_NAMES.SALT_MASS), i);
285
- t.getCol(COL_NAMES.BATCH_MW).set(i, bmw, false);
263
+ const bmw = batchMolWeight(t.getCol(COL_NAMES.COMPOUND_MOL_WEIGHT), t.getCol(COL_NAMES.SALT_MASS), i);
264
+ t.getCol(COL_NAMES.BATCH_MOL_WEIGHT).set(i, bmw, false);
286
265
  }
287
266
  }),
288
267
  ]);
File without changes
File without changes
File without changes
@@ -1,12 +1,12 @@
1
- const rnaColor = 'rgb(255,230,153)';
2
- const invAbasicColor = 'rgb(203,119,211)';
3
- export const axolabsMap:
1
+ const RNA_COLOR = 'rgb(255,230,153)';
2
+ const INVABASIC_COLOR = 'rgb(203,119,211)';
3
+ export const AXOLABS_MAP:
4
4
  {[index: string]: {fullName: string, symbols: [string, string, string, string], color: string}} =
5
5
  {
6
6
  'RNA': {
7
7
  fullName: 'RNA nucleotides',
8
8
  symbols: ['A', 'C', 'G', 'U'],
9
- color: rnaColor,
9
+ color: RNA_COLOR,
10
10
  },
11
11
  'DNA': {
12
12
  fullName: 'DNA nucleotides',
@@ -46,22 +46,22 @@ export const axolabsMap:
46
46
  'A': {
47
47
  fullName: 'Adenine',
48
48
  symbols: ['a', 'a', 'a', 'a'],
49
- color: rnaColor,
49
+ color: RNA_COLOR,
50
50
  },
51
51
  'C': {
52
52
  fullName: 'Cytosine',
53
53
  symbols: ['c', 'c', 'c', 'c'],
54
- color: rnaColor,
54
+ color: RNA_COLOR,
55
55
  },
56
56
  'G': {
57
57
  fullName: 'Guanine',
58
58
  symbols: ['g', 'g', 'g', 'g'],
59
- color: rnaColor,
59
+ color: RNA_COLOR,
60
60
  },
61
61
  'U': {
62
62
  fullName: 'Uracil',
63
63
  symbols: ['u', 'u', 'u', 'u'],
64
- color: rnaColor,
64
+ color: RNA_COLOR,
65
65
  },
66
66
  'X-New': {
67
67
  fullName: '',
@@ -81,7 +81,7 @@ export const axolabsMap:
81
81
  'InvAbasic': {
82
82
  fullName: 'Inverted abasic capped',
83
83
  symbols: ['(invabasic)', '(invabasic)', '(invabasic)', '(invabasic)'],
84
- color: invAbasicColor,
84
+ color: INVABASIC_COLOR,
85
85
  },
86
86
  "5\"-vinylps": {
87
87
  fullName: '5\'-vinylphosphonate-2\'-OMe-uridine',
@@ -91,7 +91,7 @@ export const axolabsMap:
91
91
  'InvAbasic(o)': {
92
92
  fullName: 'Inverted abasic capped (overhang)',
93
93
  symbols: ['(invabasic)', '(invabasic)', '(invabasic)', '(invabasic)'],
94
- color: invAbasicColor,
94
+ color: INVABASIC_COLOR,
95
95
  },
96
96
  "2\"-OMe-U(o)": {
97
97
  fullName: 'Nucleotide Uridine with 2\'O-Methyl protection (overhang)',
@@ -6,9 +6,10 @@ import * as svg from 'save-svg-as-png';
6
6
  import $ from 'cash-dom';
7
7
 
8
8
  import {drawAxolabsPattern} from './draw-svg';
9
- import {axolabsMap} from './constants';
9
+ import {AXOLABS_MAP} from './constants';
10
+ import {isOverhang} from './helpers';
10
11
 
11
- const baseChoices: string[] = Object.keys(axolabsMap);
12
+ const baseChoices: string[] = Object.keys(AXOLABS_MAP);
12
13
  const defaultBase: string = baseChoices[0];
13
14
  const defaultPto: boolean = true;
14
15
  const defaultSequenceLength: number = 23;
@@ -17,7 +18,7 @@ const userStorageKey: string = 'SequenceTranslator';
17
18
  const exampleMinWidth: string = '400px';
18
19
 
19
20
  function generateExample(sequenceLength: number, sequenceBasis: string): string {
20
- const uniqueSymbols = axolabsMap[sequenceBasis].symbols.join('');
21
+ const uniqueSymbols = AXOLABS_MAP[sequenceBasis].symbols.join('');
21
22
  return uniqueSymbols.repeat(Math.floor(sequenceLength / 4)) + uniqueSymbols.slice(0, sequenceLength % 4);
22
23
  }
23
24
 
@@ -64,12 +65,12 @@ function translateSequence(
64
65
  let i: number = -1;
65
66
  let mainSequence = sequence.replace(/[AUGC]/g, function(x: string) {
66
67
  i++;
67
- const indexOfSymbol = axolabsMap['RNA']['symbols'].indexOf(x);
68
- let symbol = axolabsMap[bases[i].value]['symbols'][indexOfSymbol];
69
- if (bases[i].value.slice(-3) == '(o)') {
70
- if (i < sequence.length / 2 && bases[i + 1].value.slice(-3) != '(o)')
68
+ const indexOfSymbol = AXOLABS_MAP['RNA']['symbols'].indexOf(x);
69
+ let symbol = AXOLABS_MAP[bases[i].value]['symbols'][indexOfSymbol];
70
+ if (isOverhang(bases[i].value)) {
71
+ if (i < sequence.length / 2 && !isOverhang(bases[i + 1].value))
71
72
  symbol = symbol + x + 'f';
72
- else if (i > sequence.length / 2 && bases[i - 1].value.slice(-3) != '(o)')
73
+ else if (i > sequence.length / 2 && !isOverhang(bases[i - 1].value))
73
74
  symbol = x + 'f' + symbol;
74
75
  }
75
76
  return (ptoLinkages[i].value) ? symbol + 's' : symbol;
@@ -150,12 +151,12 @@ export function defineAxolabsPattern() {
150
151
  updateSvgScheme();
151
152
  updateOutputExamples();
152
153
  });
153
- if (asBases[i].value.slice(-3) != '(o)')
154
+ if (!isOverhang(asBases[i].value))
154
155
  nucleotideCounter++;
155
156
 
156
157
  asModificationItems.append(
157
158
  ui.divH([
158
- ui.div([ui.label(asBases[i].value.slice(-3) == '(o)' ? '' : String(nucleotideCounter))],
159
+ ui.div([ui.label(isOverhang(asBases[i].value) ? '' : String(nucleotideCounter))],
159
160
  {style: {width: '20px'}})!,
160
161
  ui.block75([asBases[i]])!,
161
162
  ui.div([asPtoLinkages[i]])!,
@@ -196,12 +197,12 @@ export function defineAxolabsPattern() {
196
197
  updateSvgScheme();
197
198
  updateOutputExamples();
198
199
  });
199
- if (ssBases[i].value.slice(-3) != '(o)')
200
+ if (!isOverhang(ssBases[i].value))
200
201
  nucleotideCounter++;
201
202
 
202
203
  ssModificationItems.append(
203
204
  ui.divH([
204
- ui.div([ui.label(ssBases[i].value.slice(-3) == '(o)' ? '' : String(nucleotideCounter))],
205
+ ui.div([ui.label(isOverhang(ssBases[i].value) ? '' : String(nucleotideCounter))],
205
206
  {style: {width: '20px'}})!,
206
207
  ui.block75([ssBases[i]])!,
207
208
  ui.div([ssPtoLinkages[i]])!,