@datagrok/sequence-translator 1.0.17 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +4 -3
- package/CHANGELOG.md +36 -0
- package/detectors.js +8 -0
- package/dist/package-test.js +2 -73079
- package/dist/package-test.js.map +1 -0
- package/dist/package.js +2 -72284
- package/dist/package.js.map +1 -0
- package/files/axolabs-style.json +97 -0
- package/files/codes-to-symbols.json +67 -0
- package/files/formats-to-helm.json +63 -0
- package/files/linkers.json +22 -0
- package/files/monomer-lib.json +1142 -0
- package/link-bio +7 -0
- package/package.json +30 -31
- package/scripts/build-monomer-lib.py +391 -122
- package/src/demo/demo-st-ui.ts +71 -0
- package/src/demo/handle-error.ts +12 -0
- package/src/model/axolabs/axolabs-tab.ts +111 -0
- package/src/model/axolabs/const.ts +33 -0
- package/src/{axolabs-tab → model/axolabs}/draw-svg.ts +1 -1
- package/src/{axolabs-tab → model/axolabs}/helpers.ts +7 -5
- package/src/model/const.ts +18 -0
- package/src/model/data-loading-utils/const.ts +8 -0
- package/src/model/data-loading-utils/json-loader.ts +38 -0
- package/src/model/data-loading-utils/types.ts +30 -0
- package/src/model/format-translation/const.ts +8 -0
- package/src/model/format-translation/conversion-utils.ts +49 -0
- package/src/model/format-translation/format-converter.ts +109 -0
- package/src/model/helpers.ts +12 -0
- package/src/model/monomer-lib/const.ts +3 -0
- package/src/model/monomer-lib/lib-wrapper.ts +119 -0
- package/src/model/parsing-validation/format-detector.ts +57 -0
- package/src/model/parsing-validation/sequence-validator.ts +52 -0
- package/src/model/sequence-to-structure-utils/const.ts +1 -0
- package/src/{utils/structures-works → model/sequence-to-structure-utils}/mol-transformations.ts +33 -41
- package/src/model/sequence-to-structure-utils/monomer-code-parser.ts +92 -0
- package/src/model/sequence-to-structure-utils/sdf-tab.ts +97 -0
- package/src/model/sequence-to-structure-utils/sequence-to-molfile.ts +409 -0
- package/src/package-test.ts +3 -1
- package/src/package.ts +113 -91
- package/src/tests/const.ts +24 -0
- package/src/tests/formats-support.ts +40 -0
- package/src/tests/formats-to-helm.ts +53 -0
- package/src/tests/helm-to-nucleotides.ts +28 -0
- package/src/view/const/main-tab.ts +3 -0
- package/src/view/const/view.ts +10 -0
- package/src/view/css/axolabs-tab.css +1 -0
- package/src/view/css/colored-text-input.css +27 -0
- package/src/view/css/main-tab.css +46 -0
- package/src/view/css/sdf-tab.css +39 -0
- package/src/view/monomer-lib-viewer/viewer.ts +22 -0
- package/src/view/tabs/axolabs.ts +719 -0
- package/src/view/tabs/main.ts +174 -0
- package/src/view/tabs/sdf.ts +193 -0
- package/src/view/utils/app-info-dialog.ts +18 -0
- package/src/view/utils/colored-input/colored-text-input.ts +56 -0
- package/src/view/utils/colored-input/input-painters.ts +44 -0
- package/src/view/utils/draw-molecule.ts +86 -0
- package/src/view/utils/molecule-img.ts +106 -0
- package/src/view/view.ts +127 -0
- package/tsconfig.json +12 -18
- package/webpack.config.js +17 -4
- package/README.md +0 -84
- package/css/style.css +0 -18
- package/img/Sequence Translator Axolabs.png +0 -0
- package/jest.config.js +0 -33
- package/setup-unlink-clean.cmd +0 -14
- package/setup-unlink-clean.sh +0 -21
- package/setup.cmd +0 -14
- package/setup.sh +0 -37
- package/src/__jest__/remote.test.ts +0 -77
- package/src/__jest__/test-node.ts +0 -97
- package/src/apps/oligo-sd-file-app.ts +0 -58
- package/src/autostart/calculations.ts +0 -40
- package/src/autostart/constants.ts +0 -37
- package/src/autostart/registration.ts +0 -306
- package/src/axolabs-tab/axolabs-tab.ts +0 -873
- package/src/axolabs-tab/define-pattern.ts +0 -874
- package/src/hardcode-to-be-eliminated/ICDs.ts +0 -3
- package/src/hardcode-to-be-eliminated/IDPs.ts +0 -3
- package/src/hardcode-to-be-eliminated/const.ts +0 -5
- package/src/hardcode-to-be-eliminated/constants.ts +0 -101
- package/src/hardcode-to-be-eliminated/converters.ts +0 -323
- package/src/hardcode-to-be-eliminated/map.ts +0 -720
- package/src/hardcode-to-be-eliminated/salts.ts +0 -2
- package/src/hardcode-to-be-eliminated/sources.ts +0 -3
- package/src/hardcode-to-be-eliminated/users.ts +0 -3
- package/src/main-tab/main-tab.ts +0 -210
- package/src/sdf-tab/sdf-tab.ts +0 -163
- package/src/sdf-tab/sequence-codes-tools.ts +0 -347
- package/src/tests/smiles-tests.ts +0 -458
- package/src/utils/const.ts +0 -0
- package/src/utils/helpers.ts +0 -28
- package/src/utils/parse.ts +0 -27
- package/src/utils/sdf-add-columns.ts +0 -118
- package/src/utils/sdf-save-table.ts +0 -56
- package/src/utils/structures-works/draw-molecule.ts +0 -84
- package/src/utils/structures-works/from-monomers.ts +0 -266
- package/test-SequenceTranslator-6288c2fbe346-695b7b55.html +0 -259
- 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 SDF',
|
|
62
|
+
() => { this.saveMolfile(); },
|
|
63
|
+
'Save structure as SDF');
|
|
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() + '\n$$$$';
|
|
104
|
+
download(this.sequence + '.sdf', 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,193 @@
|
|
|
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.onInvalidInput = new rxjs.Subject<string>();
|
|
30
|
+
this.inputBase = Object.fromEntries(
|
|
31
|
+
STRANDS.map(
|
|
32
|
+
(key) => [key, ui.textInput('', '', () => { this.onInput.next(); })]
|
|
33
|
+
)
|
|
34
|
+
);
|
|
35
|
+
this.useChiralInput = ui.boolInput('Use chiral', true);
|
|
36
|
+
this.saveAllStrandsInput = ui.boolInput('Save as one entity', true);
|
|
37
|
+
ui.tooltip.bind(this.saveAllStrandsInput.root, 'Save SDF with all strands in one molfile');
|
|
38
|
+
this.directionInversion = Object.fromEntries(
|
|
39
|
+
STRANDS.map((key) => [key, false])
|
|
40
|
+
);
|
|
41
|
+
this.moleculeImgDiv = ui.block([]);
|
|
42
|
+
$(this.moleculeImgDiv).addClass('st-sdf-mol-img');
|
|
43
|
+
|
|
44
|
+
DG.debounce<string>(this.onInput, 300).subscribe(async () => {
|
|
45
|
+
await this.updateMoleculeImg();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
DG.debounce<string>(this.onInvalidInput, 1000).subscribe(async () => {
|
|
49
|
+
grok.shell.warning('Insert Sense strand');
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private onInput: rxjs.Subject<string>;
|
|
54
|
+
private onInvalidInput: rxjs.Subject<string>;
|
|
55
|
+
private useChiralInput: DG.InputBase<boolean | null>;
|
|
56
|
+
private saveAllStrandsInput: DG.InputBase<boolean | null>;
|
|
57
|
+
private inputBase: {[key: string]: DG.InputBase<string>};
|
|
58
|
+
private directionInversion: {[key: string]: boolean};
|
|
59
|
+
private moleculeImgDiv: HTMLDivElement;
|
|
60
|
+
|
|
61
|
+
async getHtmlDivElement(): Promise<HTMLDivElement> {
|
|
62
|
+
const tableLayout = this.getTableInput();
|
|
63
|
+
const boolInputsAndButton = this.getBoolInputsAndButton();
|
|
64
|
+
await this.updateMoleculeImg();
|
|
65
|
+
const bottomDiv = ui.divH([boolInputsAndButton, this.moleculeImgDiv]);
|
|
66
|
+
$(bottomDiv).addClass('st-sdf-bottom');
|
|
67
|
+
|
|
68
|
+
const sdfTabBody = ui.divV([tableLayout, bottomDiv]);
|
|
69
|
+
$(sdfTabBody).addClass('st-sdf-body');
|
|
70
|
+
|
|
71
|
+
return sdfTabBody;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private getBoolInputsAndButton(): HTMLDivElement {
|
|
75
|
+
const saveButton = ui.buttonsInput([
|
|
76
|
+
ui.bigButton('Save SDF', () => {
|
|
77
|
+
const strandData = this.getStrandData();
|
|
78
|
+
saveSdf(strandData.ss, strandData.as, strandData.as2,
|
|
79
|
+
this.useChiralInput.value!, this.saveAllStrandsInput.value!);
|
|
80
|
+
})
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const boolInputsAndButtonArray = [this.saveAllStrandsInput.root, this.useChiralInput.root, saveButton];
|
|
84
|
+
const boolInputsAndButton = ui.divV(boolInputsAndButtonArray);
|
|
85
|
+
for (const item of boolInputsAndButtonArray)
|
|
86
|
+
$(item).addClass('st-sdf-bool-button-block');
|
|
87
|
+
return boolInputsAndButton;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private getTableInput(): HTMLTableElement {
|
|
91
|
+
const coloredInput = Object.fromEntries(
|
|
92
|
+
STRANDS.map(
|
|
93
|
+
(key) => [key, new ColoredTextInput(this.inputBase[key], highlightInvalidSubsequence)]
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const directionChoiceInput = Object.fromEntries(
|
|
98
|
+
STRANDS.map(
|
|
99
|
+
(key, idx) => {
|
|
100
|
+
const selected = (idx === 0) ? DIRECTION.STRAIGHT : DIRECTION.INVERSE;
|
|
101
|
+
return [key, ui.choiceInput(
|
|
102
|
+
`${key.toUpperCase()} direction`, selected, [DIRECTION.STRAIGHT, DIRECTION.INVERSE]
|
|
103
|
+
)]
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
STRANDS.forEach((strand, idx) => {
|
|
109
|
+
directionChoiceInput[strand].onChanged(() => {
|
|
110
|
+
let value = directionChoiceInput[strand].value === DIRECTION.INVERSE;
|
|
111
|
+
// warning: the next line is necessary until the legacy notion of direction used in the molfile generation gets fixed
|
|
112
|
+
if (idx > 0) { value = !value; }
|
|
113
|
+
this.directionInversion[strand] = value;
|
|
114
|
+
this.onInput.next();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const labelNames = ['Sense Strand', 'Anti Sense', 'Anti Sense 2'];
|
|
119
|
+
const labelNameMap = new Map(STRANDS.map(
|
|
120
|
+
(key, index) => [key, labelNames[index]]
|
|
121
|
+
));
|
|
122
|
+
const label = Object.fromEntries(
|
|
123
|
+
STRANDS.map(
|
|
124
|
+
(key) => [key, ui.label(labelNameMap.get(key)!)]
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const tableRows = STRANDS.map((strand) => {
|
|
129
|
+
return {
|
|
130
|
+
label: label[strand],
|
|
131
|
+
textInput: coloredInput[strand].root,
|
|
132
|
+
choiceInput: directionChoiceInput[strand].root,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
const tableLayout = ui.table(
|
|
136
|
+
tableRows, (item) => [item.label, item.textInput, item.choiceInput]);
|
|
137
|
+
$(tableLayout).css('margin-top', '10px');
|
|
138
|
+
|
|
139
|
+
for (const strand of STRANDS) {
|
|
140
|
+
let element = label[strand].parentElement!;
|
|
141
|
+
element.classList.add('st-sdf-input-form');
|
|
142
|
+
// the following line is necessary because otherwise overridden by
|
|
143
|
+
// d4-item-table class
|
|
144
|
+
$(element).css('padding-top', '3px');
|
|
145
|
+
|
|
146
|
+
element = directionChoiceInput[strand].root.parentElement!;
|
|
147
|
+
element.classList.add('st-sdf-input-form', 'st-sdf-direction-choice');
|
|
148
|
+
|
|
149
|
+
element = this.inputBase[strand].root.parentElement!;
|
|
150
|
+
element.classList.add('st-sdf-text-input-td');
|
|
151
|
+
}
|
|
152
|
+
return tableLayout;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private getStrandData() {
|
|
156
|
+
return Object.fromEntries(
|
|
157
|
+
STRANDS.map((strand, idx) => {
|
|
158
|
+
let invert = this.directionInversion[strand];
|
|
159
|
+
return [strand, {
|
|
160
|
+
strand: this.inputBase[strand].value.replace(/\s*/g, ''),
|
|
161
|
+
invert: invert
|
|
162
|
+
}]
|
|
163
|
+
})
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private getMolfile(ss: StrandData, as: StrandData, as2: StrandData): string {
|
|
168
|
+
if (ss.strand === '' && (as.strand !== '' || as2.strand !== '')) {
|
|
169
|
+
this.onInvalidInput.next();
|
|
170
|
+
return '';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return getLinkedMolfile(ss, as, as2, this.useChiralInput.value!);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private async updateMoleculeImg(): Promise<void> {
|
|
177
|
+
let molfile = '';
|
|
178
|
+
try {
|
|
179
|
+
const strandData = this.getStrandData();
|
|
180
|
+
molfile = this.getMolfile(strandData.ss, strandData.as, strandData.as2);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const errStr = errorToConsole(err);
|
|
183
|
+
console.error(errStr);
|
|
184
|
+
}
|
|
185
|
+
// todo: calculate relative numbers
|
|
186
|
+
const canvasWidth = 650;
|
|
187
|
+
const canvasHeight = 150;
|
|
188
|
+
const molImgObj = new MoleculeImage(molfile);
|
|
189
|
+
await molImgObj.drawMolecule(this.moleculeImgDiv, canvasWidth, canvasHeight);
|
|
190
|
+
// should the canvas be returned from the above function?
|
|
191
|
+
$(this.moleculeImgDiv).find('canvas').css('float', 'inherit');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -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
|
+
}
|