@datagrok/bio 2.16.3 → 2.16.5

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,5 @@
1
+ MSA,Activity
2
+ meI/hHis/Aca/N/T/dE/Thr_PO3H2/Aca//Phe_4Me,5.307510973968128
3
+ meI/hHis/Aca/Cys_SEt/T/dK/Thr_PO3H2/Aca//Phe_4Me,5.723876853431544
4
+ Lys_Boc/hHis/Aca/Cys_SEt/T/dK/Thr_PO3H2/Aca//Phe_4Me,5.185811246022437
5
+ meI/hHis/Aca/Cys_SEt/T/dK/Thr_PO3H2///Phe_4Me,6.223502390804369
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Aleksandr Tanas",
6
6
  "email": "atanas@datagrok.ai"
7
7
  },
8
- "version": "2.16.3",
8
+ "version": "2.16.5",
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,9 +37,9 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "@biowasm/aioli": "^3.1.0",
40
- "@datagrok-libraries/bio": "^5.45.2",
40
+ "@datagrok-libraries/bio": "^5.45.3",
41
41
  "@datagrok-libraries/chem-meta": "^1.2.7",
42
- "@datagrok-libraries/math": "^1.2.1",
42
+ "@datagrok-libraries/math": "^1.2.2",
43
43
  "@datagrok-libraries/ml": "^6.7.4",
44
44
  "@datagrok-libraries/tutorials": "^1.4.3",
45
45
  "@datagrok-libraries/utils": "^4.3.6",
@@ -0,0 +1,13 @@
1
+ import * as grokNamespace from 'datagrok-api/grok';
2
+ import * as uiNamespace from 'datagrok-api/ui';
3
+ import * as DGNamespace from 'datagrok-api/dg';
4
+ import * as rxjsNamespace from 'rxjs';
5
+ import $Namespace from 'cash-dom';
6
+
7
+ declare global {
8
+ const grok: typeof grokNamespace;
9
+ const ui: typeof uiNamespace;
10
+ const DG: typeof DGNamespace;
11
+ const rjxs: typeof rxjsNamespace;
12
+ const $: typeof $Namespace;
13
+ }
package/src/package.ts CHANGED
@@ -26,6 +26,7 @@ import {getUserLibSettings, setUserLibSettings} from '@datagrok-libraries/bio/sr
26
26
  import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
27
27
  import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
28
28
  import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
29
+ import {ISeqHandler} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
29
30
 
30
31
  import {getMacromoleculeColumns} from './utils/ui-utils';
31
32
  import {MacromoleculeDifferenceCellRenderer, MacromoleculeSequenceCellRenderer,} from './utils/cell-renderer';
@@ -69,7 +70,6 @@ import {getMolColumnFromHelm} from './utils/helm-to-molfile/utils';
69
70
  import {MonomerManager} from './utils/monomer-lib/monomer-manager/monomer-manager';
70
71
  import {calculateScoresWithEmptyValues} from './utils/calculate-scores';
71
72
  import {SeqHelper} from './utils/seq-helper/seq-helper';
72
- import {ISeqHandler} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
73
73
 
74
74
  export const _package = new BioPackage(/*{debug: true}/**/);
75
75
 
