@datagrok/bio 2.4.31 → 2.4.40

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.
Files changed (100) hide show
  1. package/.eslintrc.json +6 -8
  2. package/README.md +22 -7
  3. package/css/msa.css +3 -0
  4. package/detectors.js +21 -12
  5. package/dist/1.js +2 -0
  6. package/dist/1.js.map +1 -0
  7. package/dist/18.js +2 -0
  8. package/dist/18.js.map +1 -0
  9. package/dist/190.js +2 -0
  10. package/dist/190.js.map +1 -0
  11. package/dist/452.js +2 -0
  12. package/dist/452.js.map +1 -0
  13. package/dist/729.js +2 -0
  14. package/dist/729.js.map +1 -0
  15. package/dist/package-test.js +1 -1
  16. package/dist/package-test.js.map +1 -1
  17. package/dist/package.js +1 -1
  18. package/dist/package.js.map +1 -1
  19. package/files/libraries/broken-lib.sdf +136 -0
  20. package/files/libraries/group1/mock-lib-3.json +74 -0
  21. package/files/libraries/mock-lib-2.json +48 -0
  22. package/files/tests/100_3_clustests.csv +100 -0
  23. package/files/tests/100_3_clustests_empty_vals.csv +100 -0
  24. package/files/tests/peptides_motif-with-random_10000.csv +9998 -0
  25. package/package.json +4 -4
  26. package/scripts/sequence_generator.py +164 -48
  27. package/src/analysis/sequence-activity-cliffs.ts +7 -9
  28. package/src/analysis/sequence-diversity-viewer.ts +8 -3
  29. package/src/analysis/sequence-search-base-viewer.ts +4 -3
  30. package/src/analysis/sequence-similarity-viewer.ts +13 -7
  31. package/src/analysis/sequence-space.ts +15 -12
  32. package/src/analysis/workers/mm-distance-array-service.ts +48 -0
  33. package/src/analysis/workers/mm-distance-array-worker.ts +29 -0
  34. package/src/analysis/workers/mm-distance-worker-creator.ts +6 -9
  35. package/src/apps/web-logo-app.ts +34 -0
  36. package/src/calculations/monomerLevelMols.ts +10 -12
  37. package/src/demo/bio01-similarity-diversity.ts +4 -5
  38. package/src/demo/bio01a-hierarchical-clustering-and-sequence-space.ts +6 -7
  39. package/src/demo/bio01b-hierarchical-clustering-and-activity-cliffs.ts +7 -8
  40. package/src/demo/bio03-atomic-level.ts +1 -4
  41. package/src/demo/bio05-helm-msa-sequence-space.ts +6 -4
  42. package/src/demo/utils.ts +3 -4
  43. package/src/package-test.ts +1 -2
  44. package/src/package.ts +135 -82
  45. package/src/seq_align.ts +482 -483
  46. package/src/substructure-search/substructure-search.ts +3 -3
  47. package/src/tests/Palettes-test.ts +1 -1
  48. package/src/tests/WebLogo-positions-test.ts +12 -35
  49. package/src/tests/_first-tests.ts +1 -1
  50. package/src/tests/activity-cliffs-tests.ts +10 -7
  51. package/src/tests/activity-cliffs-utils.ts +6 -5
  52. package/src/tests/bio-tests.ts +20 -25
  53. package/src/tests/checkInputColumn-tests.ts +5 -11
  54. package/src/tests/converters-test.ts +19 -37
  55. package/src/tests/detectors-benchmark-tests.ts +35 -37
  56. package/src/tests/detectors-tests.ts +29 -34
  57. package/src/tests/detectors-weak-and-likely-tests.ts +11 -21
  58. package/src/tests/fasta-export-tests.ts +3 -3
  59. package/src/tests/fasta-handler-test.ts +2 -3
  60. package/src/tests/lib-tests.ts +2 -4
  61. package/src/tests/mm-distance-tests.ts +25 -17
  62. package/src/tests/monomer-libraries-tests.ts +1 -1
  63. package/src/tests/msa-tests.ts +12 -9
  64. package/src/tests/pepsea-tests.ts +6 -3
  65. package/src/tests/renderers-test.ts +13 -11
  66. package/src/tests/sequence-space-test.ts +10 -8
  67. package/src/tests/sequence-space-utils.ts +6 -4
  68. package/src/tests/similarity-diversity-tests.ts +47 -61
  69. package/src/tests/splitters-test.ts +14 -20
  70. package/src/tests/to-atomic-level-tests.ts +9 -17
  71. package/src/tests/units-handler-splitted-tests.ts +106 -0
  72. package/src/tests/units-handler-tests.ts +22 -26
  73. package/src/tests/utils/sequences-generators.ts +6 -2
  74. package/src/tests/utils.ts +10 -4
  75. package/src/tests/viewers.ts +1 -1
  76. package/src/utils/atomic-works.ts +49 -57
  77. package/src/utils/cell-renderer.ts +25 -8
  78. package/src/utils/check-input-column.ts +19 -4
  79. package/src/utils/constants.ts +3 -3
  80. package/src/utils/convert.ts +56 -23
  81. package/src/utils/monomer-lib.ts +83 -64
  82. package/src/utils/multiple-sequence-alignment-ui.ts +35 -21
  83. package/src/utils/multiple-sequence-alignment.ts +2 -2
  84. package/src/utils/pepsea.ts +17 -7
  85. package/src/utils/save-as-fasta.ts +11 -4
  86. package/src/utils/ui-utils.ts +1 -1
  87. package/src/viewers/vd-regions-viewer.ts +21 -22
  88. package/src/viewers/web-logo-viewer.ts +189 -154
  89. package/src/widgets/bio-substructure-filter.ts +9 -6
  90. package/src/widgets/representations.ts +11 -12
  91. package/tsconfig.json +1 -1
  92. package/dist/258.js +0 -2
  93. package/dist/258.js.map +0 -1
  94. package/dist/457.js +0 -2
  95. package/dist/457.js.map +0 -1
  96. package/dist/562.js +0 -2
  97. package/dist/562.js.map +0 -1
  98. package/dist/925.js +0 -2
  99. package/dist/925.js.map +0 -1
  100. package/src/analysis/workers/mm-distance-worker.ts +0 -16
