@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/CHANGELOG.md +7 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +1 -1
- package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +44 -3
- package/src/widgets/sequence-scrolling-widget.ts +9 -8
- package/test-console-output-1.log +321 -321
- package/test-record-1.mp4 +0 -0
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
|
+
"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')
|
|
1095
|
+
const molStartIdx = lines.findIndex((line) => line.includes('V2000'));
|
|
1060
1096
|
|
|
1061
|
-
|
|
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 (
|
|
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
|
-
|
|
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;
|