@@ -12,58 +12,75 @@ category('helm', () => {
12
12
  'single-linear': {
13
13
  src: {helm: 'PEPTIDE1{R.F.Y.[GGaz].T.[meI]}$$$$'},
14
14
  tgt: {
15
- simplePolymers: [6],
15
+ simplePolymers: [6], connections: [],
16
16
  bondedRGroups: [1, 2, 2, 2, 2, 1],
17
17
  }
18
18
  },
19
19
  'single-cyclized-C-2-2': {
20
- src: {helm: 'PEPTIDE1{R.F.C.Y.G.H.[GGaz].C.T.[meI]}$PEPTIDE1,PEPTIDE1,3:R3-8,R3$$$'},
20
+ src: {helm: 'PEPTIDE1{R.F.C.Y.G.H.[GGaz].C.T.[meI]}$PEPTIDE1,PEPTIDE1,3:R3-8:R3$$$'},
21
21
  tgt: {
22
- simplePolymers: [10],
22
+ simplePolymers: [10], connections: [[['PEPTIDE1', 3, 'R3'], ['PEPTIDE1', 8, 'R3']]],
23
23
  bondedRGroups: [1, 2, 3, 2, 2, 2, 2, 3, 2, 1],
24
24
  }
25
25
  },
26
26
  'single-cyclized-C-1-1': {
27
- src: {helm: 'PEPTIDE1{F.C.Y.G.H.[GGaz].C.[meI]}$PEPTIDE1,PEPTIDE1,2:R3-7,R3$$$'},
27
+ src: {helm: 'PEPTIDE1{F.C.Y.G.H.[GGaz].C.[meI]}$PEPTIDE1,PEPTIDE1,2:R3-7:R3$$$'},
28
28
  tgt: {
29
- simplePolymers: [8],
29
+ simplePolymers: [8], connections: [[['PEPTIDE1', 2, 'R3'], ['PEPTIDE1', 7, 'R3']]],
30
30
  bondedRGroups: [1, 3, 2, 2, 2, 1, 3, 1],
31
31
  }
32
32
  },
33
33
  'single-cyclized-C-0-0': {
34
- src: {helm: 'PEPTIDE1{C.Y.G.H.[GGaz].C}$PEPTIDE1,PEPTIDE1,2:R3-7,R3$$$'},
34
+ src: {helm: 'PEPTIDE1{C.Y.G.H.[GGaz].C}$PEPTIDE1,PEPTIDE1,1:R3-6:R3$$$'},
35
35
  tgt: {
36
- simplePolymers: [6],
36
+ simplePolymers: [6], connections: [[['PEPTIDE1', 1, 'R3'], ['PEPTIDE1', 6, 'R3']]],
37
37
  bondedRGroups: [2, 2, 2, 2, 2, 2],
38
38
  }
39
39
  },
40
- 'two-separated-1': {
40
+ 'two-separated-5-1': {
41
41
  src: {helm: 'PEPTIDE1{R.F.Y.[GGaz].T}|PEPTIDE2{[meI]}$$$$'},
42
42
  tgt: {
43
- simplePolymers: [5, 1],
44
- bondedRGroups: [1, 2, 2, 2, 1, 1],
43
+ simplePolymers: [5, 1], connections: [],
44
+ bondedRGroups: [1, 2, 2, 2, 1, 0],
45
45
  }
46
46
  },
47
- 'two-separated-2': {
47
+ 'two-separated-1-5': {
48
+ src: {helm: 'PEPTIDE1{[meI]}|PEPTIDE2{R.F.Y.[GGaz].T}$$$$'},
49
+ tgt: {
50
+ simplePolymers: [1, 5], connections: [],
51
+ bondedRGroups: [0, 1, 2, 2, 2, 1],
52
+ }
53
+ },
54
+ 'two-separated-4-2': {
48
55
  src: {helm: 'PEPTIDE1{R.F.Y.[GGaz]}|PEPTIDE2{T.[meI]}$$$$'},
49
56
  tgt: {
50
- simplePolymers: [4, 2],
57
+ simplePolymers: [4, 2], connections: [],
51
58
  bondedRGroups: [1, 2, 2, 1, 1, 1],
52
59
  }
53
60
  },
54
61
  'two-connected-1': {
55
- src: {helm: 'PEPTIDE1{R.F.Y.[GGaz].T}|PEPTIDE2{[meI]}$PEPTIDE1,PEPTIDE2,5:R2-1,R1$$$'},
62
+ src: {helm: 'PEPTIDE1{R.F.Y.[GGaz].T}|PEPTIDE2{[meI]}$PEPTIDE1,PEPTIDE2,5:R2-1:R1$$$'},
56
63
  tgt: {
57
- simplePolymers: [5, 1],
64
+ simplePolymers: [5, 1], connections: [[['PEPTIDE1', 5, 'R2'], ['PEPTIDE2', 1, 'R1']]],
58
65
  bondedRGroups: [1, 2, 2, 2, 2, 1],
59
66
  }
60
67
  },
61
68
  'two-connected-2': {
62
- src: {helm: 'PEPTIDE1{R.F.Y.[GGaz]}|PEPTIDE2{T.[meI]}$PEPTIDE1,PEPTIDE2,4:R2-1,R1$$$'},
69
+ src: {helm: 'PEPTIDE1{R.F.Y.[GGaz]}|PEPTIDE2{T.[meI]}$PEPTIDE1,PEPTIDE2,4:R2-1:R1$$$'},
63
70
  tgt: {
64
- simplePolymers: [4, 2],
71
+ simplePolymers: [4, 2], connections: [[['PEPTIDE1', 4, 'R2'], ['PEPTIDE2', 1, 'R1']]],
65
72
  bondedRGroups: [1, 2, 2, 2, 2, 1],
66
73
  }
74
+ },
75
+ 'two-cyclized-1-9': {
76
+ src: {helm: 'PEPTIDE1{[meI]}|PEPTIDE2{R.F.[GGaz].T.G.H.F.Y.P}$PEPTIDE2,PEPTIDE2,3:R3-9:R2|PEPTIDE2,PEPTIDE1,3:R4-1:R1$$$V2.0'},
77
+ tgt: {
78
+ simplePolymers: [1, 9],
79
+ connections: [
80
+ [['PEPTIDE2', 3, 'R3'], ['PEPTIDE2', 9, 'R2']],
81
+ [['PEPTIDE2', 3, 'R4'], ['PEPTIDE1', 1, 'R1']]],
82
+ bondedRGroups: [1, 1, 2, 4, 2, 2, 2, 2, 2, 1],
83
+ }
67
84
  }
68
85
  };
69
86
 
@@ -74,12 +91,21 @@ category('helm', () => {
74
91
  const simplePolymers = resHelm.simplePolymers
75
92
  .map((sp) => sp.monomers.length);
76
93
  const totalMonomerCount = simplePolymers.reduce((a, b) => a + b, 0);
77
- expect(simplePolymers, tgt.simplePolymers);
94
+ expectArray(simplePolymers, tgt.simplePolymers);
95
+
96
+ const connections = resHelm.connectionList.getConnectionData()
97
+ .map((cdi) => {
98
+ return [
99
+ [cdi[0].polymerId, cdi[0].bond.monomerIdx + 1, `R${cdi[0].bond.rGroupId}`],
100
+ [cdi[1].polymerId, cdi[1].bond.monomerIdx + 1, `R${cdi[1].bond.rGroupId}`]];
101
+ });
102
+
103
+ expectArray(connections, tgt.connections);
78
104
 
79
- const bondedRGroups = wu.count(0).take(resHelm.bondedRGroupsMap.size)
80
- .map((i) => resHelm.bondedRGroupsMap.get(i)!.length).toArray();
105
+ const bondedRGroups = wu.count(0).take(resHelm.bondedRGroupsMap.length)
106
+ .map((i) => resHelm.bondedRGroupsMap[i].length).toArray();
81
107
  expect(totalMonomerCount, bondedRGroups.length);
82
108
  // expectArray(bondedRGroups, tgt.bondedRGroups);
83
- }, {skipReason: 'new tests'});
109
+ });
84
110
  }
