@datagrok/sequence-translator 1.3.1 → 1.3.3

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.
Files changed (55) hide show
  1. package/.eslintrc.json +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dist/package-test.js +1 -2
  4. package/dist/package-test.js.map +1 -1
  5. package/dist/package.js +1 -1
  6. package/dist/package.js.map +1 -1
  7. package/files/monomers-sample/README.md +12 -0
  8. package/files/samples/bulk-translation-axolabs.csv +6 -0
  9. package/files/tests/README.md +2 -0
  10. package/files/tests/axolabs1.csv +2 -0
  11. package/package.json +23 -13
  12. package/src/apps/common/model/data-loader/const.ts +1 -2
  13. package/src/apps/common/model/data-loader/json-loader.ts +22 -31
  14. package/src/apps/common/model/monomer-lib/lib-wrapper.ts +13 -19
  15. package/src/apps/common/model/oligo-toolkit-package.ts +80 -13
  16. package/src/apps/common/model/parsing-validation/format-detector.ts +9 -6
  17. package/src/apps/common/model/parsing-validation/format-handler.ts +16 -13
  18. package/src/apps/common/model/parsing-validation/sequence-validator.ts +8 -3
  19. package/src/apps/common/view/combined-app-ui.ts +12 -10
  20. package/src/apps/common/view/components/colored-input/input-painters.ts +4 -3
  21. package/src/apps/common/view/monomer-lib-viewer.ts +2 -1
  22. package/src/apps/common/view/utils.ts +5 -4
  23. package/src/apps/pattern/model/data-manager.ts +9 -6
  24. package/src/apps/pattern/model/translator.ts +8 -10
  25. package/src/apps/pattern/model/utils.ts +0 -1
  26. package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +0 -1
  27. package/src/apps/pattern/view/components/translation-examples-block.ts +2 -1
  28. package/src/apps/pattern/view/svg-utils/strands-block.ts +98 -7
  29. package/src/apps/pattern/view/svg-utils/svg-block-base.ts +4 -12
  30. package/src/apps/pattern/view/svg-utils/utils.ts +6 -4
  31. package/src/apps/pattern/view/ui.ts +4 -1
  32. package/src/apps/structure/model/monomer-code-parser.ts +15 -5
  33. package/src/apps/structure/model/oligo-structure.ts +17 -16
  34. package/src/apps/structure/model/sequence-to-molfile.ts +2 -1
  35. package/src/apps/structure/view/ui.ts +19 -11
  36. package/src/apps/translator/model/conversion-utils.ts +16 -11
  37. package/src/apps/translator/model/format-converter.ts +15 -5
  38. package/src/apps/translator/view/ui.ts +32 -20
  39. package/src/demo/demo-st-ui.ts +2 -2
  40. package/src/package-test.ts +9 -2
  41. package/src/package.ts +20 -18
  42. package/src/plugins/mermade.ts +11 -8
  43. package/src/tests/const.ts +7 -5
  44. package/src/tests/files-tests.ts +75 -0
  45. package/src/tests/formats-support.ts +12 -11
  46. package/src/tests/formats-to-helm.ts +12 -19
  47. package/src/tests/helm-to-nucleotides.ts +12 -6
  48. package/src/tests/utils.ts +20 -0
  49. package/src/types.ts +18 -0
  50. package/dist/package-test.js.LICENSE.txt +0 -8
  51. /package/files/{codes-to-symbols.json → monomers-sample/codes-to-symbols.json} +0 -0
  52. /package/files/{formats-to-helm.json → monomers-sample/formats-to-helm.json} +0 -0
  53. /package/files/{linkers.json → monomers-sample/linkers.json} +0 -0
  54. /package/files/{monomer-lib.json → monomers-sample/monomer-lib.json} +0 -0
  55. /package/files/{pattern-app-data.json → monomers-sample/pattern-app-data.json} +0 -0