@@ -6,10 +6,17 @@ import {Observable, Subject} from 'rxjs';
6
6
  import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/index';
7
7
  import {
8
8
  createJsonMonomerLibFromSdf,
9
- expectedMonomerData,
10
- IMonomerLibHelper
9
+ IMonomerLibHelper,
11
10
  } from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
11
+ import {HELM_REQUIRED_FIELDS as REQ, HELM_OPTIONAL_FIELDS as OPT} from '@datagrok-libraries/bio/src/utils/const';
12
+ import {_package} from '../package';
12
13
 
14
+ const _HELM_REQUIRED_FIELDS_ARRAY = [
15
+ REQ.SYMBOL, REQ.NAME, REQ.MOLFILE, REQ.AUTHOR, REQ.ID,
16
+ REQ.RGROUPS, REQ.SMILES, REQ.POLYMER_TYPE, REQ.MONOMER_TYPE, REQ.CREATE_DATE,
17
+ ] as const;
18
+
19
+ const _HELM_OPTIONAL_FIELDS_ARRAY = [OPT.NATURAL_ANALOG, OPT.META] as const;
13
20
  // -- Monomer libraries --
14
21
  export const LIB_STORAGE_NAME = 'Libraries';
15
22
  export const LIB_PATH = 'System:AppData/Bio/libraries/';
@@ -21,8 +28,12 @@ export type LibSettings = {
21
28
  }
22
29
 
23
30
  export async function getLibFileNameList(): Promise<string[]> {
24
- const res: string[] = (await grok.dapi.files.list(`${LIB_PATH}`, false, ''))
25
- .map((it) => it.fileName);
31
+ // list files recursively because permissions are available for folders only
32
+ const res: string[] = await Promise.all((await grok.dapi.files.list(LIB_PATH, true, ''))
33
+ .map(async (it) => {
34
+ // Get relative path (to LIB_PATH)
35
+ return it.fullPath.substring(LIB_PATH.length);
36
+ }));
26
37
  return res;
27
38
  }
28
39
 
@@ -41,36 +52,39 @@ export async function setUserLibSetting(value: LibSettings): Promise<void> {
41
52
  }
42
53
 
43
54
  export class MonomerLib implements IMonomerLib {
44
- private _monomers: { [type: string]: { [name: string]: Monomer } } = {};
55
+ public readonly error: string | undefined;
56
+
57
+ private _monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } } = {};
45
58
  private _onChanged = new Subject<any>();
46
59
 
47
- constructor(monomers: { [type: string]: { [name: string]: Monomer } }) {
60
+ constructor(monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } }, error?: string) {
48
61
  this._monomers = monomers;
62
+ this.error = error;
49
63
  }
50
64
 
