@datagrok/sequence-translator 1.2.6 → 1.2.9
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 +5 -5
- package/CHANGELOG.md +12 -0
- package/dist/package-test.js +2 -1
- package/dist/package-test.js.LICENSE.txt +8 -0
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -1
- package/dist/package.js.LICENSE.txt +8 -0
- package/dist/package.js.map +1 -1
- package/files/pattern-app-data.json +80 -0
- package/package.json +21 -14
- package/src/{model → apps/common/model}/const.ts +1 -1
- package/src/{model/data-loading-utils → apps/common/model/data-loader}/const.ts +7 -2
- package/src/apps/common/model/data-loader/json-loader.ts +48 -0
- package/src/{model/data-loading-utils → apps/common/model/data-loader}/types.ts +13 -6
- package/src/{model → apps/common/model}/monomer-lib/lib-wrapper.ts +9 -12
- package/src/apps/common/model/oligo-toolkit-package.ts +30 -0
- package/src/{model → apps/common/model}/parsing-validation/format-detector.ts +5 -5
- package/src/{model → apps/common/model}/parsing-validation/format-handler.ts +18 -19
- package/src/{model → apps/common/model}/parsing-validation/sequence-validator.ts +1 -1
- package/src/apps/common/view/app-ui-base.ts +28 -0
- package/src/apps/common/view/combined-app-ui.ts +66 -0
- package/src/{view/utils → apps/common/view/components}/colored-input/colored-text-input.ts +1 -1
- package/src/{view/utils → apps/common/view/components}/draw-molecule.ts +1 -1
- package/src/{view/utils → apps/common/view/components}/molecule-img.ts +3 -3
- package/src/{view/const/ui.ts → apps/common/view/const.ts} +4 -4
- package/src/apps/common/view/isolated-app-ui.ts +43 -0
- package/src/{view/monomer-lib-viewer/viewer.ts → apps/common/view/monomer-lib-viewer.ts} +2 -2
- package/src/apps/common/view/utils.ts +29 -0
- package/src/apps/pattern/model/const.ts +121 -0
- package/src/apps/pattern/model/data-manager.ts +297 -0
- package/src/apps/pattern/model/event-bus.ts +470 -0
- package/src/apps/pattern/model/router.ts +46 -0
- package/src/apps/pattern/model/subscription-manager.ts +21 -0
- package/src/apps/pattern/model/translator.ts +68 -0
- package/src/apps/pattern/model/types.ts +52 -0
- package/src/apps/pattern/model/utils.ts +110 -0
- package/src/apps/pattern/view/components/bulk-convert/column-input.ts +69 -0
- package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +37 -0
- package/src/apps/pattern/view/components/bulk-convert/table-input.ts +95 -0
- package/src/apps/pattern/view/components/edit-block-controls.ts +196 -0
- package/src/apps/pattern/view/components/left-section.ts +44 -0
- package/src/apps/pattern/view/components/load-block-controls.ts +198 -0
- package/src/apps/pattern/view/components/numeric-label-visibility-controls.ts +69 -0
- package/src/apps/pattern/view/components/right-section.ts +148 -0
- package/src/apps/pattern/view/components/strand-editor/dialog.ts +79 -0
- package/src/apps/pattern/view/components/strand-editor/header-controls.ts +105 -0
- package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +159 -0
- package/src/apps/pattern/view/components/terminal-modification-editor.ts +127 -0
- package/src/apps/pattern/view/components/translation-examples-block.ts +139 -0
- package/src/{view/style/pattern-app.css → apps/pattern/view/style.css} +4 -0
- package/src/apps/pattern/view/svg-utils/const.ts +63 -0
- package/src/apps/pattern/view/svg-utils/dimensions-calculator.ts +498 -0
- package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +45 -0
- package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +82 -0
- package/src/apps/pattern/view/svg-utils/svg-renderer.ts +396 -0
- package/src/apps/pattern/view/svg-utils/utils.ts +37 -0
- package/src/apps/pattern/view/types.ts +14 -0
- package/src/apps/pattern/view/ui.ts +61 -0
- package/src/{model/structure-app → apps/structure/model}/mol-transformations.ts +3 -3
- package/src/{model/structure-app → apps/structure/model}/monomer-code-parser.ts +9 -10
- package/src/{model/structure-app → apps/structure/model}/oligo-structure.ts +4 -4
- package/src/{model/structure-app → apps/structure/model}/sequence-to-molfile.ts +2 -2
- package/src/{view/apps/oligo-structure.ts → apps/structure/view/ui.ts} +31 -17
- package/src/{model/translator-app → apps/translator/model}/conversion-utils.ts +25 -7
- package/src/{model/translator-app → apps/translator/model}/format-converter.ts +7 -12
- package/src/{view/const/oligo-translator.ts → apps/translator/view/const.ts} +2 -0
- package/src/apps/translator/view/ui.ts +547 -0
- package/src/demo/demo-st-ui.ts +12 -32
- package/src/package.ts +76 -56
- package/src/plugins/mermade.ts +9 -9
- package/src/polytool/const.ts +40 -0
- package/src/polytool/csv-to-json-monomer-lib-converter.ts +40 -0
- package/src/polytool/cyclized.ts +56 -0
- package/src/polytool/monomer-lib-handler.ts +115 -0
- package/src/polytool/transformation.ts +326 -0
- package/src/polytool/ui.ts +59 -0
- package/src/polytool/utils.ts +20 -0
- package/src/tests/const.ts +5 -5
- package/src/tests/formats-support.ts +6 -6
- package/src/tests/formats-to-helm.ts +5 -5
- package/src/tests/helm-to-nucleotides.ts +5 -5
- package/tsconfig.json +4 -10
- package/webpack.config.js +3 -0
- package/files/axolabs-style.json +0 -97
- package/src/model/data-loading-utils/json-loader.ts +0 -38
- package/src/model/pattern-app/const.ts +0 -33
- package/src/model/pattern-app/draw-svg.ts +0 -193
- package/src/model/pattern-app/helpers.ts +0 -96
- package/src/model/pattern-app/oligo-pattern.ts +0 -111
- package/src/view/app-ui.ts +0 -193
- package/src/view/apps/oligo-pattern.ts +0 -759
- package/src/view/apps/oligo-translator.ts +0 -184
- /package/src/{model → apps/common/model}/helpers.ts +0 -0
- /package/src/{model → apps/common/model}/monomer-lib/const.ts +0 -0
- /package/src/{view/utils → apps/common/view/components}/app-info-dialog.ts +0 -0
- /package/src/{view/utils → apps/common/view/components}/colored-input/input-painters.ts +0 -0
- /package/src/{view/style/colored-text-input.css → apps/common/view/components/colored-input/style.css} +0 -0
- /package/src/{view/utils → apps/common/view/components}/router.ts +0 -0
- /package/src/{model/structure-app → apps/structure/model}/const.ts +0 -0
- /package/src/{view/style/structure-app.css → apps/structure/view/style.css} +0 -0
- /package/src/{model/translator-app → apps/translator/model}/const.ts +0 -0
- /package/src/{view/style/translator-app.css → apps/translator/view/style.css} +0 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
/* Do not change these import lines to match external modules in webpack configuration */
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
3
|
+
import * as grok from 'datagrok-api/grok';
|
|
4
|
+
import * as ui from 'datagrok-api/ui';
|
|
5
|
+
|
|
6
|
+
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
7
|
+
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
8
|
+
|
|
9
|
+
import * as rxjs from 'rxjs';
|
|
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';
|
|
27
|
+
|
|
28
|
+
const enum REQUIRED_COLUMN_LABEL {
|
|
29
|
+
SEQUENCE = 'Sequence',
|
|
30
|
+
}
|
|
31
|
+
const REQUIRED_COLUMN_LABELS = [REQUIRED_COLUMN_LABEL.SEQUENCE];
|
|
32
|
+
|
|
33
|
+
class TranslatorAppLayout {
|
|
34
|
+
private eventBus: EventBus;
|
|
35
|
+
private inputFormats = Object.keys(CODES_TO_HELM_DICT).concat(DEFAULT_FORMATS.HELM);
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.moleculeImgDiv = ui.div([]);
|
|
39
|
+
this.moleculeImgDiv.className = 'mol-host';
|
|
40
|
+
this.moleculeImgDiv.style.border = '1px solid var(--grey-2)';
|
|
41
|
+
this.moleculeImgDiv.style.borderRadius = '1px';
|
|
42
|
+
this.moleculeImgDiv.style.marginTop = '12px';
|
|
43
|
+
|
|
44
|
+
this.outputTableDiv = ui.div([]);
|
|
45
|
+
this.formatChoiceInput = ui.choiceInput('', DEFAULT_FORMATS.HELM, this.inputFormats, async () => {
|
|
46
|
+
this.format = this.formatChoiceInput.value;
|
|
47
|
+
this.updateTable();
|
|
48
|
+
await this.updateMolImg();
|
|
49
|
+
});
|
|
50
|
+
this.sequenceInputBase = ui.textInput('', DEFAULT_AXOLABS_INPUT,
|
|
51
|
+
() => { this.onInput.next(); });
|
|
52
|
+
|
|
53
|
+
this.init();
|
|
54
|
+
|
|
55
|
+
DG.debounce<string>(this.onInput, 300).subscribe(async () => {
|
|
56
|
+
this.init();
|
|
57
|
+
this.formatChoiceInput.value = this.format;
|
|
58
|
+
this.updateTable();
|
|
59
|
+
await this.updateMolImg();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.eventBus = EventBus.getInstance();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// todo: reduce # of state vars by further refactoring legacy code
|
|
66
|
+
private onInput = new rxjs.Subject<string>();
|
|
67
|
+
private moleculeImgDiv: HTMLDivElement;
|
|
68
|
+
private outputTableDiv: HTMLDivElement;
|
|
69
|
+
private formatChoiceInput: DG.InputBase;
|
|
70
|
+
private sequenceInputBase: DG.InputBase;
|
|
71
|
+
private molfile: string;
|
|
72
|
+
public sequence: string;
|
|
73
|
+
private format: string | null;
|
|
74
|
+
|
|
75
|
+
async getHtmlElement(): Promise<HTMLDivElement> {
|
|
76
|
+
const singleSequenceControls = this.constructSingleSequenceControls();
|
|
77
|
+
const bulkControls = this.constructBulkTranslationControls();
|
|
78
|
+
const layout = ui.box(
|
|
79
|
+
ui.panel([
|
|
80
|
+
singleSequenceControls,
|
|
81
|
+
bulkControls,
|
|
82
|
+
ui.block([ui.box(this.moleculeImgDiv)])
|
|
83
|
+
])
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
this.formatChoiceInput.value = this.format;
|
|
87
|
+
this.updateTable();
|
|
88
|
+
await this.updateMolImg();
|
|
89
|
+
return layout;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private constructBulkTranslationControls(): HTMLDivElement {
|
|
93
|
+
const title = ui.h1('Bulk');
|
|
94
|
+
ui.tooltip.bind(title, 'Bulk translation from table input');
|
|
95
|
+
|
|
96
|
+
const tableControlsManager = new TableControlsManager(this.eventBus);
|
|
97
|
+
const tableControls = tableControlsManager.createUIComponents();
|
|
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
|
+
);
|
|
111
|
+
const convertBulkButton = this.createConvertBulkButton();
|
|
112
|
+
|
|
113
|
+
const tableControlsContainer = ui.div([
|
|
114
|
+
...tableControls,
|
|
115
|
+
inputFormats,
|
|
116
|
+
outputFormats,
|
|
117
|
+
convertBulkButton
|
|
118
|
+
], 'ui-form');
|
|
119
|
+
|
|
120
|
+
const bulkTranslationControls = ui.block25([
|
|
121
|
+
title,
|
|
122
|
+
tableControlsContainer,
|
|
123
|
+
]);
|
|
124
|
+
return bulkTranslationControls;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private createConvertBulkButton(): HTMLButtonElement {
|
|
128
|
+
const convertBulkButton = ui.bigButton('Convert', () => this.processConvertBulkButtonClick());
|
|
129
|
+
|
|
130
|
+
ui.tooltip.bind(convertBulkButton, 'Convert sequences from table input');
|
|
131
|
+
$(convertBulkButton).css({
|
|
132
|
+
'float': 'right',
|
|
133
|
+
'margin-top': '20px',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return convertBulkButton;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private processConvertBulkButtonClick(): void {
|
|
140
|
+
const selectedTable = this.eventBus.getSelectedTable();
|
|
141
|
+
if (!selectedTable) {
|
|
142
|
+
grok.shell.warning('No table selected');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const inputFormat = this.eventBus.getSelectedInputFormat();
|
|
147
|
+
const outputFormat = this.eventBus.getSelectedOutputFormat();
|
|
148
|
+
const sequenceColumn = this.eventBus.getSelectedColumn(REQUIRED_COLUMN_LABEL.SEQUENCE);
|
|
149
|
+
if (!sequenceColumn) {
|
|
150
|
+
grok.shell.warning('No sequence column selected');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const newColumnName = `${sequenceColumn.name} (${outputFormat})`;
|
|
155
|
+
const translatedColumn = DG.Column.fromList(
|
|
156
|
+
DG.TYPE.STRING,
|
|
157
|
+
newColumnName,
|
|
158
|
+
sequenceColumn.toList().map((sequence: string) => {
|
|
159
|
+
const result = convert(sequence, inputFormat, outputFormat);
|
|
160
|
+
return result;
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
|
|
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);
|
|
169
|
+
const setUnits = outputFormat == NUCLEOTIDES_FORMAT ? SeqHandler.setUnitsToFastaColumn :
|
|
170
|
+
SeqHandler.setUnitsToHelmColumn;
|
|
171
|
+
setUnits(seqHandler);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// add newColumn to the table
|
|
175
|
+
selectedTable.columns.add(translatedColumn);
|
|
176
|
+
// update the view
|
|
177
|
+
|
|
178
|
+
grok.data.detectSemanticTypes(selectedTable);
|
|
179
|
+
grok.shell.v = grok.shell.getTableView(selectedTable.name);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private constructSingleSequenceControls(): HTMLDivElement {
|
|
183
|
+
const sequenceColoredInput = new ColoredTextInput(this.sequenceInputBase, highlightInvalidSubsequence);
|
|
184
|
+
|
|
185
|
+
const downloadMolfileButton = ui.button(
|
|
186
|
+
'Get SDF',
|
|
187
|
+
() => { this.saveMolfile(); },
|
|
188
|
+
'Save structure as SDF');
|
|
189
|
+
|
|
190
|
+
const copySmilesButton = ui.button(
|
|
191
|
+
'Copy SMILES',
|
|
192
|
+
() => { this.copySmiles(); },
|
|
193
|
+
'Copy SMILES for the sequence');
|
|
194
|
+
|
|
195
|
+
const formatChoiceInput = ui.div([this.formatChoiceInput]);
|
|
196
|
+
|
|
197
|
+
const clearButton = ui.button(
|
|
198
|
+
ui.icons.delete(() => { sequenceColoredInput.inputBase.value = ''; }),
|
|
199
|
+
() => {}
|
|
200
|
+
);
|
|
201
|
+
ui.tooltip.bind(clearButton, 'Clear input');
|
|
202
|
+
|
|
203
|
+
const inputTableRow = {
|
|
204
|
+
format: formatChoiceInput,
|
|
205
|
+
textInput: sequenceColoredInput.root,
|
|
206
|
+
clearBtn: clearButton
|
|
207
|
+
};
|
|
208
|
+
const singleSequenceInputControls = ui.table(
|
|
209
|
+
[inputTableRow], (item) => [item.format, item.textInput, item.clearBtn]
|
|
210
|
+
);
|
|
211
|
+
singleSequenceInputControls.classList.add('st-translator-input-table');
|
|
212
|
+
|
|
213
|
+
const singleSequenceOutputTable = ui.block([
|
|
214
|
+
this.outputTableDiv,
|
|
215
|
+
downloadMolfileButton,
|
|
216
|
+
copySmilesButton,
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
const singleSequenceControls = ui.block75([
|
|
220
|
+
ui.h1('Single sequence'),
|
|
221
|
+
singleSequenceInputControls,
|
|
222
|
+
singleSequenceOutputTable,
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
return singleSequenceControls;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private saveMolfile(): void {
|
|
229
|
+
const result = (new SequenceToMolfileConverter(this.sequence, false,
|
|
230
|
+
this.formatChoiceInput.value!)).convert() + '\n$$$$';
|
|
231
|
+
download(this.sequence + '.sdf', encodeURIComponent(result));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private copySmiles(): void {
|
|
235
|
+
const smiles = DG.chem.convert(this.molfile, DG.chem.Notation.MolBlock, DG.chem.Notation.Smiles);
|
|
236
|
+
navigator.clipboard.writeText(smiles).then(
|
|
237
|
+
() => grok.shell.info(SEQUENCE_COPIED_MSG)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private updateTable(): void {
|
|
242
|
+
this.outputTableDiv.innerHTML = '';
|
|
243
|
+
// todo: does not detect correctly (U-A)(U-A)
|
|
244
|
+
const indexOfInvalidChar = (!this.format) ? 0 :
|
|
245
|
+
(new SequenceValidator(this.sequence)).getInvalidCodeIndex(this.format!);
|
|
246
|
+
const translatedSequences = getTranslatedSequences(this.sequence, indexOfInvalidChar, this.format!);
|
|
247
|
+
const tableRows = [];
|
|
248
|
+
|
|
249
|
+
for (const key of Object.keys(translatedSequences)) {
|
|
250
|
+
const sequence = ('indexOfFirstInvalidChar' in translatedSequences) ?
|
|
251
|
+
ui.divH([]) :
|
|
252
|
+
ui.link(
|
|
253
|
+
translatedSequences[key],
|
|
254
|
+
() => navigator.clipboard.writeText(translatedSequences[key])
|
|
255
|
+
.then(() => grok.shell.info(SEQUENCE_COPIED_MSG)),
|
|
256
|
+
SEQ_TOOLTIP_MSG, ''
|
|
257
|
+
);
|
|
258
|
+
tableRows.push({
|
|
259
|
+
format: key,
|
|
260
|
+
sequence: sequence,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const outputTable = ui.table(tableRows, (item) => [item.format, item.sequence], ['FORMAT', 'SEQUENCE']);
|
|
264
|
+
|
|
265
|
+
this.outputTableDiv.append(outputTable);
|
|
266
|
+
this.outputTableDiv.classList.add('st-translator-output-table');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private async updateMolImg(): Promise<void> {
|
|
270
|
+
const canvasWidth = 500;
|
|
271
|
+
const canvasHeight = 170;
|
|
272
|
+
const molImgObj = new MoleculeImage(this.molfile);
|
|
273
|
+
await molImgObj.drawMolecule(this.moleculeImgDiv, canvasWidth, canvasHeight);
|
|
274
|
+
// should the canvas be returned from the above function?
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// todo: sort mehtods
|
|
278
|
+
private init(): void {
|
|
279
|
+
this.sequence = this.getFormattedSequence();
|
|
280
|
+
|
|
281
|
+
this.format = (new FormatDetector(this.sequence)).getFormat();
|
|
282
|
+
|
|
283
|
+
// warning: getMolfile relies on 'this.format', so the order is important
|
|
284
|
+
this.molfile = this.getMolfile();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private getFormattedSequence(): string {
|
|
288
|
+
return this.sequenceInputBase.value.replace(/\s/g, '');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private getMolfile(): string {
|
|
292
|
+
if (!this.format)
|
|
293
|
+
return '';
|
|
294
|
+
if (this.format === DEFAULT_FORMATS.HELM) {
|
|
295
|
+
const axolabs = (new FormatConverter(this.sequence, this.format).convertTo(DEFAULT_FORMATS.AXOLABS));
|
|
296
|
+
return (new SequenceToMolfileConverter(axolabs, false, DEFAULT_FORMATS.AXOLABS).convert());
|
|
297
|
+
}
|
|
298
|
+
const molfile = (new SequenceToMolfileConverter(this.sequence, false, this.format)).convert();
|
|
299
|
+
return molfile;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// todo: port to dedicated file, together with event bus
|
|
304
|
+
class TableControlsManager {
|
|
305
|
+
private tableInputManager: TableInputManager;
|
|
306
|
+
private columnInputManager: ColumnInputsManager;
|
|
307
|
+
|
|
308
|
+
constructor(eventBus: EventBus) {
|
|
309
|
+
this.tableInputManager = new TableInputManager(eventBus);
|
|
310
|
+
this.columnInputManager = new ColumnInputsManager(eventBus);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
createUIComponents(): HTMLElement[] {
|
|
314
|
+
const tableInput = this.tableInputManager.getTableInputContainer();
|
|
315
|
+
const columnControls = this.columnInputManager.getColumnControlsContainer();
|
|
316
|
+
|
|
317
|
+
return [
|
|
318
|
+
tableInput,
|
|
319
|
+
columnControls,
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
class TableInputManager {
|
|
325
|
+
private availableTables: DG.DataFrame[] = [];
|
|
326
|
+
private tableInputContainer: HTMLDivElement = ui.div([]);
|
|
327
|
+
|
|
328
|
+
constructor(private eventBus: EventBus) {
|
|
329
|
+
this.subscribeToTableEvents();
|
|
330
|
+
this.refreshTableInput();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getTableInputContainer(): HTMLDivElement {
|
|
334
|
+
return this.tableInputContainer;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private subscribeToTableEvents(): void {
|
|
338
|
+
grok.events.onTableAdded.subscribe((eventData) => this.handleTableAdded(eventData));
|
|
339
|
+
grok.events.onTableRemoved.subscribe((eventData) => this.handleTableRemoved(eventData));
|
|
340
|
+
this.eventBus.tableSelected$.subscribe(() => this.handleTableChoice());
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private getTableFromEventData(eventData: DG.EventData<DG.DataFrameArgs>): DG.DataFrame {
|
|
344
|
+
return eventData.args.dataFrame;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private handleTableAdded(eventData: DG.EventData<DG.DataFrameArgs>): void {
|
|
348
|
+
const table = this.getTableFromEventData(eventData);
|
|
349
|
+
|
|
350
|
+
if (this.availableTables.some((t: DG.DataFrame) => t.name === table.name))
|
|
351
|
+
return;
|
|
352
|
+
|
|
353
|
+
this.availableTables.push(table);
|
|
354
|
+
this.eventBus.selectTable(table);
|
|
355
|
+
this.refreshTableInput();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private handleTableRemoved(eventData: any): void {
|
|
359
|
+
const removedTable = this.getTableFromEventData(eventData);
|
|
360
|
+
this.availableTables = this.availableTables.filter((table: DG.DataFrame) => table.name !== removedTable.name);
|
|
361
|
+
|
|
362
|
+
const table = this.availableTables[0];
|
|
363
|
+
this.eventBus.selectTable(table ? table : null);
|
|
364
|
+
this.refreshTableInput();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private refreshTableInput(): void {
|
|
368
|
+
const tableInput = this.createTableInput();
|
|
369
|
+
$(this.tableInputContainer).empty();
|
|
370
|
+
this.tableInputContainer.append(tableInput.root);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private createTableInput(): DG.InputBase<DG.DataFrame | null> {
|
|
374
|
+
const currentlySelectedTable = this.eventBus.getSelectedTable();
|
|
375
|
+
|
|
376
|
+
const tableInput = ui.tableInput(
|
|
377
|
+
'Table',
|
|
378
|
+
currentlySelectedTable,
|
|
379
|
+
this.availableTables,
|
|
380
|
+
(table: DG.DataFrame) => {
|
|
381
|
+
// WARNING: non-null check necessary to prevent resetting columns to
|
|
382
|
+
// null upon handling onTableAdded
|
|
383
|
+
if (table !== null && table instanceof DG.DataFrame)
|
|
384
|
+
this.eventBus.selectTable(table);
|
|
385
|
+
});
|
|
386
|
+
return tableInput;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private handleTableChoice(): void {
|
|
390
|
+
const selectedTable = this.eventBus.getSelectedTable();
|
|
391
|
+
if (!selectedTable) return;
|
|
392
|
+
if (!this.isTableDisplayed(selectedTable))
|
|
393
|
+
this.displayTable(selectedTable);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private isTableDisplayed(table: DG.DataFrame): boolean {
|
|
397
|
+
return grok.shell.tableNames.includes(table.name);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private displayTable(table: DG.DataFrame): void {
|
|
401
|
+
const previousView = grok.shell.v;
|
|
402
|
+
grok.shell.addTableView(table);
|
|
403
|
+
grok.shell.v = previousView;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
class ColumnInputsManager {
|
|
408
|
+
private columnControlsContainer: HTMLDivElement = ui.div([]);
|
|
409
|
+
|
|
410
|
+
constructor(private eventBus: EventBus) {
|
|
411
|
+
this.eventBus.tableSelected$.subscribe(() => this.handleTableChoice());
|
|
412
|
+
this.refreshColumnControls();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
getColumnControlsContainer(): HTMLDivElement {
|
|
416
|
+
return this.columnControlsContainer;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private handleTableChoice(): void {
|
|
420
|
+
this.refreshColumnControls();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private refreshColumnControls(): void {
|
|
424
|
+
const columnInputs = this.createColumnInputs();
|
|
425
|
+
$(this.columnControlsContainer).empty();
|
|
426
|
+
const inputRoots = columnInputs.map((input) => input.root);
|
|
427
|
+
this.columnControlsContainer.append(
|
|
428
|
+
...inputRoots
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private createColumnInputs(): DG.ChoiceInput<string | null>[] {
|
|
433
|
+
const selectedTable = this.eventBus.getSelectedTable();
|
|
434
|
+
const columnNames = selectedTable !== null ?
|
|
435
|
+
selectedTable.columns.names().sort((a, b) => a.localeCompare(b)) : [];
|
|
436
|
+
|
|
437
|
+
const columnInputs = REQUIRED_COLUMN_LABELS.map((columnLabel: REQUIRED_COLUMN_LABEL) => {
|
|
438
|
+
const input = this.createColumnInput(columnLabel, columnNames, selectedTable);
|
|
439
|
+
return input;
|
|
440
|
+
});
|
|
441
|
+
return columnInputs;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private createColumnInput(
|
|
445
|
+
columnLabel: REQUIRED_COLUMN_LABEL,
|
|
446
|
+
columnNames: string[],
|
|
447
|
+
selectedTable: DG.DataFrame | null
|
|
448
|
+
): DG.ChoiceInput<string | null> {
|
|
449
|
+
const namePattern = columnLabel.toLowerCase();
|
|
450
|
+
const matchingColumnName = columnNames.find((name: string) => name.toLowerCase().includes(namePattern));
|
|
451
|
+
const selectedColumnName = matchingColumnName ? matchingColumnName : columnNames[0];
|
|
452
|
+
this.selectColumnIfTableNotNull(selectedTable, selectedColumnName, columnLabel);
|
|
453
|
+
|
|
454
|
+
const input = ui.choiceInput(
|
|
455
|
+
`${columnLabel}`,
|
|
456
|
+
selectedColumnName, columnNames,
|
|
457
|
+
(colName: string) => this.selectColumnIfTableNotNull(selectedTable, colName, columnLabel)
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
return input;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private selectColumnIfTableNotNull(
|
|
464
|
+
table: DG.DataFrame | null, columnName: string, columnLabel: REQUIRED_COLUMN_LABEL
|
|
465
|
+
): void {
|
|
466
|
+
if (table !== null) {
|
|
467
|
+
const selectedColumn = table.getCol(columnName);
|
|
468
|
+
this.eventBus.selectColumn(columnLabel, selectedColumn);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export class EventBus {
|
|
474
|
+
private static _instance: EventBus;
|
|
475
|
+
|
|
476
|
+
private _tableSelection$ = new rxjs.BehaviorSubject<DG.DataFrame | null>(null);
|
|
477
|
+
private _columnSelection = Object.fromEntries(REQUIRED_COLUMN_LABELS.map((columnLabel: string) => {
|
|
478
|
+
const columnSelection$ = new rxjs.BehaviorSubject<DG.Column<string> | null>(null);
|
|
479
|
+
return [columnLabel, columnSelection$];
|
|
480
|
+
}));
|
|
481
|
+
private _inputFormatSelection$ = new rxjs.BehaviorSubject<string>(DEFAULT_FORMATS.AXOLABS);
|
|
482
|
+
private _outputFormatSelection$ = new rxjs.BehaviorSubject<string>(NUCLEOTIDES_FORMAT);
|
|
483
|
+
|
|
484
|
+
private constructor() {}
|
|
485
|
+
|
|
486
|
+
public static getInstance(): EventBus {
|
|
487
|
+
if (EventBus._instance === undefined)
|
|
488
|
+
EventBus._instance = new EventBus();
|
|
489
|
+
return EventBus._instance;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
get tableSelected$(): rxjs.Observable<DG.DataFrame | null> {
|
|
493
|
+
return this._tableSelection$.asObservable();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
getSelectedTable(): DG.DataFrame | null {
|
|
497
|
+
return this._tableSelection$.getValue();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
selectTable(table: DG.DataFrame | null): void {
|
|
501
|
+
this._tableSelection$.next(table);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
selectColumn(columnLabel: REQUIRED_COLUMN_LABEL, column: DG.Column<string>): void {
|
|
505
|
+
this._columnSelection[columnLabel].next(column);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
getSelectedColumn(columnLabel: REQUIRED_COLUMN_LABEL): DG.Column<string> | null {
|
|
509
|
+
return this._columnSelection[columnLabel].getValue();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
getSelectedInputFormat(): string {
|
|
513
|
+
return this._inputFormatSelection$.getValue();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
selectInputFormat(format: string): void {
|
|
517
|
+
this._inputFormatSelection$.next(format);
|
|
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
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
package/src/demo/demo-st-ui.ts
CHANGED
|
@@ -3,43 +3,22 @@ import * as ui from 'datagrok-api/ui';
|
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
|
|
5
5
|
import {delay} from '@datagrok-libraries/utils/src/test';
|
|
6
|
-
import {
|
|
6
|
+
import {loadJsonData} from '../apps/common/model/data-loader/json-loader';
|
|
7
7
|
import {_package, oligoTranslatorApp, oligoPatternApp, oligoStructureApp} from '../package';
|
|
8
|
-
import {tryCatch} from '../model/helpers';
|
|
8
|
+
import {tryCatch} from '../apps/common/model/helpers';
|
|
9
9
|
|
|
10
10
|
export async function demoOligoTranslatorUI() {
|
|
11
|
-
await tryCatch(async () =>
|
|
11
|
+
await tryCatch(async () => {
|
|
12
|
+
const view = await oligoTranslatorApp();
|
|
13
|
+
grok.shell.addView(view);
|
|
14
|
+
});
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export async function demoOligoPatternUI() {
|
|
15
18
|
await tryCatch(async () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// warning: this redefinition is necessary because
|
|
20
|
-
// the ids of the elements can dynamically change
|
|
21
|
-
const choiceInputs: NodeListOf<HTMLSelectElement> = document.querySelectorAll('.st-pattern-choice-input > select');
|
|
22
|
-
len = choiceInputs.length;
|
|
23
|
-
const selectElement = choiceInputs[idxUpdate(idx)];
|
|
24
|
-
selectElement.value = value;
|
|
25
|
-
const event = new Event('input');
|
|
26
|
-
selectElement.dispatchEvent(event);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
await oligoPatternApp();
|
|
30
|
-
|
|
31
|
-
let len: number;
|
|
32
|
-
|
|
33
|
-
const ssNewValues = ['DNA', 'invAb', 'Z-New'];
|
|
34
|
-
ssNewValues.forEach(async (value, idx) => {
|
|
35
|
-
emulateUserInput(value, idx, (i) => 2 * i);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const asNewValues = ['2\'-O-Methyl', '2\'-Fluoro', '2\'-O-MOE'];
|
|
39
|
-
asNewValues.forEach(async (value, idx) => {
|
|
40
|
-
emulateUserInput(value, idx, (i) => (len - 2 - 2 * i));
|
|
41
|
-
});
|
|
42
|
-
})
|
|
19
|
+
const view = await oligoPatternApp();
|
|
20
|
+
grok.shell.addView(view);
|
|
21
|
+
});
|
|
43
22
|
}
|
|
44
23
|
|
|
45
24
|
export async function demoOligoStructureUI() {
|
|
@@ -52,10 +31,11 @@ export async function demoOligoStructureUI() {
|
|
|
52
31
|
const event = new Event('input');
|
|
53
32
|
textarea.dispatchEvent(event);
|
|
54
33
|
}
|
|
55
|
-
await oligoStructureApp();
|
|
34
|
+
const view = await oligoStructureApp();
|
|
35
|
+
grok.shell.addView(view);
|
|
56
36
|
const inputSequences = ['Afcgacsu', 'Afcgacsu', 'Afcgacsu'];
|
|
57
37
|
inputSequences.forEach(async (sequence, idx) => {
|
|
58
38
|
await setInputValue(idx, sequence);
|
|
59
|
-
})
|
|
39
|
+
});
|
|
60
40
|
});
|
|
61
41
|
}
|