@@ -11,8 +11,8 @@ import {
11
11
 
12
12
  import objectHash from 'object-hash';
13
13
  import {EventBus} from './event-bus';
14
- import {PATTERN_APP_DATA} from '../../common/model/data-loader/json-loader';
15
-
14
+ import {ITranslationHelper} from '../../../types';
15
+ import {_package} from '../../../package';
16
16
 
17
17
  export class DataManager {
18
18
  private currentUserName: string;
@@ -21,10 +21,13 @@ export class DataManager {
21
21
  private currentUserPatternNameToHash = new Map<string, string>();
22
22
 
23
23
  // WARNING: init logic encapsulated
24
- private constructor( ) { }
24
+ private constructor(
25
+ private readonly th: ITranslationHelper
26
+ ) { }
25
27
 
26
28
  static async getInstance(): Promise<DataManager> {
27
- const instance = new DataManager();
29
+ const th = await _package.getTranslationHelper();
30
+ const instance = new DataManager(th);
28
31
 
29
32
  instance.currentUserName = await instance.fetchCurrentUserName();
30
33
  instance.currentUserId = await instance.fetchCurrentUserId();
@@ -222,8 +225,8 @@ export class DataManager {
222
225
  }
223
226
 
224
227
  fetchAvailableNucleotideBases(): string[] {
225
- const format = Object.keys(PATTERN_APP_DATA)[0];
226
- const nucleotideBases: string[] = Object.keys(PATTERN_APP_DATA[format]);
228
+ const format = Object.keys(this.th.jsonData.patternAppData)[0];
229
+ const nucleotideBases: string[] = Object.keys(this.th.jsonData.patternAppData[format]);
227
230
  return nucleotideBases;
228
231
  }
229
232
 
@@ -1,7 +1,9 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import {STRAND, STRANDS, TERMINI, TERMINUS} from './const';
3
- import {PATTERN_APP_DATA} from '../../common/model/data-loader/json-loader';
4
3
  import {EventBus} from './event-bus';
4
+ import {ITranslationHelper} from '../../../types';
5
+ import {_package} from '../../../package';
6
+ import {JsonData} from '../../common/model/data-loader/json-loader';
5
7
 
6
8
  export function bulkTranslate(eventBus: EventBus): void {
7
9
  const df = eventBus.getTableSelection();
@@ -31,16 +33,12 @@ export function applyPatternToRawSequence(
31
33
  rawNucleotideSequence: string,
32
34
  modifications: string[],
33
35
  ptoFlags: boolean[],
34
- terminalModifications: Record<TERMINUS, string>
36
+ terminalModifications: Record<TERMINUS, string>,
35
37
  ): string {
36
38
  const rawNucleotides = rawNucleotideSequence.split('');
37
39
 
38
40
  const modifiedNucleotides = rawNucleotides.map((nucleotide, i) => {
39
- const modifiedNucleotide = getModifiedNucleotide(
40
- nucleotide,
41
- modifications[i]
42
-
43
- );
41
+ const modifiedNucleotide = getModifiedNucleotide(nucleotide, modifications[i], _package.jsonData);
44
42
  return modifiedNucleotide;
45
43
  });
46
44
 
@@ -51,9 +49,9 @@ export function applyPatternToRawSequence(
51
49
  return modificationsWithPTOLinkages.join('');
52
50
  }
53
51
 
54
- function getModifiedNucleotide(nucleotide: string, modification: string): string {
55
- const format = Object.keys(PATTERN_APP_DATA)[0];
56
- const substitution = PATTERN_APP_DATA[format][modification].substitution;
52
+ function getModifiedNucleotide(nucleotide: string, modification: string, jsonData: JsonData): string {
53
+ const format = Object.keys(jsonData.patternAppData)[0];
54
+ const substitution = jsonData.patternAppData[format][modification].substitution;
57
55
  return nucleotide.replace(/([AGCTU])/, substitution);
58
56
  }
59
57
 
@@ -1,7 +1,6 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
 
4
- import {PATTERN_APP_DATA} from '../../common/model/data-loader/json-loader';
5
4
  import {NucleotideSequences} from './types';
6
5
 
7
6
 
@@ -2,7 +2,6 @@ import * as ui from 'datagrok-api/ui';
2
2
 
3
3
  import $ from 'cash-dom';
4
4
 
5
- import {PATTERN_APP_DATA} from '../../../../common/model/data-loader/json-loader';
6
5
  import {STRAND, STRANDS, STRAND_LABEL} from '../../../model/const';
7
6
  import {EventBus} from '../../../model/event-bus';
8
7
  import {StrandType} from '../../../model/types';
@@ -15,6 +15,7 @@ import {SubscriptionManager} from '../../model/subscription-manager';
15
15
 
16
16
  export class TranslationExamplesBlock {
17
17
  private subscriptions = new SubscriptionManager();
18
+
18
19
  constructor(
19
20
  private eventBus: EventBus,
20
21
  private dataManager: DataManager
@@ -55,7 +56,7 @@ class StrandExample {
55
56
  constructor(
56
57
  private strand: STRAND,
57
58
  private eventBus: EventBus,
58
- private subscriptions: SubscriptionManager
59
+ private subscriptions: SubscriptionManager,
59
60
  ) { }
60
61
 
61
62
  create(): HTMLDivElement {
@@ -1,5 +1,5 @@
1
1
  import {NUCLEOTIDES} from '../../../common/model/const';
2
- import {STRAND, STRANDS, TERMINUS} from '../../model/const';
2
+ import {STRAND, STRANDS, TERMINI, TERMINUS} from '../../model/const';
3
3
  import {PatternConfiguration} from '../../model/types';
4
4
  import {isOverhangNucleotide} from '../../model/utils';
5
5
  import {SVG_CIRCLE_SIZES, SVG_ELEMENT_COLORS, SVG_TEXT_FONT_SIZES} from './const';
@@ -19,6 +19,7 @@ const RIGHT_LABEL_WIDTH = 20;
19
19
  export class StrandsBlock extends SVGBlockBase {
20
20
  private strands: SVGBlockBase[];
21
21
  private labels: SVGBlockBase[];
22
+ private terminalModifications: SVGBlockBase[];
22
23
  constructor(
23
24
  svgElementFactory: SVGElementFactory,
24
25
  config: PatternConfiguration,
@@ -30,16 +31,22 @@ export class StrandsBlock extends SVGBlockBase {
30
31
  this.strands = strandTypes
31
32
  .map((strand) => new SingleStrandBlock(this.svgElementFactory, config, yShift, strand));
32
33
 
34
+
35
+ this.terminalModifications = strandTypes.map(
36
+ (strandType, idx) =>
37
+ new TerminalModificationLabels(this.svgElementFactory, config, yShift, strandType, this.strands[idx] as SingleStrandBlock)
38
+ );
33
39
  this.labels = strandTypes.map(
34
40
  (strandType, idx) =>
35
- new StrandLabel(this.svgElementFactory, config, yShift, strandType, this.strands[idx] as SingleStrandBlock)
41
+ new StrandLabel(this.svgElementFactory, config, yShift, strandType, this.terminalModifications[idx] as TerminalModificationLabels)
36
42
  );
37
43
  }
38
44
 
39
45
  get svgElements(): SVGElement[] {
40
46
  const elements = [
41
47
  ...this.strands,
42
- ...this.labels
48
+ ...this.terminalModifications,
49
+ ...this.labels,
43
50
  ].map((block) => block.svgElements).flat();
44
51
  return elements;
45
52
  }
@@ -255,11 +262,10 @@ class StrandLabel extends SVGBlockBase {
255
262
  protected config: PatternConfiguration,
256
263
  protected yShift: number,
257
264
  private strand: STRAND,
258
- private strandSvgWrapper: SingleStrandBlock
265
+ private terminalModifications: TerminalModificationLabels
259
266
  ) {
260
267
  super(svgElementFactory, config, yShift);
261
268
  this._svgElements = this.createSVGElements();
262
- // this.strandSvgWrapper.shiftElements({x: this.getLeftLabelWidth(), y: 0});
263
269
  }
264
270
 
265
271
  private createSVGElements(): SVGElement[] {
@@ -300,7 +306,7 @@ class StrandLabel extends SVGBlockBase {
300
306
  const text = ` ${terminus}`;
301
307
  const textDimensions = TextDimensionsCalculator.getTextDimensions(text, SVG_TEXT_FONT_SIZES.NUCLEOBASE);
302
308
  const position = {
303
- x: SENSE_STRAND_HORIZONTAL_SHIFT + this.strandSvgWrapper.getContentWidth() + 5,
309
+ x: SENSE_STRAND_HORIZONTAL_SHIFT + this.terminalModifications.getContentWidth() + 5,
304
310
  y: getStrandCircleYShift(this.strand, this.yShift) + textDimensions.height / 3
305
311
  };
306
312
 
@@ -317,7 +323,92 @@ class StrandLabel extends SVGBlockBase {
317
323
  }
318
324
 
319
325
  getContentWidth(): number {
320
- return this.strandSvgWrapper.getContentWidth() + this.getLeftLabelWidth() + this.getRightLabelWidth() + SENSE_STRAND_PADDING;
326
+ return this.terminalModifications.getContentWidth() + this.getLeftLabelWidth() +
327
+ this.getRightLabelWidth() + SENSE_STRAND_PADDING;
328
+ }
329
+
330
+ getContentHeight(): number {
331
+ return this.terminalModifications.getContentHeight();
332
+ }
333
+ }
334
+
335
+ class TerminalModificationLabels extends SVGBlockBase {
336
+ private _svgElements: SVGElement[];
337
+ constructor(
338
+ protected svgElementFactory: SVGElementFactory,
339
+ protected config: PatternConfiguration,
340
+ protected yShift: number,
341
+ private strand: STRAND,
342
+ private strandSvgWrapper: SingleStrandBlock
343
+ ) {
344
+ super(svgElementFactory, config, yShift);
345
+ this._svgElements = this.createSVGElements();
346
+ }
347
+
348
+ private createSVGElements(): SVGElement[] {
349
+ const elements = this.createTerminalModifications();
350
+ return elements;
351
+ }
352
+
353
+ private getTerminalModification(terminus: TERMINUS): string {
354
+ const terminalModification = this.config.strandTerminusModifications[this.strand][terminus];
355
+ return terminalModification;
356
+ }
357
+
358
+ private getTerminalModificationTextDimensions(terminus: TERMINUS): {width: number, height: number} {
359
+ const terminalModification = this.getTerminalModification(terminus);
360
+ const textDimensions = TextDimensionsCalculator
361
+ .getTextDimensions(terminalModification, SVG_TEXT_FONT_SIZES.NUCLEOBASE);
362
+ return textDimensions;
363
+ }
364
+
365
+ private getLeftTerminus(): TERMINUS {
366
+ return this.strand === STRAND.SENSE ? TERMINUS.FIVE_PRIME : TERMINUS.THREE_PRIME;
367
+ }
368
+
369
+ private createTerminalModification(terminus: TERMINUS): SVGTextElement {
370
+ const terminalModification = this.getTerminalModification(terminus);
371
+ const dimensions = this.getTerminalModificationTextDimensions(terminus);
372
+
373
+ const isLeft = terminus === this.getLeftTerminus();
374
+ const xShift = isLeft ? SENSE_STRAND_HORIZONTAL_SHIFT :
375
+ SENSE_STRAND_HORIZONTAL_SHIFT +
376
+ this.getTerminalModificationTextDimensions(this.getLeftTerminus()).width +
377
+ this.strandSvgWrapper.getContentWidth();
378
+ const position = {
379
+ x: xShift,
380
+ y: getStrandCircleYShift(this.strand, this.yShift) + dimensions.height / 3
381
+ };
382
+ if (isLeft) {
383
+ this.strandSvgWrapper.shiftElements({
384
+ x: dimensions.width,
385
+ y: 0
386
+ });
387
+ }
388
+
389
+ return this.svgElementFactory.createTextElement(
390
+ terminalModification,
391
+ position,
392
+ SVG_TEXT_FONT_SIZES.NUCLEOBASE,
393
+ SVG_ELEMENT_COLORS.MODIFICATION_TEXT
394
+ );
395
+ }
396
+
397
+ private createTerminalModifications(): SVGTextElement[] {
398
+ const termini = (this.strand === STRAND.ANTISENSE) ? TERMINI : Array.from(TERMINI).reverse();
399
+ const textElements = termini.map((terminus) => this.createTerminalModification(terminus));
400
+ return textElements;
401
+ }
402
+
403
+ get svgElements(): SVGElement[] {
404
+ return this._svgElements;
405
+ }
406
+
407
+ getContentWidth(): number {
408
+ return this.strandSvgWrapper.getContentWidth() +
409
+ TERMINI
410
+ .map((terminus) => this.getTerminalModificationTextDimensions(terminus).width)
411
+ .reduce((acc, curr) => acc += curr, 0);
321
412
  }
322
413
 
323
414
  getContentHeight(): number {
@@ -17,21 +17,13 @@ export abstract class SVGBlockBase {
17
17
  shiftElements(shift: {x: number, y: number}): void {
18
18
  this.svgElements.forEach((element) => {
19
19
  const transform = element.getAttribute('transform') || '';
20
- const match = transform.match(/translate\(([^,]+),([^,]+)\)/);
21
- const x = match ? parseFloat(match[1]) : 0;
22
- const y = match ? parseFloat(match[2]) : 0;
23
- const newTransform = `translate(${x + shift.x},${y + shift.y})`;
20
+ // const match = transform.match(/translate\(([^,]+),([^,]+)\)/);
21
+ // const x = match ? parseFloat(match[1]) : 0;
22
+ // const y = match ? parseFloat(match[2]) : 0;
23
+ const newTransform = `translate(${shift.x},${shift.y})`;
24
24
  element.setAttribute('transform', `${transform} ${newTransform}`);
25
25
  });
26
26
  }
27
27
 
28
- adjustContentWithinGlobalContainer(globalWidth: number): void {
29
- const contentWidth = this.getContentWidth();
30
- if (contentWidth < globalWidth) {
31
- const shift = (globalWidth - contentWidth) / 2;
32
- this.shiftElements({x: shift, y: 0});
33
- }
34
- }
35
-
36
28
  abstract getContentWidth(): number;
37
29
  }
@@ -1,8 +1,10 @@
1
+ import {PATTERN_APP_DATA_FILENAME} from '../../../common/model/data-loader/const';
1
2
  import {NUCLEOTIDES} from '../../../common/model/const';
2
- import {PATTERN_APP_DATA} from '../../../common/model/data-loader/json-loader';
3
3
  import {LUMINANCE_COEFFICIENTS, TEXT_COLOR, SVG_CIRCLE_SIZES} from './const';
4
4
  import {isOverhangNucleotide} from '../../model/utils';
5
5
 
6
+ import {_package} from '../../../../package';
7
+
6
8
  export function computeLegendCircleYPosition(isAntisenseStrandActive: boolean): number {
7
9
  return (isAntisenseStrandActive ? 9.5 : 6) * SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS;
8
10
  }
@@ -28,10 +30,10 @@ export function computeTextColorForNucleobaseLabel(nucleobase: string): string {
28
30
 
29
31
  export function getNucleobaseColorFromStyleMap(nucleobase: string): string {
30
32
  // todo: optimize
31
- const format = Object.keys(PATTERN_APP_DATA)[0];
33
+ const format = Object.keys(_package.jsonData.patternAppData)[0];
32
34
  if (!format)
33
- throw new Error('No format found in PATTERN_APP_DATA');
35
+ throw new Error(`No format found in '${PATTERN_APP_DATA_FILENAME}'`);
34
36
 
35
- const styleMap = PATTERN_APP_DATA[format];
37
+ const styleMap = _package.jsonData.patternAppData[format];
36
38
  return styleMap[nucleobase].color || '';
37
39
  }
@@ -8,10 +8,13 @@ import {URLRouter} from '../model/router';
8
8
  import {PatternAppLeftSection} from './components/left-section';
9
9
  import {PatternAppRightSection} from './components/right-section';
10
10
  import {PatternConfigRecord} from '../model/types';
11
+ import {ITranslationHelper} from '../../../types';
11
12
 
12
13
 
13
14
  export class OligoPatternUI extends IsolatedAppUIBase {
14
- constructor() {
15
+ constructor(
16
+ private readonly th: ITranslationHelper
17
+ ) {
15
18
  super(APP_NAME.PATTERN);
16
19
  }
17
20
 
@@ -5,8 +5,14 @@ import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {PHOSPHATE_SYMBOL} from './const';
7
7
  import {sortByReverseLength} from '../../common/model/helpers';
8
- import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
9
- import {MONOMERS_WITH_PHOSPHATE} from '../../common/model/data-loader/json-loader';
8
+
9
+ import {_package} from '../../../package';
10
+
11
+ export class MonomerNotFoundError extends Error {
12
+ constructor(message?: string, options?: ErrorOptions) {
13
+ super(message, options);
14
+ }
15
+ }
10
16
 
11
17
  /** Wrapper for parsing a strand and getting a sequence of monomer IDs (with
12
18
  * omitted linkers, if needed) */
@@ -61,6 +67,10 @@ export class MonomerSequenceParser {
61
67
  const code = allCodesOfFormat.find(
62
68
  (s: string) => s === this.sequence.substring(i, i + s.length)
63
69
  )!;
70
+ if (code === undefined) {
71
+ throw new MonomerNotFoundError(
72
+ `Unable to match a monomer for the rest of the sequence '${this.sequence.slice(i)}'.`);
73
+ }
64
74
  parsedCodes.push(code);
65
75
  i += code.length;
66
76
  }
@@ -76,13 +86,13 @@ export class MonomerSequenceParser {
76
86
 
77
87
  // todo: to be eliminated after full helm support
78
88
  function monomerHasLeftPhosphateLinker(monomerSymbol: string): boolean {
79
- return MONOMERS_WITH_PHOSPHATE['left'].includes(monomerSymbol);
89
+ return _package.jsonData.monomersWithPhosphate['left'].includes(monomerSymbol);
80
90
  }
81
91
 
82
92
  function monomerHasRightPhosphateLinker(monomerSymbol: string): boolean {
83
- return MONOMERS_WITH_PHOSPHATE['right'].includes(monomerSymbol);
93
+ return _package.jsonData.monomersWithPhosphate['right'].includes(monomerSymbol);
84
94
  }
85
95
 
86
96
  function monomerIsPhosphateLinker(monomerSymbol: string): boolean {
87
- return MONOMERS_WITH_PHOSPHATE['phosphate'].includes(monomerSymbol);
97
+ return _package.jsonData.monomersWithPhosphate['phosphate'].includes(monomerSymbol);
88
98
  }
@@ -7,8 +7,7 @@ import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
7
7
  import {download} from '../../common/model/helpers';
8
8
  import {SequenceToMolfileConverter} from './sequence-to-molfile';
9
9
  import {linkStrandsV3000} from './mol-transformations';
10
- import {DEFAULT_FORMATS} from '../../common/model/const';
11
- import {FormatDetector} from '../../common/model/parsing-validation/format-detector';
10
+ import {ITranslationHelper} from '../../../types';
12
11
 
13
12
  export type StrandData = {
14
13
  strand: string,
@@ -16,10 +15,10 @@ export type StrandData = {
16
15
  }
17
16
 
18
17
  /** Get a molfile for a single strand */
19
- export function getMolfileForStrand(strand: string, invert: boolean): string {
18
+ export function getMolfileForStrand(strand: string, invert: boolean, th: ITranslationHelper): string {
20
19
  if (strand === '')
21
20
  return '';
22
- const format = (new FormatDetector(strand)).getFormat();
21
+ const format = th.createFormatDetector(strand).getFormat();
23
22
  if (!format)
24
23
  return '';
25
24
  let molfile = '';
@@ -34,15 +33,15 @@ export function getMolfileForStrand(strand: string, invert: boolean): string {
34
33
 
35
34
  /** Get molfile for single strand or linked strands */
36
35
  export function getLinkedMolfile(
37
- ss: StrandData, as: StrandData, as2: StrandData, useChiral: boolean
36
+ ss: StrandData, as: StrandData, as2: StrandData, useChiral: boolean, th: ITranslationHelper
38
37
  ): string {
39
38
  const nonEmptyStrands = [ss, as, as2].filter((item) => item.strand !== '');
40
39
  if (nonEmptyStrands.length === 1) {
41
- return getMolfileForStrand(nonEmptyStrands[0].strand, nonEmptyStrands[0].invert);
40
+ return getMolfileForStrand(nonEmptyStrands[0].strand, nonEmptyStrands[0].invert, th);
42
41
  } else {
43
- const ssMol = getMolfileForStrand(ss.strand, ss.invert);
44
- const asMol = getMolfileForStrand(as.strand, as.invert);
45
- const as2Mol = getMolfileForStrand(as2.strand, as2.invert);
42
+ const ssMol = getMolfileForStrand(ss.strand, ss.invert, th);
43
+ const asMol = getMolfileForStrand(as.strand, as.invert, th);
44
+ const as2Mol = getMolfileForStrand(as2.strand, as2.invert, th);
46
45
 
47
46
  // select only the non-empty anti-strands
48
47
  const antiStrands = [asMol, as2Mol].filter((item) => item !== '');
@@ -54,8 +53,8 @@ export function getLinkedMolfile(
54
53
 
55
54
  /** Save sdf in case ss and as (and optionally as2) strands entered */
56
55
  export function saveSdf(
57
- ss: StrandData, as: StrandData, as2: StrandData, useChiral: boolean,
58
- oneEntity: boolean
56
+ ss: StrandData, as: StrandData, as2: StrandData, useChiral: boolean, oneEntity: boolean,
57
+ th: ITranslationHelper
59
58
  ): void {
60
59
  const nonEmptyStrands = [ss.strand, as.strand, as2.strand].filter((item) => item !== '');
61
60
  if (
@@ -66,16 +65,16 @@ export function saveSdf(
66
65
  } else {
67
66
  let result: string;
68
67
  if (oneEntity) {
69
- result = getLinkedMolfile(ss, as, as2, useChiral) + '\n$$$$\n';
68
+ result = getLinkedMolfile(ss, as, as2, useChiral, th) + '\n$$$$\n';
70
69
  } else {
71
- const ssMol = getMolfileForStrand(ss.strand, ss.invert);
72
- const asMol = getMolfileForStrand(as.strand, as.invert);
73
- const as2Mol = getMolfileForStrand(as2.strand, as2.invert);
70
+ const ssMol = getMolfileForStrand(ss.strand, ss.invert, th);
71
+ const asMol = getMolfileForStrand(as.strand, as.invert, th);
72
+ const as2Mol = getMolfileForStrand(as2.strand, as2.invert, th);
74
73
  result = ssMol + '\n' +
75
74
  `> <Sequence>\nSense Strand\n$$$$\n`;
76
75
  if (asMol) {
77
76
  result += asMol + '\n' +
78
- `> <Sequence>\nAnti Sense\n$$$$\n`;
77
+ `> <Sequence>\nAnti Sense\n$$$$\n`;
79
78
  }
80
79
  if (as2Mol) {
81
80
  result += as2Mol + '\n' +
@@ -85,9 +84,11 @@ export function saveSdf(
85
84
 
86
85
  // construct date-time in the form yyyy-mm-dd_hh-mm-ss
87
86
  const date = new Date();
87
+
88
88
  function pad(x: number): string {
89
89
  return (x >= 10) ? x.toString() : '0' + x.toString();
90
90
  }
91
+
91
92
  const dateString: string = date.getFullYear() + '-' + pad(date.getMonth() + 1) +
92
93
  '-' + pad(date.getDate()) + '_' + pad(date.getHours()) + '-' +
93
94
  pad(date.getMinutes()) + '-' + pad(date.getSeconds());
@@ -5,12 +5,13 @@ import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {MonomerSequenceParser} from './monomer-code-parser';
7
7
  import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
8
+ import {_package} from '../../../package';
8
9
 
9
10
  export class SequenceToMolfileConverter {
10
11
  constructor(
11
12
  sequence: string, private invert: boolean = false, format: string
12
13
  ) {
13
- this.lib = MonomerLibWrapper.getInstance();
14
+ this.lib = _package.monomerLibWrapper;
14
15
  const codeToSymbolMap = this.lib.getCodeToSymbolMap(format);
15
16
  this.parser = new MonomerSequenceParser(sequence, codeToSymbolMap);
16
17
  }
@@ -10,11 +10,13 @@ import './style.css';
10
10
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
11
11
 
12
12
  import {ColoredTextInput} from '../../common/view/components/colored-input/colored-text-input';
13
- import {highlightInvalidSubsequence} from '../../common/view/components/colored-input/input-painters';
14
13
  import {MoleculeImage} from '../../common/view/components/molecule-img';
15
14
  import {APP_NAME} from '../../common/view/const';
16
15
  import {IsolatedAppUIBase} from '../../common/view/isolated-app-ui';
17
16
  import {getLinkedMolfile, saveSdf, StrandData} from '../model/oligo-structure';
17
+ import {ITranslationHelper} from '../../../types';
18
+
19
+ import {_package} from '../../../package';
18
20
 
19
21
  const enum DIRECTION {
20
22
  STRAIGHT = '5′ → 3′',
@@ -23,7 +25,10 @@ const enum DIRECTION {
23
25
  const STRANDS = ['ss', 'as', 'as2'] as const;
24
26
 
25
27
  class StructureAppLayout {
28
+ private readonly th: ITranslationHelper;
29
+
26
30
  constructor() {
31
+ this.th = _package;
27
32
  this.onInput = new rxjs.Subject<string>();
28
33
  this.onInvalidInput = new rxjs.Subject<string>();
29
34
  this.inputBase = Object.fromEntries(
@@ -53,12 +58,12 @@ class StructureAppLayout {
53
58
  private onInvalidInput: rxjs.Subject<string>;
54
59
  private useChiralInput: DG.InputBase<boolean | null>;
55
60
  private saveAllStrandsInput: DG.InputBase<boolean | null>;
56
- private inputBase: {[key: string]: DG.InputBase<string>};
57
- private directionInversion: {[key: string]: boolean};
61
+ private inputBase: { [key: string]: DG.InputBase<string> };
62
+ private directionInversion: { [key: string]: boolean };
58
63
  private moleculeImgDiv: HTMLDivElement;
59
64
 
60
- async getHtmlDivElement(): Promise<HTMLDivElement> {
61
- const tableLayout = this.getTableInput();
65
+ async getHtmlDivElement(th: ITranslationHelper): Promise<HTMLDivElement> {
66
+ const tableLayout = this.getTableInput(th);
62
67
  const boolInputsAndButton = this.getBoolInputsAndButton();
63
68
  await this.updateMoleculeImg();
64
69
  const bottomDiv = ui.divH([boolInputsAndButton, this.moleculeImgDiv]);
@@ -75,7 +80,7 @@ class StructureAppLayout {
75
80
  ui.bigButton('Save SDF', () => {
76
81
  const strandData = this.getStrandData();
77
82
  saveSdf(strandData.ss, strandData.as, strandData.as2,
78
- this.useChiralInput.value!, this.saveAllStrandsInput.value!);
83
+ this.useChiralInput.value!, this.saveAllStrandsInput.value!, this.th);
79
84
  })
80
85
  ]);
81
86
 
@@ -86,10 +91,10 @@ class StructureAppLayout {
86
91
  return boolInputsAndButton;
87
92
  }
88
93
 
89
- private getTableInput(): HTMLTableElement {
94
+ private getTableInput(th: ITranslationHelper): HTMLTableElement {
90
95
  const coloredInput = Object.fromEntries(
91
96
  STRANDS.map(
92
- (key) => [key, new ColoredTextInput(this.inputBase[key], highlightInvalidSubsequence)]
97
+ (key) => [key, new ColoredTextInput(this.inputBase[key], th.highlightInvalidSubsequence)]
93
98
  )
94
99
  );
95
100
 
@@ -181,7 +186,7 @@ class StructureAppLayout {
181
186
  // return '';
182
187
  // }
183
188
 
184
- return getLinkedMolfile(ss, as, as2, this.useChiralInput.value!);
189
+ return getLinkedMolfile(ss, as, as2, this.useChiralInput.value!, this.th);
185
190
  }
186
191
 
187
192
  private async updateMoleculeImg(): Promise<void> {
@@ -205,13 +210,16 @@ class StructureAppLayout {
205
210
  }
206
211
 
207
212
  export class OligoStructureUI extends IsolatedAppUIBase {
208
- constructor() {
213
+ constructor(
214
+ private readonly th: ITranslationHelper
215
+ ) {
209
216
  super(APP_NAME.STRUCTURE);
210
217
  this.layout = new StructureAppLayout();
211
218
  }
219
+
212
220
  private readonly layout: StructureAppLayout;
213
221
 
214
222
  protected getContent(): Promise<HTMLDivElement> {
215
- return this.layout.getHtmlDivElement();
223
+ return this.layout.getHtmlDivElement(this.th);
216
224
  }
217
225
  }
@@ -1,12 +1,14 @@
1
1
  import {DEFAULT_FORMATS, NUCLEOTIDES} from '../../common/model/const';
2
2
  import {NUCLEOTIDES_FORMAT} from '../view/const';
3
3
  import {UNKNOWN_SYMBOL} from './const';
4
- import {FormatConverter} from './format-converter';
5
- import {CODES_TO_HELM_DICT} from '../../common/model/data-loader/json-loader';
6
4
  import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
7
5
 
8
- export function getTranslatedSequences(sequence: string, indexOfFirstInvalidChar: number, sourceFormat: string): {[key: string]: string} {
9
- const supportedFormats = Object.keys(CODES_TO_HELM_DICT).concat([DEFAULT_FORMATS.HELM]) as string[];
6
+ import {ITranslationHelper} from '../../../types';
7
+
8
+ export function getTranslatedSequences(
9
+ sequence: string, indexOfFirstInvalidChar: number, sourceFormat: string, th: ITranslationHelper
10
+ ): { [key: string]: string } {
11
+ const supportedFormats = Object.keys(th.jsonData.codesToHelmDict).concat([DEFAULT_FORMATS.HELM]) as string[];
10
12
 
11
13
  if (!sequence || (indexOfFirstInvalidChar !== -1 && sourceFormat !== DEFAULT_FORMATS.HELM))
12
14
  return {};
@@ -16,7 +18,7 @@ export function getTranslatedSequences(sequence: string, indexOfFirstInvalidChar
16
18
 
17
19
  const outputFormats = supportedFormats.filter((el) => el != sourceFormat)
18
20
  .sort((a, b) => a.localeCompare(b));
19
- const converter = new FormatConverter(sequence, sourceFormat);
21
+ const converter = th.createFormatConverter(sequence, sourceFormat);
20
22
  const result = Object.fromEntries(
21
23
  outputFormats.map((format) => {
22
24
  let translation;
@@ -29,7 +31,7 @@ export function getTranslatedSequences(sequence: string, indexOfFirstInvalidChar
29
31
  }).filter(([_, translation]) => translation)
30
32
  );
31
33
  const helm = (sourceFormat === DEFAULT_FORMATS.HELM) ? sequence : result[DEFAULT_FORMATS.HELM];
32
- const nucleotides = getNucleotidesSequence(helm, MonomerLibWrapper.getInstance());
34
+ const nucleotides = getNucleotidesSequence(helm, th.monomerLibWrapper);
33
35
  if (nucleotides)
34
36
  result['Nucleotides'] = nucleotides;
35
37
  return result;
@@ -50,18 +52,21 @@ export function getNucleotidesSequence(helmString: string, monomerLib: MonomerLi
50
52
  }
51
53
 
52
54
  // todo: remove after refactoring as a workaround
53
- export function convert(sequence: string, sourceFormat: string, targetFormat: string): string | null {
54
- const converter = new FormatConverter(sequence, sourceFormat);
55
+ export function convert(
56
+ sequence: string, sourceFormat: string, targetFormat: string, th: ITranslationHelper
57
+ ): string | null {
58
+ const converter = th.createFormatConverter(sequence, sourceFormat);
55
59
  if (targetFormat === NUCLEOTIDES_FORMAT) {
56
60
  const helm = converter.convertTo(DEFAULT_FORMATS.HELM);
57
- const nucleotides = getNucleotidesSequence(helm, MonomerLibWrapper.getInstance());
61
+ const nucleotides = getNucleotidesSequence(helm, th.monomerLibWrapper);
58
62
  return nucleotides;
59
63
  }
60
64
 
61
65
  return converter.convertTo(targetFormat);
62
66
  }
63
67
 
64
- export function getSupportedTargetFormats(): string[] {
65
- const supportedTargetFormats = Object.keys(CODES_TO_HELM_DICT).concat([DEFAULT_FORMATS.HELM, NUCLEOTIDES_FORMAT]).sort() as string[];
68
+ export function getSupportedTargetFormats(th: ITranslationHelper): string[] {
69
+ const supportedTargetFormats = Object.keys(th.jsonData.codesToHelmDict)
70
+ .concat([DEFAULT_FORMATS.HELM, NUCLEOTIDES_FORMAT]).sort() as string[];
66
71
  return supportedTargetFormats;
67
72
  }