@datagrok/sequence-translator 1.0.16 → 1.1.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 (92) hide show
  1. package/.eslintrc.json +4 -3
  2. package/CHANGELOG.md +3 -0
  3. package/detectors.js +8 -28
  4. package/dist/package-test.js +2 -72987
  5. package/dist/package-test.js.map +1 -0
  6. package/dist/package.js +2 -72192
  7. package/dist/package.js.map +1 -0
  8. package/files/axolabs-style.json +97 -0
  9. package/files/codes-to-symbols.json +66 -0
  10. package/files/formats-to-helm.json +59 -0
  11. package/files/linkers.json +22 -0
  12. package/files/monomer-lib.json +1094 -0
  13. package/link-bio +7 -0
  14. package/package.json +30 -26
  15. package/scripts/build-monomer-lib.py +391 -122
  16. package/src/demo/demo-st-ui.ts +71 -0
  17. package/src/demo/handle-error.ts +12 -0
  18. package/src/model/axolabs/axolabs-tab.ts +111 -0
  19. package/src/model/axolabs/const.ts +33 -0
  20. package/src/{axolabs → model/axolabs}/draw-svg.ts +1 -1
  21. package/src/{axolabs → model/axolabs}/helpers.ts +7 -5
  22. package/src/model/const.ts +19 -0
  23. package/src/model/data-loading-utils/const.ts +8 -0
  24. package/src/model/data-loading-utils/json-loader.ts +38 -0
  25. package/src/model/data-loading-utils/types.ts +30 -0
  26. package/src/model/format-translation/const.ts +8 -0
  27. package/src/model/format-translation/conversion-utils.ts +48 -0
  28. package/src/model/format-translation/format-converter.ts +107 -0
  29. package/src/model/helpers.ts +12 -0
  30. package/src/model/monomer-lib/const.ts +3 -0
  31. package/src/model/monomer-lib/lib-wrapper.ts +106 -0
  32. package/src/model/parsing-validation/format-detector.ts +57 -0
  33. package/src/model/parsing-validation/sequence-validator.ts +52 -0
  34. package/src/model/sequence-to-structure-utils/const.ts +1 -0
  35. package/src/{structures-works → model/sequence-to-structure-utils}/mol-transformations.ts +61 -87
  36. package/src/model/sequence-to-structure-utils/monomer-code-parser.ts +92 -0
  37. package/src/model/sequence-to-structure-utils/sdf-tab.ts +94 -0
  38. package/src/model/sequence-to-structure-utils/sequence-to-molfile.ts +409 -0
  39. package/src/package.ts +106 -77
  40. package/src/tests/const.ts +17 -0
  41. package/src/tests/smiles-tests.ts +32 -457
  42. package/src/view/const/main-tab.ts +3 -0
  43. package/src/view/const/view.ts +10 -0
  44. package/src/view/css/axolabs-tab.css +1 -0
  45. package/src/view/css/colored-text-input.css +27 -0
  46. package/src/view/css/main-tab.css +46 -0
  47. package/src/view/css/sdf-tab.css +39 -0
  48. package/src/view/monomer-lib-viewer/viewer.ts +22 -0
  49. package/src/view/tabs/axolabs.ts +720 -0
  50. package/src/view/tabs/main.ts +174 -0
  51. package/src/view/tabs/sdf.ts +173 -0
  52. package/src/view/utils/app-info-dialog.ts +18 -0
  53. package/src/view/utils/colored-input/colored-text-input.ts +56 -0
  54. package/src/view/utils/colored-input/input-painters.ts +44 -0
  55. package/src/view/utils/draw-molecule.ts +86 -0
  56. package/src/view/utils/molecule-img.ts +106 -0
  57. package/src/view/view.ts +129 -0
  58. package/tsconfig.json +12 -18
  59. package/webpack.config.js +17 -4
  60. package/README.md +0 -84
  61. package/css/style.css +0 -18
  62. package/img/Sequence Translator Axolabs.png +0 -0
  63. package/jest.config.js +0 -33
  64. package/setup-unlink-clean.cmd +0 -14
  65. package/setup.cmd +0 -14
  66. package/setup.sh +0 -37
  67. package/src/__jest__/remote.test.ts +0 -77
  68. package/src/__jest__/test-node.ts +0 -97
  69. package/src/apps/oligo-sd-file-app.ts +0 -58
  70. package/src/autostart/ICDs.ts +0 -3
  71. package/src/autostart/IDPs.ts +0 -3
  72. package/src/autostart/calculations.ts +0 -40
  73. package/src/autostart/constants.ts +0 -37
  74. package/src/autostart/registration.ts +0 -241
  75. package/src/autostart/salts.ts +0 -2
  76. package/src/autostart/sources.ts +0 -3
  77. package/src/autostart/users.ts +0 -3
  78. package/src/axolabs/constants.ts +0 -101
  79. package/src/axolabs/define-pattern.ts +0 -873
  80. package/src/helpers.ts +0 -28
  81. package/src/main/main-view.ts +0 -262
  82. package/src/structures-works/const.ts +0 -5
  83. package/src/structures-works/converters.ts +0 -323
  84. package/src/structures-works/from-monomers.ts +0 -267
  85. package/src/structures-works/map.ts +0 -720
  86. package/src/structures-works/save-sense-antisense.ts +0 -91
  87. package/src/structures-works/sequence-codes-tools.ts +0 -344
  88. package/src/utils/parse.ts +0 -27
  89. package/src/utils/sdf-add-columns.ts +0 -118
  90. package/src/utils/sdf-save-table.ts +0 -56
  91. package/test-SequenceTranslator-6288c2fbe346-cce4ac1d.html +0 -259
  92. package/vendors/openchemlib-full.js +0 -293
