@datagrok/bio 2.21.8 → 2.21.10

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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Leonid Stolbov",
6
6
  "email": "lstolbov@datagrok.ai"
7
7
  },
8
- "version": "2.21.8",
8
+ "version": "2.21.10",
9
9
  "description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
10
10
  "repository": {
11
11
  "type": "git",
@@ -194,6 +194,10 @@ export class MonomerManager implements IMonomerManager {
194
194
  args.menu.item('Edit Monomer', async () => {
195
195
  await this.editMonomer(this.tv!.dataFrame.rows.get(rowIdx));
196
196
  });
197
+
198
+ args.menu.item('Fix all monomers', () => {
199
+ this.fixAllMonomers();
200
+ });
197
201
  if (this.tv!.dataFrame.selection.trueCount > 0) {
198
202
  args.menu.item('Remove Selected Monomers', async () => {
199
203
  const monomers = await Promise.all(Array.from(this.tv!.dataFrame.selection.getSelectedIndexes())
@@ -271,6 +275,8 @@ export class MonomerManager implements IMonomerManager {
271
275
  this._newMonomerForm.setEmptyMonomer();
272
276
  }, 'Add New Monomer');
273
277
 
278
+ const fixAllMonomersIcon = ui.iconFA('wand-magic', () => { this.fixAllMonomers(); }, 'Fix all monomers');
279
+
274
280
  const editButton = ui.icons.edit(async () => {
275
281
  if ((this.tv?.dataFrame?.currentRowIdx ?? -1) < 0) return;
276
282
  await this.editMonomer(this.tv!.dataFrame.rows.get(this.tv!.dataFrame.currentRowIdx));
@@ -309,7 +315,7 @@ export class MonomerManager implements IMonomerManager {
309
315
  DG.Utils.download(libName!, lib!, 'text/plain');
310
316
  }, 'Download Monomer Library');
311
317
 
312
- ribbons.push([newMonomerButton, editButton, deleteButton, downloadButton]);
318
+ ribbons.push([newMonomerButton, editButton, fixAllMonomersIcon, deleteButton, downloadButton]);
313
319
  this.tv.setRibbonPanels(ribbons);
314
320
 
315
321
 
@@ -443,6 +449,36 @@ export class MonomerManager implements IMonomerManager {
443
449
  }
444
450
  }
445
451
 
452
+ async fixAllMonomers() {
453
+ ui.dialog('Fix All Monomers')
454
+ .add(ui.divText('This action will fix all monomers in the library, standardize their smiles, molblocks and r-groups, assign correct natural analogs and save the library.'))
455
+ .add(ui.divText('Do you wish to continue?'))
456
+ .onOK(async () => {
457
+ const monomerDf = this.tv?.dataFrame;
458
+ let libName = this.libInput.value;
459
+ if (!monomerDf || !libName) {
460
+ grok.shell.error('No monomer library loaded');
461
+ return;
462
+ }
463
+ this.tv?.grid && ui.setUpdateIndicator(this.tv.grid.root, true);
464
+ try {
465
+ const monomers = await Promise.all(new Array(monomerDf.rowCount).fill(0).map((_, i) => monomerFromDfRow(monomerDf.rows.get(i))));
466
+ const monomersString = JSON.stringify(monomers.map((m) => ({...m, lib: undefined, wem: undefined})), null, 2);
467
+ if (!libName.endsWith('.json'))
468
+ libName += '.json';
469
+ await grok.dapi.files.writeAsText(LIB_PATH + libName, monomersString);
470
+ await this.monomerLibManamger.loadLibraries(true);
471
+ //await this.monomerLibManamger.loadLibraries(true);
472
+ grok.shell.v = await this.getViewRoot(libName);
473
+ } catch (e) {
474
+ grok.shell.error('Error saving library');
475
+ console.error(e);
476
+ } finally {
477
+ this.tv?.grid && ui.setUpdateIndicator(this.tv.grid.root, false);
478
+ }
479
+ }).show();
480
+ }
481
+
446
482
  public resetCurrentRowFollowing() {
447
483
  this._newMonomerForm.molChanged = false;
448
484
  }
@@ -1056,9 +1092,14 @@ function getCorrectedMolBlock(molBlock: string) {
1056
1092
  lines[isoLineIdx] = lines[isoLineIdx].substring(0, isoIndex) + 'RGP' + lines[isoLineIdx].substring(isoIndex + 3);
1057
1093
  }
1058
1094
 
1059
- const molStartIdx = lines.findIndex((line) => line.includes('V2000') || line.includes('V3000'));
1095
+ const molStartIdx = lines.findIndex((line) => line.includes('V2000'));
1060
1096
 
1061
- const atomCount = Number.parseInt(lines[molStartIdx].trim().split(' ')[0]);
1097
+ if (molStartIdx === -1) {
1098
+ console.error('Mol start line not found');
1099
+ return molBlock;
1100
+ }
1101
+ // only 3 positions are used for atom count, so we can safely parse it
1102
+ const atomCount = Number.parseInt(lines[molStartIdx].trim().split(' ')[0].slice(0, 3).trim());
1062
1103
  const rgroupLineNumbers: { [atomLine: number]: number } = {};
1063
1104
  for (let atomI = molStartIdx + 1; atomI < molStartIdx + 1 + atomCount; atomI++) {
1064
1105
  const rIdx = lines[atomI].indexOf('R ');
@@ -51,10 +51,6 @@ export function handleSequenceHeaderRendering() {
51
51
  const split = sh.splitter(seq);
52
52
  const maxSeqLen = split ? split.length : 30;
53
53
 
54
- // makes no sense to have scroller if we have shorter than 50 positions
55
- if (maxSeqLen < 50)
56
- continue;
57
-
58
54
  const defaultHeaderHeight = 40;
59
55
  const scroller = new MSAScrollingHeader({
60
56
  canvas: grid.overlay,
@@ -66,9 +62,9 @@ export function handleSequenceHeaderRendering() {
66
62
  const cur = getCurrent();
67
63
  if (start !== scrollerRange.start)
68
64
  seqCol.setTag(bioTAGS.positionShift, (scrollerRange.start - 1).toString());
69
- if (scrollerCur >= 0 && cur !== scrollerCur) {
65
+ if (cur !== scrollerCur) {
70
66
  seqCol.setTag(bioTAGS.selectedPosition, (scrollerCur).toString());
71
- if (!positionStatsViewerAddedOnce && grid.tableView) {
67
+ if (scrollerCur >= 0 && !positionStatsViewerAddedOnce && grid.tableView) {
72
68
  positionStatsViewerAddedOnce = true;
73
69
  const v = grid.tableView.addViewer('Sequence Position Statistics', {sequenceColumnName: seqCol.name});
74
70
  grid.tableView.dockManager.dock(v, DG.DOCK_TYPE.DOWN, null, 'Sequence Position Statistics', 0.4);
@@ -77,15 +73,20 @@ export function handleSequenceHeaderRendering() {
77
73
  });
78
74
  },
79
75
  });
80
- grid.props.colHeaderHeight = 65;
76
+ // adjust header hight automatically only if the sequences are long enough
77
+ if (maxSeqLen > 40)
78
+ grid.props.colHeaderHeight = 65;
79
+
81
80
  setTimeout(() => { if (grid.isDetached) return; gCol.width = 400; }, 300); // needed because renderer sets its width
82
81
  grid.sub(grid.onCellRender.subscribe((e) => {
83
82
  const cell = e.cell;
84
83
  if (!cell || !cell.isColHeader || cell?.gridColumn?.name !== gCol?.name)
85
84
  return;
86
85
  const cellBounds = e.bounds;
87
- if (!cellBounds || cellBounds.height <= 50)
86
+ if (!cellBounds || cellBounds.height <= 50) {
87
+ scroller.headerHeight = 0;
88
88
  return;
89
+ }
89
90
 
90
91
  const headerTitleSpace = 20;
91
92
  const availableTitleSpace = cellBounds.height - defaultHeaderHeight;