@datagrok/bio 2.16.9 → 2.17.1

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
@@ -2,10 +2,10 @@
2
2
  "name": "@datagrok/bio",
3
3
  "friendlyName": "Bio",
4
4
  "author": {
5
- "name": "Aleksandr Tanas",
6
- "email": "atanas@datagrok.ai"
5
+ "name": "Leonid Stolbov",
6
+ "email": "lstolbov@datagrok.ai"
7
7
  },
8
- "version": "2.16.9",
8
+ "version": "2.17.1",
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",
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "@biowasm/aioli": "^3.1.0",
40
- "@datagrok-libraries/bio": "^5.45.7",
40
+ "@datagrok-libraries/bio": "^5.45.11",
41
41
  "@datagrok-libraries/chem-meta": "^1.2.7",
42
42
  "@datagrok-libraries/math": "^1.2.2",
43
43
  "@datagrok-libraries/ml": "^6.7.4",
@@ -62,7 +62,7 @@
62
62
  "@datagrok-libraries/js-draw-lite": "^0.0.10",
63
63
  "@datagrok/chem": "^1.12.3",
64
64
  "@datagrok/dendrogram": "^1.2.33",
65
- "@datagrok/helm": "^2.5.3",
65
+ "@datagrok/helm": "^2.5.10",
66
66
  "@types/node": "^17.0.24",
67
67
  "@types/wu": "^2.1.44",
68
68
  "@typescript-eslint/eslint-plugin": "^8.8.1",
package/src/package.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint-disable max-params */
2
+ /* eslint-disable max-len */
1
3
  /* eslint max-lines: "off" */
2
4
  import * as grok from 'datagrok-api/grok';
3
5
  import * as ui from 'datagrok-api/ui';
@@ -9,7 +11,7 @@ import {getActivityCliffs} from '@datagrok-libraries/ml/src/viewers/activity-cli
9
11
  import {MmDistanceFunctionsNames} from '@datagrok-libraries/ml/src/macromolecule-distance-functions';
10
12
  import {BitArrayMetrics, KnownMetrics} from '@datagrok-libraries/ml/src/typed-metrics';
