@datagrok/sequence-translator 1.2.7 → 1.3.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.
Files changed (107) hide show
  1. package/.eslintrc.json +5 -5
  2. package/CHANGELOG.md +14 -0
  3. package/dist/package-test.js +2 -1
  4. package/dist/package-test.js.LICENSE.txt +8 -0
  5. package/dist/package-test.js.map +1 -1
  6. package/dist/package.js +2 -1
  7. package/dist/package.js.LICENSE.txt +8 -0
  8. package/dist/package.js.map +1 -1
  9. package/files/pattern-app-data.json +80 -0
  10. package/package.json +22 -14
  11. package/src/{model → apps/common/model}/const.ts +1 -1
  12. package/src/{model/data-loading-utils → apps/common/model/data-loader}/const.ts +7 -2
  13. package/src/apps/common/model/data-loader/json-loader.ts +48 -0
  14. package/src/{model/data-loading-utils → apps/common/model/data-loader}/types.ts +13 -6
  15. package/src/{model → apps/common/model}/monomer-lib/lib-wrapper.ts +9 -12
  16. package/src/apps/common/model/oligo-toolkit-package.ts +30 -0
  17. package/src/{model → apps/common/model}/parsing-validation/format-detector.ts +5 -5
  18. package/src/{model → apps/common/model}/parsing-validation/format-handler.ts +18 -19
  19. package/src/{model → apps/common/model}/parsing-validation/sequence-validator.ts +1 -1
  20. package/src/apps/common/view/app-ui-base.ts +28 -0
  21. package/src/apps/common/view/combined-app-ui.ts +66 -0
  22. package/src/{view/utils → apps/common/view/components}/colored-input/colored-text-input.ts +1 -1
  23. package/src/{view/utils → apps/common/view/components}/draw-molecule.ts +1 -1
  24. package/src/{view/utils → apps/common/view/components}/molecule-img.ts +3 -3
  25. package/src/{view/const/ui.ts → apps/common/view/const.ts} +4 -4
  26. package/src/apps/common/view/isolated-app-ui.ts +43 -0
  27. package/src/{view/monomer-lib-viewer/viewer.ts → apps/common/view/monomer-lib-viewer.ts} +2 -2
  28. package/src/apps/common/view/utils.ts +29 -0
  29. package/src/apps/pattern/model/const.ts +121 -0
  30. package/src/apps/pattern/model/data-manager.ts +297 -0
  31. package/src/apps/pattern/model/event-bus.ts +487 -0
  32. package/src/apps/pattern/model/router.ts +46 -0
  33. package/src/apps/pattern/model/subscription-manager.ts +21 -0
  34. package/src/apps/pattern/model/translator.ts +94 -0
  35. package/src/apps/pattern/model/types.ts +52 -0
  36. package/src/apps/pattern/model/utils.ts +110 -0
  37. package/src/apps/pattern/view/components/bulk-convert/column-input.ts +79 -0
  38. package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +38 -0
  39. package/src/apps/pattern/view/components/bulk-convert/table-input.ts +95 -0
  40. package/src/apps/pattern/view/components/edit-block-controls.ts +196 -0
  41. package/src/apps/pattern/view/components/left-section.ts +44 -0
  42. package/src/apps/pattern/view/components/load-block-controls.ts +200 -0
  43. package/src/apps/pattern/view/components/numeric-label-visibility-controls.ts +69 -0
  44. package/src/apps/pattern/view/components/right-section.ts +148 -0
  45. package/src/apps/pattern/view/components/strand-editor/dialog.ts +79 -0
  46. package/src/apps/pattern/view/components/strand-editor/header-controls.ts +105 -0
  47. package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +159 -0
  48. package/src/apps/pattern/view/components/terminal-modification-editor.ts +127 -0
  49. package/src/apps/pattern/view/components/translation-examples-block.ts +139 -0
  50. package/src/{view/style/pattern-app.css → apps/pattern/view/style.css} +4 -0
  51. package/src/apps/pattern/view/svg-utils/const.ts +77 -0
  52. package/src/apps/pattern/view/svg-utils/legend-block.ts +92 -0
  53. package/src/apps/pattern/view/svg-utils/strands-block.ts +335 -0
  54. package/src/apps/pattern/view/svg-utils/svg-block-base.ts +37 -0
  55. package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +44 -0
  56. package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +94 -0
  57. package/src/apps/pattern/view/svg-utils/svg-renderer.ts +51 -0
  58. package/src/apps/pattern/view/svg-utils/text-dimensions-calculator.ts +29 -0
  59. package/src/apps/pattern/view/svg-utils/title-block.ts +53 -0
  60. package/src/apps/pattern/view/svg-utils/utils.ts +37 -0
  61. package/src/apps/pattern/view/types.ts +14 -0
  62. package/src/apps/pattern/view/ui.ts +61 -0
  63. package/src/{model/structure-app → apps/structure/model}/mol-transformations.ts +3 -3
  64. package/src/{model/structure-app → apps/structure/model}/monomer-code-parser.ts +9 -10
  65. package/src/{model/structure-app → apps/structure/model}/oligo-structure.ts +4 -4
  66. package/src/{model/structure-app → apps/structure/model}/sequence-to-molfile.ts +2 -2
  67. package/src/{view/apps/oligo-structure.ts → apps/structure/view/ui.ts} +31 -17
  68. package/src/{model/translator-app → apps/translator/model}/conversion-utils.ts +25 -7
  69. package/src/{model/translator-app → apps/translator/model}/format-converter.ts +7 -12
  70. package/src/{view/const/oligo-translator.ts → apps/translator/view/const.ts} +1 -1
  71. package/src/{view/apps/oligo-translator.ts → apps/translator/view/ui.ts} +88 -42
  72. package/src/demo/demo-st-ui.ts +12 -32
  73. package/src/package.ts +91 -55
  74. package/src/plugins/mermade.ts +9 -9
  75. package/src/polytool/const.ts +28 -0
  76. package/src/polytool/csv-to-json-monomer-lib-converter.ts +40 -0
  77. package/src/polytool/cyclized.ts +56 -0
  78. package/src/polytool/monomer-lib-handler.ts +115 -0
  79. package/src/polytool/pt-conversion.ts +307 -0
  80. package/src/polytool/pt-dialog.ts +115 -0
  81. package/src/polytool/pt-enumeration.ts +127 -0
  82. package/src/polytool/pt-rules.ts +73 -0
  83. package/src/polytool/utils.ts +27 -0
  84. package/src/tests/const.ts +5 -5
  85. package/src/tests/formats-support.ts +6 -6
  86. package/src/tests/formats-to-helm.ts +5 -5
  87. package/src/tests/helm-to-nucleotides.ts +5 -10
  88. package/tsconfig.json +3 -9
  89. package/webpack.config.js +3 -0
  90. package/files/axolabs-style.json +0 -97
  91. package/src/model/data-loading-utils/json-loader.ts +0 -38
  92. package/src/model/pattern-app/const.ts +0 -33
  93. package/src/model/pattern-app/draw-svg.ts +0 -193
  94. package/src/model/pattern-app/helpers.ts +0 -96
  95. package/src/model/pattern-app/oligo-pattern.ts +0 -111
  96. package/src/view/app-ui.ts +0 -193
  97. package/src/view/apps/oligo-pattern.ts +0 -759
  98. /package/src/{model → apps/common/model}/helpers.ts +0 -0
  99. /package/src/{model → apps/common/model}/monomer-lib/const.ts +0 -0
  100. /package/src/{view/utils → apps/common/view/components}/app-info-dialog.ts +0 -0
  101. /package/src/{view/utils → apps/common/view/components}/colored-input/input-painters.ts +0 -0
  102. /package/src/{view/style/colored-text-input.css → apps/common/view/components/colored-input/style.css} +0 -0
  103. /package/src/{view/utils → apps/common/view/components}/router.ts +0 -0
  104. /package/src/{model/structure-app → apps/structure/model}/const.ts +0 -0
  105. /package/src/{view/style/structure-app.css → apps/structure/view/style.css} +0 -0
  106. /package/src/{model/translator-app → apps/translator/model}/const.ts +0 -0
  107. /package/src/{view/style/translator-app.css → apps/translator/view/style.css} +0 -0
