@datagrok/helm 2.1.30 → 2.1.32

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/helm",
3
3
  "friendlyName": "Helm",
4
- "version": "2.1.30",
4
+ "version": "2.1.32",
5
5
  "author": {
6
6
  "name": "Oleksandra Serhiienko",
7
7
  "email": "oserhiienko@datagrok.ai"
@@ -15,8 +15,8 @@
15
15
  "helm/JSDraw/Pistoia.HELM-uncompressed.js"
16
16
  ],
17
17
  "dependencies": {
18
- "@datagrok-libraries/bio": "^5.39.29",
19
- "@datagrok-libraries/chem-meta": "^1.2.2",
18
+ "@datagrok-libraries/bio": "^5.40.7",
19
+ "@datagrok-libraries/chem-meta": "^1.2.3",
20
20
  "@datagrok-libraries/utils": "^4.1.45",
21
21
  "cash-dom": "^8.1.1",
22
22
  "datagrok-api": "^1.17.0",
@@ -37,8 +37,8 @@
37
37
  "@typescript-eslint/parser": "^5.11.0",
38
38
  "eslint": "^8.18.0",
39
39
  "eslint-config-google": "^0.14.0",
40
- "@datagrok/bio": "^2.11.38",
41
- "@datagrok/chem": "^1.8.13"
40
+ "@datagrok/bio": "^2.12.11",
41
+ "@datagrok/chem": "^1.9.0"
42
42
  },
43
43
  "scripts": {
44
44
  "link-all": "npm link @datagrok-libraries/chem-meta datagrok-api @datagrok-libraries/utils @datagrok-libraries/bio",
@@ -29,7 +29,7 @@ function getSeqMonomerFromHelm(
29
29
  resSeqMonomer = {symbol: symbol, polymerType: polymerType};
30
30
  }
31
31
  if (!resSeqMonomer)
32
- resSeqMonomer = {symbol: symbol};
32
+ throw new Error(`Monomer not found for symbol = '${symbol}' and helmPrefix = '${helmPrefix}'.`);
33
33
  return resSeqMonomer;
34
34
  }
35
35
 
@@ -162,7 +162,7 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
162
162
  const argsX = e.offsetX - gcb.x;
163
163
  const argsY = e.offsetY - gcb.y;
164
164
 
165
- const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
165
+ const helmPlacer = HelmMonomerPlacer.getOrCreate(gridCell.gridColumn, tableCol);
166
166
  const editor: IEditor | null = helmPlacer.getEditor(gridCell.tableRowIndex!);
167
167
  let seqMonomer: ISeqMonomer | null;
168
168
  let missedMonomers: Set<string> = new Set<string>(); // of .size = 0
@@ -189,15 +189,10 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
189
189
 