85
111
  });
@@ -4,10 +4,9 @@ import * as ui from 'datagrok-api/ui';
4
4
 
5
5
  import wu from 'wu';
6
6
 
7
- import {after, before, category, expect, test} from '@datagrok-libraries/utils/src/test';
7
+ import {after, before, category, expect, expectArray, test} from '@datagrok-libraries/utils/src/test';
8
8
  import {MonomerPlacer, hitBounds} from '@datagrok-libraries/bio/src/utils/cell-renderer-monomer-placer';
9
9
  import {monomerToShort} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
- import {getSeqHelper, ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
11
10
  import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
12
11
  import {
13
12
  getUserLibSettings, setUserLibSettings
@@ -164,4 +163,60 @@ id3,QHIRE--LT
164
163
  expect(res, testData.tgt);
165
164
  });
166
165
  }
166
+
167
+ const lengthsTests = {
168
+ mono1: {
169
+ src: {
170
+ csv: 'seq' + '\n' +
171
+ 'm1/m2/m3/m4/m5/m6/m7/m8/m9' + '\n' +
172
+ 'n1/m2/n3/m4/n5/m6/n7/m8/n9' + '\n' +
173
+ 'm1/n2/m3/n4/m5/n6/m7/n8/m9' + '\n',
174
+ },
175
+ tgt: {
176
+ lengths: [5, 31, 57, 83, 109, 135, 161, 187, 213, 239],
177
+ }
178
+ },
179
+ monoWithGaps: {
180
+ src: {
181
+ csv: 'seq' + '\n' +
182
+ 'm1/m2/m3/m4/m5/m6//m8/m9' + '\n' +
183
+ 'n1/m2/n3/m4/n5/m6//m8/n9' + '\n' +
184
+ 'm1/n2/m3/n4/m5/n6/m7/n8/m9' + '\n',
185
+ },
186
+ tgt: {
187
+ lengths: [5, 31, 57, 83, 109, 135, 161, 187, 213, 239],
188
+ }
189
+ },
190
+ monoWithGapColumn: {
191
+ src: {
192
+ csv: 'seq' + '\n' +
193
+ 'm1/m2/m3/m4/m5/m6//m8/m9' + '\n' +
194
+ 'n1/m2/n3/m4/n5/m6//m8/n9' + '\n' +
195
+ 'm1/n2/m3/n4/m5///n8/m9' + '\n',
196
+ },
197
+ tgt: {lengths: [5, 31, 57, 83, 109, 135, 161, 180, 206, 232],}
198
+ },
199
+ };
200
+
201
+ for (const [testName, testData] of Object.entries(lengthsTests)) {
202
+ test(`getCellMonomerLengths-${testName}`, async () => {
203
+ const df: DG.DataFrame = DG.DataFrame.fromCsv(testData.src.csv);
204
+ await grok.data.detectSemanticTypes(df);
205
+ const seqCol = df.getCol('seq');
206
+
207
+ const monLengthLimit: number = 3;
208
+ const charWidth: number = 7;
209
+ const sepWidth: number = 12;
210
+ const colTemp = new MonomerPlacer(null, seqCol, _package.logger, monLengthLimit, () => {
211
+ return {
212
+ monomerCharWidth: charWidth,
213
+ separatorWidth: sepWidth,
214
+ monomerToShort: monomerToShort,
215
+ };
216
+ });
217
+ await colTemp.init();
218
+ const resLengths = colTemp.getCellMonomerLengths(0, 1000)[1];
219
+ expectArray(resLengths, testData.tgt.lengths);
220
+ });
221
+ }
167
222
  });
