@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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "cpu": 1,
3
+ "memory": 1024,
4
+ "on_demand": true,
5
+ "timeout_minutes": 30
6
+ }
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.17.0",
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.45.7",
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.3",
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",
@@ -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);
@@ -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';
@@ -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: 185}, tgt: {pos: 7}},
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
- monomerCharWidth: charWidth,
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
- monomerCharWidth: charWidth,
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
 
@@ -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
- monomerCharWidth: 7, separatorWidth: !sh.isMsa() ? gapLength : msaGapLength,
173
+ font: font, fontCharWidth: fontWidth, separatorWidth: !sh.isMsa() ? gapLength : msaGapLength,
173
174
  monomerToShort: monomerToShortFunction,
174
175
  };
175
176
  });
@@ -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)
@@ -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 rdKitInputs = ui.inputs([
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(rdKitInputs);
119
+ return new DG.Widget(sequenceConfigInputs);
100
120
  }
101
121
 
102
122
  /**
@@ -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
- }