11
13
  import {NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
12
- import {IMonomerLib, IMonomerLibBase, IMonomerSet} from '@datagrok-libraries/bio/src/types';
14
+ import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
13
15
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
14
16
  import {FastaFileHandler} from '@datagrok-libraries/bio/src/utils/fasta-handler';
15
17
  import {SCORE} from '@datagrok-libraries/bio/src/utils/macromolecule/scoring';
@@ -24,14 +26,13 @@ import {ITSNEOptions, IUMAPOptions} from '@datagrok-libraries/ml/src/multi-colum
24
26
  import {generateLongSequence, generateLongSequence2} from '@datagrok-libraries/bio/src/utils/generator';
25
27
  import {getUserLibSettings, setUserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
26
28
  import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
27
- import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
29
+ import {RDModule as _RDMoule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
28
30
  import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
29
31
  import {ISeqHandler} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
30
32
  import {MmcrTemps} from '@datagrok-libraries/bio/src/utils/cell-renderer-consts';
31
33
 
32
34
  import {getMacromoleculeColumns} from './utils/ui-utils';
33
35
  import {MacromoleculeDifferenceCellRenderer, MacromoleculeSequenceCellRenderer,} from './utils/cell-renderer';
34
- import {MacromoleculeCustomCellRenderer} from './utils/cell-renderer-custom';
35
36
  import {VdRegionsViewer} from './viewers/vd-regions-viewer';
36
37
  import {SequenceAlignment} from './seq_align';
37
38
  import {getEncodedSeqSpaceCol} from './analysis/sequence-space';
@@ -318,7 +319,7 @@ export function SeqActivityCliffsEditor(call: DG.FuncCall) {
318
319
  //meta.columnTags: quality=Macromolecule, units=custom
319
320
  //output: grid_cell_renderer result
320
321
  export function customSequenceCellRenderer(): DG.GridCellRenderer {
321
- return new MacromoleculeCustomCellRenderer();
322
+ return new MacromoleculeSequenceCellRenderer();
322
323
  }
323
324
 
324
325
  //name: fastaSequenceCellRenderer
@@ -764,7 +765,7 @@ export function convertDialog() {
764
765
  export async function convertSeqNotation(sequence: string, targetNotation: NOTATION, separator?: string): Promise<string | undefined | null> {
765
766
  try {
766
767
  const col = DG.Column.fromStrings('sequence', [sequence]);
767
- const df = DG.DataFrame.fromColumns([col]);
768
+ const _df = DG.DataFrame.fromColumns([col]);
768
769
  const semType = await grok.functions.call('Bio:detectMacromolecule', {col: col});
769
770
  if (semType)
770
771
  col.semType = semType;
@@ -44,7 +44,7 @@ category('activityCliffs', async () => {
44
44
  test('activityCliffsOpens', async () => {
45
45
  const testData = !DG.Test.isInBenchmark ?
46
46
  {fileName: 'tests/100_3_clustests.csv', tgt: {cliffCount: 3}} :
47
- {fileName: 'tests/peptides_motif-with-random_10000.csv', tgt: {cliffCount: 53}};
47
+ {fileName: 'tests/peptides_with_random_motif_1600.csv', tgt: {cliffCount: 64}};
48
48
  const actCliffsDf = await readDataframe(testData.fileName);
49
49
  const actCliffsTableView = grok.shell.addTableView(actCliffsDf);
50
50
 
@@ -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: 60000 /* docker */});
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: 60000 /* docker */});
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';
@@ -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);
@@ -16,7 +16,7 @@ category('sequenceSpace', async () => {
16
16
  test('sequenceSpaceOpens', async () => {
17
17
  const testData = !DG.Test.isInBenchmark ?
18
18
  {fileName: 'tests/100_3_clustests.csv'} :
19
- {fileName: 'tests/peptides_motif-with-random_10000.csv'};
19
+ {fileName: 'tests/peptides_with_random_motif_1600.csv'};
20
20
  testFastaDf = await readDataframe(testData.fileName);
21
21
  testFastaTableView = grok.shell.addTableView(testFastaDf);
22
22
  await _testSequenceSpaceReturnsResult(testFastaDf, DimReductionMethods.UMAP, 'sequence');
@@ -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
 
@@ -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
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-len */
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';
@@ -8,6 +9,7 @@ import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
8
9
 
9
10
  import {_package} from '../package';
10
11
 
12
+ /// NB! UNUSED LEGACY CODE
11
13
  export class MacromoleculeCustomCellRenderer extends DG.GridCellRenderer {
12
14
  private readonly seqHelper: ISeqHelper;
13
15
 
@@ -41,7 +43,7 @@ export class MacromoleculeCustomCellRenderer extends DG.GridCellRenderer {
41
43
  back?.onMouseEnter(gridCell, e);
42
44
  }
43
45
 
44
- override onMouseLeave(gridCell: DG.GridCell, e: MouseEvent) {
46
+ override onMouseLeave(_gridCell: DG.GridCell, _e: MouseEvent) {
45
47
  // TODO: We get gridCell from another column here, so we can not get back object from the column rendered.
46
48
  ui.tooltip.hide();
47
49
  // const back = this.getRendererBack(gridCell);
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-len */
1
2
  /* eslint-disable max-lines */
2
3
  /* eslint-disable max-params */
3
4
  /* eslint-disable max-lines-per-function */
@@ -22,7 +23,7 @@ import {getSplitter} from '@datagrok-libraries/bio/src/utils/macromolecule/utils
22
23
  import {IMonomerLibBase} from '@datagrok-libraries/bio/src/types';
23
24
  import {GapOriginals} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
24
25
  import {execMonomerHoverLinks} from '@datagrok-libraries/bio/src/monomer-works/monomer-hover';
25
- import {getGridCellColTemp} from '@datagrok-libraries/bio/src/utils/cell-renderer-back-base';
26
+ import {CellRendererBackBase, getGridCellColTemp} from '@datagrok-libraries/bio/src/utils/cell-renderer-back-base';
26
27
  import {HelmTypes} from '@datagrok-libraries/bio/src/helm/consts';
27
28
  import {MmcrTemps, rendererSettingsChangedState, tempTAGS} from '@datagrok-libraries/bio/src/utils/cell-renderer-consts';
28
29
 
@@ -53,6 +54,7 @@ export function processSequence(subParts: string[]): [string[], boolean] {
53
54
  return [text, simplified];
54
55
  }
55
56
 
57
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
56
58
  type RendererGridCellTemp = {
57
59
  [MmcrTemps.monomerPlacer]: MonomerPlacer
58
60
  }
@@ -73,21 +75,58 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
73
75
  this.seqHelper = _package.seqHelper;
74
76
  }
75
77
 
76
- onClick(gridCell: DG.GridCell, _e: MouseEvent): void {
78
+ getRendererBack(gridCell: DG.GridCell): CellRendererBackBase<string> | null {
79
+ const [gridCol, tableCol, _temp] = getGridCellColTemp<string, any>(gridCell);
80
+ if (tableCol.meta.units !== NOTATION.CUSTOM)
81
+ return _temp.rendererBack;
82
+ let back: CellRendererBackBase<string> | null = null;
83
+
84
+ if (this.seqHelper) {
85
+ const sh = this.seqHelper.getSeqHandler(tableCol);
86
+ back = sh.getRendererBack(gridCol, tableCol);
87
+ }
88
+ return back;
89
+ }
90
+
91
+ override onClick(gridCell: DG.GridCell, _e: MouseEvent): void {
77
92
  const colTemp: TempType = gridCell.cell.column.temp;
78
93
  colTemp[tempTAGS.currentWord] = gridCell.cell.value;
79
94
  gridCell.grid.invalidate();
95
+ if (gridCell.cell.column.meta.units === NOTATION.CUSTOM) {
96
+ const back = this.getRendererBack(gridCell);
97
+ back?.onClick(gridCell, _e);
98
+ }
99
+ }
100
+
101
+ override onMouseEnter(gridCell: DG.GridCell, e: MouseEvent) {
102
+ const back = this.getRendererBack(gridCell);
103
+ back?.onMouseEnter(gridCell, e);
80
104
  }
81
105
 
82
106
  override onMouseMove(gridCell: DG.GridCell, e: MouseEvent): void {
83
- const [gridCol, tableCol, temp] = getGridCellColTemp<string, MonomerPlacer>(gridCell);
84
- temp.rendererBack?.onMouseMove(gridCell, e);
107
+ const back = this.getRendererBack(gridCell);
108
+ back?.onMouseMove(gridCell, e);
85
109
  }
86
110
 
87
- override onMouseLeave(gridCell: DG.GridCell, e: MouseEvent) {
111
+ override onMouseLeave(gridCell: DG.GridCell, _e: MouseEvent) {
88
112
  execMonomerHoverLinks(gridCell, null);
89
113
  }
90
114
 
115
+ override onDoubleClick(gridCell: DG.GridCell, e: MouseEvent) {
116
+ const back = this.getRendererBack(gridCell);
117
+ back?.onDoubleClick(gridCell, e);
118
+ }
119
+
120
+ override onKeyDown(gridCell: DG.GridCell, e: KeyboardEvent) {
121
+ const back = this.getRendererBack(gridCell);
122
+ back?.onKeyDown(gridCell, e);
123
+ }
124
+
125
+ override onKeyPress(gridCell: DG.GridCell, e: KeyboardEvent) {
126
+ const back = this.getRendererBack(gridCell);
127
+ back?.onKeyPress(gridCell, e);
128
+ }
129
+
91
130
  /**
92
131
  * Cell renderer function.
93
132
  *
@@ -99,11 +138,11 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
99
138
  * @param {DG.GridCell} gridCell Grid cell.
100
139
  * @param {DG.GridCellStyle} _cellStyle Cell style.
101
140
  */
102
- render(
141
+ renderInt(
103
142
  g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, gridCell: DG.GridCell,
104
143
  _cellStyle: DG.GridCellStyle
105
144
  ): void {
106
- const logPrefix: string = 'MacromoleculeSequenceCellRenderer.render()';
145
+ const _logPrefix: string = 'MacromoleculeSequenceCellRenderer.render()';
107
146
 
108
147
  const [gridCol, tableCol, temp] =
109
148
  getGridCellColTemp<string, MonomerPlacer>(gridCell);
@@ -111,7 +150,7 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
111
150
  const tableColTemp: TempType = tableCol.temp;
112
151
  const sh = this.seqHelper.getSeqHandler(tableCol);
113
152
 
114
- let gapLength = 0;
153
+ const gapLength = 0;
115
154
  const msaGapLength = 8;
116
155
 
117
156
  // Cell renderer settings
@@ -140,6 +179,15 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
140
179
 
141
180
  seqColTemp.render(g, x, y, w, h, gridCell, _cellStyle);
142
181
  }
182
+
183
+ override render(g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, gridCell: DG.GridCell, cellStyle: DG.GridCellStyle): void {
184
+ if (gridCell.cell.column?.meta?.units === NOTATION.CUSTOM) {
185
+ const back = this.getRendererBack(gridCell);
186
+ back?.render(g, x, y, w, h, gridCell, cellStyle);
187
+ return;
188
+ }
189
+ this.renderInt(g, x, y, w, h, gridCell, cellStyle);
190
+ }
143
191
  }
144
192
 
145
193
  export class MacromoleculeDifferenceCellRendererBack extends CellRendererWithMonomerLibBackBase {
@@ -151,7 +199,7 @@ export class MacromoleculeDifferenceCellRendererBack extends CellRendererWithMon
151
199
  }
152
200
 
153
201
  render(g: CanvasRenderingContext2D,
154
- x: number, y: number, w: number, h: number, gridCell: DG.GridCell, cellStyle: DG.GridCellStyle
202
+ x: number, y: number, w: number, h: number, gridCell: DG.GridCell, _cellStyle: DG.GridCellStyle
155
203
  ): void {
156
204
  const dpr = window.devicePixelRatio;
157
205
  const grid = gridCell.grid;
@@ -172,7 +220,7 @@ export class MacromoleculeDifferenceCellRendererBack extends CellRendererWithMon
172
220
  drawMoleculeDifferenceOnCanvas(g, x, y, w, h, subParts1, subParts2, biotype, this.monomerLib, undefined, undefined);
173
221
  }
174
222
 
175
- async awaitRendered(timeout: number = 10000, reason: string = `${timeout} timeout`): Promise<void> {
223
+ async awaitRendered(timeout: number = 10000, _reason: string = `${timeout} timeout`): Promise<void> {
176
224
  return Promise.resolve();
177
225
  }
178
226
 
@@ -258,15 +306,15 @@ export function drawMoleculeDifferenceOnCanvas(
258
306
  const amino2 = subParts2[i];
259
307
 
260
308
  let color1 = undefinedColor;
261
- if (monomerLib) {
309
+ if (monomerLib)
262
310
  color1 = monomerLib.getMonomerTextColor(biotype, amino1);
263
- }
311
+
264
312
 
265
313
  if (amino1 != amino2) {
266
314
  let color2 = undefinedColor;
267
- if (monomerLib) {
315
+ if (monomerLib)
268
316
  color2 = monomerLib.getMonomerTextColor(biotype, amino2);
269
- }
317
+
270
318
  const subX0 = printLeftOrCentered(g, amino1, updatedX, updatedY - vShift, w, h,
271
319
  {color: color1, pivot: 0, left: true});
272
320
  const subX1 = printLeftOrCentered(g, amino2, updatedX, updatedY + vShift, w, h,
@@ -4,5 +4,7 @@
4
4
  display: flex;
5
5
  flex-direction: column;
6
6
  align-items: center;
7
+ min-width: 220px;
7
8
  width: 100%;
9
+
8
10
  }
@@ -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.form(libraryControls, undefined, false);
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(async () => {
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
- {style: {
51
- alignItems: 'center',
52
- width: '100%',
53
- overflow: 'hidden',
54
- visibility: 'visible',
55
- }, classes: 'duplicate-monomer-symbol-row'}
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
- this.libInput && ((this.libInput as DG.ChoiceInput<string>).items = availableMonLibs);
223
- libName && (this.libInput.value = libName);
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
- this.rgroupsGrid.items = rGroupItems;
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
  };
@@ -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)