@datagrok/bio 2.23.2 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +0 -2
- package/CHANGELOG.md +9 -0
- package/README.md +1 -1
- package/dist/455.js.map +1 -1
- package/dist/package-test.js +3 -3
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/files/samples/HELM_CHEMS.csv +11 -0
- package/package.json +2 -2
- package/src/analysis/sequence-space.ts +1 -1
- package/src/demo/bio03-atomic-level.ts +1 -1
- package/src/package-types.ts +2 -2
- package/src/package.ts +10 -4
- package/src/tests/activity-cliffs-tests.ts +2 -2
- package/src/tests/monomer-libraries-tests.ts +9 -7
- package/src/tests/renderers-monomer-placer-tests.ts +1 -1
- package/src/tests/scoring.ts +1 -1
- package/src/tests/seq-handler-get-helm-tests.ts +1 -1
- package/src/tests/splitters-test.ts +2 -2
- package/src/tests/substructure-filters-tests.ts +11 -11
- package/src/tests/to-atomic-level-tests.ts +2 -2
- package/src/tests/to-atomic-level-ui-tests.ts +13 -14
- package/src/utils/cell-renderer.ts +1 -1
- package/src/utils/get-region.ts +2 -2
- package/src/utils/helm-to-molfile/converter/const.ts +0 -1
- package/src/utils/helm-to-molfile/converter/converter.ts +4 -4
- package/src/utils/helm-to-molfile/converter/helm.ts +14 -6
- package/src/utils/helm-to-molfile/converter/mol-bonds.ts +2 -2
- package/src/utils/helm-to-molfile/converter/mol-wrapper.ts +2 -2
- package/src/utils/helm-to-molfile/converter/monomer-wrapper.ts +1 -1
- package/src/utils/helm-to-molfile/converter/polymer.ts +1 -1
- package/src/utils/helm-to-molfile/converter/r-group-handler.ts +2 -2
- package/src/utils/helm-to-molfile/utils.ts +3 -2
- package/src/utils/monomer-cell-renderer-base.ts +1 -2
- package/src/utils/monomer-lib/consts.ts +1 -6
- package/src/utils/monomer-lib/lib-manager.ts +239 -112
- package/src/utils/monomer-lib/library-file-manager/file-validator.ts +1 -1
- package/src/utils/monomer-lib/library-file-manager/monomers-lib-provider.ts +378 -0
- package/src/utils/monomer-lib/library-file-manager/ui.ts +120 -81
- package/src/utils/monomer-lib/monomer-colors.ts +37 -39
- package/src/utils/monomer-lib/monomer-lib-base.ts +34 -7
- package/src/utils/monomer-lib/monomer-lib.ts +7 -33
- package/src/utils/monomer-lib/monomer-manager/duplicate-monomer-manager.ts +3 -3
- package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +91 -82
- package/src/utils/monomer-lib/smiles2Monomer.ts +128 -0
- package/src/utils/monomer-lib/web-editor-monomer-dummy.ts +15 -1
- package/src/utils/monomer-lib/web-editor-monomer-of-library.ts +2 -1
- package/src/utils/multiple-sequence-alignment-ui.ts +1 -1
- package/src/utils/multiple-sequence-alignment.ts +1 -1
- package/src/utils/seq-helper/seq-handler.ts +26 -13
- package/src/utils/seq-helper/seq-helper.ts +1 -2
- package/src/utils/sequence-to-mol.ts +1 -1
- package/src/utils/ui-utils.ts +1 -1
- package/src/viewers/web-logo-viewer.ts +20 -9
- package/src/widgets/composition-analysis-widget.ts +1 -1
- package/src/widgets/sequence-scrolling-widget.ts +1 -2
- package/test-console-output-1.log +670 -721
- package/test-record-1.mp4 +0 -0
- package/src/utils/monomer-lib/library-file-manager/custom-monomer-lib-handlers.ts +0 -41
- package/src/utils/monomer-lib/library-file-manager/event-manager.ts +0 -93
- package/src/utils/monomer-lib/library-file-manager/file-manager.ts +0 -317
- package/src/utils/monomer-lib/monomer-set.ts +0 -61
|
@@ -3,11 +3,12 @@ import * as grok from 'datagrok-api/grok';
|
|
|
3
3
|
import * as ui from 'datagrok-api/ui';
|
|
4
4
|
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
|
-
import {Monomer} from '@datagrok-libraries/bio/src/types';
|
|
6
|
+
import {Monomer} from '@datagrok-libraries/bio/src/types/monomer-library';
|
|
7
7
|
import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
|
|
8
8
|
import {getUserLibSettings, setUserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
|
|
9
|
-
import '../../../../css/monomer-manager.css';
|
|
10
9
|
import {MonomerLibManager} from '../lib-manager';
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
import '../../../../css/monomer-manager.css';
|
|
11
12
|
|
|
12
13
|
class MonomerCard {
|
|
13
14
|
root: HTMLElement = ui.divV([], {classes: 'monomer-card-root'});
|
|
@@ -109,7 +110,6 @@ export class DuplicateMonomerManager {
|
|
|
109
110
|
this.settings = await getUserLibSettings();
|
|
110
111
|
const libManager = await MonomerLibManager.getInstance();
|
|
111
112
|
await libManager.awaitLoaded();
|
|
112
|
-
await libManager.loadLibrariesPromise;
|
|
113
113
|
this.monomers = libManager.duplicateMonomers;
|
|
114
114
|
this.monomerCardRows = [];
|
|
115
115
|
for (const polymerType in this.monomers) {
|
|
@@ -6,18 +6,20 @@ import * as ui from 'datagrok-api/ui';
|
|
|
6
6
|
import * as DG from 'datagrok-api/dg';
|
|
7
7
|
|
|
8
8
|
import {IMonomerManager, INewMonomerForm} from '@datagrok-libraries/bio/src/utils/monomer-ui';
|
|
9
|
-
import {IMonomerLib, Monomer, RGroup} from '@datagrok-libraries/bio/src/types';
|
|
9
|
+
import {findProviderWithLibraryName, IMonomerLib, IMonomerLibProvider, Monomer, RGroup} from '@datagrok-libraries/bio/src/types/monomer-library';
|
|
10
10
|
import {DUMMY_MONOMER, HELM_RGROUP_FIELDS} from '@datagrok-libraries/bio/src/utils/const';
|
|
11
11
|
import {ItemsGrid} from '@datagrok-libraries/utils/src/items-grid';
|
|
12
12
|
import {mostSimilarNaturalAnalog} from '@datagrok-libraries/bio/src/utils/macromolecule/monomers';
|
|
13
13
|
import {PolymerType, MonomerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
14
14
|
|
|
15
15
|
import {MonomerLibManager} from '../lib-manager';
|
|
16
|
-
import {LIB_PATH} from '../consts';
|
|
17
16
|
|
|
18
|
-
import '../../../../css/monomer-manager.css';
|
|
19
17
|
import {MONOMER_RENDERER_TAGS} from '@datagrok-libraries/bio/src/utils/cell-renderer';
|
|
20
18
|
import {BioTags} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
19
|
+
//@ts-ignore
|
|
20
|
+
import '../../../../css/monomer-manager.css';
|
|
21
|
+
import {Subscription} from 'rxjs';
|
|
22
|
+
|
|
21
23
|
|
|
22
24
|
// columns of monomers dataframe, note that rgroups is hidden and will be displayed as separate columns
|
|
23
25
|
export enum MONOMER_DF_COLUMN_NAMES {
|
|
@@ -268,7 +270,6 @@ export class MonomerManager implements IMonomerManager {
|
|
|
268
270
|
if (!this.instance) {
|
|
269
271
|
const monManager = await MonomerLibManager.getInstance();
|
|
270
272
|
await monManager.awaitLoaded();
|
|
271
|
-
await monManager.loadLibrariesPromise;
|
|
272
273
|
this.instance = new MonomerManager(monManager);
|
|
273
274
|
}
|
|
274
275
|
return this.instance;
|
|
@@ -277,19 +278,18 @@ export class MonomerManager implements IMonomerManager {
|
|
|
277
278
|
public static async getNewInstance(): Promise<MonomerManager> {
|
|
278
279
|
const monManager = await MonomerLibManager.getInstance();
|
|
279
280
|
await monManager.awaitLoaded();
|
|
280
|
-
await monManager.loadLibrariesPromise;
|
|
281
281
|
return new MonomerManager(monManager);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
async createNewMonomerLib(libName: string, _monomers: Monomer[]): Promise<void> {
|
|
284
|
+
async createNewMonomerLib(providerName: string, libName: string, _monomers: Monomer[]): Promise<void> {
|
|
285
285
|
this.tv?.grid && ui.setUpdateIndicator(this.tv.grid.root, true);
|
|
286
286
|
try {
|
|
287
|
-
const
|
|
288
|
-
if (!
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
await
|
|
292
|
-
await this.monomerLibManamger.
|
|
287
|
+
const provider = (await this.monomerLibManamger.getProviders()).find((p) => p.name === providerName);
|
|
288
|
+
if (!provider)
|
|
289
|
+
throw new Error(`Provider ${providerName} not found`);
|
|
290
|
+
const monomersMapped = _monomers.map((m) => ({...m, lib: undefined, wem: undefined}));
|
|
291
|
+
await provider.addOrUpdateLibrary(libName, monomersMapped);
|
|
292
|
+
await this.monomerLibManamger.loadMonomerLib(false);
|
|
293
293
|
|
|
294
294
|
//await this.monomerLibManamger.loadLibraries(true);
|
|
295
295
|
grok.shell.v = await this.getViewRoot(libName);
|
|
@@ -302,7 +302,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
async createNewLibDialog(monomers?: Monomer[]) {
|
|
305
|
-
const monomerLibs =
|
|
305
|
+
const monomerLibs = await this.monomerLibManamger.getAvaliableLibraryNames();
|
|
306
306
|
const libNameInput = ui.input.string('Library Name', {
|
|
307
307
|
placeholder: 'Enter library name',
|
|
308
308
|
nullable: false,
|
|
@@ -311,20 +311,20 @@ export class MonomerManager implements IMonomerManager {
|
|
|
311
311
|
d.getButton('Create')?.classList?.toggle('d4-disabled', !!res);
|
|
312
312
|
}
|
|
313
313
|
});
|
|
314
|
-
function getFileNameInputValue() {
|
|
315
|
-
let fileName = libNameInput.value;
|
|
316
|
-
if (!fileName.endsWith('.json'))
|
|
317
|
-
fileName += '.json';
|
|
318
|
-
return fileName;
|
|
319
|
-
};
|
|
320
314
|
function validateInput(v: string) {
|
|
321
315
|
if (!v || !v.trim()) return 'Library name cannot be empty';
|
|
322
|
-
if (
|
|
316
|
+
if (monomerLibs.includes(v) || monomerLibs.includes(v + '.json'))
|
|
323
317
|
return 'Library with this name already exists';
|
|
324
318
|
return null;
|
|
325
319
|
}
|
|
326
320
|
libNameInput.addValidator(validateInput);
|
|
321
|
+
const providersNames = (await this.monomerLibManamger.getProviders()).map((p) => p.name);
|
|
322
|
+
const providerInput = ui.input.choice('Storage', {items: providersNames, value: providersNames[0], nullable: false, tooltipText: 'Select storage provider for the new library'});
|
|
323
|
+
if (providersNames.length === 1)
|
|
324
|
+
providerInput.readOnly = true; // consider hiding instead
|
|
325
|
+
|
|
327
326
|
const d = ui.dialog('Create New Library')
|
|
327
|
+
.add(providerInput)
|
|
328
328
|
.add(libNameInput)
|
|
329
329
|
.addButton('Create', async () => {
|
|
330
330
|
const vr = validateInput(libNameInput.value);
|
|
@@ -332,8 +332,12 @@ export class MonomerManager implements IMonomerManager {
|
|
|
332
332
|
grok.shell.warning(vr);
|
|
333
333
|
return;
|
|
334
334
|
}
|
|
335
|
+
if (!providerInput.value) {
|
|
336
|
+
grok.shell.warning('Please select storage provider');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
335
339
|
try {
|
|
336
|
-
await this.createNewMonomerLib(
|
|
340
|
+
await this.createNewMonomerLib(providerInput.value!, libNameInput.value!, monomers ?? []);
|
|
337
341
|
} catch (e) {
|
|
338
342
|
grok.shell.error('Error creating library');
|
|
339
343
|
console.error(e);
|
|
@@ -350,12 +354,14 @@ export class MonomerManager implements IMonomerManager {
|
|
|
350
354
|
return this._newMonomerForm;
|
|
351
355
|
}
|
|
352
356
|
|
|
357
|
+
private _contextMenuSub: Subscription | null = null;
|
|
353
358
|
private async getMonomersTableView(fileName?: string, addView = true): Promise<DG.TableView> {
|
|
354
359
|
const df = await this.getMonomersDf(fileName);
|
|
355
360
|
this.tv = DG.TableView.create(df, addView);
|
|
356
361
|
|
|
357
362
|
this.adjustTable();
|
|
358
|
-
this.
|
|
363
|
+
this._contextMenuSub?.unsubscribe();
|
|
364
|
+
this._contextMenuSub =
|
|
359
365
|
grok.events.onContextMenu.subscribe(({args}) => {
|
|
360
366
|
if (!args || !args.menu || !args.context || args.context.type !== DG.VIEWER.GRID || !args.context.tableView ||
|
|
361
367
|
args.context.tableView.id !== (this.tv!.id ?? '') || !args.item || !args.item.isTableCell || (args.item.tableRowIndex ?? -1) < 0)
|
|
@@ -385,8 +391,8 @@ export class MonomerManager implements IMonomerManager {
|
|
|
385
391
|
this._newMonomerForm.removeMonomers([monomer], this.libInput.value!);
|
|
386
392
|
});
|
|
387
393
|
}
|
|
388
|
-
})
|
|
389
|
-
|
|
394
|
+
});
|
|
395
|
+
|
|
390
396
|
this.tv.grid && (this.tv.grid.props.allowEdit = false); // disable editing
|
|
391
397
|
return this.tv;
|
|
392
398
|
}
|
|
@@ -410,7 +416,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
410
416
|
private _skipLibInputOnchange: boolean = false;
|
|
411
417
|
|
|
412
418
|
async getViewRoot(libName?: string, addView = true) {
|
|
413
|
-
const availableMonLibs =
|
|
419
|
+
const availableMonLibs = await this.monomerLibManamger.getAvaliableLibraryNames();
|
|
414
420
|
this._newMonomerForm.molSketcher.resize();
|
|
415
421
|
if (addView && (this.tv = this.findActiveManagerView()) && (libName ?? this.libInput.value)) {
|
|
416
422
|
// get monomer library list
|
|
@@ -475,7 +481,10 @@ export class MonomerManager implements IMonomerManager {
|
|
|
475
481
|
return grok.shell.error('No library selected');
|
|
476
482
|
let lib: string | null = null;
|
|
477
483
|
try {
|
|
478
|
-
|
|
484
|
+
const provider = await findProviderWithLibraryName(await this.monomerLibManamger.getProviders(), libName);
|
|
485
|
+
if (!provider)
|
|
486
|
+
throw new Error(`Library ${libName} not found in any provider`);
|
|
487
|
+
lib = await provider.getLibraryAsString(libName);
|
|
479
488
|
} catch (e) {
|
|
480
489
|
grok.shell.error(`Error reading library ${libName}`);
|
|
481
490
|
return console.error(e);
|
|
@@ -518,8 +527,19 @@ export class MonomerManager implements IMonomerManager {
|
|
|
518
527
|
async getMonomersDf(fileName?: string) {
|
|
519
528
|
this.tv?.grid && ui.setUpdateIndicator(this.tv.grid.root, true);
|
|
520
529
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
530
|
+
let provider: IMonomerLibProvider | null = null;
|
|
531
|
+
const providers = await this.monomerLibManamger.getProviders();
|
|
532
|
+
if (providers.length === 0)
|
|
533
|
+
throw new Error('No monomer library providers available');
|
|
534
|
+
if (!fileName) {
|
|
535
|
+
provider = providers[0];
|
|
536
|
+
fileName = (await provider.listLibraries())[0];
|
|
537
|
+
} else {
|
|
538
|
+
provider = await findProviderWithLibraryName(providers, fileName);
|
|
539
|
+
if (!provider)
|
|
540
|
+
throw new Error(`Library ${fileName} not found in any provider`);
|
|
541
|
+
}
|
|
542
|
+
this.activeMonomerLib = await this.monomerLibManamger.readSingleLibrary(provider!.name, fileName);
|
|
523
543
|
if (!this.activeMonomerLib) {
|
|
524
544
|
grok.shell.error(`Library ${fileName} not found`);
|
|
525
545
|
return DG.DataFrame.create();
|
|
@@ -548,19 +568,20 @@ export class MonomerManager implements IMonomerManager {
|
|
|
548
568
|
.add(ui.divText('Do you wish to continue?'))
|
|
549
569
|
.onOK(async () => {
|
|
550
570
|
const monomerDf = this.tv?.dataFrame;
|
|
551
|
-
|
|
571
|
+
const libName = this.libInput.value;
|
|
552
572
|
if (!monomerDf || !libName) {
|
|
553
573
|
grok.shell.error('No monomer library loaded');
|
|
554
574
|
return;
|
|
555
575
|
}
|
|
556
576
|
this.tv?.grid && ui.setUpdateIndicator(this.tv.grid.root, true);
|
|
557
577
|
try {
|
|
578
|
+
const provider = await findProviderWithLibraryName(await this.monomerLibManamger.getProviders(), libName);
|
|
579
|
+
if (!provider)
|
|
580
|
+
throw new Error(`Library ${libName} not found in any provider`); // should not happen
|
|
558
581
|
const monomers = await Promise.all(new Array(monomerDf.rowCount).fill(0).map((_, i) => monomerFromDfRow(monomerDf.rows.get(i))));
|
|
559
|
-
const monomersString =
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
await grok.dapi.files.writeAsText(LIB_PATH + libName, monomersString);
|
|
563
|
-
await this.monomerLibManamger.loadLibraries(true);
|
|
582
|
+
const monomersString = monomers.map((m) => ({...m, lib: undefined, wem: undefined}));
|
|
583
|
+
await provider.addOrUpdateLibrary(libName, monomersString);
|
|
584
|
+
await this.monomerLibManamger.loadMonomerLib(true);
|
|
564
585
|
//await this.monomerLibManamger.loadLibraries(true);
|
|
565
586
|
grok.shell.v = await this.getViewRoot(libName);
|
|
566
587
|
} catch (e) {
|
|
@@ -998,73 +1019,61 @@ class MonomerForm implements INewMonomerForm {
|
|
|
998
1019
|
}
|
|
999
1020
|
|
|
1000
1021
|
async removeMonomers(monomers: Monomer[], libName: string, notify = true) {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
} catch (e) {
|
|
1006
|
-
grok.shell.error(`Error reading library ${libName}`);
|
|
1007
|
-
return console.error(e);
|
|
1008
|
-
}
|
|
1009
|
-
const monomerIdxs = monomers.map((monomer) => findLastIndex(libJSON, (m) => m.symbol === monomer.symbol && m.polymerType === monomer.polymerType));
|
|
1010
|
-
for (let i = 0; i < monomerIdxs.length; i++) {
|
|
1011
|
-
const monomerIdx = monomerIdxs[i];
|
|
1012
|
-
if (monomerIdx === -1) {
|
|
1013
|
-
grok.shell.error(`Monomer ${monomers[i].symbol} not found in library ${libName}`);
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1022
|
+
const provider = await findProviderWithLibraryName(await this.monomerLibManager.getProviders(), libName);
|
|
1023
|
+
if (!provider) {
|
|
1024
|
+
grok.shell.error(`Library ${libName} not found in any provider`);
|
|
1025
|
+
return;
|
|
1016
1026
|
}
|
|
1017
|
-
|
|
1018
|
-
const
|
|
1019
|
-
const infoTables = ui.divV(removingMonomers.map((m) => this.getMonomerInfoTable(m)), {style: {maxHeight: '500px', overflow: 'scroll'}});
|
|
1020
|
-
const isPlural = removingMonomers.length > 1;
|
|
1027
|
+
const infoTables = ui.divV(monomers.map((m) => this.getMonomerInfoTable(m)), {style: {maxHeight: '500px', overflow: 'scroll'}});
|
|
1028
|
+
const isPlural = monomers.length > 1;
|
|
1021
1029
|
const promptText = isPlural ?
|
|
1022
|
-
`Are you sure you want to remove monomers ${
|
|
1023
|
-
`Are you sure you want to remove monomer with symbol ${
|
|
1030
|
+
`Are you sure you want to remove monomers ${monomers.map((m) => m.symbol).join(', ')} from ${libName} library?` :
|
|
1031
|
+
`Are you sure you want to remove monomer with symbol ${monomers[0].symbol} from ${libName} library?`;
|
|
1024
1032
|
|
|
1025
1033
|
|
|
1026
1034
|
const dlg = ui.dialog('Remove Monomer' + (isPlural ? 's' : ''))
|
|
1027
1035
|
.add(ui.h1(promptText))
|
|
1028
1036
|
.add(infoTables)
|
|
1029
1037
|
.addButton('Remove', async () => {
|
|
1030
|
-
|
|
1031
|
-
await
|
|
1032
|
-
await (await MonomerLibManager.getInstance()).loadLibraries(true);
|
|
1038
|
+
await provider.deleteMonomersFromLibrary(libName, monomers);
|
|
1039
|
+
await this.monomerLibManager.loadMonomerLib(true);
|
|
1033
1040
|
await this.refreshTable();
|
|
1034
|
-
|
|
1035
1041
|
if (notify)
|
|
1036
|
-
grok.shell.info(`Monomer${isPlural ? 's' : ''} ${
|
|
1042
|
+
grok.shell.info(`Monomer${isPlural ? 's' : ''} ${monomers.map((m) => m.symbol).join(', ')} ${isPlural ? 'were' : 'was'} successfully removed from ${libName} library`);
|
|
1037
1043
|
dlg.close();
|
|
1038
1044
|
})
|
|
1039
1045
|
.show();
|
|
1040
1046
|
}
|
|
1041
1047
|
|
|
1042
1048
|
private async addMonomerToLib(monomer: Monomer, libName: string) {
|
|
1043
|
-
// TODO: permissions logic;
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1049
|
+
// TODO: permissions logic -- to be handled on the side of providers;
|
|
1050
|
+
const curDf = this.getMonomersDataFrame();
|
|
1051
|
+
if (!curDf) {
|
|
1052
|
+
grok.shell.error('No monomer library loaded');
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const polymerTypes = curDf?.col(MONOMER_DF_COLUMN_NAMES.POLYMER_TYPE)?.toList() ?? [];
|
|
1056
|
+
const symbols = curDf?.col(MONOMER_DF_COLUMN_NAMES.SYMBOL)?.toList() ?? [];
|
|
1057
|
+
const monomerSmiles = curDf?.col(MONOMER_DF_COLUMN_NAMES.MONOMER)?.toList() ?? [];
|
|
1058
|
+
if (polymerTypes.length !== symbols.length || polymerTypes.length !== monomerSmiles.length) {
|
|
1059
|
+
grok.shell.error('Monomer library data frame is corrupted');
|
|
1060
|
+
return;
|
|
1051
1061
|
}
|
|
1052
1062
|
// check if monomer with given symbol exists in library. search from the end to get the last monomer with that symbol (there can be duplicates)
|
|
1053
|
-
const existingMonomerIdx =
|
|
1063
|
+
const existingMonomerIdx = polymerTypes.findIndex((pt, idx) => pt === monomer.polymerType && symbols[idx] === monomer.symbol);
|
|
1054
1064
|
// check if the same structure already exists in the library. as everything is in canonical smiles, we can directly do string matching
|
|
1055
|
-
const existingStructureIdx =
|
|
1065
|
+
const existingStructureIdx = monomerSmiles.findIndex((smi) => smi === monomer.smiles);
|
|
1056
1066
|
|
|
1057
1067
|
const saveLib = async () => {
|
|
1058
1068
|
try {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
await
|
|
1067
|
-
await (await MonomerLibManager.getInstance()).loadLibraries(true);
|
|
1069
|
+
const provider = await findProviderWithLibraryName(await this.monomerLibManager.getProviders(), libName);
|
|
1070
|
+
if (!provider) {
|
|
1071
|
+
grok.shell.error(`Library ${libName} not found in any provider`);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
await provider.updateOrAddMonomersInLibrary(libName, [{...monomer, lib: undefined, wem: undefined}]);
|
|
1075
|
+
|
|
1076
|
+
await this.monomerLibManager.loadMonomerLib(true);
|
|
1068
1077
|
await this.refreshTable(monomer.symbol);
|
|
1069
1078
|
this._molChanged = false; // reset the flag
|
|
1070
1079
|
grok.shell.info(`Monomer ${monomer.symbol} was successfully saved in library ${libName}`);
|
|
@@ -1077,7 +1086,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
1077
1086
|
let infoTable: HTMLDivElement | null = null;
|
|
1078
1087
|
let promptMessage = '';
|
|
1079
1088
|
if (existingMonomerIdx >= 0) {
|
|
1080
|
-
infoTable = this.getMonomerInfoTable(
|
|
1089
|
+
infoTable = this.getMonomerInfoTable(await monomerFromDfRow(curDf!.row(existingMonomerIdx)));
|
|
1081
1090
|
promptMessage = `Monomer with symbol '${monomer.symbol}' already exists in library ${libName}.\nAre you sure you want to overwrite it?`;
|
|
1082
1091
|
} else if ((existingStructureIdx ?? -1) >= 0) {
|
|
1083
1092
|
const m = await monomerFromDfRow(this.getMonomersDataFrame()!.rows.get(existingStructureIdx!));
|
|
@@ -1145,7 +1154,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
1145
1154
|
}
|
|
1146
1155
|
}
|
|
1147
1156
|
|
|
1148
|
-
function findLastIndex<T>(ar: ArrayLike<T>, pred: (el: T) => boolean): number {
|
|
1157
|
+
export function findLastIndex<T>(ar: ArrayLike<T>, pred: (el: T) => boolean): number {
|
|
1149
1158
|
let foundIdx = -1;
|
|
1150
1159
|
for (let i = ar.length - 1; i >= 0; i--) {
|
|
1151
1160
|
if (pred(ar[i])) {
|
|
@@ -1169,7 +1178,7 @@ function getCorrectedSmiles(rgroups: RGroup[], smiles?: string, molBlock?: strin
|
|
|
1169
1178
|
return isSmilesMalformed ? canonical : grok.chem.convert(canonical, DG.chem.Notation.Unknown, DG.chem.Notation.Smiles);
|
|
1170
1179
|
}
|
|
1171
1180
|
|
|
1172
|
-
function getCorrectedMolBlock(molBlock: string) {
|
|
1181
|
+
export function getCorrectedMolBlock(molBlock: string) {
|
|
1173
1182
|
// to correct molblock, we should make sure that
|
|
1174
1183
|
// 1. RGP field is present at the end, before the M END line
|
|
1175
1184
|
// 2. RGP field is present in the correct format
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
/* eslint-disable max-len */
|
|
3
|
+
import {Monomer} from '@datagrok-libraries/bio/src/types/monomer-library';
|
|
4
|
+
import {_package} from '../../package';
|
|
5
|
+
import {PolymerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
6
|
+
import {HELM_RGROUP_FIELDS as RGP} from '@datagrok-libraries/bio/src/utils/const';
|
|
7
|
+
import * as grok from 'datagrok-api/grok';
|
|
8
|
+
import {getCorrectedMolBlock} from './monomer-manager/monomer-manager';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Exaple r groups
|
|
12
|
+
* {
|
|
13
|
+
"capGroupSMILES": "[*:1][H]",
|
|
14
|
+
"alternateId": "R1-H",
|
|
15
|
+
"capGroupName": "H",
|
|
16
|
+
"label": "R1"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"capGroupSMILES": "O[*:2]",
|
|
20
|
+
"alternateId": "R2-OH",
|
|
21
|
+
"capGroupName": "OH",
|
|
22
|
+
"label": "R2"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const cx_smiles_regexp = /.*\|\$.*R.*\$\|/;
|
|
28
|
+
const rgroup_regexp = /\[R(\d+)\]/;
|
|
29
|
+
const rgroup_regexpg = /\[R(\d+)\]/g;
|
|
30
|
+
const ambig_regexp = /\[\*:(\d+)\]/g;
|
|
31
|
+
|
|
32
|
+
export type MonomerWithoutSymbol = Omit<Monomer, 'symbol'>;
|
|
33
|
+
|
|
34
|
+
export function getMonomerFromRSmiles(smiles: string, polymerType?: PolymerType): MonomerWithoutSymbol | null {
|
|
35
|
+
const rgroupNumbers = Array.from(smiles.matchAll(rgroup_regexpg)).map((m) => m[1]);
|
|
36
|
+
const res: MonomerWithoutSymbol = {
|
|
37
|
+
name: 'Explicit SMILES Monomer',
|
|
38
|
+
smiles: smiles,
|
|
39
|
+
polymerType: polymerType ?? 'CHEM',
|
|
40
|
+
molfile: '',
|
|
41
|
+
rgroups: rgroupNumbers.map((numString) => ({
|
|
42
|
+
[RGP.LABEL]: `R${numString}`,
|
|
43
|
+
[RGP.CAP_GROUP_NAME]: `H`,
|
|
44
|
+
[RGP.CAP_GROUP_SMILES]: `[*:${numString}][H]`,
|
|
45
|
+
[RGP.ALTERNATE_ID]: `R${numString}-H`,
|
|
46
|
+
})),
|
|
47
|
+
author: 'Datagrok auto-generated',
|
|
48
|
+
id: 0,
|
|
49
|
+
createDate: null,
|
|
50
|
+
monomerType: 'Backbone',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
//try to generate corrected molfile and smiles
|
|
55
|
+
let corSmiles = smiles;
|
|
56
|
+
res.rgroups.forEach((rg) => {
|
|
57
|
+
const labelNum = rg[RGP.LABEL].substring(1); // R1 -> 1
|
|
58
|
+
corSmiles = corSmiles.replace(`[R${labelNum}]`, `[*:${labelNum}]`);
|
|
59
|
+
});
|
|
60
|
+
const molFile = getCorrectedMolBlock(grok.chem.convert(corSmiles, grok.chem.Notation.Smiles, grok.chem.Notation.MolBlock));
|
|
61
|
+
res.molfile = molFile;
|
|
62
|
+
res.smiles = corSmiles;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
_package.logger.error(`getMonomerFromRSmiles: cannot convert SMILES to Molfile: ${smiles}\n${e}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return res;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Generate Monomer Object directly from inline smiles
|
|
71
|
+
* Purely string based, no external calls
|
|
72
|
+
*
|
|
73
|
+
* Currently accepts (to be extended):
|
|
74
|
+
*
|
|
75
|
+
* cxsmiles written as *N[C@H](C(=O)*)Cc1ccc(cc1)OP(=O)(O)O |$_R1;;;;;_R2;;;;;;;;;;;;$| where * are connection points
|
|
76
|
+
*
|
|
77
|
+
* or * is square brackets like [*]N[C@H](C(=O)[*])Cc1ccc(cc1)OP(=O)(O)O |$_R1;;;;;_R2;;;;;;;;;;;;$|
|
|
78
|
+
*
|
|
79
|
+
* simple smiles with R notations like CCC[R1] or CCC(=O)[R2]
|
|
80
|
+
*
|
|
81
|
+
* simple smiles with ambiguety defined as [*:1] like CCC[*:1] or CCC(=O)[*:2]
|
|
82
|
+
*/
|
|
83
|
+
export function smiles2Monomer(smiles: string, polymerType?: PolymerType): MonomerWithoutSymbol | null {
|
|
84
|
+
try {
|
|
85
|
+
const isCxSmiles = cx_smiles_regexp.test(smiles);
|
|
86
|
+
if (isCxSmiles) {
|
|
87
|
+
// CXSMILES parsing
|
|
88
|
+
const parts = smiles.split('|$');
|
|
89
|
+
const molPart = parts[0].trim();
|
|
90
|
+
const rGroupPart = parts[1];
|
|
91
|
+
// make sure all R groups are captured
|
|
92
|
+
const rGroupMatches = Array.from(rGroupPart.matchAll(/R(\d+)/g));
|
|
93
|
+
const starsInMolecule = Array.from(molPart.matchAll(/(\*)/g));
|
|
94
|
+
if (rGroupMatches.length !== starsInMolecule.length)
|
|
95
|
+
return null; // make sure that number of R groups and stars are the same
|
|
96
|
+
// remove brackets from stars if any
|
|
97
|
+
let cleanMol = molPart.replaceAll(/\[\*\]/g, '*');
|
|
98
|
+
// speaking in terms of consecutiveness, R groups in definition and stars in Smiles will be in the same order
|
|
99
|
+
// so we can just iterate through them
|
|
100
|
+
const rGroupNumbers = rGroupMatches.map((m) => m[1]); // numbers as strings
|
|
101
|
+
for (let i = 0; i < rGroupNumbers.length; i++) {
|
|
102
|
+
const rNum = rGroupNumbers[i];
|
|
103
|
+
// replace first matched star with R group
|
|
104
|
+
cleanMol = cleanMol.replace('*', `[R${rNum}]`);
|
|
105
|
+
}
|
|
106
|
+
return getMonomerFromRSmiles(cleanMol, polymerType);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// simple smiles parsing
|
|
110
|
+
// to simplify, replace all ambigous [*:1] with R1, etc
|
|
111
|
+
let cleanSmiles = smiles;
|
|
112
|
+
const ambigMatches = Array.from(smiles.matchAll(ambig_regexp));
|
|
113
|
+
for (const match of ambigMatches) {
|
|
114
|
+
const fullMatch = match[0];
|
|
115
|
+
const rNum = match[1];
|
|
116
|
+
cleanSmiles = cleanSmiles.replace(fullMatch, `[R${rNum}]`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// make sure monomer has at least one R group
|
|
120
|
+
if (rgroup_regexp.test(cleanSmiles))
|
|
121
|
+
return getMonomerFromRSmiles(cleanSmiles, polymerType);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
_package.logger.error(`smiles2Monomer: cannot parse SMILES: ${smiles}\n${e}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
@@ -18,7 +18,7 @@ export abstract class WebEditorMonomerDummy implements IWebEditorMonomer {
|
|
|
18
18
|
get issmiles(): boolean { return !!this.smiles; }
|
|
19
19
|
|
|
20
20
|
/** R-Group index os single digit only is allowed in Pistoia code */
|
|
21
|
-
|
|
21
|
+
at: WebEditorRGroups = {
|
|
22
22
|
R1: 'H', R2: 'H', R3: 'H', R4: 'H', R5: 'H', R6: 'H', R7: 'H', R8: 'H', R9: 'H'
|
|
23
23
|
};
|
|
24
24
|
|
|
@@ -80,6 +80,20 @@ export abstract class WebEditorMonomerDummy implements IWebEditorMonomer {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
export class SmilesWebEditorMonomer extends WebEditorMonomerDummy {
|
|
84
|
+
public readonly backgroundcolor: string = '#808080';
|
|
85
|
+
public readonly linecolor: string = '#000000';
|
|
86
|
+
public readonly textcolor: string = '#000000';
|
|
87
|
+
|
|
88
|
+
constructor(biotype: string, id: string, smiles: string, name: string, rgpLabels: string[]) {
|
|
89
|
+
super(biotype, id, name, undefined, undefined, undefined, smiles);
|
|
90
|
+
this.at = {};
|
|
91
|
+
rgpLabels.forEach((label) => {
|
|
92
|
+
this.at[label] = 'H';
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
export class GapWebEditorMonomer extends WebEditorMonomerDummy {
|
|
84
98
|
public readonly backgroundcolor: string = '#FFFFFF';
|
|
85
99
|
public readonly linecolor: string = '#808080';
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
import * as grok from 'datagrok-api/grok';
|
|
2
3
|
import * as ui from 'datagrok-api/ui';
|
|
3
4
|
import * as DG from 'datagrok-api/dg';
|
|
4
5
|
|
|
5
6
|
import {HelmType, IWebEditorMonomer, MonomerType, PolymerType, WebEditorRGroups} from '@datagrok-libraries/bio/src/helm/types';
|
|
6
|
-
import {IMonomerLibBase, Monomer} from '@datagrok-libraries/bio/src/types/
|
|
7
|
+
import {IMonomerLibBase, Monomer} from '@datagrok-libraries/bio/src/types/monomer-library';
|
|
7
8
|
import {HELM_OPTIONAL_FIELDS as OPT, HELM_REQUIRED_FIELD as REQ, HELM_RGROUP_FIELDS as RGP} from '@datagrok-libraries/bio/src/utils/const';
|
|
8
9
|
|
|
9
10
|
import {BrokenWebEditorMonomer} from './web-editor-monomer-dummy';
|
|
@@ -138,7 +138,7 @@ async function onDialogOk(
|
|
|
138
138
|
colInput.fireChanged();
|
|
139
139
|
if (colInput.value.semType !== DG.SEMTYPE.MACROMOLECULE)
|
|
140
140
|
throw new Error('Chosen column has to be of Macromolecule semantic type');
|
|
141
|
-
if (performAlignment
|
|
141
|
+
if (performAlignment == undefined) // value can only be undefined when column can't be processed with either method
|
|
142
142
|
throw new Error('Invalid column format');
|
|
143
143
|
msaCol = await performAlignment(); // progress
|
|
144
144
|
if (msaCol == null)
|
|
@@ -149,7 +149,7 @@ function parseKalignError(out: string, limit?: number): string {
|
|
|
149
149
|
const errLineList: string[] = [];
|
|
150
150
|
const errLineRe = /^.+ERROR : (.+)$/gm;
|
|
151
151
|
let ma: RegExpExecArray | null;
|
|
152
|
-
while ((ma = errLineRe.exec(out)) != null && (limit
|
|
152
|
+
while ((ma = errLineRe.exec(out)) != null && (limit == undefined || errLineList.length < limit)) {
|
|
153
153
|
//
|
|
154
154
|
errLineList.push(ma[1]);
|
|
155
155
|
}
|