190
190
  if (seqMonomer) {
191
191
  if (!missedMonomers.has(seqMonomer.symbol)) {
192
- const monomer = helmPlacer.getMonomer(seqMonomer);
193
- if (monomer) {
194
- const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
195
- const monomerSvg = grok.chem.svgMol(monomer.smiles, undefined, undefined, options);
196
- ui.tooltip.show(ui.divV([
197
- ui.divText(seqMonomer.symbol),
198
- monomerSvg,
199
- ]), e.x + 16, e.y + 16);
200
- }
192
+ const tooltipElements: HTMLElement[] = [ui.div(seqMonomer.symbol)];
193
+ const monomerDiv = helmPlacer.monomerLib.getTooltip(seqMonomer.polymerType, seqMonomer.symbol);
194
+ tooltipElements.push(monomerDiv);
195
+ ui.tooltip.show(ui.divV(tooltipElements), e.x + 16, e.y + 16);
201
196
  } else {
202
197
  ui.tooltip.show(ui.divV([
203
198
  ui.divText(`Monomer '${seqMonomer.symbol}' not found.`),
@@ -244,7 +239,7 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
244
239
  const monomerList = parseHelm(seq);
245
240
  const monomers: Set<string> = new Set<string>(monomerList);
246
241
  const missedMonomers: Set<string> = findMonomers(monomerList);
247
- const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
242
+ const helmPlacer = HelmMonomerPlacer.getOrCreate(gridCell.gridColumn, tableCol);
248
243
 
249
244
  if (missedMonomers.size == 0) {
250
245
  // Recreate host to avoid hanging in window.dojox.gfx.svg.Text.prototype.getTextWidth
@@ -3,11 +3,13 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import wu from 'wu';
6
+ import {Unsubscribable} from 'rxjs';
6
7
 
7
8
  import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/index';
8
9
 
9
10
  import {getParts, parseHelm} from './utils';
10
- import {getMonomerLib} from './package';
11
+
12
+ import {_package, getMonomerLib} from './package';
11
13
 
12
14
  export const enum Temps {
13
15
  helmMonomerPlacer = 'bio-helmMonomerPlacer',
@@ -36,31 +38,60 @@ export interface IEditorPoint {
36
38
  }
37
39
 
38
40
  export interface ISeqMonomer {
41
+ polymerType: string
39
42
  symbol: string,
40
- polymerType?: string
41
43
  }
42
44
 
43
45
  export class HelmMonomerPlacer {
44
- private readonly _allPartsList: (string[] | null)[];
45
- private readonly _lengthsList: (number[] | null)[];
46
- private readonly _editorList: (IEditor | null)[];
47
-
46
+ private readonly grid: DG.Grid;
48
47
  public readonly monomerLib: IMonomerLib;
49
48
 
49
+ private _allPartsList: (string[] | null)[];
50
+ private _lengthsList: (number[] | null)[];
51
+ private _editorList: (IEditor | null)[];
52
+
50
53
  public monomerCharWidth: number = 7;
51
54
  public leftPadding: number = 5;
52
55
  public monomerTextSizeMap: { [p: string]: TextMetrics } = {};
53
56
 
54
- constructor(public readonly col: DG.Column<string>) {
55
- this.col.dataFrame.onDataChanged.subscribe();
57
+ private subs: Unsubscribable[] = [];
58
+
59
+ protected constructor(
60
+ public readonly gridCol: DG.GridColumn,
61
+ public readonly col: DG.Column<string>
62
+ ) {
63
+ this.grid = this.gridCol.grid;
64
+ this.reset();
65
+ if (this.grid) {
66
+ this.subs.push(this.col.dataFrame.onDataChanged.subscribe(() => {
67
+ try {
68
+ this.reset();
69
+ } catch (err: any) {
70
+ console.error(err);
71
+ }
72
+ }));
73
+ this.subs.push(grok.events.onViewRemoved.subscribe((view: DG.View) => {
74
+ try {
75
+ if (this.gridCol.grid.view?.id === view.id) this.destroy();
76
+ } catch (err: any) {
77
+ console.error(err);
78
+ }
79
+ }));
80
+ }
56
81
  this.monomerLib = getMonomerLib();
57
- this.monomerLib.onChanged.subscribe(this.monomerLibOnChanged.bind(this));
82
+ this.subs.push(this.monomerLib.onChanged.subscribe(this.monomerLibOnChanged.bind(this)));
83
+ }
58
84
 
85
+ private reset(): void {
59
86
  this._allPartsList = new Array<string[] | null>(this.col.length).fill(null);
60
87
  this._lengthsList = new Array<number[] | null>(this.col.length).fill(null);
61
88
  this._editorList = new Array<IEditor | null>(this.col.length).fill(null);
62
89
  }
63
90
 
91
+ private destroy(): void {
92
+ for (const sub of this.subs) sub.unsubscribe();
93
+ }
94
+
64
95
  /** Skips cell for the fallback rendering */
65
96
  public skipCell(rowIdx: number): void {
66
97
  this._allPartsList[rowIdx] = [];
@@ -80,8 +111,10 @@ export class HelmMonomerPlacer {
80
111
 
81
112
  private getCellMonomerLengthsForSeq(rowIdx: number): [string[], number[]] {
82
113
  let allParts: string[] | null = this._allPartsList![rowIdx];
83
- if (allParts === null)
84
- allParts = this._allPartsList![rowIdx] = this.getAllParts(rowIdx);
114
+ if (allParts === null) {
115
+ const seq = this.col.get(rowIdx);
116
+ allParts = this._allPartsList![rowIdx] = getAllParts(seq);
117
+ }
85
118
 
86
119
  let lengths: number[] | null = this._lengthsList![rowIdx];
87
120
  if (lengths === null) {
@@ -95,12 +128,6 @@ export class HelmMonomerPlacer {
95
128
  return [allParts, lengths];
96
129
  }
97
130
 
98
- getAllParts(rowIdx: number): string[] {
99
- const seq: string | null = this.col.get(rowIdx);
100
- const monomerList: string[] = seq ? parseHelm(seq) : [];
101
- return seq ? getParts(monomerList, seq) : [];
102
- }
103
-
104
131
  getMonomer(monomer: ISeqMonomer): Monomer | null {
105
132
  let res: Monomer | null = null;
106
133
  if (monomer.polymerType)
@@ -117,15 +144,14 @@ export class HelmMonomerPlacer {
117
144
  // -- Handle events --
118
145
 
119
146
  private monomerLibOnChanged(_value: any): void {
120
- this._lengthsList.fill(null);
121
- this._allPartsList.fill(null);
122
- this._editorList.fill(null);
123
- // TODO: Invalidate all grids of this.col.dataFrame
147
+ this.reset();
148
+ if (this.grid) this.grid.invalidate();
124
149
  }
125
150
 
126
- public static getOrCreate(col: DG.Column<string>): HelmMonomerPlacer {
127
- if (!(Temps.helmMonomerPlacer in col.temp)) col.temp[Temps.helmMonomerPlacer] = new HelmMonomerPlacer(col);
128
- return col.temp[Temps.helmMonomerPlacer];
151
+ public static getOrCreate(gridCol: DG.GridColumn, col: DG.Column<string>): HelmMonomerPlacer {
152
+ let res: HelmMonomerPlacer = gridCol.temp[Temps.helmMonomerPlacer];
153
+ if (!res) res = gridCol.temp[Temps.helmMonomerPlacer] = new HelmMonomerPlacer(gridCol, col);
154
+ return res;
129
155
  }
130
156
 
131
157
  public setEditor(tableRowIndex: number, editor: IEditor) {
@@ -136,3 +162,8 @@ export class HelmMonomerPlacer {
136
162
  return this._editorList![tableRowIndex];
137
163
  }
138
164
  }
165
+
166
+ export function getAllParts(seq: string | null): string[] {
167
+ const monomerList: string[] = seq ? parseHelm(seq) : [];
168
+ return seq ? getParts(monomerList, seq) : [];
169
+ }
@@ -8,6 +8,7 @@ import './tests/findMonomers-tests';
8
8
  import './tests/renderers-tests';
9
9
  import './tests/get-molfiles';
10
10
  import './tests/properties-widget-tests';
11
+ import './tests/get-all-parts-tests';
11
12
 
12
13
  export const _package = new DG.Package();
13
14
  export {tests};
package/src/package.ts CHANGED
@@ -7,7 +7,7 @@ import $ from 'cash-dom';
7
7
 
8
8
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
9
9
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
- import {GapSymbols, UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
10
+ import {GapOriginals, SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
11
11
  import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types';
12
12
  import {IHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
13
13
 
@@ -156,17 +156,17 @@ export function helmCellRenderer(): HelmCellRenderer {
156
156
  return new HelmCellRenderer();
157
157
  }
158
158
 
159
- function checkMonomersAndOpenWebEditor(cell?: DG.Cell, value?: string, units?: string) {
159
+ function checkMonomersAndOpenWebEditor(cell: DG.Cell, value?: string, units?: string) {
160
160
  const cellValue: string = !!cell && units === undefined ? cell.value : value;
161
161
  const monomerList: string[] = parseHelm(cellValue);
162
- const monomers = findMonomers(monomerList);
163
- if (monomers.size === 0)
162
+ const missedMonomerSet = findMonomers(monomerList);
163
+ if (missedMonomerSet.size === 0)
164
164
  webEditor(cell, value, units);
165
- else if (monomers.size === 1 && monomers.has(GapSymbols[NOTATION.HELM]))
166
- grok.shell.warning(`WebEditor doesn't support Helm with gaps '${GapSymbols[NOTATION.HELM]}'.`);
165
+ else if (missedMonomerSet.size === 1 && missedMonomerSet.has(GapOriginals[NOTATION.HELM]))
166
+ grok.shell.warning(`WebEditor doesn't support Helm with gaps '${GapOriginals[NOTATION.HELM]}'.`);
167
167
  else {
168
168
  grok.shell.warning(
169
- `Monomers ${Array.from(monomers).map((m) => `'${m}'`).join(', ')} are absent! <br/>` +
169
+ `Monomers ${Array.from(missedMonomerSet).map((m) => `'${m}'`).join(', ')} are absent! <br/>` +
170
170
  `Please, upload the monomer library! <br/>` +
171
171
  `<a href="https://datagrok.ai/help/domains/bio/macromolecules" target="_blank">Learn more</a>`);
172
172
  }
@@ -187,11 +187,11 @@ export function editMoleculeCell(cell: DG.GridCell): void {
187
187
  export function openEditor(mol: string): void {
188
188
  const df = grok.shell.tv.grid.dataFrame;
189
189
  const col = df.columns.bySemType('Macromolecule')!;
190
- const colUh = UnitsHandler.getOrCreate(col);
190
+ const colSh = SeqHandler.forColumn(col);
191
191
  const colUnits = col.getTag(DG.TAGS.UNITS);
192
192
  if (colUnits === NOTATION.HELM)
193
193
  checkMonomersAndOpenWebEditor(df.currentCell, undefined, undefined);
194
- const convert = colUh.getConverter(NOTATION.HELM);
194
+ const convert = colSh.getConverter(NOTATION.HELM);
195
195
  const helmMol = convert(mol);
196
196
  checkMonomersAndOpenWebEditor(df.currentCell, helmMol, col.getTag(DG.TAGS.UNITS));
197
197
  }
@@ -204,11 +204,13 @@ export function propertiesWidget(sequence: DG.SemanticValue): DG.Widget {
204
204
  return getPropertiesWidget(sequence);
205
205
  }
206
206
 
207
- function webEditor(cell?: DG.Cell, value?: string, units?: string) {
207
+ function webEditor(cell: DG.Cell, value?: string, units?: string) {
208
208
  const view = ui.div();
209
- const df = grok.shell.tv.grid.dataFrame;
210
- const col = df.columns.bySemType('Macromolecule')!;
211
- const uh = UnitsHandler.getOrCreate(col);
209
+ // const df = grok.shell.tv.grid.dataFrame;
210
+ // const col = df.columns.bySemType('Macromolecule')!;
211
+ const col = cell.column;
212
+ const sh = SeqHandler.forColumn(col);
213
+ const rowIdx = cell.rowIndex;
212
214
  // @ts-ignore
213
215
  org.helm.webeditor.MolViewer.molscale = 0.8;
214
216
  // @ts-ignore
@@ -244,13 +246,13 @@ function webEditor(cell?: DG.Cell, value?: string, units?: string) {
244
246
  ui.dialog({showHeader: false, showFooter: true})
245
247
  .add(view)
246
248
  .onOK(() => {
247
- const helmValue = app.canvas.getHelm(true).replace(/<\/span>/g, '')
249
+ const helmValue: string = app.canvas.getHelm(true).replace(/<\/span>/g, '')
248
250
  .replace(/<span style='background:#bbf;'>/g, '');
249
251
  if (!!cell) {
250
252
  if (units === undefined)
251
253
  cell.value = helmValue;
252
254
  else {
253
- const convertedRes = uh.convertHelmToFastaSeparator(helmValue, units!);
255
+ const convertedRes = sh.convertHelmToFastaSeparator(helmValue, units!, sh.separator);
254
256
  cell.value = convertedRes;
255
257
  }
256
258
  }
@@ -0,0 +1,34 @@
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 {after, before, category, delay, expect, test, expectArray} from '@datagrok-libraries/utils/src/test';
6
+
7
+ import {getAllParts} from '../helm-monomer-placer';
8
+
9
+ category('getAllParts', () => {
10
+ /** All parts of Helm string are required for fallback rendering. */
11
+
12
+ const tests: { [testName: string]: { tgt: string[]; seq: string } } = {
13
+ 'peptideSimpleSingleChar': {
14
+ seq: 'PEPTIDE1{L.V.A}$$$$',
15
+ tgt: ['PEPTIDE1{', 'L', '.', 'V', '.', 'A', '}$$$$'],
16
+ },
17
+ 'peptideSimpleMultiChar': {
18
+ seq: 'PEPTIDE1{[Ac].V.A}$$$$',
19
+ /* doubt joining square brackets to separator or notation is correct */
20
+ tgt: ['PEPTIDE1{[', 'Ac', '].', 'V', '.', 'A', '}$$$$'],
21
+ }
22
+ };
23
+
24
+ for (const [testName, testData] of Object.entries(tests)) {
25
+ test(`${testName}`, async () => {
26
+ await _testAllParts(testData.seq, testData.tgt);
27
+ });
28
+ }
29
+ });
30
+
31
+ async function _testAllParts(seq: string | null, tgt: string[]): Promise<void> {
32
+ const res: string[] = getAllParts(seq);
33
+ expectArray(res, tgt);
34
+ }
@@ -2,9 +2,11 @@ import * as ui from 'datagrok-api/ui';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
  import * as grok from 'datagrok-api/grok';
4
4
 
5
- import {category, expect, expectObject, test} from '@datagrok-libraries/utils/src/test';
5
+ import {after, before, category, expect, expectObject, test} from '@datagrok-libraries/utils/src/test';
6
6
  import {ALPHABET, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
7
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
7
+ import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
8
+ import {getUserLibSettings, setUserLibSettings, setUserLibSettingsForTests} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
9
+ import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
8
10
 
9
11
  import {getPropertiesDict} from '../widgets/properties-widget';
10
12
 
@@ -60,6 +62,22 @@ const TestsData: {
60
62
  };
61
63
 
62
64
  category('properties-widget', () => {
65
+ let monomerLibHelper: IMonomerLibHelper;
66
+ /** Backup actual user's monomer libraries settings */
67
+ let userLibSettings: any = null;
68
+
69
+ before(async () => {
70
+ monomerLibHelper = await getMonomerLibHelper();
71
+ userLibSettings = getUserLibSettings();
72
+ await setUserLibSettingsForTests();
73
+ await monomerLibHelper.loadLibraries(true); // load default libraries
74
+ });
75
+
76
+ after(async () => {
77
+ await setUserLibSettings(userLibSettings);
78
+ await monomerLibHelper.loadLibraries(true); // load user settings libraries
79
+ });
80
+
63
81
  for (const [testName, testData] of Object.entries(TestsData)) {
64
82
  test(testName, async () => {
65
83
  testPropertiesDict(testData.seq, testData.units, testData.separator, testData.alphabet, testData.res);
@@ -75,7 +93,7 @@ function testPropertiesDict(
75
93
  col.setTag(DG.TAGS.UNITS, units);
76
94
  if (separator) col.setTag(bioTAGS.separator, separator);
77
95
  if (alphabet) col.setTag(bioTAGS.alphabet, alphabet);
78
- const uh = UnitsHandler.getOrCreate(col);
96
+ const uh = SeqHandler.forColumn(col);
79
97
  const actPropDict = getPropertiesDict(seq, uh);
80
98
  expectObject(actPropDict, expPropDict);
81
99
  }
@@ -4,13 +4,13 @@ import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import $ from 'cash-dom';
6
6
 
7
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
7
+ import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
8
8
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
9
9
 
10
10
  import {removeGapsFromHelm} from '../utils';
11
11
 
12
- export function getPropertiesDict(seq: string, uh: UnitsHandler): {} {
13
- const helmString = uh.isHelm() ? seq : uh.getConverter(NOTATION.HELM)(seq);
12
+ export function getPropertiesDict(seq: string, sh: SeqHandler): {} {
13
+ const helmString = sh.isHelm() ? seq : sh.getConverter(NOTATION.HELM)(seq);
14
14
  // Remove gap symbols for compatibility with WebEditor
15
15
  const helmString2 = removeGapsFromHelm(helmString);
16
16
 
@@ -36,8 +36,8 @@ export function getPropertiesDict(seq: string, uh: UnitsHandler): {} {
36
36
 
37
37
 
38
38
  export function getPropertiesWidget(value: DG.SemanticValue): DG.Widget {
39
- const uh = UnitsHandler.getOrCreate(value.cell.column);
40
- const propDict = getPropertiesDict(value.value, uh);
39
+ const sh = SeqHandler.forColumn(value.cell.column);
40
+ const propDict = getPropertiesDict(value.value, sh);
41
41
  return new DG.Widget(
42
42
  ui.tableFromMap(propDict)
43
43
  );