@@ -4,12 +4,12 @@ import {Bond} from './types';
4
4
 
5
5
  export class ConnectionList {
6
6
  constructor(connectionList: string) {
7
- const splitted = connectionList.split(HELM_ITEM_SEPARATOR);
7
+ const splitted = connectionList.split(HELM_ITEM_SEPARATOR).filter((ci) => ci);
8
8
  splitted.forEach((connectionItem: string) => this.validateConnectionItem(connectionItem));
9
9
  this.connectionItems = splitted;
10
10
  }
11
11
 
12
- private connectionItems: string[];
12
+ public connectionItems: string[];
13
13
 
14
14
  private validateConnectionItem(connectionItem: string): void {
15
15
  const allowedType = `(${HELM_POLYMER_TYPE.PEPTIDE}|${HELM_POLYMER_TYPE.RNA})`;
@@ -18,10 +18,10 @@ export class ConnectionList {
18
18
  throw new Error(`Cannot parse connection item from ${connectionItem}`);
19
19
  }
20
20
 
21
- getConnectionData(): {polymerId: string, bond: Bond}[][] {
22
- const result: {polymerId: string, bond: Bond}[][] = [];
21
+ getConnectionData(): { polymerId: string, bond: Bond }[][] {
22
+ const result: { polymerId: string, bond: Bond }[][] = [];
23
23
  this.connectionItems.forEach((connectionItem: string) => {
24
- const pair: {polymerId: string, bond: Bond}[] = [];
24
+ const pair: { polymerId: string, bond: Bond }[] = [];
25
25
  const splitted = connectionItem.split(',');
26
26
  splitted[2].split('-').forEach((item, idx) => {
27
27
  const polymerId = splitted[idx];
@@ -10,8 +10,7 @@ export class Helm {
10
10
  const simplePolymers = helmSections[0].split(HELM_ITEM_SEPARATOR);
11
11
  this.simplePolymers = simplePolymers
12
12
  .map((item) => new SimplePolymer(item));
13
- if (helmSections[1] !== '')
14
- this.connectionList = new ConnectionList(helmSections[1]);
13
+ this.connectionList = new ConnectionList(helmSections[1]);
15
14
  this.bondData = this.getBondData();
16
15
 
17
16
  this.bondedRGroupsMap = this.getBondedRGroupsMap();
@@ -22,25 +21,25 @@ export class Helm {
22
21
  readonly bondData: Bond[][];
23
22
 
24
23
  public readonly simplePolymers: SimplePolymer[];
25
- public readonly connectionList?: ConnectionList;
24
+ public readonly connectionList: ConnectionList;
26
25
 
27
26
  /** Maps global monomer index to r-group ids (starting from 1) participating
28
27
  * in connection */
29
- readonly bondedRGroupsMap: Map<number, number[]>;
28
+ readonly bondedRGroupsMap: number[][];
30
29
 
31
- private getBondedRGroupsMap(): Map<number, number[]> {
32
- const bondedRGroupsMap = new Map<number, number[]>();
30
+ private getBondedRGroupsMap(): number[][] {
31
+ const monomerCount = this.simplePolymers.map((sp) => sp.monomers.length)
32
+ .reduce((a, b) => a + b, 0);
33
+ const bondedRGroupsList: number[][] = Array.from({length: monomerCount}, () => []);
33
34
  this.bondData.forEach((bond) => {
34
35
  bond.forEach((bondPart) => {
35
36
  const monomerIdx = bondPart.monomerIdx;
36
37
  const rGroupId = bondPart.rGroupId;
37
- if (!bondedRGroupsMap.get(monomerIdx))
38
- bondedRGroupsMap.set(monomerIdx, []);
39
- bondedRGroupsMap.get(monomerIdx)!.push(rGroupId);
38
+ bondedRGroupsList[monomerIdx].push(rGroupId);
40
39
  });
41
40
  });
42
41
 
43
- return bondedRGroupsMap;
42
+ return bondedRGroupsList;
44
43
  }
45
44
 
46
45
  toString() {
@@ -93,19 +92,17 @@ export class Helm {
93
92
  this.shiftBondMonomerIds(shift, bondData);
94
93
  result.push(...bondData);
95
94
  });
96
- if (this.connectionList) {
97
- const connectionData = this.connectionList.getConnectionData();
98
- connectionData.forEach((connection) => {
99
- const data: Bond[] = [];
100
- connection.forEach((connectionItem) => {
101
- const shift = shifts[connectionItem.polymerId];
102
- const bond = connectionItem.bond;
103
- bond.monomerIdx += shift;
104
- data.push(bond);
105
- });
106
- result.push(data);
95
+ const connectionData = this.connectionList.getConnectionData();
96
+ connectionData.forEach((connection) => {
97
+ const data: Bond[] = [];
98
+ connection.forEach((connectionItem) => {
99
+ const shift = shifts[connectionItem.polymerId];
100
+ const bond = connectionItem.bond;
101
+ bond.monomerIdx += shift;
102
+ data.push(bond);
107
103
  });
108
- }
104
+ result.push(data);
105
+ });
109
106
  return result;
110
107
  }
111
108
  }
@@ -28,8 +28,7 @@ export class MonomerWrapper {
28
28
  this.molfileWrapper = MolfileWrapperFactory.getInstance(molfile, monomerSymbol);
29
29
  this.capGroupElements = this.getCapGroupElements(libraryMonomerObject);
30
30
 
31
- if (helm.bondedRGroupsMap.has(monomerIdx))
32
- this.removeRGroups(helm.bondedRGroupsMap.get(monomerIdx)!);
31
+ this.removeRGroups(helm.bondedRGroupsMap[monomerIdx]!);
33
32
  this.capRemainingRGroups();
34
33
 
35
34
  this.shiftCoordinates(shift);
@@ -161,12 +161,12 @@ export class MonomerLibBase implements IMonomerLibBase {
161
161
  getTooltip(biotype: HelmType, monomerSymbol: string): HTMLElement {
162
162
  const polymerType = helmTypeToPolymerType(biotype);
163
163
  const res = ui.div([], {classes: 'ui-form ui-tooltip'});
164
+ const wem = this.getWebEditorMonomer(biotype, monomerSymbol)!;
164
165
  const monomer = this.getMonomer(polymerType, monomerSymbol);
165
166
  if (monomer) {
166
167
  // Symbol & Name
167
168
  const symbol = monomer[REQ.SYMBOL];
168
169
  const _name = monomer[REQ.NAME];
169
- const wem = this.getWebEditorMonomer(biotype, monomerSymbol)!;
170
170
 
171
171
  const htmlColor = wem.backgroundcolor;
172
172
  res.append(ui.divH([
@@ -16,7 +16,7 @@ import {MonomerLibManager} from '../lib-manager';
16
16
  import {LIB_PATH} from '../consts';
17
17
 
18
18
  import '../../../../css/monomer-manager.css';
19
- import { MONOMER_RENDERER_TAGS } from '@datagrok-libraries/bio/src/utils/cell-renderer';
19
+ import {MONOMER_RENDERER_TAGS} from '@datagrok-libraries/bio/src/utils/cell-renderer';
20
20
 
21
21
  // columns of monomers dataframe, note that rgroups is hidden and will be displayed as separate columns
22
22
  export enum MONOMER_DF_COLUMN_NAMES {
@@ -51,7 +51,6 @@ export const MONOMER_DF_COLUMNS = {
51
51
 
52
52
 
53
53
  export class MonomerManager implements IMonomerManager {
54
-
55
54
  private adjustColWidths() {
56
55
  setTimeout(() => {
57
56
  if (this.tv?.grid) {
@@ -87,7 +86,6 @@ export class MonomerManager implements IMonomerManager {
87
86
  }, 500);
88
87
  }
89
88
  }
90
-
91
89
  }, () => this.tv?.dataFrame);
92
90
  }
93
91
 
@@ -203,6 +201,7 @@ export class MonomerManager implements IMonomerManager {
203
201
  }
204
202
  })
205
203
  );
204
+ this.tv.grid && (this.tv.grid.props.allowEdit = false); // disable editing
206
205
  return this.tv;
207
206
  }
208
207
 
@@ -239,6 +238,10 @@ export class MonomerManager implements IMonomerManager {
239
238
  });
240
239
  ribbons = ribbons.filter((r) => r.length > 0);
241
240
 
241
+ const newMonomerButton = ui.icons.add(() => {
242
+ this._newMonomerForm.setEmptyMonomer();
243
+ }, 'Add New Monomer');
244
+
242
245
  const editButton = ui.icons.edit(() => {
243
246
  if ((this.tv?.dataFrame?.currentRowIdx ?? -1) < 0) return;
244
247
  this.cloneMonomer(this.tv!.dataFrame.rows.get(this.tv!.dataFrame.currentRowIdx));
@@ -277,19 +280,19 @@ export class MonomerManager implements IMonomerManager {
277
280
  DG.Utils.download(libName!, lib!, 'text/plain');
278
281
  }, 'Download Monomer Library');
279
282
 
280
- ribbons.push([editButton, deleteButton, downloadButton]);
283
+ ribbons.push([newMonomerButton, editButton, deleteButton, downloadButton]);
281
284
  this.tv.setRibbonPanels(ribbons);
282
285
 
283
286
 
284
287
  this.tv.name = MonomerManager.VIEW_NAME;
285
288
  this.libInput = ui.input.choice('Monomer Library', {value: libName, items: availableMonLibs, nullable: false, onValueChanged: async () => {
286
- try {
287
- const df = await this.getMonomersDf(this.libInput.value!);
289
+ try {
290
+ const df = await this.getMonomersDf(this.libInput.value!);
288
291
  this.tv!.dataFrame = df;
289
292
  this.adjustColWidths();
290
- } catch (e) {
291
- console.error(e);
292
- }
293
+ } catch (e) {
294
+ console.error(e);
295
+ }
293
296
  }});
294
297
  this.libInput.addOptions(ui.icons.add(() => { this.createNewLibDialog(); }, 'Create new monomer library...'));
295
298
  const monForm = this._newMonomerForm.form;
@@ -370,6 +373,7 @@ export class MonomerManager implements IMonomerManager {
370
373
  df.col(rgName)!.semType = DG.SEMTYPE.MOLECULE;
371
374
  });
372
375
  df.currentRowIdx = -1;
376
+ // eslint-disable-next-line rxjs/no-ignored-subscription
373
377
  df.onCurrentRowChanged.subscribe((_) => {
374
378
  try {
375
379
  if (df.currentRowIdx === -1 || this._newMonomerForm.molChanged)
@@ -541,6 +545,7 @@ class MonomerForm implements INewMonomerForm {
541
545
  await this.saveMonomer();
542
546
  });
543
547
  // this.saveButton.style.pointerEvents = 'revert';
548
+ // eslint-disable-next-line rxjs/no-async-subscribe
544
549
  this.molSketcher.subs.push(this.molSketcher.onChanged.subscribe(async () => {
545
550
  if (!this.triggerMolChange) {
546
551
  this.triggerMolChange = true;
@@ -551,6 +556,9 @@ class MonomerForm implements INewMonomerForm {
551
556
  let smiles = this.molSketcher.getSmiles();
552
557
  if (!smiles) {
553
558
  this.rgroupsGrid.items = [];
559
+ this.rgroupsGrid.render();
560
+ this.saveValidationResult = 'Monomer molecule is required';
561
+ this.invalidateSaveButton();
554
562
  return;
555
563
  }
556
564
  smiles = getCorrectedSmiles([], smiles);
@@ -559,6 +567,8 @@ class MonomerForm implements INewMonomerForm {
559
567
  if (rGroupMatches.length === 0) {
560
568
  this.rgroupsGrid.items = [];
561
569
  this.rgroupsGrid.render();
570
+ this.saveValidationResult = 'At least one R-group is required';
571
+ this.invalidateSaveButton();
562
572
  return;
563
573
  }
564
574
  const rGroupNums = rGroupMatches.map((match) => Number.parseInt(match[0].match(/[1-9]/g)![0]));
@@ -606,6 +616,7 @@ class MonomerForm implements INewMonomerForm {
606
616
  [HELM_RGROUP_FIELDS.LABEL]: 'Label',
607
617
  },
608
618
  });
619
+ // eslint-disable-next-line rxjs/no-ignored-subscription
609
620
  this.rgroupsGrid.onItemChanged.subscribe(() => this.onMonomerInputChanged());
610
621
 
611
622
  this.rgroupsGridRoot = ui.divV([this.rgroupsGrid.root]);
@@ -633,17 +644,19 @@ class MonomerForm implements INewMonomerForm {
633
644
  'Meta': ui.divV([this.metaGrid.root]),
634
645
  'Colors': this.colorsEditor.form,
635
646
  }, false);
647
+ }
636
648
 
649
+ invalidateSaveButton() {
650
+ if (this.saveValidationResult)
651
+ this.saveButton.classList.add('d4-disabled');
652
+ else
653
+ this.saveButton.classList.remove('d4-disabled');
637
654
  }
638
655
 
639
656
  onMonomerInputChanged() {
640
657
  setTimeout(() => {
641
658
  this.saveValidationResult = this.validateInputs();
642
- if (this.saveValidationResult)
643
- this.saveButton.classList.add('d4-disabled');
644
- else
645
- this.saveButton.classList.remove('d4-disabled');
646
-
659
+ this.invalidateSaveButton();
647
660
  const monomerExists = this.polymerTypeInput.value && this.polymerTypeInput.value &&
648
661
  !!this.getMonomerLib()?.getMonomer(this.polymerTypeInput.value as PolymerType, this.monomerSymbolInput.value);
649
662
 
@@ -651,6 +664,27 @@ class MonomerForm implements INewMonomerForm {
651
664
  }, 200);
652
665
  }
653
666
 
667
+ setEmptyMonomer() {
668
+ this.triggerMolChange = false;
669
+ this.molSketcher.setSmiles('');
670
+ // leave polymer and monomer type as is
671
+ this.monomerSymbolInput.value = '';
672
+ this.monomerNameInput.value = '';
673
+ this.monomerIdInput.value = null;
674
+ this.monomerNaturalAnalogInput.value = null;
675
+ this.rgroupsGrid.items = [];
676
+ this.metaGrid.items = [];
677
+ this.rgroupsGrid.render();
678
+ this.metaGrid.render();
679
+ this.rgroupsGridRoot.style.display = 'none';
680
+ this.onMonomerInputChanged();
681
+ this.colorsEditor.colors = {
682
+ line: '#000000',
683
+ background: '#000000',
684
+ text: '#000000',
685
+ };
686
+ }
687
+
654
688
  setMonomer(monomer: Monomer) {
655
689
  this.triggerMolChange = false;
656
690
  this.molSketcher.setSmiles(monomer.smiles);
@@ -661,7 +695,7 @@ class MonomerForm implements INewMonomerForm {
661
695
  this.monomerIdInput.value = monomer.id;
662
696
  this.monomerNaturalAnalogInput.value = monomer.naturalAnalog ?? null;
663
697
  this.rgroupsGrid.items = resolveRGroupInfo(monomer.rgroups);
664
- this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k, v]) => k?.toLowerCase() !== 'colors').map(([k, v]) => {
698
+ this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k, _v]) => k?.toLowerCase() !== 'colors').map(([k, v]) => {
665
699
  return {Property: k, Value: v};
666
700
  });
667
701
  this.rgroupsGrid.render();
@@ -681,7 +715,7 @@ class MonomerForm implements INewMonomerForm {
681
715
  } catch (e) {
682
716
  console.error(e);
683
717
  }
684
-
718
+
685
719
  this.colorsEditor.colors = {
686
720
  line: colorsObj.line ?? '#000000',
687
721
  background: colorsObj.background ?? '#000000',
@@ -693,7 +727,7 @@ class MonomerForm implements INewMonomerForm {
693
727
  const rGroupsPane = this.inputsTabControl.panes.find((p) => p.name?.toLowerCase() === 'r-groups');
694
728
  rGroupsPane && (rGroupsPane.header.style.removeProperty('background-color'));
695
729
  if (!this.molSketcher.getSmiles()) return 'Monomer Molecule field is required';
696
- for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput, this.monomerIdInput]) {
730
+ for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput]) {
697
731
  if (i.value == null || i.value === '')
698
732
  return `${i.caption} field is required`;
699
733
  }
@@ -703,17 +737,18 @@ class MonomerForm implements INewMonomerForm {
703
737
  if (!rgroupError) {
704
738
  outerFor:
705
739
  for (const item of this.rgroupsGrid.items) {
706
- for (const [k, v] of Object.entries(item))
707
- if (!v){
740
+ for (const [k, v] of Object.entries(item)) {
741
+ if (!v) {
708
742
  rgroupError = `R-group ${k} field is required for ${item[HELM_RGROUP_FIELDS.LABEL]}`;
709
743
  break outerFor;
710
- }
744
+ }
745
+ }
711
746
  }
712
747
  }
713
748
 
714
- if (!rgroupError && this.rgroupsGrid.hasErrors()){
749
+ if (!rgroupError && this.rgroupsGrid.hasErrors())
715
750
  rgroupError = 'R-group fields contain errors';
716
- }
751
+
717
752
  if (rgroupError) {
718
753
  rGroupsPane && (rGroupsPane.header.style.setProperty('background-color', '#ff000030'));
719
754
  return rgroupError;
@@ -730,7 +765,6 @@ class MonomerForm implements INewMonomerForm {
730
765
  }
731
766
 
732
767
  get form() {
733
-
734
768
  this.inputsTabControl.root.classList.add('monomer-manager-form-tab-control');
735
769
  this.inputsTabControl.header.style.marginBottom = '10px';
736
770
  const saveB = ui.buttonsInput([this.saveButton]);
@@ -826,11 +860,11 @@ class MonomerForm implements INewMonomerForm {
826
860
  try {
827
861
  // first remove the existing monomer with that symbol
828
862
  const monomerIdx = libJSON.findIndex((m) => m.symbol === monomer.symbol && m.polymerType === monomer.polymerType);
829
- if (monomerIdx >= 0) {
863
+ if (monomerIdx >= 0)
830
864
  libJSON[monomerIdx] = {...monomer, lib: undefined, wem: undefined};
831
- } else {
865
+ else
832
866
  libJSON.push({...monomer, lib: undefined, wem: undefined});
833
- }
867
+
834
868
  await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON));
835
869
  await (await MonomerLibManager.getInstance()).loadLibraries(true);
836
870
  await this.refreshTable(monomer.symbol);
@@ -839,6 +873,7 @@ class MonomerForm implements INewMonomerForm {
839
873
  grok.shell.error('Error saving monomer');
840
874
  console.error(e);
841
875
  }
876
+ this.onMonomerInputChanged();
842
877
  };
843
878
  let infoTable: HTMLDivElement | null = null;
844
879
  let promptMessage = '';
@@ -882,9 +917,9 @@ class MonomerForm implements INewMonomerForm {
882
917
  meta[item['Property']] = item['Value'];
883
918
  });
884
919
  const addingItem = this.metaGrid.addingItem;
885
- if (addingItem && addingItem['Property'] && addingItem['Value']) {
920
+ if (addingItem && addingItem['Property'] && addingItem['Value'])
886
921
  meta[addingItem['Property']] = addingItem['Value'];
887
- }
922
+
888
923
  //console.log(this.metaGrid.addingItem);
889
924
  if (this.colorsEditor.colors.line !== '#000000' || this.colorsEditor.colors.background !== '#000000' || this.colorsEditor.colors.text !== '#000000')
890
925
  meta.colors = {default: this.colorsEditor.colors};
@@ -975,7 +1010,7 @@ function getCorrectedMolBlock(molBlock: string) {
975
1010
 
976
1011
  if (rgpLineIdx === -1) {
977
1012
  // number of r groups has 3 empty slots before it, atom numbers have 4 empty slots before them
978
- let rgpLine = `M RGP${rgroupLineNums.length.toString().padStart(3,' ')}${Object.entries(rgroupLineNumbers).map(([atomLine, rGroupNum]) => `${atomLine.toString().padStart(4, ' ')}${rGroupNum.toString().padStart(4, ' ')}`).join('')}`;
1013
+ const rgpLine = `M RGP${rgroupLineNums.length.toString().padStart(3, ' ')}${Object.entries(rgroupLineNumbers).map(([atomLine, rGroupNum]) => `${atomLine.toString().padStart(4, ' ')}${rGroupNum.toString().padStart(4, ' ')}`).join('')}`;
979
1014
  const mEndIdx = lines.findIndex((line) => line.startsWith('M') && line.includes('END'));
980
1015
  lines.splice(mEndIdx, 0, rgpLine);
981
1016
  }
@@ -1042,9 +1077,8 @@ class ColorsEditor {
1042
1077
  };
1043
1078
 
1044
1079
  this._colors = colsHex;
1045
- for (const key in this._colorInputs) {
1080
+ for (const key in this._colorInputs)
1046
1081
  this._colorInputs[key as keyof ColorsEditor['_colors']].value = colsHex[key as keyof ColorsEditor['_colors']];
1047
- }
1048
1082
  }
1049
1083
 
1050
1084
  get colorsMetaFormat() {
@@ -1054,4 +1088,4 @@ class ColorsEditor {
1054
1088
  get form() {
1055
1089
  return ui.form(Object.values(this._colorInputs));
1056
1090
  }
1057
- }
1091
+ }