51
- getMonomer(monomerType: string, monomerName: string): Monomer | null {
52
- if (monomerType in this._monomers! && monomerName in this._monomers![monomerType])
53
- return this._monomers![monomerType][monomerName];
65
+ getMonomer(polymerType: string, monomerSymbol: string): Monomer | null {
66
+ if (polymerType in this._monomers! && monomerSymbol in this._monomers![polymerType])
67
+ return this._monomers![polymerType][monomerSymbol];
54
68
  else
55
69
  return null;
56
70
  }
57
71
 
58
- getTypes(): string[] {
72
+ getPolymerTypes(): string[] {
59
73
  return Object.keys(this._monomers);
60
74
  }
61
75
 
62
- getMonomerMolsByType(type: string): { [symbol: string]: string } {
63
- const res: { [symbol: string]: string } = {};
76
+ getMonomerMolsByPolymerType(polymerType: string): { [monomerSymbol: string]: string } {
77
+ const res: { [monomerSymbol: string]: string } = {};
64
78
 
65
- Object.keys(this._monomers[type]).forEach((monomerSymbol) => {
66
- res[monomerSymbol] = this._monomers[type][monomerSymbol].molfile;
79
+ Object.keys(this._monomers[polymerType]).forEach((monomerSymbol) => {
80
+ res[monomerSymbol] = this._monomers[polymerType][monomerSymbol].molfile;
67
81
  });
68
82
 
69
83
  return res;
70
84
  }
71
85
 
72
- getMonomerNamesByType(type: string): string[] {
73
- return Object.keys(this._monomers[type]);
86
+ getMonomerSymbolsByType(polymerType: string): string[] {
87
+ return Object.keys(this._monomers[polymerType]);
74
88
  }
75
89
 
76
90
  get onChanged(): Observable<any> {
@@ -78,8 +92,8 @@ export class MonomerLib implements IMonomerLib {
78
92
  }
79
93
 
80
94
  private _updateInt(lib: IMonomerLib): void {
81
- const typesNew = lib.getTypes();
82
- const types = this.getTypes();
95
+ const typesNew = lib.getPolymerTypes();
96
+ const types = this.getPolymerTypes();
83
97
 
84
98
  typesNew.forEach((type) => {
85
99
  //could possibly rewrite -> TODO: check duplicated monomer symbol
@@ -87,9 +101,9 @@ export class MonomerLib implements IMonomerLib {
87
101
  if (!types.includes(type))
88
102
  this._monomers![type] = {};
89
103
 
90
- const monomers = lib.getMonomerNamesByType(type);
91
- monomers.forEach((monomerName) => {
92
- this._monomers[type][monomerName] = lib.getMonomer(type, monomerName)!;
104
+ const monomers = lib.getMonomerSymbolsByType(type);
105
+ monomers.forEach((monomerSymbol) => {
106
+ this._monomers[type][monomerSymbol] = lib.getMonomer(type, monomerSymbol)!;
93
107
  });
94
108
  });
95
109
  }
@@ -101,7 +115,7 @@ export class MonomerLib implements IMonomerLib {
101
115
 
102
116
  public updateLibs(libList: IMonomerLib[], reload: boolean = false): void {
103
117
  if (reload) this._monomers = {};
104
- for (const lib of libList) this._updateInt(lib);
118
+ for (const lib of libList) if (!lib.error) this._updateInt(lib);
105
119
  this._onChanged.next();
106
120
  }
107
121
 
@@ -117,35 +131,58 @@ export class MonomerLibHelper implements IMonomerLibHelper {
117
131
  /** Protect constructor to prevent multiple instantiation. */
118
132
  protected constructor() {}
119
133
 
120
- /** Singleton monomer library */
134
+ /** Singleton monomer library
135
+ * @return {MonomerLibHelper} MonomerLibHelper instance
136
+ */
121
137
  getBioLib(): IMonomerLib {
122
138
  return this._monomerLib;
123
139
  }
124
140
 
125
- private loadLibrariesPromise: Promise<void> = Promise.resolve();
141
+ /** Allows syncing with managing settings/loading libraries */
142
+ public loadLibrariesPromise: Promise<void> = Promise.resolve();
126
143
 
127
144
  /** Loads libraries based on settings in user storage {@link LIB_STORAGE_NAME}
128
145
  * @param {boolean} reload Clean {@link monomerLib} before load libraries [false]
129
146
  */
130
147
  async loadLibraries(reload: boolean = false): Promise<void> {
131
148
  return this.loadLibrariesPromise = this.loadLibrariesPromise.then(async () => {
132
- const [libFileNameList, settings]: [string[], LibSettings] = await Promise.all([
133
- getLibFileNameList(),
134
- getUserLibSettings()
135
- ]);
136
- const libs: IMonomerLib[] = await Promise.all(libFileNameList
137
- .filter((libFileName) => !settings.exclude.includes(libFileName))
138
- .map((libFileName) => {
139
- //TODO handle whether files are in place
140
- return this.readLibrary(LIB_PATH, libFileName);
141
- }));
142
- this._monomerLib.updateLibs(libs, reload);
149
+ // This function is not allowed to throw any exception,
150
+ // because it will prevent further handling monomer library settings
151
+ // through blocking this.loadLibrariesPromise
152
+ try {
153
+ const [libFileNameList, settings]: [string[], LibSettings] = await Promise.all([
154
+ getLibFileNameList(),
155
+ getUserLibSettings(),
156
+ ]);
157
+ const libs: IMonomerLib[] = await Promise.all(libFileNameList
158
+ .filter((libFileName) => !settings.exclude.includes(libFileName))
159
+ .map((libFileName) => {
160
+ //TODO handle whether files are in place
161
+ return this.readLibrary(LIB_PATH, libFileName).catch((err: any) => {
162
+ const errMsg: string = `Loading monomers from '${libFileName}' error: ` +
163
+ `${err instanceof Error ? err.message : err.toString()}`;
164
+ return new MonomerLib({}, errMsg);
165
+ });
166
+ }));
167
+ this._monomerLib.updateLibs(libs, reload);
168
+ } catch (err: any) {
169
+ const errMsg: string = 'Loading monomer libraries error: ' +
170
+ `${err instanceof Error ? err.message : err.toString()}`;
171
+ grok.shell.warning(errMsg);
172
+
173
+ const errStack = err instanceof Error ? err.stack : undefined;
174
+ _package.logger.error(errMsg, undefined, errStack);
175
+ }
143
176
  });
144
177
  }
145
178
 
146
- /** Reads library from file shares, handles .json and .sdf */
179
+ /** Reads library from file shares, handles .json and .sdf
180
+ * @param {string} path Path to library file
181
+ * @param {string} fileName Name of library file
182
+ * @return {Promise<IMonomerLib>} Promise of IMonomerLib
183
+ */
147
184
  async readLibrary(path: string, fileName: string): Promise<IMonomerLib> {
148
- let data: any[] = [];
185
+ let rawLibData: any[] = [];
149
186
  let file;
150
187
  let dfSdf;
151
188
  const fileSource = new DG.FileSource(path);
@@ -154,41 +191,23 @@ export class MonomerLibHelper implements IMonomerLibHelper {
154
191
  if (funcList.length === 1) {
155
192
  file = await fileSource.readAsBytes(fileName);
156
193
  dfSdf = await grok.functions.call('Chem:importSdf', {bytes: file});
157
- data = createJsonMonomerLibFromSdf(dfSdf[0]);
194
+ rawLibData = createJsonMonomerLibFromSdf(dfSdf[0]);
158
195
  } else {
159
196
  grok.shell.warning('Chem package is not installed');
160
197
  }
161
198
  } else {
162
199
  const file = await fileSource.readAsText(fileName);
163
- data = JSON.parse(file);
200
+ rawLibData = JSON.parse(file);
164
201
  }
165
202
 
166
- const monomers: { [type: string]: { [name: string]: Monomer } } = {};
167
- const types: string[] = [];
168
- //group monomers by their type
169
- data.forEach((monomer) => {
170
- const monomerAdd: Monomer = {
171
- 'symbol': monomer['symbol'],
172
- 'name': monomer['name'],
173
- 'naturalAnalog': monomer['naturalAnalog'],
174
- 'molfile': monomer['molfile'],
175
- 'rgroups': monomer['rgroups'],
176
- 'polymerType': monomer['polymerType'],
177
- 'monomerType': monomer['monomerType'],
178
- 'data': {}
179
- };
180
-
181
- Object.keys(monomer).forEach((prop) => {
182
- if (!expectedMonomerData.includes(prop))
183
- monomerAdd.data[prop] = monomer[prop];
184
- });
185
-
186
- if (!types.includes(monomer['polymerType'])) {
187
- monomers[monomer['polymerType']] = {};
188
- types.push(monomer['polymerType']);
203
+ const monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } } = {};
204
+ const polymerTypes: string[] = [];
205
+ rawLibData.forEach((monomer) => {
206
+ if (!polymerTypes.includes(monomer[REQ.POLYMER_TYPE])) {
207
+ monomers[monomer[REQ.POLYMER_TYPE]] = {};
208
+ polymerTypes.push(monomer[REQ.POLYMER_TYPE]);
189
209
  }
190
-
191
- monomers[monomer['polymerType']][monomer['symbol']] = monomerAdd;
210
+ monomers[monomer[REQ.POLYMER_TYPE]][monomer[REQ.SYMBOL]] = monomer as Monomer;
192
211
  });
193
212
 
194
213
  return new MonomerLib(monomers);
@@ -11,6 +11,7 @@ import {_package} from '../package';
11
11
  import {multipleSequenceAlginmentUIOptions} from './types';
12
12
  import {kalignVersion, msaDefaultOptions} from './constants';
13
13
  import '../../css/msa.css';
14
+ import {ColumnInputOptions} from '@datagrok-libraries/utils/src/type-declarations';
14
15
  export class MsaWarning extends Error {
15
16
  constructor(message: string, options?: ErrorOptions) {
16
17
  super(message, options);
@@ -18,7 +19,7 @@ export class MsaWarning extends Error {
18
19
  }
19
20
 
20
21
  export async function multipleSequenceAlignmentUI(
21
- options: multipleSequenceAlginmentUIOptions = {}
22
+ options: multipleSequenceAlginmentUIOptions = {},
22
23
  ): Promise<DG.Column> {
23
24
  return new Promise(async (resolve, reject) => {
24
25
  options.clustersCol ??= null;
@@ -50,18 +51,27 @@ export async function multipleSequenceAlignmentUI(
50
51
  const gapExtendInput = ui.floatInput('Gap extend', options.pepsea.gapExtend);
51
52
  gapExtendInput.setTooltip('Gap extension penalty to skip the alignment');
52
53
 
54
+ const msaParamsDiv = ui.inputs([gapOpenInput, gapExtendInput, terminalGapInput]);
55
+ const msaParamsButton = ui.button('Alignment parameters', () => {
56
+ msaParamsDiv.hidden = !msaParamsDiv.hidden;
57
+ }, 'Adjust alignment parameters such as penalties for opening and extending gaps');
58
+ msaParamsButton.classList.add('msa-params-button');
59
+ msaParamsDiv.hidden = true;
60
+ msaParamsButton.prepend(ui.icons.settings(() => null));
53
61
  const pepseaInputRootStyles: CSSStyleDeclaration[] = [methodInput.root.style];
54
62
  const kalignInputRootStyles: CSSStyleDeclaration[] = [terminalGapInput.root.style, kalignVersionDiv.style];
55
63
 
56
- let performAlignment: (() => Promise<DG.Column<string>>) | undefined;
64
+ let performAlignment: (() => Promise<DG.Column<string> | null>) | undefined;
57
65
 
58
- // TODO: allow only macromolecule colums to be chosen
66
+ //TODO: remove when the new version of datagrok-api is available
67
+ //TODO: allow only macromolecule colums to be chosen
59
68
  const colInput = ui.columnInput('Sequence', table, seqCol, async () => {
60
69
  performAlignment = await onColInputChange(
61
70
  colInput.value, table, pepseaInputRootStyles, kalignInputRootStyles,
62
- methodInput, clustersColInput, gapOpenInput, gapExtendInput, terminalGapInput
71
+ methodInput, clustersColInput, gapOpenInput, gapExtendInput, terminalGapInput,
63
72
  );
64
- }
73
+ //@ts-ignore
74
+ }, {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE} as ColumnInputOptions
65
75
  ) as DG.InputBase<DG.Column<string>>;
66
76
  colInput.setTooltip('Sequences column to use for alignment');
67
77
  const clustersColInput = ui.columnInput('Clusters', table, options.clustersCol);
@@ -71,19 +81,18 @@ export async function multipleSequenceAlignmentUI(
71
81
  if (options.col) {
72
82
  performAlignment = await onColInputChange(
73
83
  options.col, table, pepseaInputRootStyles, kalignInputRootStyles,
74
- methodInput, clustersColInput, gapOpenInput, gapExtendInput, terminalGapInput
84
+ methodInput, clustersColInput, gapOpenInput, gapExtendInput, terminalGapInput,
75
85
  );
76
86
 
77
87
  await onDialogOk(colInput, table, performAlignment, resolve, reject);
78
88
  return;
79
89
  }
80
- const dlg = ui.dialog('MSA')
90
+ const _dlg = ui.dialog('MSA')
81
91
  .add(colInput)
82
92
  .add(clustersColInput)
83
93
  .add(methodInput)
84
- .add(gapOpenInput)
85
- .add(gapExtendInput)
86
- .add(terminalGapInput)
94
+ .add(msaParamsDiv)
95
+ .add(msaParamsButton)
87
96
  .add(kalignVersionDiv)
88
97
  .onOK(async () => { await onDialogOk(colInput, table, performAlignment, resolve, reject); })
89
98
  .show();
@@ -93,9 +102,9 @@ export async function multipleSequenceAlignmentUI(
93
102
  async function onDialogOk(
94
103
  colInput: DG.InputBase< DG.Column<any>>,
95
104
  table: DG.DataFrame,
96
- performAlignment: (() => Promise<DG.Column<string>>) | undefined,
105
+ performAlignment: (() => Promise<DG.Column<string> | null>) | undefined,
97
106
  resolve: (value: DG.Column<any>) => void,
98
- reject: (reason: any) => void
107
+ reject: (reason: any) => void,
99
108
  ): Promise<void> {
100
109
  let msaCol: DG.Column<string> | null = null;
101
110
  const pi = DG.TaskBarProgressIndicator.create('Analyze for MSA ...');
@@ -107,7 +116,7 @@ async function onDialogOk(
107
116
  throw new Error('Invalid column format');
108
117
  msaCol = await performAlignment(); // progress
109
118
  if (msaCol == null)
110
- return grok.shell.warning('Wrong column format');
119
+ return reject('PepSeA container has not started');
111
120
 
112
121
  table.columns.add(msaCol);
113
122
  await grok.data.detectSemanticTypes(table);
@@ -128,8 +137,8 @@ async function onColInputChange(
128
137
  pepseaInputRootStyles: CSSStyleDeclaration[], kalignInputRootStyles: CSSStyleDeclaration[],
129
138
  methodInput: DG.InputBase<string | null>, clustersColInput: DG.InputBase<DG.Column<any> | null>,
130
139
  gapOpenInput: DG.InputBase<number | null>, gapExtendInput: DG.InputBase<number | null>,
131
- terminalGapInput: DG.InputBase<number | null>
132
- ): Promise<(() => Promise<DG.Column<string>>) | undefined> {
140
+ terminalGapInput: DG.InputBase<number | null>,
141
+ ): Promise<(() => Promise<DG.Column<string> | null>) | undefined> {
133
142
  try {
134
143
  if (col.semType !== DG.SEMTYPE.MACROMOLECULE)
135
144
  return;
@@ -150,25 +159,30 @@ async function onColInputChange(
150
159
  [NOTATION.HELM], [], false)
151
160
  ) { // PepSeA branch - Helm notation or separator notation with unknown alphabets
152
161
  switchDialog(pepseaInputRootStyles, kalignInputRootStyles, 'pepsea');
153
- gapOpenInput.value = msaDefaultOptions.pepsea.gapOpen;
154
- gapExtendInput.value = msaDefaultOptions.pepsea.gapExtend;
162
+ gapOpenInput.value ??= msaDefaultOptions.pepsea.gapOpen;
163
+ gapExtendInput.value ??= msaDefaultOptions.pepsea.gapExtend;
155
164
 
156
165
  return async () => await runPepsea(col, unusedName, methodInput.value!,
157
166
  gapOpenInput.value!, gapExtendInput.value!, clustersColInput.value);
158
167
  } else if (checkInputColumnUI(col, col.name, [NOTATION.SEPARATOR], [ALPHABET.UN], false)) {
159
168
  //if the column is separator with unknown alphabet, it might be helm. check if it can be converted to helm
160
169
  const potentialColNC = new NotationConverter(col);
161
- if (!await potentialColNC.checkHelmCompatibility())
170
+ if (!await potentialColNC.checkHelmCompatibility()) {
171
+ switchDialog(pepseaInputRootStyles, kalignInputRootStyles, 'pepsea');
162
172
  return;
173
+ }
163
174
  const helmCol = potentialColNC.convert(NOTATION.HELM);
164
175
  switchDialog(pepseaInputRootStyles, kalignInputRootStyles, 'pepsea');
165
- gapOpenInput.value = msaDefaultOptions.pepsea.gapOpen;
166
- gapExtendInput.value = msaDefaultOptions.pepsea.gapExtend;
176
+ gapOpenInput.value ??= msaDefaultOptions.pepsea.gapOpen;
177
+ gapExtendInput.value ??= msaDefaultOptions.pepsea.gapExtend;
167
178
  // convert to helm and assign alignment function to PepSea
168
179
 
169
180
  return async () => await runPepsea(helmCol, unusedName, methodInput.value!,
170
181
  gapOpenInput.value!, gapExtendInput.value!, clustersColInput.value);
171
182
  } else {
183
+ gapOpenInput.value = null;
184
+ gapExtendInput.value = null;
185
+ terminalGapInput.value = null;
172
186
  switchDialog(pepseaInputRootStyles, kalignInputRootStyles, 'kalign');
173
187
  return;
174
188
  }
@@ -182,7 +196,7 @@ async function onColInputChange(
182
196
  type MSADialogType = 'kalign' | 'pepsea';
183
197
 
184
198
  function switchDialog(
185
- pepseaInputRootStyles: CSSStyleDeclaration[], kalignInputRootStyles: CSSStyleDeclaration[], dialogType: MSADialogType
199
+ pepseaInputRootStyles: CSSStyleDeclaration[], kalignInputRootStyles: CSSStyleDeclaration[], dialogType: MSADialogType,
186
200
  ) {
187
201
  if (dialogType === 'kalign') {
188
202
  for (const inputRootStyle of pepseaInputRootStyles)
@@ -34,7 +34,7 @@ function _stringsToFasta(sequences: string[]): string {
34
34
  * @return {Promise<DG.Column>} Aligned sequences.
35
35
  */
36
36
  export async function runKalign(srcCol: DG.Column<string>, isAligned: boolean = false, unUsedName: string = '',
37
- clustersCol: DG.Column | null = null, gapOpen?: number, gapExtend?: number, terminalGap?: number
37
+ clustersCol: DG.Column | null = null, gapOpen?: number, gapExtend?: number, terminalGap?: number,
38
38
  ): Promise<DG.Column> {
39
39
  let sequences: string[] = srcCol.toList();
40
40
 
@@ -60,7 +60,7 @@ export async function runKalign(srcCol: DG.Column<string>, isAligned: boolean =
60
60
 
61
61
  const CLI = await new Aioli([
62
62
  'base/1.0.0',
63
- {tool: 'kalign', version: kalignVersion, reinit: true}
63
+ {tool: 'kalign', version: kalignVersion, reinit: true},
64
64
  ]);
65
65
  const tgtCol = DG.Column.string(unUsedName, sequencesLength);
66
66
 
@@ -16,11 +16,25 @@ type PepseaBodyUnit = { ID: string, HELM: string };
16
16
 
17
17
  /** Gets the column containing MSA sequences produced by the 'PepSeA' tool from the {@link srcCol} column.
18
18
  * Does not add the result column to the dataframe of {@link srcCol}.
19
+ * @async
20
+ * @param {DG.Column} srcCol - The column containing the sequences to be aligned.
21
+ * @param {string} unUsedName - The name of the result column.
22
+ * @param {string} method - The method used for alignment.
23
+ * @param {number} gapOpen - The gap open penalty.
24
+ * @param {number} gapExtend - The gap extension penalty.
25
+ * @param {DG.Column} clustersCol - The column containing the clusters of the sequences.
19
26
  */
20
27
  export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
21
28
  method: typeof pepseaMethods[number] = 'ginsi', gapOpen: number = 1.53, gapExtend: number = 0.0,
22
29
  clustersCol: DG.Column<string | number> | null = null,
23
- ): Promise<DG.Column<string>> {
30
+ ): Promise<DG.Column<string> | null> {
31
+ const pepseaContainer = await grok.dapi.docker.dockerContainers.filter('bio').first();
32
+ if (pepseaContainer.status !== 'started' && pepseaContainer.status !== 'checking') {
33
+ grok.log.warning('PepSeA container has not started yet');
34
+ return null;
35
+ }
36
+
37
+
24
38
  const peptideCount = srcCol.length;
25
39
  clustersCol ??= DG.Column.int('Clusters', peptideCount).init(0);
26
40
  if (clustersCol.type != DG.COLUMN_TYPE.STRING)
@@ -41,14 +55,12 @@ export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
41
55
  (bodies[clusterId] ??= []).push({ID: rowIndex.toString(), HELM: helmSeq});
42
56
  }
43
57
 
44
- //@ts-ignore: this is a temporary workaround for the issue with docker containers. This will be fixed in 1.14.0
45
- const pepseaContainer = await (grok.dapi.docker !== undefined ? grok.dapi.docker.dockerContainers : grok.dapi.dockerfiles).filter('bio').first();
46
58
  const alignedSequences: string[] = new Array(peptideCount);
47
59
  for (const body of bodies) { // getting aligned sequences for each cluster
48
60
  const alignedObject = await requestAlignedObjects(pepseaContainer.id, body, method, gapOpen, gapExtend);
49
61
  const alignments = alignedObject.Alignment;
50
62
 
51
- for (const alignment of alignments) { // filling alignedSequencesCol
63
+ for (const alignment of alignments) { // filling alignedSequencesCol
52
64
  alignedSequences[parseInt(alignment.ID)] = Object.entries(alignment)
53
65
  .filter((v) => !alignmentObjectMetaKeys.includes(v[0]))
54
66
  .map((v) => v[1] !== '-' ? v[1] : '')
@@ -75,8 +87,6 @@ async function requestAlignedObjects(dockerfileId: string, body: PepseaBodyUnit[
75
87
  body: JSON.stringify(body),
76
88
  };
77
89
  const path = `/align?method=${method}&gap_open=${gapOpen}&gap_extend=${gapExtend}`;
78
- //@ts-ignore: this is a temporary workaround for the issue with docker containers
79
- const response = await (grok.dapi.docker !== undefined ? grok.dapi.docker.dockerContainers : grok.dapi.dockerfiles)
80
- .request(dockerfileId, path, params);
90
+ const response = await grok.dapi.docker.dockerContainers.request(dockerfileId, path, params);
81
91
  return JSON.parse(response ?? '{}');
82
92
  }
@@ -28,7 +28,7 @@ export function saveAsFastaUI() {
28
28
  .filter((gc: DG.GridColumn) => {
29
29
  const col: DG.Column | null = gc.column;
30
30
  if (col && col.semType === DG.SEMTYPE.MACROMOLECULE) {
31
- const uh = new UnitsHandler(col);
31
+ const uh = UnitsHandler.getOrCreate(col);
32
32
  return uh.isFasta();
33
33
  }
34
34
  return false;
@@ -44,7 +44,7 @@ export function saveAsFastaUI() {
44
44
  .add(ui.inputs([
45
45
  idGColListInput,
46
46
  seqColInput,
47
- lineWidthInput
47
+ lineWidthInput,
48
48
  ]))
49
49
  .onOK(() => {
50
50
  const valueIdColList: DG.Column[] = idGColListInput.value ?
@@ -66,9 +66,16 @@ export function saveAsFastaUI() {
66
66
  .show();
67
67
  }
68
68
 
69
- /** */
69
+ /**
70
+ * Builds FASTA content from id columns list and seq column
71
+ * @param {DG.Column[]} idColList - list of columns with identifiers
72
+ * @param {DG.Column} seqCol - column with sequence
73
+ * @param {number} lineWidth - FASTA line width
74
+ * @param {string} lineSeparator - FASTA line separator
75
+ * @return {string} FASTA content
76
+ */
70
77
  export function saveAsFastaDo(
71
- idColList: DG.Column[], seqCol: DG.Column, lineWidth: number = FASTA_LINE_WIDTH, lineSeparator: string = '\n'
78
+ idColList: DG.Column[], seqCol: DG.Column, lineWidth: number = FASTA_LINE_WIDTH, lineSeparator: string = '\n',
72
79
  ): string {
73
80
  const splitter: SplitterFunc = splitterAsFasta;
74
81
 
@@ -13,4 +13,4 @@ export function getMacromoleculeColumn(): DG.Column | any {
13
13
  export function updateDivInnerHTML(div: HTMLElement, content: string | Node): void {
14
14
  div.innerHTML = '';
15
15
  div.append(content);
16
- }
16
+ }
@@ -3,9 +3,8 @@ import * as DG from 'datagrok-api/dg';
3
3
 
4
4
  import * as rxjs from 'rxjs';
5
5
  import {WebLogoViewer, PROPS as wlPROPS} from '../viewers/web-logo-viewer';
6
- import {
7
- VdRegionsPropsDefault, VdRegionsProps, IVdRegionsViewer,
8
- VdRegion, VdRegionType
6
+ import {IVdRegionsViewer,
7
+ VdRegion, VdRegionType,
9
8
  } from '@datagrok-libraries/bio/src/viewers/vd-regions';
10
9
  import {FilterSources, PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
11
10
  import {Unsubscribable} from 'rxjs';
@@ -128,12 +127,12 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
128
127
 
129
128
  if (property) {
130
129
  switch (property.name) {
131
- case 'regionTypes':
132
- break;
133
- case 'chains':
134
- break;
135
- case 'sequenceColumnNamePostfix':
136
- break;
130
+ case 'regionTypes':
131
+ break;
132
+ case 'chains':
133
+ break;
134
+ case 'sequenceColumnNamePostfix':
135
+ break;
137
136
  // for (let orderI = 0; orderI < this.logos.length; orderI++) {
138
137
  // for (let chainI = 0; chainI < this.chains.length; chainI++) {
139
138
  // const chain: string = this.chains[chainI];
@@ -145,11 +144,11 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
145
144
  }
146
145
 
147
146
  switch (property.name) {
148
- case 'skipEmptyPositions':
149
- case 'positionWidth':
150
- case 'positionHeight':
151
- this.setData(this.dataFrame, this.regions); // onPropertyChanged
152
- break;
147
+ case 'skipEmptyPositions':
148
+ case 'positionWidth':
149
+ case 'positionHeight':
150
+ this.setData(this.dataFrame, this.regions); // onPropertyChanged
151
+ break;
153
152
  }
154
153
  }
155
154
 
@@ -259,7 +258,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
259
258
  width: '16px',
260
259
  marginTop: '24px',
261
260
  marginLeft: '6px',
262
- }
261
+ },
263
262
  })] : []),
264
263
  // List with controls for regions
265
264
  ...[...Array(orderList.length).keys()].map((orderI) => {
@@ -271,7 +270,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
271
270
  // height: '100%',
272
271
  marginTop: '4px',
273
272
  marginBottom: '4px',
274
- }
273
+ },
275
274
  });
276
275
 
277
276
  return resDiv;
@@ -280,8 +279,8 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
280
279
  },
281
280
  ['', ...[...Array(orderList.length).keys()].map(
282
281
  (orderI: number) => regionsFiltered.find(
283
- (r: VdRegion) => r.order == orderList[orderI] && r.chain == this.chains[0]
284
- )!.name || 'Name')]
282
+ (r: VdRegion) => r.order == orderList[orderI] && r.chain == this.chains[0],
283
+ )!.name || 'Name')],
285
284
  );
286
285
  this.mainLayout.className = 'mlb-vd-regions-viewer-table2';
287
286
  // this.mainLayout.style.background = '#EEEEFF';
@@ -294,7 +293,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
294
293
  this.filterSourceInput.root.style.top = '-3px';
295
294
  ui.tooltip.bind(this.filterSourceInput.root, 'Check to filter sequences for selected VRs');
296
295
 
297
- const color: string = `#ffbb${Math.ceil(Math.random() * 255).toString(16)}`;
296
+ const _color: string = `#ffbb${Math.ceil(Math.random() * 255).toString(16)}`;
298
297
  this.host = ui.div([this.mainLayout, this.filterSourceInput!.root],
299
298
  {/*style: {backgroundColor: color}*/});
300
299
  this.root.appendChild(this.host);
@@ -310,7 +309,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
310
309
 
311
310
  const maxHeight: number = Math.min(logoHeight,
312
311
  Math.max(...this.logos.map((wlDict) =>
313
- Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight))))
312
+ Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight)))),
314
313
  );
315
314
 
316
315
  for (let orderI = 0; orderI < this.logos.length; orderI++) {
@@ -323,11 +322,11 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
323
322
 
324
323
  // -- Handle events --
325
324
 
326
- private rootOnSizeChanged(args: any): void {
325
+ private rootOnSizeChanged(_args: any): void {
327
326
  this.calcSize();
328
327
  }
329
328
 
330
- private rootOnMouseMove(e: MouseEvent) {
329
+ private rootOnMouseMove(_e: MouseEvent) {
331
330
  // ui.tooltip.show('text', e.x + 8, e.y + 8,);
332
331
  // console.log(`onMouseMoveRoot.( x: ${e.x}, y: ${e.y} )`);
333
332
  }