@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.
@@ -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
- export async function getEnumerationHelm(helmString: string, helmSelections: number[], screenLibrary: string):
10
- Promise<string[]> {
11
- const variableMonomers = await getAvailableMonomers(screenLibrary);
12
- const chain: Chain = Chain.fromHelm(helmString);
13
- const size = helmSelections.length * variableMonomers.length;
14
- const enumerations = new Array<string>(size);
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
- for (let i = 0; i < helmSelections.length; i++) {
17
- for (let j = 0; j < variableMonomers.length; j++)
18
- enumerations[i * variableMonomers.length + j] = chain.getHelmChanged(helmSelections[i], variableMonomers[j]);
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 enumerations;
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
+ }