@datagrok/bio 2.17.0 → 2.17.2
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 +11 -0
- package/README.md +7 -0
- package/css/monomer-manager.css +4 -0
- package/dist/package-test.js +2 -2
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/dockerfiles/container.json +6 -0
- package/package.json +12 -5
- package/src/package-types.ts +11 -0
- package/src/tests/msa-tests.ts +3 -5
- package/src/tests/pepsea-tests.ts +0 -5
- package/src/tests/renderers-monomer-placer-tests.ts +6 -3
- package/src/tests/renderers-test.ts +6 -1
- package/src/tests/substructure-filters-tests.ts +13 -5
- package/src/tests/utils.ts +0 -2
- package/src/utils/cell-renderer.ts +2 -1
- package/src/utils/monomer-lib/library-file-manager/style.css +2 -0
- package/src/utils/monomer-lib/library-file-manager/ui.ts +5 -2
- package/src/utils/monomer-lib/monomer-manager/duplicate-monomer-manager.ts +6 -6
- package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +32 -7
- package/src/utils/multiple-sequence-alignment-ui.ts +0 -3
- package/src/utils/pepsea.ts +0 -3
- package/src/viewers/web-logo-viewer.ts +5 -1
- package/src/widgets/representations.ts +22 -2
- package/src/utils/docker.ts +0 -52
package/package.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"name": "@datagrok/bio",
|
|
3
3
|
"friendlyName": "Bio",
|
|
4
4
|
"author": {
|
|
5
|
-
"name": "
|
|
6
|
-
"email": "
|
|
5
|
+
"name": "Leonid Stolbov",
|
|
6
|
+
"email": "lstolbov@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.17.
|
|
8
|
+
"version": "2.17.2",
|
|
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",
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
"directory": "packages/Bio"
|
|
14
14
|
},
|
|
15
15
|
"properties": [
|
|
16
|
+
{
|
|
17
|
+
"name": "FontSize",
|
|
18
|
+
"description": "Font size for monomer symbols in the sequence renderer",
|
|
19
|
+
"propertyType": "int",
|
|
20
|
+
"defaultValue": "12",
|
|
21
|
+
"nullable": false
|
|
22
|
+
},
|
|
16
23
|
{
|
|
17
24
|
"name": "MaxMonomerLength",
|
|
18
25
|
"description": "The max length of monomer symbol displayed without shortening, 'long' to no limit",
|
|
@@ -37,7 +44,7 @@
|
|
|
37
44
|
],
|
|
38
45
|
"dependencies": {
|
|
39
46
|
"@biowasm/aioli": "^3.1.0",
|
|
40
|
-
"@datagrok-libraries/bio": "^5.
|
|
47
|
+
"@datagrok-libraries/bio": "^5.46.0",
|
|
41
48
|
"@datagrok-libraries/chem-meta": "^1.2.7",
|
|
42
49
|
"@datagrok-libraries/math": "^1.2.2",
|
|
43
50
|
"@datagrok-libraries/ml": "^6.7.4",
|
|
@@ -62,7 +69,7 @@
|
|
|
62
69
|
"@datagrok-libraries/js-draw-lite": "^0.0.10",
|
|
63
70
|
"@datagrok/chem": "^1.12.3",
|
|
64
71
|
"@datagrok/dendrogram": "^1.2.33",
|
|
65
|
-
"@datagrok/helm": "^2.5.
|
|
72
|
+
"@datagrok/helm": "^2.5.10",
|
|
66
73
|
"@types/node": "^17.0.24",
|
|
67
74
|
"@types/wu": "^2.1.44",
|
|
68
75
|
"@typescript-eslint/eslint-plugin": "^8.8.1",
|
package/src/package-types.ts
CHANGED
|
@@ -16,6 +16,7 @@ export const enum BioPackagePropertiesNames {
|
|
|
16
16
|
MaxMonomerLength = 'MaxMonomerLength',
|
|
17
17
|
TooltipWebLogo = 'TooltipWebLogo',
|
|
18
18
|
DefaultSeparator = 'DefaultSeparator',
|
|
19
|
+
FontSize = 'FontSize',
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
|
|
@@ -23,6 +24,16 @@ export class BioPackageProperties extends Map<string, any> {
|
|
|
23
24
|
private _onPropertyChanged: Subject<string> = new Subject<string>();
|
|
24
25
|
public get onPropertyChanged(): Observable<string> { return this._onPropertyChanged; }
|
|
25
26
|
|
|
27
|
+
public get fontSize(): number {
|
|
28
|
+
const vs = super.get(BioPackagePropertiesNames.FontSize);
|
|
29
|
+
return !!vs && !isNaN(vs) ? Number.parseInt(vs) : 12;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public set fontSize(value: number) {
|
|
33
|
+
super.set(BioPackagePropertiesNames.FontSize, value);
|
|
34
|
+
this._onPropertyChanged.next(BioPackagePropertiesNames.FontSize);
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
/** Monomer symbol maximum length displayed, null for unlimited. */
|
|
27
38
|
public get maxMonomerLength(): number | null {
|
|
28
39
|
const vs = super.get(BioPackagePropertiesNames.MaxMonomerLength);
|
package/src/tests/msa-tests.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-lines-per-function */
|
|
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';
|
|
@@ -8,7 +9,6 @@ import {ISeqHelper, getSeqHelper} from '@datagrok-libraries/bio/src/utils/seq-he
|
|
|
8
9
|
|
|
9
10
|
import {runKalign} from '../utils/multiple-sequence-alignment';
|
|
10
11
|
import {multipleSequenceAlignmentUI} from '../utils/multiple-sequence-alignment-ui';
|
|
11
|
-
import {awaitContainerStart} from '../utils/docker';
|
|
12
12
|
|
|
13
13
|
category('MSA', async () => {
|
|
14
14
|
let seqHelper: ISeqHelper;
|
|
@@ -87,14 +87,12 @@ MWRSWYCKHPMWRSWYCKHPMWRSWYCKHPMWRSWYCKHPMWRSWYCKHPMWRSWYCKHPMWRSWYCKHPMWRSWYCKHP
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
test('isCorrectHelm', async () => {
|
|
90
|
-
await awaitContainerStart();
|
|
91
90
|
await _testMSAOnColumn(helmFromCsv, helmToCsv, NOTATION.HELM, NOTATION.SEPARATOR, undefined, 'mafft');
|
|
92
|
-
}, {timeout:
|
|
91
|
+
}, {timeout: 80000 /* docker */});
|
|
93
92
|
|
|
94
93
|
test('isCorrectHelmLong', async () => {
|
|
95
|
-
await awaitContainerStart();
|
|
96
94
|
await _testMSAOnColumn(longHelmFromCsv, longHelmToCsv, NOTATION.HELM, NOTATION.SEPARATOR, undefined, 'mafft');
|
|
97
|
-
}, {timeout:
|
|
95
|
+
}, {timeout: 80000 /* docker */});
|
|
98
96
|
|
|
99
97
|
test('isCorrectSeparator', async () => {
|
|
100
98
|
await _testMSAOnColumn(
|
|
@@ -4,7 +4,6 @@ import {before, category, expect, expectArray, test} from '@datagrok-libraries/u
|
|
|
4
4
|
import {runPepsea} from '../utils/pepsea';
|
|
5
5
|
import {TestLogger} from './utils/test-logger';
|
|
6
6
|
import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
|
|
7
|
-
import {awaitContainerStart} from '../utils/docker';
|
|
8
7
|
|
|
9
8
|
category('PepSeA', () => {
|
|
10
9
|
const testCsv = `HELM,MSA
|
|
@@ -30,10 +29,6 @@ category('PepSeA', () => {
|
|
|
30
29
|
`;
|
|
31
30
|
const pepseaErrorError: string = 'PepSeA error: The pair (*,M) couldn\'t be found in the substitution matrix';
|
|
32
31
|
|
|
33
|
-
before(async () => {
|
|
34
|
-
await awaitContainerStart();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
32
|
test('Basic alignment', async () => {
|
|
38
33
|
const df = DG.DataFrame.fromCsv(testCsv);
|
|
39
34
|
const resMsaCol = await runPepsea(df.getCol('HELM'), 'msa(HELM)');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-lines-per-function */
|
|
1
2
|
import * as grok from 'datagrok-api/grok';
|
|
2
3
|
import * as DG from 'datagrok-api/dg';
|
|
3
4
|
import * as ui from 'datagrok-api/ui';
|
|
@@ -49,7 +50,7 @@ category('renderers: monomerPlacer', () => {
|
|
|
49
50
|
{src: {row: 1, x: 6}, tgt: {pos: 0}},
|
|
50
51
|
{src: {row: 1, x: 26}, tgt: {pos: 1}},
|
|
51
52
|
{src: {row: 1, x: 160}, tgt: {pos: 6}},
|
|
52
|
-
{src: {row: 1, x:
|
|
53
|
+
{src: {row: 1, x: 190}, tgt: {pos: 7}},
|
|
53
54
|
{src: {row: 2, x: 140}, tgt: {pos: 5}},
|
|
54
55
|
{src: {row: 2, x: 145}, tgt: {pos: 5}},
|
|
55
56
|
]
|
|
@@ -115,7 +116,8 @@ id3,QHIRE--LT
|
|
|
115
116
|
const colTemp: MonomerPlacer = new MonomerPlacer(null, seqCol, _package.logger, monLength,
|
|
116
117
|
() => {
|
|
117
118
|
return {
|
|
118
|
-
|
|
119
|
+
font: '12px monospace',
|
|
120
|
+
fontCharWidth: charWidth,
|
|
119
121
|
separatorWidth: sepWidth,
|
|
120
122
|
monomerToShort: monomerToShort,
|
|
121
123
|
};
|
|
@@ -209,7 +211,8 @@ id3,QHIRE--LT
|
|
|
209
211
|
const sepWidth: number = 12;
|
|
210
212
|
const colTemp = new MonomerPlacer(null, seqCol, _package.logger, monLengthLimit, () => {
|
|
211
213
|
return {
|
|
212
|
-
|
|
214
|
+
fontCharWidth: charWidth,
|
|
215
|
+
font: '12px monospace',
|
|
213
216
|
separatorWidth: sepWidth,
|
|
214
217
|
monomerToShort: monomerToShort,
|
|
215
218
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-lines-per-function */
|
|
1
2
|
import * as grok from 'datagrok-api/grok';
|
|
2
3
|
import * as DG from 'datagrok-api/dg';
|
|
3
4
|
import * as ui from 'datagrok-api/ui';
|
|
@@ -196,6 +197,8 @@ category('renderers', () => {
|
|
|
196
197
|
const df: DG.DataFrame = await grok.dapi.files.readCsv('System:AppData/Bio/samples/FASTA_PT.csv');
|
|
197
198
|
const view = grok.shell.addTableView(df);
|
|
198
199
|
await awaitGrid(view.grid);
|
|
200
|
+
await df.meta.detectSemanticTypes();
|
|
201
|
+
await grok.data.detectSemanticTypes(df);
|
|
199
202
|
|
|
200
203
|
const srcCol = df.getCol('sequence');
|
|
201
204
|
const sh = seqHelper.getSeqHandler(srcCol);
|
|
@@ -239,6 +242,8 @@ CTCGGCATGC,2,0
|
|
|
239
242
|
const df = DG.DataFrame.fromCsv(seqCoordsCsv);
|
|
240
243
|
df.currentRowIdx = 0;
|
|
241
244
|
const view = grok.shell.addTableView(df);
|
|
245
|
+
await df.meta.detectSemanticTypes();
|
|
246
|
+
await grok.data.detectSemanticTypes(df);
|
|
242
247
|
const sp: DG.ScatterPlotViewer = df.plot.scatter({x: 'x', y: 'y'});
|
|
243
248
|
view.dockManager.dock(sp, DG.DOCK_TYPE.RIGHT, null);
|
|
244
249
|
await Promise.all([
|
|
@@ -253,7 +258,7 @@ CTCGGCATGC,2,0
|
|
|
253
258
|
clientX: spBcr.left + wp.x, clientY: spBcr.top + wp.y
|
|
254
259
|
});
|
|
255
260
|
const spCanvas = $(sp.root).find('canvas').get()[0] as HTMLCanvasElement;
|
|
256
|
-
await testEvent(fromEvent(spCanvas, 'mousemove'), () => {
|
|
261
|
+
await testEvent(DG.debounce(fromEvent(spCanvas, 'mousemove'), 200), () => {
|
|
257
262
|
_package.logger.debug(`Test: event, currentRowIdx=${df.currentRowIdx}`);
|
|
258
263
|
expect($(ui.tooltip.root).find('div table.d4-row-tooltip-table tr td canvas').length, 1);
|
|
259
264
|
expect(sp.hitTest(wp.x, wp.y), 1);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
/* eslint-disable max-lines-per-function */
|
|
1
3
|
import * as grok from 'datagrok-api/grok';
|
|
2
4
|
import * as ui from 'datagrok-api/ui';
|
|
3
5
|
import * as DG from 'datagrok-api/dg';
|
|
@@ -152,8 +154,9 @@ category('bio-substructure-filters', async () => {
|
|
|
152
154
|
test('helm-dialog', async () => {
|
|
153
155
|
const logPrefix = 'Bio tests: substructureFilters/helm-dialog';
|
|
154
156
|
const df = await readDataframe('tests/filter_HELM.csv');
|
|
155
|
-
await grok.data.detectSemanticTypes(df);
|
|
156
157
|
const view = grok.shell.addTableView(df);
|
|
158
|
+
await grok.data.detectSemanticTypes(df);
|
|
159
|
+
await df.meta.detectSemanticTypes();
|
|
157
160
|
|
|
158
161
|
_package.logger.debug(`${logPrefix}, filter attaching.`);
|
|
159
162
|
const filter = new BioSubstructureFilter(seqHelper, _package.logger);
|
|
@@ -178,7 +181,9 @@ category('bio-substructure-filters', async () => {
|
|
|
178
181
|
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
179
182
|
bf.props = new BioFilterProps('PEPTIDE1{C}$$$$V2.0');
|
|
180
183
|
}, 20000);
|
|
184
|
+
setTimeout(() => view.grid.invalidate(), 500);
|
|
181
185
|
await awaitGrid(view.grid);
|
|
186
|
+
await delay(1000);
|
|
182
187
|
_package.logger.debug(`${logPrefix}, filter 2 changed.`);
|
|
183
188
|
expect(filter.dataFrame!.filter.trueCount, 2);
|
|
184
189
|
expect(filter.dataFrame!.filter.toBinaryString(), '1001');
|
|
@@ -187,7 +192,7 @@ category('bio-substructure-filters', async () => {
|
|
|
187
192
|
}
|
|
188
193
|
await filter.awaitRendered();
|
|
189
194
|
await delay(3000); //TODO: await for grid.onLookChanged
|
|
190
|
-
});
|
|
195
|
+
}, {});
|
|
191
196
|
|
|
192
197
|
// Generates unhandled exception accessing isFiltering before bioFilter created
|
|
193
198
|
test('helm-view', async () => {
|
|
@@ -398,8 +403,9 @@ category('bio-substructure-filters', async () => {
|
|
|
398
403
|
|
|
399
404
|
test('reset-fasta', async () => {
|
|
400
405
|
const df = await readDataframe('tests/filter_FASTA.csv');
|
|
401
|
-
await grok.data.detectSemanticTypes(df);
|
|
402
406
|
const view = grok.shell.addTableView(df);
|
|
407
|
+
await grok.data.detectSemanticTypes(df);
|
|
408
|
+
await df.meta.detectSemanticTypes();
|
|
403
409
|
|
|
404
410
|
const fSeqColName: string = 'fasta';
|
|
405
411
|
const fSubStr: string = 'MD';
|
|
@@ -432,6 +438,8 @@ category('bio-substructure-filters', async () => {
|
|
|
432
438
|
test('reopen', async () => {
|
|
433
439
|
const df = await _package.files.readCsv('tests/filter_FASTA.csv');
|
|
434
440
|
const view = grok.shell.addTableView(df);
|
|
441
|
+
await grok.data.detectSemanticTypes(df);
|
|
442
|
+
await df.meta.detectSemanticTypes();
|
|
435
443
|
|
|
436
444
|
const filterList = [{type: 'Bio:bioSubstructureFilter', columnName: 'fasta'}];
|
|
437
445
|
|
|
@@ -448,7 +456,7 @@ category('bio-substructure-filters', async () => {
|
|
|
448
456
|
const fg2Dn = view.dockManager.dock(fg2, DG.DOCK_TYPE.LEFT);
|
|
449
457
|
await delay(100);
|
|
450
458
|
await awaitGrid(view.grid);
|
|
451
|
-
});
|
|
459
|
+
}, {});
|
|
452
460
|
|
|
453
461
|
async function createFilter(colName: string, df: DG.DataFrame): Promise<BioSubstructureFilter> {
|
|
454
462
|
if (!df.columns.names().includes(colName)) {
|
|
@@ -464,5 +472,5 @@ category('bio-substructure-filters', async () => {
|
|
|
464
472
|
//filter.tableName = df.name;
|
|
465
473
|
return filter;
|
|
466
474
|
};
|
|
467
|
-
});
|
|
475
|
+
}, );
|
|
468
476
|
|
package/src/tests/utils.ts
CHANGED
|
@@ -4,8 +4,6 @@ import * as grok from 'datagrok-api/grok';
|
|
|
4
4
|
import {delay, expect, testEvent} from '@datagrok-libraries/utils/src/test';
|
|
5
5
|
import {asRenderer, IRenderer, isRenderer} from '@datagrok-libraries/bio/src/types/renderer';
|
|
6
6
|
|
|
7
|
-
import {startDockerContainer} from '../utils/docker';
|
|
8
|
-
|
|
9
7
|
import {_package} from '../package-test';
|
|
10
8
|
import {CellRendererBackBase, getGridCellColTemp} from '@datagrok-libraries/bio/src/utils/cell-renderer-back-base';
|
|
11
9
|
|
|
@@ -168,8 +168,9 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
168
168
|
if (!seqColTemp) {
|
|
169
169
|
seqColTemp = temp.rendererBack = new MonomerPlacer(gridCol, tableCol, _package.logger, maxLengthOfMonomer,
|
|
170
170
|
() => {
|
|
171
|
+
const {font, fontWidth} = MonomerPlacer.getFontSettings(tableCol);
|
|
171
172
|
return {
|
|
172
|
-
|
|
173
|
+
font: font, fontCharWidth: fontWidth, separatorWidth: !sh.isMsa() ? gapLength : msaGapLength,
|
|
173
174
|
monomerToShort: monomerToShortFunction,
|
|
174
175
|
};
|
|
175
176
|
});
|
|
@@ -77,6 +77,9 @@ class MonomerLibraryManagerWidget {
|
|
|
77
77
|
private async getWidgetContent(): Promise<HTMLElement> {
|
|
78
78
|
const libControlsForm = await LibraryControlsManager.createControlsForm();
|
|
79
79
|
$(libControlsForm).addClass('monomer-lib-controls-form');
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
libControlsForm && $(libControlsForm) && $(libControlsForm).removeClass('ui-form-condensed');
|
|
82
|
+
}, 200);
|
|
80
83
|
const widgetContent = ui.divV([libControlsForm]);
|
|
81
84
|
return widgetContent;
|
|
82
85
|
}
|
|
@@ -132,7 +135,7 @@ class LibraryControlsManager {
|
|
|
132
135
|
|
|
133
136
|
private _createControlsForm(): HTMLElement {
|
|
134
137
|
const libraryControls = this.createLibraryControls();
|
|
135
|
-
const inputsForm = ui.
|
|
138
|
+
const inputsForm = ui.wideForm(libraryControls, undefined);
|
|
136
139
|
$(inputsForm).addClass('monomer-lib-controls-form');
|
|
137
140
|
|
|
138
141
|
return inputsForm;
|
|
@@ -287,7 +290,7 @@ class LibManagerView {
|
|
|
287
290
|
const rightWidth = combinedWidth - leftWidth;
|
|
288
291
|
right.style.width = `${rightWidth}px`;
|
|
289
292
|
}, 100);
|
|
290
|
-
this._view.subs.push(grok.events.onCurrentViewChanged.subscribe(
|
|
293
|
+
this._view.subs.push(grok.events.onCurrentViewChanged.subscribe(() => {
|
|
291
294
|
try {
|
|
292
295
|
const inst = LibManagerView._instance;
|
|
293
296
|
if (inst && inst._view && 'id' in grok.shell.v && grok.shell.v.id === inst._view.id)
|
|
@@ -47,12 +47,12 @@ class MonomerCard {
|
|
|
47
47
|
|
|
48
48
|
class DuplicateSymbolRow {
|
|
49
49
|
root: HTMLElement = ui.divH([],
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
{style: {
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
width: '100%',
|
|
53
|
+
overflow: 'hidden',
|
|
54
|
+
visibility: 'visible',
|
|
55
|
+
}, classes: 'duplicate-monomer-symbol-row'}
|
|
56
56
|
);
|
|
57
57
|
monomerCards: MonomerCard[];
|
|
58
58
|
constructor(
|
|
@@ -102,7 +102,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
102
102
|
async createNewMonomerLib(libName: string, _monomers: Monomer[]): Promise<void> {
|
|
103
103
|
this.tv?.grid && ui.setUpdateIndicator(this.tv.grid.root, true);
|
|
104
104
|
try {
|
|
105
|
-
const monomersString = JSON.stringify(_monomers.map((m) => ({...m, lib: undefined, wem: undefined})));
|
|
105
|
+
const monomersString = JSON.stringify(_monomers.map((m) => ({...m, lib: undefined, wem: undefined})), null, 2);
|
|
106
106
|
if (!libName.endsWith('.json'))
|
|
107
107
|
libName += '.json';
|
|
108
108
|
await (await this.monomerLibManamger.getFileManager()).addLibraryFile(monomersString, libName);
|
|
@@ -214,13 +214,23 @@ export class MonomerManager implements IMonomerManager {
|
|
|
214
214
|
return tv ?? null;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
private _skipLibInputOnchange: boolean = false;
|
|
218
|
+
|
|
217
219
|
async getViewRoot(libName?: string) {
|
|
218
220
|
const availableMonLibs = (await this.monomerLibManamger.getFileManager()).getValidLibraryPaths();
|
|
219
221
|
this._newMonomerForm.molSketcher.resize();
|
|
220
222
|
if ((this.tv = this.findActiveManagerView()) && (libName ?? this.libInput.value)) {
|
|
221
223
|
// get monomer library list
|
|
222
|
-
|
|
223
|
-
|
|
224
|
+
try {
|
|
225
|
+
this._skipLibInputOnchange = true;
|
|
226
|
+
this.libInput && ((this.libInput as DG.ChoiceInput<string>).items = availableMonLibs);
|
|
227
|
+
libName && (this.libInput.value = libName);
|
|
228
|
+
} catch (e) {
|
|
229
|
+
grok.shell.error('Error updating library list');
|
|
230
|
+
console.error(e);
|
|
231
|
+
} finally {
|
|
232
|
+
this._skipLibInputOnchange = false;
|
|
233
|
+
}
|
|
224
234
|
const df = await this.getMonomersDf(libName);
|
|
225
235
|
this.tv.dataFrame = df;
|
|
226
236
|
this.adjustColWidths();
|
|
@@ -287,6 +297,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
287
297
|
this.tv.name = MonomerManager.VIEW_NAME;
|
|
288
298
|
this.libInput = ui.input.choice('Monomer Library', {value: libName, items: availableMonLibs, nullable: false, onValueChanged: async () => {
|
|
289
299
|
try {
|
|
300
|
+
if (this._skipLibInputOnchange) return;
|
|
290
301
|
const df = await this.getMonomersDf(this.libInput.value!);
|
|
291
302
|
this.tv!.dataFrame = df;
|
|
292
303
|
this.adjustColWidths();
|
|
@@ -553,6 +564,8 @@ class MonomerForm implements INewMonomerForm {
|
|
|
553
564
|
}
|
|
554
565
|
try {
|
|
555
566
|
this.rgroupsGridRoot.style.display = 'none';
|
|
567
|
+
const rGroupsPane = this.inputsTabControl.panes.find((p) => p.name?.toLowerCase() === 'r-groups');
|
|
568
|
+
rGroupsPane && (rGroupsPane.header.style.removeProperty('background-color'));
|
|
556
569
|
let smiles = this.molSketcher.getSmiles();
|
|
557
570
|
if (!smiles) {
|
|
558
571
|
this.rgroupsGrid.items = [];
|
|
@@ -568,9 +581,21 @@ class MonomerForm implements INewMonomerForm {
|
|
|
568
581
|
this.rgroupsGrid.items = [];
|
|
569
582
|
this.rgroupsGrid.render();
|
|
570
583
|
this.saveValidationResult = 'At least one R-group is required';
|
|
584
|
+
rGroupsPane && (rGroupsPane.header.style.setProperty('background-color', '#ff000030'));
|
|
571
585
|
this.invalidateSaveButton();
|
|
572
586
|
return;
|
|
573
587
|
}
|
|
588
|
+
|
|
589
|
+
// check for duplicate r-groups
|
|
590
|
+
const rGroupsSet = new Set(rGroupMatches.map((match) => match[0]));
|
|
591
|
+
if (rGroupsSet.size !== rGroupMatches.length) {
|
|
592
|
+
this.saveValidationResult = 'Duplicate R-groups are not allowed';
|
|
593
|
+
this.rgroupsGridRoot.style.display = 'flex';
|
|
594
|
+
rGroupsPane && (rGroupsPane.header.style.setProperty('background-color', '#ff000030'));
|
|
595
|
+
this.invalidateSaveButton();
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
574
599
|
const rGroupNums = rGroupMatches.map((match) => Number.parseInt(match[0].match(/[1-9]/g)![0]));
|
|
575
600
|
const rGroupItems: RGroup[] = rGroupNums.map((num) => {
|
|
576
601
|
const existingRGroup = this.rgroupsGrid.items.find((rg) => rg[HELM_RGROUP_FIELDS.LABEL] === `R${num}`) as RGroup | undefined;
|
|
@@ -581,8 +606,8 @@ class MonomerForm implements INewMonomerForm {
|
|
|
581
606
|
[HELM_RGROUP_FIELDS.LABEL]: `R${num}`,
|
|
582
607
|
} as unknown as RGroup;
|
|
583
608
|
});
|
|
584
|
-
if (this.rgroupsGrid.items.length !== rGroupItems.length)
|
|
585
|
-
|
|
609
|
+
// if (this.rgroupsGrid.items.length !== rGroupItems.length)
|
|
610
|
+
this.rgroupsGrid.items = rGroupItems.sort((a, b) => a.label.localeCompare(b.label));
|
|
586
611
|
this.rgroupsGrid.render();
|
|
587
612
|
this.rgroupsGridRoot.style.display = 'flex';
|
|
588
613
|
const mostSimilar = await mostSimilarNaturalAnalog(capSmiles(smiles, rGroupItems), this.polymerTypeInput.value ?? '');
|
|
@@ -830,7 +855,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
830
855
|
.add(infoTables)
|
|
831
856
|
.addButton('Remove', async () => {
|
|
832
857
|
libJSON = libJSON.filter((m) => !removingMonomers.includes(m));
|
|
833
|
-
await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON));
|
|
858
|
+
await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON, null, 2));
|
|
834
859
|
await (await MonomerLibManager.getInstance()).loadLibraries(true);
|
|
835
860
|
await this.refreshTable();
|
|
836
861
|
|
|
@@ -865,7 +890,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
865
890
|
else
|
|
866
891
|
libJSON.push({...monomer, lib: undefined, wem: undefined});
|
|
867
892
|
|
|
868
|
-
await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON));
|
|
893
|
+
await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON, null, 2));
|
|
869
894
|
await (await MonomerLibManager.getInstance()).loadLibraries(true);
|
|
870
895
|
await this.refreshTable(monomer.symbol);
|
|
871
896
|
this._molChanged = false; // reset the flag
|
|
@@ -12,7 +12,6 @@ import {pepseaMethods, runPepsea} from './pepsea';
|
|
|
12
12
|
import {checkInputColumn} from './check-input-column';
|
|
13
13
|
import {MultipleSequenceAlignmentUIOptions} from './types';
|
|
14
14
|
import {kalignVersion, msaDefaultOptions} from './constants';
|
|
15
|
-
import {awaitContainerStart} from './docker';
|
|
16
15
|
|
|
17
16
|
import '../../css/msa.css';
|
|
18
17
|
import {_package} from '../package';
|
|
@@ -176,7 +175,6 @@ async function onColInputChange(
|
|
|
176
175
|
gapExtendInput.value ??= msaDefaultOptions.pepsea.gapExtend;
|
|
177
176
|
|
|
178
177
|
return async () => {
|
|
179
|
-
await awaitContainerStart();
|
|
180
178
|
return runPepsea(col, unusedName, methodInput.value!,
|
|
181
179
|
gapOpenInput.value!, gapExtendInput.value!, clustersColInput.value);
|
|
182
180
|
};
|
|
@@ -190,7 +188,6 @@ async function onColInputChange(
|
|
|
190
188
|
// convert to helm and assign alignment function to PepSea
|
|
191
189
|
|
|
192
190
|
return async () => {
|
|
193
|
-
await awaitContainerStart();
|
|
194
191
|
return runPepsea(helmCol, unusedName, methodInput.value!,
|
|
195
192
|
gapOpenInput.value!, gapExtendInput.value!, clustersColInput.value);
|
|
196
193
|
};
|
package/src/utils/pepsea.ts
CHANGED
|
@@ -49,9 +49,6 @@ export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
|
|
|
49
49
|
clustersCol: DG.Column<string | number> | null = null, logger?: ILogger
|
|
50
50
|
): Promise<DG.Column<string>> {
|
|
51
51
|
const pepseaContainer = await Pepsea.getDockerContainer();
|
|
52
|
-
if (pepseaContainer.status !== 'started' && pepseaContainer.status !== 'checking')
|
|
53
|
-
throw new Error('PepSeA container has not started yet');
|
|
54
|
-
|
|
55
52
|
const peptideCount = srcCol.length;
|
|
56
53
|
clustersCol ??= DG.Column.int('Clusters', peptideCount).init(0);
|
|
57
54
|
if (clustersCol.type != DG.COLUMN_TYPE.STRING)
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
/* eslint-disable max-len */
|
|
3
|
+
/* eslint-disable max-params */
|
|
4
|
+
/* eslint-disable max-lines-per-function */
|
|
1
5
|
import * as grok from 'datagrok-api/grok';
|
|
2
6
|
import * as ui from 'datagrok-api/ui';
|
|
3
7
|
import * as DG from 'datagrok-api/dg';
|
|
@@ -548,7 +552,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
548
552
|
style: {
|
|
549
553
|
display: 'flex',
|
|
550
554
|
flexDirection: 'row',
|
|
551
|
-
flexGrow: 0,
|
|
555
|
+
flexGrow: '0',
|
|
552
556
|
/** For alignContent to have an effect */ flexWrap: 'wrap',
|
|
553
557
|
/* backgroundColor: '#EEFFEE' */
|
|
554
558
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-lines-per-function */
|
|
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';
|
|
@@ -49,6 +50,24 @@ export function getMacromoleculeColumnPropertyPanel(col: DG.Column): DG.Widget {
|
|
|
49
50
|
tooltipText: `The max length of monomer symbol displayed without shortening, empty to no limit`
|
|
50
51
|
});
|
|
51
52
|
|
|
53
|
+
let fontSize: number | null = (_package.properties ? _package.properties.fontSize : 12);
|
|
54
|
+
if (MmcrTemps.fontSize in col.temp && !!col.temp[MmcrTemps.fontSize] && !isNaN(col.temp[MmcrTemps.fontSize]))
|
|
55
|
+
fontSize = col.temp[MmcrTemps.fontSize];
|
|
56
|
+
|
|
57
|
+
const fontSizeInput = ui.input.int('Font Size', {
|
|
58
|
+
value: fontSize!,
|
|
59
|
+
nullable: true, min: 1, max: 50, step: 1,
|
|
60
|
+
onValueChanged: (value) => {
|
|
61
|
+
if (value && value > 0) {
|
|
62
|
+
const newValue = value ?? 12;
|
|
63
|
+
col.temp[MmcrTemps.fontSize] = newValue;
|
|
64
|
+
col.temp[MmcrTemps.rendererSettingsChanged] = rendererSettingsChangedState.true;
|
|
65
|
+
col.dataFrame.fireValuesChanged();
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
tooltipText: `The font size of monomer symbol in sequence renderer`
|
|
69
|
+
});
|
|
70
|
+
|
|
52
71
|
const gapLengthInput = ui.input.int('Monomer Margin', {
|
|
53
72
|
value: col.temp[MmcrTemps.gapLength] ?? 0,
|
|
54
73
|
onValueChanged: (value) => {
|
|
@@ -88,7 +107,8 @@ export function getMacromoleculeColumnPropertyPanel(col: DG.Column): DG.Widget {
|
|
|
88
107
|
tooltipText: 'When on, all sequences get rendered in the "diff" mode'
|
|
89
108
|
});
|
|
90
109
|
|
|
91
|
-
const
|
|
110
|
+
const sequenceConfigInputs = ui.inputs([
|
|
111
|
+
fontSizeInput,
|
|
92
112
|
maxMonomerLengthInput,
|
|
93
113
|
gapLengthInput,
|
|
94
114
|
referenceSequenceInput,
|
|
@@ -96,7 +116,7 @@ export function getMacromoleculeColumnPropertyPanel(col: DG.Column): DG.Widget {
|
|
|
96
116
|
compareWithCurrentInput,
|
|
97
117
|
]);
|
|
98
118
|
|
|
99
|
-
return new DG.Widget(
|
|
119
|
+
return new DG.Widget(sequenceConfigInputs);
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
/**
|
package/src/utils/docker.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import * as grok from 'datagrok-api/grok';
|
|
2
|
-
import * as ui from 'datagrok-api/ui';
|
|
3
|
-
import * as DG from 'datagrok-api/dg';
|
|
4
|
-
|
|
5
|
-
import {delay} from '@datagrok-libraries/utils/src/test';
|
|
6
|
-
|
|
7
|
-
import {Pepsea} from './pepsea';
|
|
8
|
-
|
|
9
|
-
/** Waits if container is not started
|
|
10
|
-
* @param {number} ms - time to wait in milliseconds */
|
|
11
|
-
export async function awaitContainerStart(ms: number = 60000): Promise<void> {
|
|
12
|
-
const dc = await Pepsea.getDockerContainer();
|
|
13
|
-
await startDockerContainer(dc, ms);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function startDockerContainer(argDc: DG.DockerContainer, timeout: number = 60000): Promise<void> {
|
|
17
|
-
// argDc contains current container status
|
|
18
|
-
let dc: DG.DockerContainer | null = argDc;
|
|
19
|
-
let end: boolean = false;
|
|
20
|
-
for (let i = 0; i < timeout / 200; ++i) {
|
|
21
|
-
if (dc === null) dc = await grok.dapi.docker.dockerContainers.find(argDc.id);
|
|
22
|
-
|
|
23
|
-
if (isStarted(dc)) {
|
|
24
|
-
end = true;
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
switch (dc.status) {
|
|
29
|
-
case 'stopped': {
|
|
30
|
-
// TODO: Use the new dockerContainers API
|
|
31
|
-
await grok.dapi.docker.dockerContainers.run(dc.id);
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
case 'pending change':
|
|
35
|
-
case 'changing': {
|
|
36
|
-
// skip to wait
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
case 'error': {
|
|
40
|
-
throw new Error('Docker container error state.');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
dc = null;
|
|
44
|
-
await delay(200);
|
|
45
|
-
}
|
|
46
|
-
if (!end) throw new Error('Docker container start timeout.');
|
|
47
|
-
// this.dc = await grok.dapi.docker.dockerContainers.find(dcId);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function isStarted(dc: DG.DockerContainer): boolean {
|
|
51
|
-
return dc.status === 'checking' || dc.status === 'started';
|
|
52
|
-
}
|