@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.
Files changed (63) hide show
  1. package/.eslintrc.json +0 -2
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +1 -1
  4. package/dist/455.js.map +1 -1
  5. package/dist/package-test.js +3 -3
  6. package/dist/package-test.js.map +1 -1
  7. package/dist/package.js +2 -2
  8. package/dist/package.js.map +1 -1
  9. package/files/samples/HELM_CHEMS.csv +11 -0
  10. package/package.json +2 -2
  11. package/src/analysis/sequence-space.ts +1 -1
  12. package/src/demo/bio03-atomic-level.ts +1 -1
  13. package/src/package-types.ts +2 -2
  14. package/src/package.ts +10 -4
  15. package/src/tests/activity-cliffs-tests.ts +2 -2
  16. package/src/tests/monomer-libraries-tests.ts +9 -7
  17. package/src/tests/renderers-monomer-placer-tests.ts +1 -1
  18. package/src/tests/scoring.ts +1 -1
  19. package/src/tests/seq-handler-get-helm-tests.ts +1 -1
  20. package/src/tests/splitters-test.ts +2 -2
  21. package/src/tests/substructure-filters-tests.ts +11 -11
  22. package/src/tests/to-atomic-level-tests.ts +2 -2
  23. package/src/tests/to-atomic-level-ui-tests.ts +13 -14
  24. package/src/utils/cell-renderer.ts +1 -1
  25. package/src/utils/get-region.ts +2 -2
  26. package/src/utils/helm-to-molfile/converter/const.ts +0 -1
  27. package/src/utils/helm-to-molfile/converter/converter.ts +4 -4
  28. package/src/utils/helm-to-molfile/converter/helm.ts +14 -6
  29. package/src/utils/helm-to-molfile/converter/mol-bonds.ts +2 -2
  30. package/src/utils/helm-to-molfile/converter/mol-wrapper.ts +2 -2
  31. package/src/utils/helm-to-molfile/converter/monomer-wrapper.ts +1 -1
  32. package/src/utils/helm-to-molfile/converter/polymer.ts +1 -1
  33. package/src/utils/helm-to-molfile/converter/r-group-handler.ts +2 -2
  34. package/src/utils/helm-to-molfile/utils.ts +3 -2
  35. package/src/utils/monomer-cell-renderer-base.ts +1 -2
  36. package/src/utils/monomer-lib/consts.ts +1 -6
  37. package/src/utils/monomer-lib/lib-manager.ts +239 -112
  38. package/src/utils/monomer-lib/library-file-manager/file-validator.ts +1 -1
  39. package/src/utils/monomer-lib/library-file-manager/monomers-lib-provider.ts +378 -0
  40. package/src/utils/monomer-lib/library-file-manager/ui.ts +120 -81
  41. package/src/utils/monomer-lib/monomer-colors.ts +37 -39
  42. package/src/utils/monomer-lib/monomer-lib-base.ts +34 -7
  43. package/src/utils/monomer-lib/monomer-lib.ts +7 -33
  44. package/src/utils/monomer-lib/monomer-manager/duplicate-monomer-manager.ts +3 -3
  45. package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +91 -82
  46. package/src/utils/monomer-lib/smiles2Monomer.ts +128 -0
  47. package/src/utils/monomer-lib/web-editor-monomer-dummy.ts +15 -1
  48. package/src/utils/monomer-lib/web-editor-monomer-of-library.ts +2 -1
  49. package/src/utils/multiple-sequence-alignment-ui.ts +1 -1
  50. package/src/utils/multiple-sequence-alignment.ts +1 -1
  51. package/src/utils/seq-helper/seq-handler.ts +26 -13
  52. package/src/utils/seq-helper/seq-helper.ts +1 -2
  53. package/src/utils/sequence-to-mol.ts +1 -1
  54. package/src/utils/ui-utils.ts +1 -1
  55. package/src/viewers/web-logo-viewer.ts +20 -9
  56. package/src/widgets/composition-analysis-widget.ts +1 -1
  57. package/src/widgets/sequence-scrolling-widget.ts +1 -2
  58. package/test-console-output-1.log +670 -721
  59. package/test-record-1.mp4 +0 -0
  60. package/src/utils/monomer-lib/library-file-manager/custom-monomer-lib-handlers.ts +0 -41
  61. package/src/utils/monomer-lib/library-file-manager/event-manager.ts +0 -93
  62. package/src/utils/monomer-lib/library-file-manager/file-manager.ts +0 -317
  63. 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 monomersString = JSON.stringify(_monomers.map((m) => ({...m, lib: undefined, wem: undefined})), null, 2);
