@datagrok/sequence-translator 1.3.11 → 1.3.13
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/CHANGELOG.md +24 -0
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +9 -8
- package/src/apps/common/model/oligo-toolkit-package.ts +5 -3
- package/src/apps/common/view/components/colored-input/style.css +1 -0
- package/src/apps/structure/view/style.css +14 -19
- package/src/apps/structure/view/ui.ts +9 -25
- package/src/package-test.ts +1 -0
- package/src/package.ts +11 -11
- package/src/polytool/const.ts +10 -0
- package/src/polytool/pt-conversion.ts +1 -0
- package/src/polytool/pt-dialog.ts +6 -78
- package/src/polytool/pt-enumeration-chem.ts +1 -16
- package/src/polytool/pt-enumeration-helm-dialog.ts +415 -0
- package/src/polytool/pt-enumeration-helm.ts +71 -11
- package/src/polytool/pt-placeholders-input.ts +141 -0
- package/src/polytool/types.ts +20 -0
- package/src/tests/polytool-enumerate-tests.ts +95 -0
- package/src/utils/context-menu.ts +5 -3
- package/src/utils/err-info.ts +3 -2
- package/webpack.config.js +3 -10
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import * as ui from 'datagrok-api/ui';
|
|
2
|
+
import * as grok from 'datagrok-api/grok';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import $ from 'cash-dom';
|
|
6
|
+
import wu from 'wu';
|
|
7
|
+
import {fromEvent, Unsubscribable} from 'rxjs';
|
|
8
|
+
|
|
9
|
+
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
10
|
+
import {HelmAtom, HelmMol} from '@datagrok-libraries/helm-web-editor/src/types/org-helm';
|
|
11
|
+
import {getHelmHelper, HelmInputBase} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
12
|
+
import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
13
|
+
import {HelmType, ISeqMonomer} from '@datagrok-libraries/bio/src/helm/types';
|
|
14
|
+
import {helmTypeToPolymerType} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
|
|
15
|
+
import {getSeqHelper, ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
16
|
+
import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
PolyToolEnumeratorParams, PolyToolEnumeratorType, PolyToolEnumeratorTypes
|
|
20
|
+
} from './types';
|
|
21
|
+
import {getLibrariesList} from './utils';
|
|
22
|
+
import {getPtEnumeratorHelm, PT_HELM_EXAMPLE} from './pt-enumeration-helm';
|
|
23
|
+
import {PolyToolPlaceholdersInput} from './pt-placeholders-input';
|
|
24
|
+
import {defaultErrorHandler} from '../utils/err-info';
|
|
25
|
+
import {PT_UI_DIALOG_ENUMERATION} from './const';
|
|
26
|
+
|
|
27
|
+
import {_package} from '../package';
|
|
28
|
+
|
|
29
|
+
type PolyToolEnumerateInputs = {
|
|
30
|
+
enumeratorType: DG.ChoiceInput<PolyToolEnumeratorType>
|
|
31
|
+
macromolecule: HelmInputBase;
|
|
32
|
+
placeholders: PolyToolPlaceholdersInput;
|
|
33
|
+
toAtomicLevel: DG.InputBase<boolean>;
|
|
34
|
+
keepOriginal: DG.InputBase<boolean>;
|
|
35
|
+
/** Trivial names source column */ trivialNameCol: DG.InputBase<DG.Column<string> | null>,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export async function polyToolEnumerateHelmUI(cell?: DG.Cell): Promise<void> {
|
|
39
|
+
const maxWidth = window.innerWidth;
|
|
40
|
+
const maxHeight = window.innerHeight;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const resizeInputs = () => {
|
|
44
|
+
const dialogContentEl = $(dialog.root).find('div.d4-dialog-contents').get(0)! as HTMLElement;
|
|
45
|
+
const contentHeight = dialogContentEl.clientHeight;
|
|
46
|
+
|
|
47
|
+
const fitInputs: { [idx: number]: number } = {0: 1 /*, 3: 0.5*/};
|
|
48
|
+
const fitInputsSumHeight = Object.values(fitInputs).reduce((sum, h) => sum + h, 0);
|
|
49
|
+
|
|
50
|
+
const otherInputsHeight: number = wu.count(0).take(dialogContentEl.children.length)
|
|
51
|
+
.filter((i) => !(i in fitInputs))
|
|
52
|
+
.map((idx) => dialogContentEl.children[idx])
|
|
53
|
+
.filter((el) => el instanceof HTMLElement)
|
|
54
|
+
.map((el) => (el as HTMLElement).offsetHeight).reduce((sum, h) => sum + h, 0);
|
|
55
|
+
const remainFitHeight = contentHeight - otherInputsHeight - 38;
|
|
56
|
+
dialog.inputs.forEach((input, idx) => {
|
|
57
|
+
if (idx in fitInputs) {
|
|
58
|
+
const inputFitHeight = remainFitHeight * fitInputs[idx] / fitInputsSumHeight;
|
|
59
|
+
input.root.style.height = `${inputFitHeight}px`;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const [dialog, inputs] = await getPolyToolEnumerateDialog(cell, resizeInputs);
|
|
64
|
+
|
|
65
|
+
let isFirstShow = true;
|
|
66
|
+
ui.onSizeChanged(dialog.root).subscribe(() => {
|
|
67
|
+
if (isFirstShow) {
|
|
68
|
+
const dialogInputList = dialog.inputs;
|
|
69
|
+
const dialogRootCash = $(dialog.root);
|
|
70
|
+
const contentMaxHeight = maxHeight
|
|
71
|
+
- dialogRootCash.find('div.d4-dialog-header').get(0)!.offsetHeight
|
|
72
|
+
- dialogRootCash.find('div.d4-dialog-footer').get(0)!.offsetHeight;
|
|
73
|
+
|
|
74
|
+
// dialog.inputs2.macromolecule.root.style.backgroundColor = '#CCFFCC';
|
|
75
|
+
|
|
76
|
+
const dialogWidth = maxWidth * 0.7;
|
|
77
|
+
const dialogHeight = maxHeight * 0.7;
|
|
78
|
+
|
|
79
|
+
// Centered, but resizable dialog
|
|
80
|
+
dialog.root.style.width = `${Math.min(maxWidth, dialogWidth)}px`;
|
|
81
|
+
dialog.root.style.height = `${Math.min(maxHeight, dialogHeight)}px`;
|
|
82
|
+
dialog.root.style.left = `${Math.floor((maxWidth - dialog.root.offsetWidth) / 2)}px`;
|
|
83
|
+
dialog.root.style.top = `${Math.floor((maxHeight - dialog.root.offsetHeight) / 2)}px`;
|
|
84
|
+
|
|
85
|
+
isFirstShow = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
resizeInputs();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
_package.logger.debug('PolyToolEnumerateHelmUI: dialog before show');
|
|
92
|
+
const res = dialog.show({width: Math.max(350, maxWidth * 0.7), /* center: true,*/ resizable: true});
|
|
93
|
+
_package.logger.debug('PolyToolEnumerateHelmUI: dialog after show');
|
|
94
|
+
const k = 42;
|
|
95
|
+
} catch (_err: any) {
|
|
96
|
+
grok.shell.warning('To run PolyTool Enumeration, sketch the macromolecule and select monomers to vary');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async function getPolyToolEnumerateDialog(
|
|
102
|
+
cell?: DG.Cell, resizeInputs?: () => void
|
|
103
|
+
): Promise<[DG.Dialog, PolyToolEnumerateInputs]> {
|
|
104
|
+
const logPrefix = `ST: PT: HelmDialog()`;
|
|
105
|
+
const monomerLib = (await getMonomerLibHelper()).getMonomerLib();
|
|
106
|
+
const seqHelper = await getSeqHelper();
|
|
107
|
+
|
|
108
|
+
const [libList, helmHelper] = await Promise.all([getLibrariesList(), getHelmHelper()]);
|
|
109
|
+
|
|
110
|
+
const helmValue = (cell && cell.rowIndex >= 0) ? cell.value : PT_HELM_EXAMPLE;
|
|
111
|
+
|
|
112
|
+
const macromoleculeInput = helmHelper.createHelmInput(
|
|
113
|
+
'Macromolecule', {value: helmValue, editable: false});
|
|
114
|
+
|
|
115
|
+
const createTrivialNameColInput = (cell?: DG.Cell): DG.InputBase<DG.Column<string> | null> => {
|
|
116
|
+
return ui.input.column(
|
|
117
|
+
'Trivial name', {
|
|
118
|
+
table: cell?.dataFrame,
|
|
119
|
+
filter: (col: DG.Column): boolean => {
|
|
120
|
+
return col.type === DG.COLUMN_TYPE.STRING && col != cell?.column; /* id */
|
|
121
|
+
},
|
|
122
|
+
onValueChanged: (): void => {
|
|
123
|
+
const valueCol = inputs.trivialNameCol.value;
|
|
124
|
+
let newSrcId: typeof srcId = null;
|
|
125
|
+
if (cell && valueCol) {
|
|
126
|
+
const originalId = valueCol.get(cell.rowIndex)!;
|
|
127
|
+
newSrcId = {value: originalId, colName: valueCol.name};
|
|
128
|
+
}
|
|
129
|
+
srcId = newSrcId;
|
|
130
|
+
trivialNameSampleDiv.textContent = srcId ? `Original ID: ${srcId.value}` : '';
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
let srcId: { value: string, colName: string } | null = null;
|
|
136
|
+
const trivialNameSampleDiv = ui.divText('', {style: {marginLeft: '8px', marginTop: '2px'}});
|
|
137
|
+
const warningsTextDiv = ui.divText('', {style: {color: 'red'}});
|
|
138
|
+
const inputs: PolyToolEnumerateInputs = {
|
|
139
|
+
enumeratorType: ui.input.choice<PolyToolEnumeratorType>(
|
|
140
|
+
'Enumerator type', {
|
|
141
|
+
value: PolyToolEnumeratorTypes.Single,
|
|
142
|
+
items: Object.values(PolyToolEnumeratorTypes)
|
|
143
|
+
}) as DG.ChoiceInput<PolyToolEnumeratorType>,
|
|
144
|
+
macromolecule: macromoleculeInput,
|
|
145
|
+
|
|
146
|
+
placeholders: await PolyToolPlaceholdersInput.create(
|
|
147
|
+
'Placeholders', {
|
|
148
|
+
showAddNewRowIcon: true,
|
|
149
|
+
showRemoveRowIcon: true,
|
|
150
|
+
showRowHeader: false,
|
|
151
|
+
showCellTooltip: false,
|
|
152
|
+
}/*, 2/**/),
|
|
153
|
+
toAtomicLevel: ui.input.bool(
|
|
154
|
+
'To atomic level', {value: false}),
|
|
155
|
+
keepOriginal: ui.input.bool(
|
|
156
|
+
'Keep original', {value: false}),
|
|
157
|
+
trivialNameCol: createTrivialNameColInput(cell),
|
|
158
|
+
// warnings: ui.input.textArea('' +
|
|
159
|
+
// 'Warnings', {value: ''}),
|
|
160
|
+
};
|
|
161
|
+
const elementList = [
|
|
162
|
+
inputs.toAtomicLevel,
|
|
163
|
+
inputs.placeholders
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
inputs.trivialNameCol.addOptions(trivialNameSampleDiv);
|
|
167
|
+
// inputs.warnings.readOnly = true;
|
|
168
|
+
|
|
169
|
+
let placeholdersValidity: string | null = null;
|
|
170
|
+
inputs.placeholders.addValidator((value: string): string | null => {
|
|
171
|
+
try {
|
|
172
|
+
const missedMonomerList: ISeqMonomer[] = [];
|
|
173
|
+
for (const [posVal, monomerSymbolList] of Object.entries(inputs.placeholders.placeholdersValue)) {
|
|
174
|
+
const pos = parseInt(posVal);
|
|
175
|
+
const a = inputs.macromolecule.molValue.atoms[pos];
|
|
176
|
+
const helmType: HelmType = a.biotype()!;
|
|
177
|
+
const polymerType = helmTypeToPolymerType(helmType);
|
|
178
|
+
for (const symbol of monomerSymbolList) {
|
|
179
|
+
const substituteMonomer = monomerLib.getMonomer(polymerType, symbol)!;
|
|
180
|
+
// TODO: Check substitution monomer is presented in the library
|
|
181
|
+
if (!substituteMonomer || !substituteMonomer.lib)
|
|
182
|
+
missedMonomerList.push({polymerType, symbol});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const byType: { [polymerType: string]: string[] } = {};
|
|
187
|
+
for (const sm of missedMonomerList) {
|
|
188
|
+
let byTypeList = byType[sm.polymerType];
|
|
189
|
+
if (!byTypeList) byTypeList = byType[sm.polymerType] = [];
|
|
190
|
+
byTypeList.push(sm.symbol);
|
|
191
|
+
}
|
|
192
|
+
const byTypeStr: string = Object.entries(byType)
|
|
193
|
+
.map(([polymerType, symbolList]) => `${polymerType}: ${symbolList.join(', ')}`)
|
|
194
|
+
.join('\n');
|
|
195
|
+
placeholdersValidity = Object.keys(byTypeStr).length > 0 ?
|
|
196
|
+
`Placeholders contain missed monomers: ${byTypeStr}` : null;
|
|
197
|
+
} catch (err: any) {
|
|
198
|
+
const [errMsg, errStack] = defaultErrorHandler(err, false);
|
|
199
|
+
placeholdersValidity = errMsg;
|
|
200
|
+
}
|
|
201
|
+
setTimeout(() => { updateWarnings(); }, 0);
|
|
202
|
+
return placeholdersValidity;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const subs: Unsubscribable[] = [];
|
|
206
|
+
const destroy = () => {
|
|
207
|
+
inputs.placeholders.detach();
|
|
208
|
+
for (const sub of subs) sub.unsubscribe();
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
subs.push(inputs.macromolecule.onMouseMove.subscribe((e: MouseEvent) => {
|
|
212
|
+
try {
|
|
213
|
+
_package.logger.debug(`${logPrefix}, placeholdersInput.onMouseMove()`);
|
|
214
|
+
|
|
215
|
+
const argsX = e.offsetX;
|
|
216
|
+
const argsY = e.offsetY;
|
|
217
|
+
const mol = inputs.macromolecule.molValue;
|
|
218
|
+
const hoveredAtom = helmHelper.getHoveredAtom(argsX, argsY, mol, inputs.macromolecule.root.clientHeight);
|
|
219
|
+
if (hoveredAtom) {
|
|
220
|
+
const hoveredAtomContIdx = hoveredAtom._parent.atoms.indexOf(hoveredAtom);
|
|
221
|
+
const hoveredAtomContIdxStr = (hoveredAtomContIdx + 1).toString();
|
|
222
|
+
const substitutingMonomers = inputs.placeholders.placeholdersValue[hoveredAtomContIdx];
|
|
223
|
+
|
|
224
|
+
if (substitutingMonomers) {
|
|
225
|
+
const cnt = ui.divText(substitutingMonomers.join(', '));
|
|
226
|
+
inputs.macromolecule.showTooltip(cnt, hoveredAtom);
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
e.stopPropagation();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch (err: any) {
|
|
232
|
+
defaultErrorHandler(err, false);
|
|
233
|
+
}
|
|
234
|
+
}));
|
|
235
|
+
subs.push(inputs.macromolecule.onClick.subscribe((e: MouseEvent) => {
|
|
236
|
+
try {
|
|
237
|
+
_package.logger.debug(`${logPrefix}, placeholdersInput.onClick()`);
|
|
238
|
+
|
|
239
|
+
const argsX = e.offsetX;
|
|
240
|
+
const argsY = e.offsetY;
|
|
241
|
+
const mol = inputs.macromolecule.molValue;
|
|
242
|
+
const clickedAtom = helmHelper.getHoveredAtom(argsX, argsY, mol, inputs.macromolecule.root.clientHeight);
|
|
243
|
+
if (clickedAtom) {
|
|
244
|
+
const clickedAtomContIdx = clickedAtom._parent.atoms.indexOf(clickedAtom);
|
|
245
|
+
const clickedAtomContIdxStr = (clickedAtomContIdx + 1).toString();
|
|
246
|
+
|
|
247
|
+
const phDf = inputs.placeholders.grid.dataFrame;
|
|
248
|
+
const posList = phDf.columns.byName('Position').toList();
|
|
249
|
+
let rowIdx = posList.indexOf(clickedAtomContIdxStr);
|
|
250
|
+
if (rowIdx === -1) {
|
|
251
|
+
rowIdx = posList.findIndex((v) => isNaN(v));
|
|
252
|
+
if (rowIdx === -1) {
|
|
253
|
+
rowIdx = phDf.rows.addNew([clickedAtomContIdxStr, '']).idx;
|
|
254
|
+
}
|
|
255
|
+
phDf.set('Position', rowIdx, clickedAtomContIdxStr);
|
|
256
|
+
// const tgtCell = inputs.placeholders.grid.cell('Monomers', rowIdx);
|
|
257
|
+
}
|
|
258
|
+
phDf.currentCell = phDf.cell(rowIdx, 'Monomers');
|
|
259
|
+
//const gridRowIdx = inputs.placeholders.grid.tableRowToGrid(rowIdx);
|
|
260
|
+
//const monomersGCell = inputs.placeholders.grid.cell('Monomers', gridRowIdx);
|
|
261
|
+
const k = 42;
|
|
262
|
+
}
|
|
263
|
+
} catch (err: any) {
|
|
264
|
+
defaultErrorHandler(err);
|
|
265
|
+
}
|
|
266
|
+
}));
|
|
267
|
+
subs.push(inputs.placeholders.grid.dataFrame.onDataChanged.subscribe(() => {
|
|
268
|
+
updateMolView();
|
|
269
|
+
}));
|
|
270
|
+
subs.push(fromEvent<KeyboardEvent>(inputs.placeholders.grid.root, 'keydown')
|
|
271
|
+
.subscribe((e: KeyboardEvent) => {
|
|
272
|
+
if (e.key === 'Enter') e.stopPropagation();
|
|
273
|
+
}));
|
|
274
|
+
|
|
275
|
+
// TODO: suspect
|
|
276
|
+
subs.push(ui.onSizeChanged(inputs.placeholders.root).subscribe(() => {
|
|
277
|
+
if (resizeInputs) resizeInputs();
|
|
278
|
+
}));
|
|
279
|
+
|
|
280
|
+
// Displays the molecule from a current cell (monitors changes)
|
|
281
|
+
subs.push(grok.events.onCurrentCellChanged.subscribe(() => {
|
|
282
|
+
const cell = grok.shell.tv.dataFrame.currentCell;
|
|
283
|
+
|
|
284
|
+
if (cell.column.semType === DG.SEMTYPE.MACROMOLECULE && cell.column.meta.units === NOTATION.HELM)
|
|
285
|
+
inputs.macromolecule.stringValue = cell.value;
|
|
286
|
+
|
|
287
|
+
fillTrivialNameList(cell);
|
|
288
|
+
}));
|
|
289
|
+
|
|
290
|
+
inputs.macromolecule.root.style.setProperty('min-width', '250px', 'important');
|
|
291
|
+
// inputs.macromolecule.root.style.setProperty('max-height', '300px', 'important');
|
|
292
|
+
|
|
293
|
+
const updateMolView = () => {
|
|
294
|
+
const mol = inputs.macromolecule.molValue;
|
|
295
|
+
for (let aI = 0; aI < mol.atoms.length; aI++) {
|
|
296
|
+
const a = mol.atoms[aI];
|
|
297
|
+
a.highlighted = aI in inputs.placeholders.placeholdersValue;
|
|
298
|
+
}
|
|
299
|
+
inputs.macromolecule.redraw();
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const updateWarnings = () => {
|
|
303
|
+
const warnings = placeholdersValidity;
|
|
304
|
+
// const iw = inputs.warnings;
|
|
305
|
+
const w = warningsTextDiv;
|
|
306
|
+
if (!!warnings) {
|
|
307
|
+
// iw.value = warnings; // <- breaks dialog resize
|
|
308
|
+
// iw.enabled = true;
|
|
309
|
+
// iw.root.style.removeProperty('display');
|
|
310
|
+
|
|
311
|
+
w.innerText = warnings;
|
|
312
|
+
w.style.removeProperty('display');
|
|
313
|
+
} else {
|
|
314
|
+
// iw.value = ''; // <- breaks dialog resize
|
|
315
|
+
// iw.enabled = false;
|
|
316
|
+
// iw.root.style.setProperty('display', 'none');
|
|
317
|
+
|
|
318
|
+
w.innerText = '';
|
|
319
|
+
w.style.setProperty('display', 'none');
|
|
320
|
+
}
|
|
321
|
+
//resizeInputs();
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const fillTrivialNameList = (cell: DG.Cell) => {
|
|
325
|
+
// const colList: DG.Column[] = [];
|
|
326
|
+
// const colCount: number = cell.dataFrame.columns.length;
|
|
327
|
+
//
|
|
328
|
+
// // TODO: by semType?
|
|
329
|
+
// for (let colI: number = 0; colI < colCount; ++colI) {
|
|
330
|
+
// const col = cell.dataFrame.columns.byIndex(colI);
|
|
331
|
+
// if (col.type === DG.COLUMN_TYPE.STRING) colList.push(col);
|
|
332
|
+
// }
|
|
333
|
+
|
|
334
|
+
// TODO: Change table column items in inputs.trivialName
|
|
335
|
+
inputs.trivialNameCol = createTrivialNameColInput(cell);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const execDialog = async (): Promise<void> => {
|
|
339
|
+
try {
|
|
340
|
+
const srcHelm = inputs.macromolecule.stringValue;
|
|
341
|
+
const helmSelections: number[] = wu.enumerate<HelmAtom>(inputs.macromolecule.molValue.atoms)
|
|
342
|
+
.filter(([a, aI]) => a.highlighted)
|
|
343
|
+
.map(([a, aI]) => aI).toArray();
|
|
344
|
+
if (srcHelm === undefined || srcHelm === '') {
|
|
345
|
+
grok.shell.warning('PolyTool: no molecule was provided');
|
|
346
|
+
} else /* if (helmSelections === undefined || helmSelections.length < 1) {
|
|
347
|
+
grok.shell.warning('PolyTool: no selection was provided');
|
|
348
|
+
} else /**/ {
|
|
349
|
+
if (Object.keys(inputs.placeholders.placeholdersValue).length === 0) {
|
|
350
|
+
grok.shell.warning(`${PT_UI_DIALOG_ENUMERATION}: placeholders are empty`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
await getHelmHelper(); // initializes JSDraw and org
|
|
354
|
+
const params: PolyToolEnumeratorParams = {
|
|
355
|
+
type: inputs.enumeratorType.value!,
|
|
356
|
+
placeholders: inputs.placeholders.placeholdersValue,
|
|
357
|
+
keepOriginal: inputs.keepOriginal.value,
|
|
358
|
+
};
|
|
359
|
+
const enumeratorResDf = await enumerateHelm(srcHelm, srcId, params, inputs.toAtomicLevel.value, seqHelper);
|
|
360
|
+
grok.shell.addTableView(enumeratorResDf);
|
|
361
|
+
}
|
|
362
|
+
} catch (err: any) {
|
|
363
|
+
defaultErrorHandler(err);
|
|
364
|
+
} finally {
|
|
365
|
+
destroy();
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const dialog = ui.dialog({title: PT_UI_DIALOG_ENUMERATION, showFooter: true})
|
|
370
|
+
.add(inputs.macromolecule)
|
|
371
|
+
.add(inputs.placeholders)
|
|
372
|
+
.add(inputs.enumeratorType)
|
|
373
|
+
.add(inputs.trivialNameCol)
|
|
374
|
+
.add(inputs.toAtomicLevel)
|
|
375
|
+
.add(inputs.keepOriginal)
|
|
376
|
+
.add(warningsTextDiv)
|
|
377
|
+
// .addButton('Enumerate', () => {
|
|
378
|
+
// execDialog()
|
|
379
|
+
// .then(() => {});
|
|
380
|
+
// }, 0, 'Keeps the dialog open')
|
|
381
|
+
.onOK(() => {
|
|
382
|
+
execDialog()
|
|
383
|
+
.then(() => {destroy();});
|
|
384
|
+
})
|
|
385
|
+
.onCancel(() => { destroy(); });
|
|
386
|
+
return [dialog, inputs];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function enumerateHelm(
|
|
390
|
+
srcHelm: string, srcId: { value: string, colName: string } | null, params: PolyToolEnumeratorParams,
|
|
391
|
+
toAtomicLevel: boolean, seqHelper: ISeqHelper,
|
|
392
|
+
): Promise<DG.DataFrame> {
|
|
393
|
+
await getHelmHelper(); // initializes JSDraw and org
|
|
394
|
+
|
|
395
|
+
const resList = getPtEnumeratorHelm(srcHelm, srcId?.value ?? '', params);
|
|
396
|
+
const enumHelmCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'Enumerated', resList.length)
|
|
397
|
+
.init((rowIdx: number) => resList[rowIdx][0]);
|
|
398
|
+
const enumeratorResDf = DG.DataFrame.fromColumns([enumHelmCol]);
|
|
399
|
+
|
|
400
|
+
if (toAtomicLevel) {
|
|
401
|
+
const seqHelper: ISeqHelper = await getSeqHelper();
|
|
402
|
+
const toAtomicLevelRes = await seqHelper.helmToAtomicLevel(enumHelmCol, true, true);
|
|
403
|
+
toAtomicLevelRes.molCol.semType = DG.SEMTYPE.MOLECULE;
|
|
404
|
+
enumeratorResDf.columns.add(toAtomicLevelRes.molCol, false);
|
|
405
|
+
enumeratorResDf.columns.add(toAtomicLevelRes.molHighlightCol, false);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (srcId) {
|
|
409
|
+
const enumIdCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, srcId.colName, resList.length)
|
|
410
|
+
.init((rowIdx: number) => resList[rowIdx][1]);
|
|
411
|
+
enumeratorResDf.columns.add(enumIdCol);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return enumeratorResDf;
|
|
415
|
+
}
|
|
@@ -1,22 +1,82 @@
|
|
|
1
1
|
import * as ui from 'datagrok-api/ui';
|
|
2
2
|
import * as grok from 'datagrok-api/grok';
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
HelmType, HelmMol,
|
|
7
|
+
JSDraw2ModuleType, OrgType
|
|
8
|
+
} from '@datagrok-libraries/bio/src/helm/types';
|
|
9
|
+
|
|
10
|
+
|
|
4
11
|
import {Chain} from './pt-conversion';
|
|
5
|
-
import {getAvailableMonomers} from './utils'
|
|
12
|
+
import {getAvailableMonomers} from './utils';
|
|
13
|
+
import {PolyToolEnumeratorParams, PolyToolEnumeratorTypes, PolyToolPlaceholders} from './types';
|
|
6
14
|
|
|
7
15
|
export const PT_HELM_EXAMPLE = 'PEPTIDE1{[R].[F].[T].[G].[H].[F].[G].[A].[A].[Y].[P].[E].[NH2]}$$$$';
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
17
|
+
/** Initialized by getHelmHelper via init Helm package */
|
|
18
|
+
declare const JSDraw2: JSDraw2ModuleType;
|
|
19
|
+
declare const org: OrgType;
|
|
20
|
+
|
|
21
|
+
function polyToolEnumeratorCore(m: HelmMol, position: number, monomerList: string[]): HelmMol[] {
|
|
22
|
+
const resMolList: HelmMol[] = new Array<HelmMol>(monomerList.length);
|
|
23
|
+
for (let i = 0; i < monomerList.length; i++) {
|
|
24
|
+
const newSymbol = monomerList[i];
|
|
25
|
+
const resM = resMolList[i] = m.clone() as HelmMol;
|
|
26
|
+
const oldSymbol = resM.atoms[position].elem;
|
|
27
|
+
resM.atoms[position].elem = newSymbol;
|
|
28
|
+
|
|
29
|
+
const idOldSymbol = oldSymbol?.length > 1 ? `[${oldSymbol}]` : oldSymbol;
|
|
30
|
+
const idNewSymbol = newSymbol?.length > 1 ? `[${newSymbol}]` : newSymbol;
|
|
31
|
+
resM.name = `${m.name}-${idOldSymbol}${position + 1}${idNewSymbol}`;
|
|
32
|
+
}
|
|
33
|
+
return resMolList;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} helm Molecule string Helm format
|
|
38
|
+
* @param placeholders Placeholders by zero-based position key
|
|
39
|
+
* @returns {string[]} List of enumerated molecules in Helm format
|
|
40
|
+
*/
|
|
41
|
+
function getPtEnumeratorSingle(m: HelmMol, placeholders: PolyToolPlaceholders): HelmMol[] {
|
|
42
|
+
const coreResList: HelmMol[][] = Object.entries(placeholders)
|
|
43
|
+
.map(([p, monomerList]: [string, string[]]) => polyToolEnumeratorCore(m, parseInt(p), monomerList));
|
|
44
|
+
const resMolList = coreResList.reduce((acc, posList) => acc.concat(posList), []);
|
|
45
|
+
return resMolList;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getPtEnumeratorMatrix(m: HelmMol, placeholders: PolyToolPlaceholders): HelmMol[] {
|
|
49
|
+
let resMolList = [m];
|
|
50
|
+
for (const [p, monomerList] of Object.entries(placeholders)) {
|
|
51
|
+
const pos: number = parseInt(p);
|
|
52
|
+
const posResMolList: HelmMol[][] = resMolList.map((m: HelmMol) => polyToolEnumeratorCore(m, pos, monomerList));
|
|
53
|
+
resMolList = posResMolList.reduce((acc, l) => acc.concat(l), []);
|
|
54
|
+
}
|
|
55
|
+
return resMolList;
|
|
56
|
+
}
|
|
15
57
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
58
|
+
export function getPtEnumeratorHelm(helm: string, id: string, params: PolyToolEnumeratorParams): [string, string][] {
|
|
59
|
+
const molHandler = new JSDraw2.MolHandler<HelmType>();
|
|
60
|
+
const plugin = new org.helm.webeditor.Plugin(molHandler);
|
|
61
|
+
org.helm.webeditor.IO.parseHelm(plugin, helm, new JSDraw2.Point(0, 0), undefined);
|
|
62
|
+
const m = molHandler.m;
|
|
63
|
+
m.name = id;
|
|
64
|
+
|
|
65
|
+
let resMolList: HelmMol[];
|
|
66
|
+
switch (params.type) {
|
|
67
|
+
case PolyToolEnumeratorTypes.Single: {
|
|
68
|
+
resMolList = getPtEnumeratorSingle(molHandler.m, params.placeholders);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case PolyToolEnumeratorTypes.Matrix: {
|
|
72
|
+
resMolList = getPtEnumeratorMatrix(molHandler.m, params.placeholders);
|
|
73
|
+
break;
|
|
19
74
|
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (params.keepOriginal)
|
|
78
|
+
resMolList = [m, ...resMolList];
|
|
20
79
|
|
|
21
|
-
return
|
|
80
|
+
const resList = resMolList.map<[string, string]>((m: HelmMol) => { return [org.helm.webeditor.IO.getHelm(m)!, m.name!]; });
|
|
81
|
+
return resList;
|
|
22
82
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as ui from 'datagrok-api/ui';
|
|
2
|
+
import * as grok from 'datagrok-api/grok';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import {Unsubscribable} from 'rxjs';
|
|
6
|
+
|
|
7
|
+
import {PolyToolPlaceholders} from './types';
|
|
8
|
+
|
|
9
|
+
export class PolyToolPlaceholdersInput extends DG.JsInputBase<DG.DataFrame> {
|
|
10
|
+
get inputType(): string { return 'Positions'; }
|
|
11
|
+
|
|
12
|
+
get dataType(): string { return DG.TYPE.DATA_FRAME; }
|
|
13
|
+
|
|
14
|
+
getInput(): HTMLElement { return this.gridHost; }
|
|
15
|
+
|
|
16
|
+
getValue(): DG.DataFrame {
|
|
17
|
+
return this.grid.dataFrame;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setValue(value: DG.DataFrame): void {
|
|
21
|
+
this.grid.dataFrame = value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getStringValue(): string {
|
|
25
|
+
return this.grid.dataFrame.toCsv();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setStringValue(str: string): void {
|
|
29
|
+
this.grid.dataFrame = DG.DataFrame.fromCsv(str);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get placeholdersValue() {
|
|
33
|
+
return dfToPlaceholders(this.grid.dataFrame);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private readonly gridHost: HTMLDivElement;
|
|
37
|
+
public readonly grid: DG.Grid;
|
|
38
|
+
|
|
39
|
+
private subs: Unsubscribable[] = [];
|
|
40
|
+
|
|
41
|
+
protected constructor(name: string | undefined, grid: DG.Grid, heightRowCount?: number) {
|
|
42
|
+
super();
|
|
43
|
+
|
|
44
|
+
if (name) this.captionLabel.innerText = name;
|
|
45
|
+
|
|
46
|
+
this.gridHost = ui.div([], {
|
|
47
|
+
classes: 'ui-input-editor',
|
|
48
|
+
style: {width: '100%', height: '100%', marginTop: '-8px', marginBottom: '8px', paddingBottom: '4px'},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.grid = grid;
|
|
52
|
+
this.gridHost.append(this.grid.root);
|
|
53
|
+
|
|
54
|
+
if (heightRowCount != null) {
|
|
55
|
+
this.updateGridHeight(heightRowCount + 0.7);
|
|
56
|
+
} else {
|
|
57
|
+
this.updateGridHeight(this.grid.dataFrame.rowCount + 0.6);
|
|
58
|
+
this.subs.push(this.grid.dataFrame.onRowsAdded
|
|
59
|
+
.subscribe(() => { this.updateGridHeight(this.grid.dataFrame.rowCount + 0.6); }));
|
|
60
|
+
}
|
|
61
|
+
this.grid.root.style.width = `100%`;
|
|
62
|
+
|
|
63
|
+
this.subs.push(this.grid.dataFrame.onDataChanged.subscribe(() => {
|
|
64
|
+
this.fireChanged();
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
this.subs.push(ui.onSizeChanged(this.grid.root).subscribe(() => {
|
|
68
|
+
this.grid.columns.byIndex(2)!.width = this.grid.root.clientWidth - this.grid.horzScroll.root.offsetWidth -
|
|
69
|
+
this.grid.columns.byIndex(0)!.width - this.grid.columns.byIndex(1)!.width - 10;
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
this.root.classList.add('ui-input-polytool-pos-grid');
|
|
73
|
+
this.root.append(this.gridHost);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
detach(): void {
|
|
77
|
+
for (const sub of this.subs) sub.unsubscribe();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public static async create(
|
|
81
|
+
name?: string, options?: {}, heightRowCount?: number
|
|
82
|
+
): Promise<PolyToolPlaceholdersInput> {
|
|
83
|
+
const df: DG.DataFrame = DG.DataFrame.fromObjects([{Position: '', Monomers: ''}])!;
|
|
84
|
+
const grid = (await df.plot.fromType(DG.VIEWER.GRID, options)) as DG.Grid;
|
|
85
|
+
grid.sort(['Position']);
|
|
86
|
+
return new PolyToolPlaceholdersInput(name, grid, heightRowCount);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -- Update view --
|
|
90
|
+
|
|
91
|
+
private updateGridHeight(visibleRowCount: number): void {
|
|
92
|
+
const gridHeight = this.grid.colHeaderHeight + visibleRowCount * this.grid.props.rowHeight + 6 + 2;
|
|
93
|
+
this.grid.root.style.height = `${gridHeight}px`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// -- Handle events --
|
|
97
|
+
|
|
98
|
+
private gridRootOnSizeChanged(): void {
|
|
99
|
+
this.grid.columns.byIndex(2)!.width = this.grid.root.clientWidth - this.grid.horzScroll.root.offsetWidth -
|
|
100
|
+
this.grid.columns.byIndex(0)!.width - this.grid.columns.byIndex(1)!.width - 10;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getPlaceholdersFromText(src: string): PolyToolPlaceholders {
|
|
105
|
+
const res: PolyToolPlaceholders = {};
|
|
106
|
+
for (const line of src.split('\n')) {
|
|
107
|
+
const lineM = /^\s*(?<pos>\d+)\s*:\s*(?<monomers>.+)$/.exec(line);
|
|
108
|
+
if (lineM) {
|
|
109
|
+
const pos: number = parseInt(lineM.groups!['pos']) - 1;
|
|
110
|
+
const monomerList: string[] = lineM.groups!['monomers'].split(',').map(m => m.trim());
|
|
111
|
+
if (!(pos in res)) res[pos] = [];
|
|
112
|
+
res[pos].push(...monomerList);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return res;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function dfToPlaceholders(df: DG.DataFrame): PolyToolPlaceholders {
|
|
119
|
+
const res: PolyToolPlaceholders = {};
|
|
120
|
+
for (let rowI = 0; rowI < df.rowCount; rowI++) {
|
|
121
|
+
const pos = parseInt(df.get('Position', rowI));
|
|
122
|
+
if (!isNaN(pos)) {
|
|
123
|
+
const monomerSymbolList = parseMonomerSymbolList(df.get('Monomers', rowI));
|
|
124
|
+
if (monomerSymbolList.length > 0)
|
|
125
|
+
res[pos - 1] = monomerSymbolList;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return res;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function parseMonomerSymbolList(src: string): string[] {
|
|
132
|
+
// L, L-hArg(Et,Et), "hArg(Et,Et)"
|
|
133
|
+
return src.split(/,(?![^(]*\))/)
|
|
134
|
+
.map((s) => {
|
|
135
|
+
s = s.trim();
|
|
136
|
+
if (s.slice(0, 1) === `"` && s.slice(-1) === `"`) s = s.slice(1, -1);
|
|
137
|
+
if (s.slice(0, 1) === `'` && s.slice(-1) === `'`) s = s.slice(1, -1);
|
|
138
|
+
return s.trim();
|
|
139
|
+
})
|
|
140
|
+
.filter((s) => !!s);
|
|
141
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as ui from 'datagrok-api/ui';
|
|
2
|
+
import * as grok from 'datagrok-api/grok';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
export enum PolyToolEnumeratorTypes {
|
|
6
|
+
Single = 'single',
|
|
7
|
+
Matrix = 'matrix',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type PolyToolEnumeratorType = typeof PolyToolEnumeratorTypes[keyof typeof PolyToolEnumeratorTypes];
|
|
11
|
+
|
|
12
|
+
export type PolyToolPlaceholders = { [position: number]: string[] };
|
|
13
|
+
|
|
14
|
+
export type PolyToolEnumeratorParams = {
|
|
15
|
+
type: PolyToolEnumeratorType;
|
|
16
|
+
/** position key is zero-based */
|
|
17
|
+
placeholders: PolyToolPlaceholders;
|
|
18
|
+
keepOriginal?: boolean;
|
|
19
|
+
trivialName?: boolean;
|
|
20
|
+
}
|