@@ -0,0 +1,14 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+
3
+ import {STRANDS, STRAND_ENDS, TERMINI} from '../model/const';
4
+
5
+ export type BooleanInput = DG.InputBase<boolean | null>;
6
+ export type StringInput = DG.InputBase<string | null>;
7
+ export type NumberInput = DG.InputBase<number | null>;
8
+
9
+ export type Position = { x: number, y: number };
10
+
11
+ export type StrandEndToNumberMap = Record<typeof STRAND_ENDS[number], number>;
12
+ export type StrandToNumberMap = Record<typeof STRANDS[number], number>;
13
+ export type StrandEndToSVGElementsMap = Record<typeof STRAND_ENDS[number], SVGElement | null>;
14
+ export type TerminusToSVGElementMap = Record<typeof TERMINI[number], SVGElement | null>
@@ -0,0 +1,61 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+
3
+ import {APP_NAME} from '../../common/view/const';
4
+ import {IsolatedAppUIBase} from '../../common/view/isolated-app-ui';
5
+ import {DataManager} from '../model/data-manager';
6
+ import {EventBus} from '../model/event-bus';
7
+ import {URLRouter} from '../model/router';
8
+ import {PatternAppLeftSection} from './components/left-section';
9
+ import {PatternAppRightSection} from './components/right-section';
10
+ import {PatternConfigRecord} from '../model/types';
11
+
12
+
13
+ export class OligoPatternUI extends IsolatedAppUIBase {
14
+ constructor() {
15
+ super(APP_NAME.PATTERN);
16
+ }
17
+
18
+ protected getContent(): Promise<HTMLDivElement> {
19
+ return getContent();
20
+ }
21
+ }
22
+
23
+
24
+ async function getContent(): Promise<HTMLDivElement> {
25
+ const dataManager = await DataManager.getInstance();
26
+ const urlRouter = new URLRouter();
27
+
28
+ const initialPatternRecord = await getInitialPatternRecord(dataManager, urlRouter);
29
+ const eventBus = new EventBus(dataManager, initialPatternRecord);
30
+ urlRouter.subscribeToObservables(eventBus);
31
+
32
+ const leftSection = new PatternAppLeftSection(eventBus, dataManager).getLayout();
33
+ const rightSection = new PatternAppRightSection(eventBus, dataManager).getLayout();
34
+
35
+ const isResizeable = true;
36
+
37
+ const layout = ui.splitH([
38
+ leftSection,
39
+ rightSection,
40
+ ], {}, isResizeable);
41
+
42
+ return layout;
43
+ }
44
+
45
+ async function getInitialPatternRecord(
46
+ dataManager: DataManager,
47
+ urlRouter: URLRouter
48
+ ): Promise<PatternConfigRecord> {
49
+ const patternHash = urlRouter.getPatternHash();
50
+ if (!patternHash) {
51
+ urlRouter.clearPatternURL();
52
+ return dataManager.getDefaultPatternRecord();
53
+ }
54
+
55
+ let initialPatternRecord = await dataManager.getPatternRecord(patternHash);
56
+ if (!initialPatternRecord) {
57
+ urlRouter.clearPatternURL();
58
+ initialPatternRecord = dataManager.getDefaultPatternRecord();
59
+ }
60
+ return initialPatternRecord;
61
+ }
@@ -36,7 +36,7 @@ export function linkStrandsV3000(
36
36
 
37
37
  let inverted = false;
38
38
  const molBlocks = strands.senseStrands.concat(strands.antiStrands);
39
- /** Minimal value of AS and AS2 shift */
39
+ /** Minimal value of ANTISENSE_STRAND and AS2 shift */
40
40
  let ssYShift = 0;
41
41
 
42
42
  for (let i = 0; i < molBlocks.length; i++) {
@@ -47,12 +47,12 @@ export function linkStrandsV3000(
47
47
 
48
48
  if (i >= strands.senseStrands.length) {
49
49
  if (inverted === false) {
50
- // AS strand
50
+ // ANTISENSE_STRAND strand
51
51
  inverted = true;
52
52
  xShift = 0;
53
53
  }
54
54
  } else {
55
- // SS strands
55
+ // SENSE_STRAND strands
56
56
  ssYShift = Math.min(ssYShift, Math.min(
57
57
  ...coordinates.y.filter((item) => item < 0)
58
58
  ));
@@ -4,15 +4,15 @@ import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {PHOSPHATE_SYMBOL} from './const';
7
- import {sortByReverseLength} from '../helpers';
8
- import {MonomerLibWrapper} from '../monomer-lib/lib-wrapper';
9
- import {monomersWithPhosphateLinkers} from '../data-loading-utils/json-loader';
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';
10
10
 
11
11
  /** Wrapper for parsing a strand and getting a sequence of monomer IDs (with
12
12
  * omitted linkers, if needed) */
13
13
  export class MonomerSequenceParser {
14
14
  constructor(
15
- private sequence: string,
15
+ private sequence: string,
16
16
  // todo: remove from the list of parameters
17
17
  private codeMap: Map<string, string>
18
18
  ) {
@@ -39,9 +39,8 @@ export class MonomerSequenceParser {
39
39
  const nextMonomerIsPhosphate = (i + 1 < parsedRawCodes.length && monomerIsPhosphateLinker(this.getSymbolForCode(parsedRawCodes[i + 1])));
40
40
 
41
41
  // todo: refactor as molfile-specific
42
- if (!isPhosphate && !monomerHasRightPhosphateLinker(monomerSymbol) && !nextMonomerIsPhosphate && !lastMonomer) {
42
+ if (!isPhosphate && !monomerHasRightPhosphateLinker(monomerSymbol) && !nextMonomerIsPhosphate && !lastMonomer)
43
43
  monomerSymbolSequence.push(PHOSPHATE_SYMBOL);
44
- }
45
44
  });
46
45
  return monomerSymbolSequence;
47
46
  }
@@ -70,20 +69,20 @@ export class MonomerSequenceParser {
70
69
 
71
70
  // todo: port to monomer handler
72
71
  private getAllCodesOfFormat(): string[] {
73
- let allCodesInTheFormat = Array.from(this.codeMap.keys());
72
+ const allCodesInTheFormat = Array.from(this.codeMap.keys());
74
73
  return sortByReverseLength(allCodesInTheFormat);
75
74
  }
76
75
  }
77
76
 
78
77
  // todo: to be eliminated after full helm support
79
78
  function monomerHasLeftPhosphateLinker(monomerSymbol: string): boolean {
80
- return monomersWithPhosphateLinkers['left'].includes(monomerSymbol);
79
+ return MONOMERS_WITH_PHOSPHATE['left'].includes(monomerSymbol);
81
80
  }
82
81
 
83
82
  function monomerHasRightPhosphateLinker(monomerSymbol: string): boolean {
84
- return monomersWithPhosphateLinkers['right'].includes(monomerSymbol);
83
+ return MONOMERS_WITH_PHOSPHATE['right'].includes(monomerSymbol);
85
84
  }
86
85
 
87
86
  function monomerIsPhosphateLinker(monomerSymbol: string): boolean {
88
- return monomersWithPhosphateLinkers['phosphate'].includes(monomerSymbol);
87
+ return MONOMERS_WITH_PHOSPHATE['phosphate'].includes(monomerSymbol);
89
88
  }
@@ -4,11 +4,11 @@ import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
6
6
 
7
- import {download} from '../helpers';
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 '../const';
11
- import {FormatDetector} from '../parsing-validation/format-detector';
10
+ import {DEFAULT_FORMATS} from '../../common/model/const';
11
+ import {FormatDetector} from '../../common/model/parsing-validation/format-detector';
12
12
 
13
13
  export type StrandData = {
14
14
  strand: string,
@@ -62,7 +62,7 @@ export function saveSdf(
62
62
  nonEmptyStrands.length === 0 ||
63
63
  nonEmptyStrands.length === 1 && ss.strand === ''
64
64
  ) {
65
- grok.shell.warning('Enter SS and optionally AS/AS2 to save SDF');
65
+ grok.shell.warning('Enter SENSE_STRAND and optionally ANTISENSE_STRAND/AS2 to save SDF');
66
66
  } else {
67
67
  let result: string;
68
68
  if (oneEntity) {
@@ -4,7 +4,7 @@ import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {MonomerSequenceParser} from './monomer-code-parser';
7
- import {MonomerLibWrapper} from '../monomer-lib/lib-wrapper';
7
+ import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
8
8
 
9
9
  export class SequenceToMolfileConverter {
10
10
  constructor(
@@ -24,7 +24,7 @@ export class SequenceToMolfileConverter {
24
24
  parsedSequence.forEach((monomerSymbol, idx) => {
25
25
  const monomerMolfile = this.getMonomerMolfile(monomerSymbol, idx);
26
26
  monomerMolfiles.push(monomerMolfile);
27
- })
27
+ });
28
28
  let molfile = this.getPolymerMolfile(monomerMolfiles);
29
29
  if (this.invert) {
30
30
  molfile = this.reflect(molfile);
@@ -1,19 +1,20 @@
1
1
  /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as DG from 'datagrok-api/dg';
2
3
  import * as grok from 'datagrok-api/grok';
3
4
  import * as ui from 'datagrok-api/ui';
4
- import * as DG from 'datagrok-api/dg';
5
5
 
6
- import * as rxjs from 'rxjs';
7
- import '../style/structure-app.css';
8
6
  import $ from 'cash-dom';
7
+ import * as rxjs from 'rxjs';
8
+ import './style.css';
9
9
 
10
10
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
11
11
 
12
- import {highlightInvalidSubsequence} from '../utils/colored-input/input-painters';
13
- import {getLinkedMolfile, saveSdf} from '../../model/structure-app/oligo-structure';
14
- import {ColoredTextInput} from '../utils/colored-input/colored-text-input';
15
- import {MoleculeImage} from '../utils/molecule-img';
16
- import {StrandData} from '../../model/structure-app/oligo-structure';
12
+ import {ColoredTextInput} from '../../common/view/components/colored-input/colored-text-input';
13
+ import {highlightInvalidSubsequence} from '../../common/view/components/colored-input/input-painters';
14
+ import {MoleculeImage} from '../../common/view/components/molecule-img';
15
+ import {APP_NAME} from '../../common/view/const';
16
+ import {IsolatedAppUIBase} from '../../common/view/isolated-app-ui';
17
+ import {getLinkedMolfile, saveSdf, StrandData} from '../model/oligo-structure';
17
18
 
18
19
  const enum DIRECTION {
19
20
  STRAIGHT = '5′ → 3′',
@@ -21,7 +22,7 @@ const enum DIRECTION {
21
22
  };
22
23
  const STRANDS = ['ss', 'as', 'as2'] as const;
23
24
 
24
- export class StructureLayoutHandler {
25
+ class StructureAppLayout {
25
26
  constructor() {
26
27
  this.onInput = new rxjs.Subject<string>();
27
28
  this.onInvalidInput = new rxjs.Subject<string>();
@@ -98,7 +99,7 @@ export class StructureLayoutHandler {
98
99
  const selected = (idx === 0) ? DIRECTION.STRAIGHT : DIRECTION.INVERSE;
99
100
  return [key, ui.choiceInput(
100
101
  `${key.toUpperCase()} direction`, selected, [DIRECTION.STRAIGHT, DIRECTION.INVERSE]
101
- )]
102
+ )];
102
103
  }
103
104
  )
104
105
  );
@@ -106,8 +107,9 @@ export class StructureLayoutHandler {
106
107
  STRANDS.forEach((strand, idx) => {
107
108
  directionChoiceInput[strand].onChanged(() => {
108
109
  let value = directionChoiceInput[strand].value === DIRECTION.INVERSE;
109
- // warning: the next line is necessary until the legacy notion of direction used in the molfile generation gets fixed
110
- if (idx > 0) { value = !value; }
110
+ // warning: the next line is necessary
111
+ // until the legacy notion of direction used in the molfile generation gets fixed
112
+ if (idx > 0) value = !value;
111
113
  this.directionInversion[strand] = value;
112
114
  this.onInput.next();
113
115
  });
@@ -126,7 +128,7 @@ export class StructureLayoutHandler {
126
128
  const clearBlock = Object.fromEntries(
127
129
  STRANDS.map(
128
130
  (key) => {
129
- const clearIcon = ui.icons.delete(() => { coloredInput[key].inputBase.value = '' });
131
+ const clearIcon = ui.icons.delete(() => { coloredInput[key].inputBase.value = ''; });
130
132
  const clearButton = ui.button(clearIcon, () => {});
131
133
  ui.tooltip.bind(clearButton, `Clear ${key.toUpperCase()}`);
132
134
  return [key, clearIcon];
@@ -163,12 +165,12 @@ export class StructureLayoutHandler {
163
165
 
164
166
  private getStrandData() {
165
167
  return Object.fromEntries(
166
- STRANDS.map((strand, idx) => {
167
- let invert = this.directionInversion[strand];
168
+ STRANDS.map((strand) => {
169
+ const invert = this.directionInversion[strand];
168
170
  return [strand, {
169
171
  strand: this.inputBase[strand].value.replace(/\s*/g, ''),
170
172
  invert: invert
171
- }]
173
+ }];
172
174
  })
173
175
  );
174
176
  }
@@ -192,7 +194,7 @@ export class StructureLayoutHandler {
192
194
  const errStr = errorToConsole(err);
193
195
  console.error(errStr);
194
196
  }
195
- // todo: calculate relative numbers
197
+ // todo: compute relative numbers
196
198
  const canvasWidth = 650;
197
199
  const canvasHeight = 150;
198
200
  const molImgObj = new MoleculeImage(molfile);
@@ -201,3 +203,15 @@ export class StructureLayoutHandler {
201
203
  $(this.moleculeImgDiv).find('canvas').css('float', 'inherit');
202
204
  }
203
205
  }
206
+
207
+ export class OligoStructureUI extends IsolatedAppUIBase {
208
+ constructor() {
209
+ super(APP_NAME.STRUCTURE);
210
+ this.layout = new StructureAppLayout();
211
+ }
212
+ private readonly layout: StructureAppLayout;
213
+
214
+ protected getContent(): Promise<HTMLDivElement> {
215
+ return this.layout.getHtmlDivElement();
216
+ }
217
+ }
@@ -1,17 +1,18 @@
1
- import {DEFAULT_FORMATS, NUCLEOTIDES} from '../const';
1
+ import {DEFAULT_FORMATS, NUCLEOTIDES} from '../../common/model/const';
2
+ import {NUCLEOTIDES_FORMAT} from '../view/const';
2
3
  import {UNKNOWN_SYMBOL} from './const';
3
4
  import {FormatConverter} from './format-converter';
4
- import {codesToHelmDictionary} from '../data-loading-utils/json-loader';
5
- import {MonomerLibWrapper} from '../monomer-lib/lib-wrapper';
5
+ import {CODES_TO_HELM_DICT} from '../../common/model/data-loader/json-loader';
6
+ import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
6
7
 
7
8
  export function getTranslatedSequences(sequence: string, indexOfFirstInvalidChar: number, sourceFormat: string): {[key: string]: string} {
8
- const supportedFormats = Object.keys(codesToHelmDictionary).concat([DEFAULT_FORMATS.HELM]) as string[];
9
+ const supportedFormats = Object.keys(CODES_TO_HELM_DICT).concat([DEFAULT_FORMATS.HELM]) as string[];
9
10
 
10
11
  if (!sequence || (indexOfFirstInvalidChar !== -1 && sourceFormat !== DEFAULT_FORMATS.HELM))
11
12
  return {};
12
13
 
13
14
  if (!supportedFormats.includes(sourceFormat))
14
- throw new Error(`${sourceFormat} format is not supported by SequenceTranslator`)
15
+ throw new Error(`${sourceFormat} format is not supported by SequenceTranslator`);
15
16
 
16
17
  const outputFormats = supportedFormats.filter((el) => el != sourceFormat)
17
18
  .sort((a, b) => a.localeCompare(b));
@@ -25,8 +26,8 @@ export function getTranslatedSequences(sequence: string, indexOfFirstInvalidChar
25
26
  translation = null;
26
27
  }
27
28
  return [format, translation];
28
- }).filter(([format, translation]) => translation)
29
- )
29
+ }).filter(([_, translation]) => translation)
30
+ );
30
31
  const helm = (sourceFormat === DEFAULT_FORMATS.HELM) ? sequence : result[DEFAULT_FORMATS.HELM];
31
32
  const nucleotides = getNucleotidesSequence(helm, MonomerLibWrapper.getInstance());
32
33
  if (nucleotides)
@@ -47,3 +48,20 @@ export function getNucleotidesSequence(helmString: string, monomerLib: MonomerLi
47
48
  }).map((el) => el ? el : UNKNOWN_SYMBOL).join('');
48
49
  return nucleotides;
49
50
  }
51
+
52
+ // 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
+ if (targetFormat === NUCLEOTIDES_FORMAT) {
56
+ const helm = converter.convertTo(DEFAULT_FORMATS.HELM);
57
+ const nucleotides = getNucleotidesSequence(helm, MonomerLibWrapper.getInstance());
58
+ return nucleotides;
59
+ }
60
+
61
+ return converter.convertTo(targetFormat);
62
+ }
63
+
64
+ export function getSupportedTargetFormats(): string[] {
65
+ const supportedTargetFormats = Object.keys(CODES_TO_HELM_DICT).concat([DEFAULT_FORMATS.HELM, NUCLEOTIDES_FORMAT]).sort() as string[];
66
+ return supportedTargetFormats;
67
+ }
@@ -1,7 +1,7 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
- import {DEFAULT_FORMATS} from '../const';
2
+ import {DEFAULT_FORMATS} from '../../common/model/const';
3
3
  import {PHOSPHATE_SYMBOL, UNKNOWN_SYMBOL} from './const';
4
- import {FormatHandler, getRegExpPattern} from '../parsing-validation/format-handler';
4
+ import {FormatHandler, getRegExpPattern} from '../../common/model/parsing-validation/format-handler';
5
5
 
6
6
  const HELM_WRAPPER = {
7
7
  LEFT: 'RNA1{',
@@ -16,21 +16,16 @@ export class FormatConverter {
16
16
  convertTo(targetFormat: string): string {
17
17
  const formats = this.formats.getFormatNames();
18
18
 
19
- if (this.sourceFormat === DEFAULT_FORMATS.HELM && formats.includes(targetFormat))
20
- return this.helmToFormat(this.sequence, targetFormat);
21
- else if (formats.includes(this.sourceFormat) && targetFormat === DEFAULT_FORMATS.HELM)
22
- return this.formatToHelm(this.sequence, this.sourceFormat);
23
- else if ([this.sourceFormat, targetFormat].every((el) => formats.includes(el))) {
19
+ if (this.sourceFormat === DEFAULT_FORMATS.HELM && formats.includes(targetFormat)) { return this.helmToFormat(this.sequence, targetFormat); } else if (formats.includes(this.sourceFormat) && targetFormat === DEFAULT_FORMATS.HELM) { return this.formatToHelm(this.sequence, this.sourceFormat); } else if ([this.sourceFormat, targetFormat].every((el) => formats.includes(el))) {
24
20
  const helm = this.formatToHelm(this.sequence, this.sourceFormat);
25
21
  return this.helmToFormat(helm, targetFormat);
26
- }
27
- else {
28
- throw new Error (`ST: unsupported translation direction ${this.sourceFormat} -> ${targetFormat}`);
22
+ } else {
23
+ throw new Error(`ST: unsupported translation direction ${this.sourceFormat} -> ${targetFormat}`);
29
24
  }
30
25
  }
31
26
 
32
27
  private helmToFormat(helmSequence: string, targetFormat: string): string {
33
- const wrapperRegExp = new RegExp(getRegExpPattern(Object.values(HELM_WRAPPER)), 'g')
28
+ const wrapperRegExp = new RegExp(getRegExpPattern(Object.values(HELM_WRAPPER)), 'g');
34
29
  let result = helmSequence.replace(wrapperRegExp, '');
35
30
 
36
31
  const dict = this.formats.getHelmToFormatDict(targetFormat);
@@ -54,7 +49,7 @@ export class FormatConverter {
54
49
  const phosphateRegExp = this.formats.getPhosphateHelmCodesRegExp(sourceFormat);
55
50
 
56
51
  let helm = sequence.replace(formatRegExp, (match) => {
57
- const result = formatCodes.includes(match) ? dict[match] + '.' : '?';
52
+ const result = formatCodes.includes(match) ? dict[match] + '.' : '?';
58
53
  return result;
59
54
  });
60
55
  helm = helm.replace(/\?+/g, `${UNKNOWN_SYMBOL}.`);
@@ -2,4 +2,4 @@ export const DEFAULT_INPUT = 'fAmCmGmAmCpsmU';
2
2
  export const SEQUENCE_COPIED_MSG = 'Copied';
3
3
  export const SEQ_TOOLTIP_MSG = 'Copy sequence';
4
4
 
5
- export const NUCLEOTIDES = 'Nucleotides';
5
+ export const NUCLEOTIDES_FORMAT = 'Nucleotides';
@@ -1,36 +1,39 @@
1
1
  /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as DG from 'datagrok-api/dg';
2
3
  import * as grok from 'datagrok-api/grok';
3
4
  import * as ui from 'datagrok-api/ui';
4
- import * as DG from 'datagrok-api/dg';
5
5
 
6
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
7
- import {ALPHABET, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
6
+ import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
7
+ import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
8
8
 
9
9
  import * as rxjs from 'rxjs';
10
- import '../style/translator-app.css';
11
-
12
- import {highlightInvalidSubsequence} from '../utils/colored-input/input-painters';
13
- import {ColoredTextInput} from '../utils/colored-input/colored-text-input';
14
- import {SequenceToMolfileConverter} from '../../model/structure-app/sequence-to-molfile';
15
- import {getTranslatedSequences} from '../../model/translator-app/conversion-utils';
16
- import {MoleculeImage} from '../utils/molecule-img';
17
- import {download} from '../../model/helpers';
18
- import {SEQUENCE_COPIED_MSG, SEQ_TOOLTIP_MSG, NUCLEOTIDES} from '../const/oligo-translator';
19
- import {DEFAULT_AXOLABS_INPUT} from '../const/ui';
20
- import {FormatDetector} from '../../model/parsing-validation/format-detector';
21
- import {SequenceValidator} from '../../model/parsing-validation/sequence-validator';
22
- import {FormatConverter} from '../../model/translator-app/format-converter';
23
- import {codesToHelmDictionary} from '../../model/data-loading-utils/json-loader';
24
- import {DEFAULT_FORMATS} from '../../model/const';
10
+
11
+ import {DEFAULT_FORMATS} from '../../common/model/const';
12
+ import {CODES_TO_HELM_DICT} from '../../common/model/data-loader/json-loader';
13
+ import {download} from '../../common/model/helpers';
14
+ import {FormatDetector} from '../../common/model/parsing-validation/format-detector';
15
+ import {SequenceValidator} from '../../common/model/parsing-validation/sequence-validator';
16
+ import {ColoredTextInput} from '../../common/view/components/colored-input/colored-text-input';
17
+ import {highlightInvalidSubsequence} from '../../common/view/components/colored-input/input-painters';
18
+ import {MoleculeImage} from '../../common/view/components/molecule-img';
19
+ import {APP_NAME, DEFAULT_AXOLABS_INPUT} from '../../common/view/const';
20
+ import {IsolatedAppUIBase} from '../../common/view/isolated-app-ui';
21
+ import {MonomerLibViewer} from '../../common/view/monomer-lib-viewer';
22
+ import {SequenceToMolfileConverter} from '../../structure/model/sequence-to-molfile';
23
+ import {convert, getSupportedTargetFormats, getTranslatedSequences} from '../model/conversion-utils';
24
+ import {FormatConverter} from '../model/format-converter';
25
+ import {NUCLEOTIDES_FORMAT, SEQUENCE_COPIED_MSG, SEQ_TOOLTIP_MSG} from './const';
26
+ import './style.css';
25
27
 
26
28
  const enum REQUIRED_COLUMN_LABEL {
27
29
  SEQUENCE = 'Sequence',
28
30
  }
29
- const REQUIRED_COLUMN_LABELS = [ REQUIRED_COLUMN_LABEL.SEQUENCE ];
31
+ const REQUIRED_COLUMN_LABELS = [REQUIRED_COLUMN_LABEL.SEQUENCE];
30
32
 
31
- export class TranslatorLayoutHandler {
33
+ class TranslatorAppLayout {
32
34
  private eventBus: EventBus;
33
- private inputFormats = Object.keys(codesToHelmDictionary).concat(DEFAULT_FORMATS.HELM);
35
+ private inputFormats = Object.keys(CODES_TO_HELM_DICT).concat(DEFAULT_FORMATS.HELM);
36
+
34
37
  constructor() {
35
38
  this.moleculeImgDiv = ui.div([]);
36
39
  this.moleculeImgDiv.className = 'mol-host';
@@ -77,7 +80,7 @@ export class TranslatorLayoutHandler {
77
80
  singleSequenceControls,
78
81
  bulkControls,
79
82
  ui.block([ui.box(this.moleculeImgDiv)])
80
- ]),
83
+ ])
81
84
  );
82
85
 
83
86
  this.formatChoiceInput.value = this.format;
@@ -92,15 +95,26 @@ export class TranslatorLayoutHandler {
92
95
 
93
96
  const tableControlsManager = new TableControlsManager(this.eventBus);
94
97
  const tableControls = tableControlsManager.createUIComponents();
95
- const inputFormats = ui.choiceInput('Input format', DEFAULT_FORMATS.AXOLABS, this.inputFormats, (value: string) => this.eventBus.selectInputFormat(value));
96
- const outputFormats = ui.choiceInput('Output format', NUCLEOTIDES, [NUCLEOTIDES], () => {});
98
+ const inputFormats = ui.choiceInput(
99
+ 'Input format',
100
+ DEFAULT_FORMATS.AXOLABS,
101
+ this.inputFormats,
102
+ (value: string) => this.eventBus.selectInputFormat(value)
103
+ );
104
+
105
+ const outputFormats = ui.choiceInput(
106
+ 'Output format',
107
+ NUCLEOTIDES_FORMAT,
108
+ getSupportedTargetFormats(),
109
+ (value: string) => this.eventBus.selectOutputFormat(value)
110
+ );
97
111
  const convertBulkButton = this.createConvertBulkButton();
98
112
 
99
113
  const tableControlsContainer = ui.div([
100
114
  ...tableControls,
101
115
  inputFormats,
102
116
  outputFormats,
103
- convertBulkButton
117
+ convertBulkButton
104
118
  ], 'ui-form');
105
119
 
106
120
  const bulkTranslationControls = ui.block25([
@@ -130,7 +144,7 @@ export class TranslatorLayoutHandler {
130
144
  }
131
145
 
132
146
  const inputFormat = this.eventBus.getSelectedInputFormat();
133
- const outputFormat = NUCLEOTIDES;
147
+ const outputFormat = this.eventBus.getSelectedOutputFormat();
134
148
  const sequenceColumn = this.eventBus.getSelectedColumn(REQUIRED_COLUMN_LABEL.SEQUENCE);
135
149
  if (!sequenceColumn) {
136
150
  grok.shell.warning('No sequence column selected');
@@ -142,15 +156,20 @@ export class TranslatorLayoutHandler {
142
156
  DG.TYPE.STRING,
143
157
  newColumnName,
144
158
  sequenceColumn.toList().map((sequence: string) => {
145
- const translatedSequences = getTranslatedSequences(sequence, -1, inputFormat);
146
- return translatedSequences[outputFormat];
159
+ const result = convert(sequence, inputFormat, outputFormat);
160
+ return result;
147
161
  })
148
162
  );
149
163
 
150
- translatedColumn.semType = DG.SEMTYPE.MACROMOLECULE;
151
- translatedColumn.setTag(DG.TAGS.UNITS, NOTATION.FASTA);
152
- const unitsHandler = UnitsHandler.getOrCreate(translatedColumn);
153
- UnitsHandler.setUnitsToFastaColumn(unitsHandler);
164
+ if (outputFormat === NUCLEOTIDES_FORMAT || outputFormat === DEFAULT_FORMATS.HELM) {
165
+ translatedColumn.semType = DG.SEMTYPE.MACROMOLECULE;
166
+ const units = outputFormat == NUCLEOTIDES_FORMAT ? NOTATION.FASTA : NOTATION.HELM;
167
+ translatedColumn.setTag(DG.TAGS.UNITS, units);
168
+ const seqHandler = SeqHandler.forColumn(translatedColumn as DG.Column<string>);
169
+ const setUnits = outputFormat == NUCLEOTIDES_FORMAT ? SeqHandler.setUnitsToFastaColumn :
170
+ SeqHandler.setUnitsToHelmColumn;
171
+ setUnits(seqHandler);
172
+ }
154
173
 
155
174
  // add newColumn to the table
156
175
  selectedTable.columns.add(translatedColumn);
@@ -176,7 +195,7 @@ export class TranslatorLayoutHandler {
176
195
  const formatChoiceInput = ui.div([this.formatChoiceInput]);
177
196
 
178
197
  const clearButton = ui.button(
179
- ui.icons.delete(() => { sequenceColoredInput.inputBase.value = '' }),
198
+ ui.icons.delete(() => { sequenceColoredInput.inputBase.value = ''; }),
180
199
  () => {}
181
200
  );
182
201
  ui.tooltip.bind(clearButton, 'Clear input');
@@ -201,7 +220,7 @@ export class TranslatorLayoutHandler {
201
220
  ui.h1('Single sequence'),
202
221
  singleSequenceInputControls,
203
222
  singleSequenceOutputTable,
204
- ])
223
+ ]);
205
224
 
206
225
  return singleSequenceControls;
207
226
  }
@@ -222,7 +241,8 @@ export class TranslatorLayoutHandler {
222
241
  private updateTable(): void {
223
242
  this.outputTableDiv.innerHTML = '';
224
243
  // todo: does not detect correctly (U-A)(U-A)
225
- const indexOfInvalidChar = (!this.format) ? 0 : (new SequenceValidator(this.sequence)).getInvalidCodeIndex(this.format!);
244
+ const indexOfInvalidChar = (!this.format) ? 0 :
245
+ (new SequenceValidator(this.sequence)).getInvalidCodeIndex(this.format!);
226
246
  const translatedSequences = getTranslatedSequences(this.sequence, indexOfInvalidChar, this.format!);
227
247
  const tableRows = [];
228
248
 
@@ -320,14 +340,11 @@ class TableInputManager {
320
340
  this.eventBus.tableSelected$.subscribe(() => this.handleTableChoice());
321
341
  }
322
342
 
323
- private getTableFromEventData(eventData: any): DG.DataFrame {
324
- if (! eventData && eventData.args && eventData.args.dataFrame instanceof DG.DataFrame)
325
- throw new Error(`EventData does not contain a dataframe`, eventData);
326
-
327
- return eventData.args.dataFrame as DG.DataFrame;
343
+ private getTableFromEventData(eventData: DG.EventData<DG.DataFrameArgs>): DG.DataFrame {
344
+ return eventData.args.dataFrame;
328
345
  }
329
346
 
330
- private handleTableAdded(eventData: any): void {
347
+ private handleTableAdded(eventData: DG.EventData<DG.DataFrameArgs>): void {
331
348
  const table = this.getTableFromEventData(eventData);
332
349
 
333
350
  if (this.availableTables.some((t: DG.DataFrame) => t.name === table.name))
@@ -462,6 +479,7 @@ export class EventBus {
462
479
  return [columnLabel, columnSelection$];
463
480
  }));
464
481
  private _inputFormatSelection$ = new rxjs.BehaviorSubject<string>(DEFAULT_FORMATS.AXOLABS);
482
+ private _outputFormatSelection$ = new rxjs.BehaviorSubject<string>(NUCLEOTIDES_FORMAT);
465
483
 
466
484
  private constructor() {}
467
485
 
@@ -490,7 +508,7 @@ export class EventBus {
490
508
  getSelectedColumn(columnLabel: REQUIRED_COLUMN_LABEL): DG.Column<string> | null {
491
509
  return this._columnSelection[columnLabel].getValue();
492
510
  }
493
-
511
+
494
512
  getSelectedInputFormat(): string {
495
513
  return this._inputFormatSelection$.getValue();
496
514
  }
@@ -498,4 +516,32 @@ export class EventBus {
498
516
  selectInputFormat(format: string): void {
499
517
  this._inputFormatSelection$.next(format);
500
518
  }
519
+
520
+ selectOutputFormat(format: string): void {
521
+ this._outputFormatSelection$.next(format);
522
+ }
523
+
524
+ getSelectedOutputFormat(): string {
525
+ return this._outputFormatSelection$.getValue();
526
+ }
527
+ }
528
+
529
+ export class OligoTranslatorUI extends IsolatedAppUIBase {
530
+ private readonly topPanel: HTMLElement[];
531
+ private readonly layout = new TranslatorAppLayout();
532
+
533
+ constructor() {
534
+ super(APP_NAME.TRANSLATOR);
535
+
536
+ const viewMonomerLibIcon = ui.iconFA('book', MonomerLibViewer.view, 'View monomer library');
537
+ this.topPanel = [
538
+ viewMonomerLibIcon,
539
+ ];
540
+ this.view.setRibbonPanels([this.topPanel]);
541
+ }
542
+
543
+ protected getContent(): Promise<HTMLDivElement> {
544
+ return this.layout.getHtmlElement();
545
+ };
501
546
  }
547
+