288
- if (!libName.endsWith('.json'))
289
- libName += '.json';
290
- await (await this.monomerLibManamger.getFileManager()).addLibraryFile(monomersString, libName);
291
- await grok.dapi.files.writeAsText(LIB_PATH + libName, monomersString);
292
- await this.monomerLibManamger.loadLibraries(false);
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 = (await this.monomerLibManamger.getFileManager()).getValidLibraryPaths();
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 ((v.endsWith('.json') && monomerLibs.includes(v)) || monomerLibs.includes(v + '.json'))
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(getFileNameInputValue(), monomers ?? []);
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.tv.subs.push(
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 = (await this.monomerLibManamger.getFileManager()).getValidLibraryPaths();
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
- lib = await grok.dapi.files.readAsText(LIB_PATH + libName);
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
- fileName ??= (await this.monomerLibManamger.getFileManager()).getValidLibraryPaths()[0];
522
- this.activeMonomerLib = await this.monomerLibManamger.readLibrary(LIB_PATH, fileName);
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
- let libName = this.libInput.value;
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 = JSON.stringify(monomers.map((m) => ({...m, lib: undefined, wem: undefined})), null, 2);
560
- if (!libName.endsWith('.json'))
561
- libName += '.json';
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
- let libJSON: Monomer[] = [];
1002
- try {
1003
- const libTXT = await grok.dapi.files.readAsText(LIB_PATH + libName);
1004
- libJSON = JSON.parse(libTXT);
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 removingMonomers = monomerIdxs.map((idx) => libJSON[idx]);
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 ${removingMonomers.map((m) => m.symbol).join(', ')} from ${libName} library?` :
1023
- `Are you sure you want to remove monomer with symbol ${removingMonomers[0].symbol} from ${libName} library?`;
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
- libJSON = libJSON.filter((m) => !removingMonomers.includes(m));
1031
- await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON, null, 2));
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' : ''} ${removingMonomers.map((m) => m.symbol).join(', ')} ${isPlural ? 'were' : 'was'} successfully removed from ${libName} library`);
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
- let libJSON: Monomer[] = [];
1045
- try {
1046
- const libTXT = await grok.dapi.files.readAsText(LIB_PATH + libName);
1047
- libJSON = JSON.parse(libTXT);
1048
- } catch (e) {
1049
- grok.shell.error(`Error reading library ${libName}`);
1050
- return console.error(e);
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 = findLastIndex(libJSON, (m) => m.symbol === monomer.symbol && m.polymerType === monomer.polymerType);
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 = this.getMonomersDataFrame()?.col(MONOMER_DF_COLUMN_NAMES.MONOMER)?.toList()?.findIndex((smi) => smi === monomer.smiles);
1065
+ const existingStructureIdx = monomerSmiles.findIndex((smi) => smi === monomer.smiles);
1056
1066
 
1057
1067
  const saveLib = async () => {
1058
1068
  try {
1059
- // first remove the existing monomer with that symbol
1060
- const monomerIdx = libJSON.findIndex((m) => m.symbol === monomer.symbol && m.polymerType === monomer.polymerType);
1061
- if (monomerIdx >= 0)
1062
- libJSON[monomerIdx] = {...monomer, lib: undefined, wem: undefined};
1063
- else
1064
- libJSON.push({...monomer, lib: undefined, wem: undefined});
1065
-
1066
- await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON, null, 2));
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(libJSON[existingMonomerIdx]);
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
- public readonly at: WebEditorRGroups = {
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/index';
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 === undefined) // value can only be undefined when column can't be processed with either method
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 === undefined || errLineList.length < limit)) {
152
+ while ((ma = errLineRe.exec(out)) != null && (limit == undefined || errLineList.length < limit)) {
153
153
  //
154
154
  errLineList.push(ma[1]);
155
155
  }