@@ -0,0 +1,174 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ import * as rxjs from 'rxjs';
7
+ import '../css/main-tab.css';
8
+ import $ from 'cash-dom';
9
+
10
+ import {highlightInvalidSubsequence} from '../utils/colored-input/input-painters';
11
+ import {ColoredTextInput} from '../utils/colored-input/colored-text-input';
12
+ import {SequenceToMolfileConverter} from '../../model/sequence-to-structure-utils/sequence-to-molfile';
13
+ import {getTranslatedSequences} from '../../model/format-translation/conversion-utils';
14
+ import {MoleculeImage} from '../utils/molecule-img';
15
+ import {download} from '../../model/helpers';
16
+ import {SEQUENCE_COPIED_MSG, SEQ_TOOLTIP_MSG} from '../const/main-tab';
17
+ import {DEFAULT_AXOLABS_INPUT} from '../const/view';
18
+ import {FormatDetector} from '../../model/parsing-validation/format-detector';
19
+ import {SequenceValidator} from '../../model/parsing-validation/sequence-validator';
20
+ import {FormatConverter} from '../../model/format-translation/format-converter';
21
+ import {codesToHelmDictionary} from '../../model/data-loading-utils/json-loader';
22
+ import {DEFAULT_FORMATS} from '../../model/const';
23
+
24
+ export class MainTabUI {
25
+ constructor() {
26
+ const INPUT_FORMATS = Object.keys(codesToHelmDictionary).concat(DEFAULT_FORMATS.HELM);
27
+ this.moleculeImgDiv = ui.block([]);
28
+ this.outputTableDiv = ui.div([]);
29
+ this.formatChoiceInput = ui.choiceInput('', DEFAULT_FORMATS.HELM, INPUT_FORMATS, async () => {
30
+ this.format = this.formatChoiceInput.value;
31
+ this.updateTable();
32
+ await this.updateMolImg();
33
+ });
34
+ this.sequenceInputBase = ui.textInput('', DEFAULT_AXOLABS_INPUT,
35
+ () => { this.onInput.next(); });
36
+
37
+ this.init();
38
+
39
+ DG.debounce<string>(this.onInput, 300).subscribe(async () => {
40
+ this.init();
41
+ this.formatChoiceInput.value = this.format;
42
+ this.updateTable();
43
+ await this.updateMolImg();
44
+ });
45
+ }
46
+
47
+ // todo: reduce # of state vars by further refactoring legacy code
48
+ private onInput = new rxjs.Subject<string>();
49
+ private moleculeImgDiv: HTMLDivElement;
50
+ private outputTableDiv: HTMLDivElement;
51
+ private formatChoiceInput: DG.InputBase;
52
+ private sequenceInputBase: DG.InputBase;
53
+ private molfile: string;
54
+ public sequence: string;
55
+ private format: string | null;
56
+
57
+ async getHtmlElement(): Promise<HTMLDivElement> {
58
+ const sequenceColoredInput = new ColoredTextInput(this.sequenceInputBase, highlightInvalidSubsequence);
59
+
60
+ const downloadMolfileButton = ui.button(
61
+ 'Get Molfile',
62
+ () => { this.saveMolfile(); },
63
+ 'Save sequence as Molfile V3000');
64
+
65
+ const copySmilesButton = ui.button(
66
+ 'Copy SMILES',
67
+ () => { this.copySmiles(); },
68
+ 'Copy SMILES for the sequence');
69
+
70
+ const formatChoiceInput = ui.div([this.formatChoiceInput]);
71
+
72
+ const inputTableRow = {
73
+ format: formatChoiceInput,
74
+ textInput: sequenceColoredInput.root,
75
+ };
76
+ const upperBlock = ui.table(
77
+ [inputTableRow], (item) => [item.format, item.textInput]
78
+ );
79
+ upperBlock.classList.add('st-main-input-table');
80
+
81
+ const outputTable = ui.block([
82
+ this.outputTableDiv,
83
+ downloadMolfileButton,
84
+ copySmilesButton,
85
+ ]);
86
+
87
+ const mainTabBody = ui.box(
88
+ ui.div([
89
+ upperBlock,
90
+ outputTable,
91
+ this.moleculeImgDiv,
92
+ ], {style: {paddingTop: '20px', paddingLeft: '20px'}})
93
+ );
94
+
95
+ this.formatChoiceInput.value = this.format;
96
+ this.updateTable();
97
+ await this.updateMolImg();
98
+ return mainTabBody;
99
+ }
100
+
101
+ private saveMolfile(): void {
102
+ const result = (new SequenceToMolfileConverter(this.sequence, false,
103
+ this.formatChoiceInput.value!)).convert();
104
+ download(this.sequence + '.mol', encodeURIComponent(result));
105
+ }
106
+
107
+ private copySmiles(): void {
108
+ const smiles = DG.chem.convert(this.molfile, DG.chem.Notation.MolBlock, DG.chem.Notation.Smiles);
109
+ navigator.clipboard.writeText(smiles).then(
110
+ () => grok.shell.info(SEQUENCE_COPIED_MSG)
111
+ );
112
+ }
113
+
114
+ private updateTable(): void {
115
+ this.outputTableDiv.innerHTML = '';
116
+ // todo: does not detect correctly (U-A)(U-A)
117
+ const indexOfInvalidChar = (!this.format) ? 0 : (new SequenceValidator(this.sequence)).getInvalidCodeIndex(this.format!);
118
+ const translatedSequences = getTranslatedSequences(this.sequence, indexOfInvalidChar, this.format!);
119
+ const tableRows = [];
120
+
121
+ for (const key of Object.keys(translatedSequences)) {
122
+ const sequence = ('indexOfFirstInvalidChar' in translatedSequences) ?
123
+ ui.divH([]) :
124
+ ui.link(
125
+ translatedSequences[key],
126
+ () => navigator.clipboard.writeText(translatedSequences[key])
127
+ .then(() => grok.shell.info(SEQUENCE_COPIED_MSG)),
128
+ SEQ_TOOLTIP_MSG, ''
129
+ );
130
+ tableRows.push({
131
+ format: key,
132
+ sequence: sequence,
133
+ });
134
+ }
135
+ const outputTable = ui.table(tableRows, (item) => [item.format, item.sequence], ['FORMAT', 'SEQUENCE']);
136
+
137
+ this.outputTableDiv.append(outputTable);
138
+ this.outputTableDiv.classList.add('st-main-output-table');
139
+ }
140
+
141
+ private async updateMolImg(): Promise<void> {
142
+ const canvasWidth = 500;
143
+ const canvasHeight = 170;
144
+ const molImgObj = new MoleculeImage(this.molfile);
145
+ await molImgObj.drawMolecule(this.moleculeImgDiv, canvasWidth, canvasHeight);
146
+ // should the canvas be returned from the above function?
147
+ $(this.moleculeImgDiv).find('canvas').css('float', 'inherit');
148
+ }
149
+
150
+ // todo: sort mehtods
151
+ private init(): void {
152
+ this.sequence = this.getFormattedSequence();
153
+
154
+ this.format = (new FormatDetector(this.sequence)).getFormat();
155
+
156
+ // warning: getMolfile relies on 'this.format', so the order is important
157
+ this.molfile = this.getMolfile();
158
+ }
159
+
160
+ private getFormattedSequence(): string {
161
+ return this.sequenceInputBase.value.replace(/\s/g, '');
162
+ }
163
+
164
+ private getMolfile(): string {
165
+ if (!this.format)
166
+ return '';
167
+ if (this.format === DEFAULT_FORMATS.HELM) {
168
+ const axolabs = (new FormatConverter(this.sequence, this.format).convertTo(DEFAULT_FORMATS.AXOLABS));
169
+ return (new SequenceToMolfileConverter(axolabs, false, DEFAULT_FORMATS.AXOLABS).convert());
170
+ }
171
+ const molfile = (new SequenceToMolfileConverter(this.sequence, false, this.format)).convert();
172
+ return molfile;
173
+ }
174
+ }
@@ -0,0 +1,173 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ import * as rxjs from 'rxjs';
7
+ import '../css/sdf-tab.css';
8
+ import $ from 'cash-dom';
9
+
10
+ import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
11
+
12
+ // import {drawMolecule} from '../../utils/structures-works/draw-molecule';
13
+ import {highlightInvalidSubsequence} from '../utils/colored-input/input-painters';
14
+ import {getLinkedMolfile, saveSdf} from '../../model/sequence-to-structure-utils/sdf-tab';
15
+ import {ColoredTextInput} from '../utils/colored-input/colored-text-input';
16
+ import {MoleculeImage} from '../utils/molecule-img';
17
+ import {StrandData} from '../../model/sequence-to-structure-utils/sdf-tab';
18
+ import {DEFAULT_AXOLABS_INPUT} from '../const/view';
19
+
20
+ const enum DIRECTION {
21
+ STRAIGHT = '5′ → 3′',
22
+ INVERSE = '3′ → 5′',
23
+ };
24
+ const STRANDS = ['ss', 'as', 'as2'] as const;
25
+
26
+ export class SdfTabUI {
27
+ constructor() {
28
+ this.onInput = new rxjs.Subject<string>();
29
+ this.inputBase = Object.fromEntries(
30
+ STRANDS.map(
31
+ (key) => [key, ui.textInput('', DEFAULT_AXOLABS_INPUT, () => { this.onInput.next(); })]
32
+ )
33
+ );
34
+ this.useChiralInput = ui.boolInput('Use chiral', true);
35
+ this.saveAllStrandsInput = ui.boolInput('Save as one entity', true);
36
+ ui.tooltip.bind(this.saveAllStrandsInput.root, 'Save SDF with all strands in one molfile');
37
+ this.directionInversion = Object.fromEntries(
38
+ STRANDS.map((key) => [key, false])
39
+ );
40
+ this.moleculeImgDiv = ui.block([]);
41
+ $(this.moleculeImgDiv).addClass('st-sdf-mol-img');
42
+
43
+ DG.debounce<string>(this.onInput, 300).subscribe(async () => {
44
+ await this.updateMoleculeImg();
45
+ });
46
+ }
47
+
48
+ private onInput: rxjs.Subject<string>;
49
+ private useChiralInput: DG.InputBase<boolean | null>;
50
+ private saveAllStrandsInput: DG.InputBase<boolean | null>;
51
+ private inputBase: {[key: string]: DG.InputBase<string>};
52
+ private directionInversion: {[key: string]: boolean};
53
+ private moleculeImgDiv: HTMLDivElement;
54
+
55
+ async getHtmlDivElement(): Promise<HTMLDivElement> {
56
+ const tableLayout = this.getTableInput();
57
+ const boolInputsAndButton = this.getBoolInputsAndButton();
58
+ await this.updateMoleculeImg();
59
+ const bottomDiv = ui.divH([boolInputsAndButton, this.moleculeImgDiv]);
60
+ $(bottomDiv).addClass('st-sdf-bottom');
61
+
62
+ const sdfTabBody = ui.divV([tableLayout, bottomDiv]);
63
+ $(sdfTabBody).addClass('st-sdf-body');
64
+
65
+ return sdfTabBody;
66
+ }
67
+
68
+ private getBoolInputsAndButton(): HTMLDivElement {
69
+ const saveButton = ui.buttonsInput([
70
+ ui.bigButton('Save SDF', () => {
71
+ const strandData = this.getStrandData();
72
+ saveSdf(strandData.ss, strandData.as, strandData.as2,
73
+ this.useChiralInput.value!, this.saveAllStrandsInput.value!);
74
+ })
75
+ ]);
76
+
77
+ const boolInputsAndButtonArray = [this.saveAllStrandsInput.root, this.useChiralInput.root, saveButton];
78
+ const boolInputsAndButton = ui.divV(boolInputsAndButtonArray);
79
+ for (const item of boolInputsAndButtonArray)
80
+ $(item).addClass('st-sdf-bool-button-block');
81
+ return boolInputsAndButton;
82
+ }
83
+
84
+ private getTableInput(): HTMLTableElement {
85
+ const coloredInput = Object.fromEntries(
86
+ STRANDS.map(
87
+ (key) => [key, new ColoredTextInput(this.inputBase[key], highlightInvalidSubsequence)]
88
+ )
89
+ );
90
+
91
+ const directionChoiceInput = Object.fromEntries(
92
+ STRANDS.map(
93
+ (key) => [key, ui.choiceInput(
94
+ `${key.toUpperCase()} direction`, DIRECTION.STRAIGHT, [DIRECTION.STRAIGHT, DIRECTION.INVERSE]
95
+ )]
96
+ )
97
+ );
98
+
99
+ STRANDS.forEach((strand) => {
100
+ directionChoiceInput[strand].onChanged(() => {
101
+ this.directionInversion[strand] = directionChoiceInput[strand].value === DIRECTION.INVERSE;
102
+ this.onInput.next();
103
+ });
104
+ });
105
+
106
+ const labelNames = ['Sense Strand', 'Anti Sense', 'Anti Sense 2'];
107
+ const labelNameMap = new Map(STRANDS.map(
108
+ (key, index) => [key, labelNames[index]]
109
+ ));
110
+ const label = Object.fromEntries(
111
+ STRANDS.map(
112
+ (key) => [key, ui.label(labelNameMap.get(key)!)]
113
+ )
114
+ );
115
+
116
+ const tableRows = STRANDS.map((strand) => {
117
+ return {
118
+ label: label[strand],
119
+ textInput: coloredInput[strand].root,
120
+ choiceInput: directionChoiceInput[strand].root,
121
+ };
122
+ });
123
+ const tableLayout = ui.table(
124
+ tableRows, (item) => [item.label, item.textInput, item.choiceInput]);
125
+ $(tableLayout).css('margin-top', '10px');
126
+
127
+ for (const strand of STRANDS) {
128
+ let element = label[strand].parentElement!;
129
+ element.classList.add('st-sdf-input-form');
130
+ // the following line is necessary because otherwise overridden by
131
+ // d4-item-table class
132
+ $(element).css('padding-top', '3px');
133
+
134
+ element = directionChoiceInput[strand].root.parentElement!;
135
+ element.classList.add('st-sdf-input-form', 'st-sdf-direction-choice');
136
+
137
+ element = this.inputBase[strand].root.parentElement!;
138
+ element.classList.add('st-sdf-text-input-td');
139
+ }
140
+ return tableLayout;
141
+ }
142
+
143
+ private getStrandData() {
144
+ return Object.fromEntries(
145
+ STRANDS.map((strand) => [strand, {
146
+ strand: this.inputBase[strand].value.replace(/\s*/g, ''),
147
+ invert: this.directionInversion[strand]
148
+ }])
149
+ );
150
+ }
151
+
152
+ private getMolfile(ss: StrandData, as: StrandData, as2: StrandData): string {
153
+ return getLinkedMolfile(ss, as, as2, this.useChiralInput.value!);
154
+ }
155
+
156
+ private async updateMoleculeImg(): Promise<void> {
157
+ let molfile = '';
158
+ try {
159
+ const strandData = this.getStrandData();
160
+ molfile = this.getMolfile(strandData.ss, strandData.as, strandData.as2);
161
+ } catch (err) {
162
+ const errStr = errorToConsole(err);
163
+ console.error(errStr);
164
+ }
165
+ // todo: calculate relative numbers
166
+ const canvasWidth = 650;
167
+ const canvasHeight = 150;
168
+ const molImgObj = new MoleculeImage(molfile);
169
+ await molImgObj.drawMolecule(this.moleculeImgDiv, canvasWidth, canvasHeight);
170
+ // should the canvas be returned from the above function?
171
+ $(this.moleculeImgDiv).find('canvas').css('float', 'inherit');
172
+ }
173
+ }
@@ -0,0 +1,18 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ export function showAppInfo() {
7
+ const textDiv = ui.divText('hello');
8
+ ui.dialog('Dialog name').add(textDiv).show();
9
+ }
10
+
11
+ const appMainDescription = ui.info([
12
+ ui.divText('How to convert one sequence:', {style: {'font-weight': 'bolder'}}),
13
+ ui.divText('Paste sequence into the text field below'),
14
+ ui.divText('\n How to convert many sequences:', {style: {'font-weight': 'bolder'}}),
15
+ ui.divText('1. Drag & drop an Excel or CSV file with sequences into Datagrok'),
16
+ ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
17
+ ui.divText('This will add the result column to the right of the table'),
18
+ ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.');
@@ -0,0 +1,56 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ // external modules dependencies
7
+ import $ from 'cash-dom';
8
+
9
+ // inner dependencies
10
+ import '../../css/colored-text-input.css';
11
+
12
+
13
+ /** Class for colorizing input in the textarea of DG.InputBase. */
14
+ export class ColoredTextInput {
15
+ constructor(
16
+ private textInputBase: DG.InputBase<string>,
17
+ painter: (str: string) => HTMLSpanElement[],
18
+ /** Resize, no scrolls */
19
+ resizeable: boolean = true
20
+ ) {
21
+ this.textInputBase = textInputBase;
22
+ this.painter = painter;
23
+ $(this.root).addClass('colored-text-input');
24
+ if (resizeable) {
25
+ // make input field automatically resizeable
26
+ this.textInputBase.onInput(
27
+ () => {
28
+ // necessary for the field to be squeezable, not only expandable
29
+ $(this.textArea).css('height', 0);
30
+ $(this.textArea).css('height', (this.textArea!.scrollHeight) + 'px');
31
+ }
32
+ );
33
+ }
34
+ this.highlights = ui.div([]);
35
+ this.root.appendChild(this.highlights);
36
+ this.colorize();
37
+
38
+ this.textInputBase.onInput(() => this.colorize());
39
+ }
40
+
41
+ private highlights: HTMLDivElement;
42
+ /** Divide input value into an array of spans, each with its own text color, use -webkit-text-fill-color. */
43
+ private painter: (str: string) => HTMLSpanElement[];
44
+
45
+ get textArea() {
46
+ return this.textInputBase.root.getElementsByTagName('textarea').item(0);
47
+ };
48
+
49
+ get root() { return this.textInputBase.root; };
50
+
51
+ private colorize() {
52
+ const spans = this.painter(this.textInputBase.value);
53
+ this.highlights.innerHTML = '';
54
+ spans.forEach((span: HTMLSpanElement) => this.highlights.appendChild(span));
55
+ }
56
+ }
@@ -0,0 +1,44 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ import {SequenceValidator} from '../../../model/parsing-validation/sequence-validator';
7
+ import {FormatDetector} from '../../../model/parsing-validation/format-detector';
8
+
9
+ import $ from 'cash-dom';
10
+
11
+ /** Set different colors for letters, can be upgraded to color various monomers */
12
+ export function demoPainter(input: string): HTMLSpanElement[] {
13
+ const colors = ['red', 'blueviolet', 'chartreuse',
14
+ 'aquamarine', 'darkcyan', 'gold', 'green', 'aqua', 'orange',
15
+ 'blue'];
16
+ const spans: HTMLSpanElement[] = [];
17
+ for (let i = 0; i < input.length; ++i) {
18
+ const span = ui.span([input.charAt(i)]);
19
+ // $(span).css('-webkit-text-fill-color', colors.at(Math.round(Math.random() * colors.length))!);
20
+ $(span).css('-webkit-text-fill-color', colors[i % colors.length]!);
21
+ spans.push(span);
22
+ }
23
+ return spans;
24
+ }
25
+
26
+ // todo: port to another place
27
+ export function highlightInvalidSubsequence(input: string): HTMLSpanElement[] {
28
+ // validate sequence
29
+ let cutoff = 0;
30
+ const format = (new FormatDetector(input)).getFormat();
31
+ if (format !== null)
32
+ cutoff = (new SequenceValidator(input)).getInvalidCodeIndex(format!);
33
+ const isValid = cutoff < 0 || input === '';
34
+ const greyTextSpan = ui.span([]);
35
+ $(greyTextSpan).css('-webkit-text-fill-color', 'var(--grey-6)');
36
+ const redTextSpan = ui.span([]);
37
+ $(redTextSpan).css('-webkit-text-fill-color', 'red');
38
+
39
+ if (!isValid) {
40
+ greyTextSpan.innerHTML = input.slice(0, cutoff);
41
+ redTextSpan.innerHTML = input.slice(cutoff);
42
+ } else { greyTextSpan.innerHTML = input; }
43
+ return [greyTextSpan, redTextSpan];
44
+ };
@@ -0,0 +1,86 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+ import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
6
+
7
+ import $ from 'cash-dom';
8
+
9
+ import {extractAtomDataV3000} from '../../model/sequence-to-structure-utils/mol-transformations';
10
+
11
+ /** Draw molecule on the canvas and append it to the specified div, with the
12
+ * option of zoom-in */
13
+ export async function drawMolecule(
14
+ moleculeImgDiv: HTMLDivElement,
15
+ canvasWidth: number, canvasHeight: number,
16
+ molfile: string
17
+ ): Promise<void> {
18
+ // clear div's content if any
19
+ moleculeImgDiv.innerHTML = '';
20
+
21
+ if (molfile !== '') {
22
+ const canvas = ui.canvas(canvasWidth * window.devicePixelRatio, canvasHeight * window.devicePixelRatio);
23
+
24
+ // Draw zoomed-out molecule
25
+ canvas.style.width = `${canvasWidth}px`;
26
+ canvas.style.height = `${canvasHeight}px`;
27
+ canvas.style.borderStyle = 'solid';
28
+ canvas.style.borderColor = 'var(--grey-3)';
29
+ canvas.style.borderWidth = 'thin';
30
+ drawMolfileOnCanvas(canvas, molfile);
31
+
32
+ // Dialog with zoomed-in molecule
33
+ $(canvas).on('click', async () => { await drawZoomedInMolecule(molfile); });
34
+ $(canvas).on('mouseover', () => $(canvas).css('cursor', 'grab')); // for some reason 'zoom-in' value wouldn't work
35
+ $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
36
+
37
+ moleculeImgDiv.append(canvas);
38
+ }
39
+ }
40
+
41
+
42
+ export async function drawZoomedInMolecule(molfile: string): Promise<void> {
43
+ try {
44
+ const dialogDivStyle = {
45
+ overflowX: 'scroll',
46
+ };
47
+ const dialogDiv = ui.div([], {style: dialogDivStyle});
48
+
49
+ // dialogDiv size required, but now available before dialog show()
50
+ const atomCoordinates = extractAtomDataV3000(molfile);
51
+ // const cw: number = $(window).width() * 0.80; // dialogDiv.clientWidth
52
+ const clientHeight: number = $(window).height() * 0.70; // dialogDiv.clientHeight
53
+ const molWidth: number = Math.max(...atomCoordinates.x) - Math.min(...atomCoordinates.x);
54
+ const molHeight: number = Math.max(...atomCoordinates.y) - Math.min(...atomCoordinates.y);
55
+
56
+ // const wR: number = cw / molWidth;
57
+ const hR: number = clientHeight / molHeight;
58
+ const r: number = hR; // Math.max(wR, hR);
59
+ const dialogCanvasWidth = r * molWidth;
60
+ const dialogCanvasHeight = r * molHeight;
61
+
62
+ const dialogCanvas = ui.canvas(
63
+ dialogCanvasWidth * window.devicePixelRatio, dialogCanvasHeight * window.devicePixelRatio
64
+ );
65
+ dialogCanvas.style.width = `${dialogCanvasWidth}px`;
66
+ dialogCanvas.style.height = `${dialogCanvasHeight}px`;
67
+ await drawMolfileOnCanvas(dialogCanvas, molfile);
68
+
69
+ dialogDiv.appendChild(dialogCanvas);
70
+ ui.dialog('Molecule')
71
+ .add(dialogDiv)
72
+ .showModal(true);
73
+ } catch (err) {
74
+ const errStr = errorToConsole(err);
75
+ console.error(errStr);
76
+ }
77
+ };
78
+
79
+
80
+ export async function drawMolfileOnCanvas(canvas: HTMLCanvasElement, molfile: string): Promise<void> {
81
+ await grok.functions.call('Chem:canvasMol', {
82
+ x: 0, y: 0, w: canvas.width, h: canvas.height, canvas: canvas,
83
+ molString: molfile, scaffoldMolString: '',
84
+ options: {normalizeDepiction: false, straightenDepiction: false}
85
+ });
86
+ };
@@ -0,0 +1,106 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ import {MolfileHandler} from '@datagrok-libraries/chem-meta/src/parsing-utils/molfile-handler';
7
+ import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
8
+
9
+ import $ from 'cash-dom';
10
+
11
+ const InvalidMolfileError = class extends Error {
12
+ constructor(message: string) { super(message); }
13
+ };
14
+
15
+ export class MoleculeImage {
16
+ constructor(molblok: string) {
17
+ this.molblock = molblok;
18
+ }
19
+
20
+ private _validMolBlock: string;
21
+ get molblock(): string { return this._validMolBlock; }
22
+
23
+ set molblock(value: string) {
24
+ try {
25
+ this.validateMolBlock(value);
26
+ } catch (error) {
27
+ if (error instanceof InvalidMolfileError)
28
+ value = '';
29
+ const errorMessage = errorToConsole(error);
30
+ console.error(errorMessage);
31
+ }
32
+ this._validMolBlock = value;
33
+ }
34
+
35
+ private validateMolBlock(molblock: string): void {
36
+ // todo: add a sound criterion
37
+ if (molblock === '')
38
+ throw new InvalidMolfileError('MoleculeImage: invalid molblock');
39
+ }
40
+
41
+ private async drawMolBlockOnCanvas(canvas: HTMLCanvasElement): Promise<void> {
42
+ try {
43
+ await grok.functions.call('Chem:canvasMol', {
44
+ x: 0, y: 0, w: canvas.width, h: canvas.height, canvas: canvas,
45
+ molString: this.molblock, scaffoldMolString: '',
46
+ options: {normalizeDepiction: false, straightenDepiction: false}
47
+ });
48
+ } catch (err) {
49
+ const errStr = errorToConsole(err);
50
+ console.error(errStr);
51
+ }
52
+ };
53
+
54
+ private getMoleculeDimensions(): {height: number, width: number} {
55
+ const molData = MolfileHandler.getInstance(this.molblock);
56
+ const width: number = Math.max(...molData.x) - Math.min(...molData.x);
57
+ const height: number = Math.max(...molData.y) - Math.min(...molData.y);
58
+ return {height: height, width: width};
59
+ }
60
+
61
+ private async zoomIn(): Promise<void> {
62
+ const dialog = ui.dialog('Molecule');
63
+ const dialogDivStyle = {
64
+ overflowX: 'scroll',
65
+ };
66
+ const dialogDiv = ui.div([], {style: dialogDivStyle});
67
+
68
+ const height = $(window).height() * 0.7;
69
+ const molDimensions = this.getMoleculeDimensions();
70
+
71
+ const zoomRatio = height / molDimensions.height;
72
+
73
+ const dialogCanvasHeight = height;
74
+ const dialogCanvasWidth = molDimensions.width * zoomRatio;
75
+
76
+ const dialogCanvas = ui.canvas(dialogCanvasWidth, dialogCanvasHeight);
77
+ await this.drawMolBlockOnCanvas(dialogCanvas);
78
+
79
+ dialogDiv.appendChild(dialogCanvas);
80
+ dialog.add(dialogDiv).showModal(true);
81
+ }
82
+
83
+ public async drawMolecule(
84
+ moleculeImgDiv: HTMLDivElement,
85
+ canvasWidth: number, canvasHeight: number
86
+ ): Promise<void> {
87
+ moleculeImgDiv.innerHTML = '';
88
+
89
+ const canvas = ui.canvas(canvasWidth * window.devicePixelRatio, canvasHeight * window.devicePixelRatio);
90
+
91
+ // Draw zoomed-out molecule
92
+ canvas.style.width = `${canvasWidth}px`;
93
+ canvas.style.height = `${canvasHeight}px`;
94
+ canvas.style.borderStyle = 'solid';
95
+ canvas.style.borderColor = 'var(--grey-3)';
96
+ canvas.style.borderWidth = 'thin';
97
+ this.drawMolBlockOnCanvas(canvas);
98
+
99
+ // Dialog with zoomed-in molecule
100
+ $(canvas).on('click', async () => { await this.zoomIn(); });
101
+ $(canvas).on('mouseover', () => $(canvas).css('cursor', 'grab')); // for some reason 'zoom-in' value wouldn't work
102
+ $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
103
+
104
+ moleculeImgDiv.append(canvas);
105
+ }
106
+ }