@datagrok/helm 2.1.17 → 2.1.19

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.17",
4
+ "version": "2.1.19",
5
5
  "author": {
6
6
  "name": "Oleksandra Serhiienko",
7
7
  "email": "oserhiienko@datagrok.ai"
@@ -15,7 +15,7 @@
15
15
  "helm/JSDraw/Pistoia.HELM-uncompressed.js"
16
16
  ],
17
17
  "dependencies": {
18
- "@datagrok-libraries/bio": "^5.38.0",
18
+ "@datagrok-libraries/bio": "^5.38.8",
19
19
  "@datagrok-libraries/utils": "^4.0.17",
20
20
  "cash-dom": "^8.1.1",
21
21
  "datagrok-api": "^1.10.2",
@@ -8,7 +8,9 @@ import {printLeftOrCentered} from '@datagrok-libraries/bio/src/utils/cell-render
8
8
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
9
9
 
10
10
  import {findMonomers, parseHelm} from './utils';
11
- import {HelmMonomerPlacer} from './helm-monomer-placer';
11
+ import {IEditor, HelmMonomerPlacer} from './helm-monomer-placer';
12
+
13
+ // import {_package} from './package'; // NullError: method not found: '_package' on null
12
14
 
13
15
  const enum tempTAGS {
14
16
  helmSumMaxLengthWords = 'helm-sum-maxLengthWords',
@@ -22,7 +24,120 @@ const helmGapStartRe = /\{(\*\.)+/g;
22
24
  const helmGapIntRe = /\.(\*\.)+/g;
23
25
  const helmGapEndRe = /(\.\*)+\}/g;
24
26
 
25
- type TempType = { [tagName: string]: any };
27
+ function getHoveredMonomerFromEditor(
28
+ argsX: number, argsY: number, gridCell: DG.GridCell, editor: IEditor
29
+ ): string | null {
30
+ let hoveredMonomerSymbol: string | null = null;
31
+
32
+ /** @return {[number, number]} [atom, distance] */
33
+ function getNearest(excluded: (number | undefined)[]): [number | undefined, number | undefined] {
34
+ let atom: number | undefined = undefined;
35
+ let distance: number | undefined = undefined;
36
+ for (let atomI = 0; atomI < editor.m.atoms.length; ++atomI) {
37
+ if (!excluded.includes(atomI)) {
38
+ const aX: number = editor.m.atoms[atomI].p.x;
39
+ const aY: number = editor.m.atoms[atomI].p.y;
40
+ const distanceToAtomI: number = Math.sqrt((argsX - aX) ** 2 + (argsY - aY) ** 2);
41
+ if (distance === undefined || distance > distanceToAtomI) {
42
+ atom = atomI;
43
+ distance = distanceToAtomI;
44
+ }
45
+ }
46
+ }
47
+ return [atom, distance];
48
+ }
49
+
50
+ const [firstAtomI, firstDistance] = getNearest([]);
51
+ const [secondAtomI, secondDistance] = getNearest([firstAtomI]);
52
+
53
+ // for (let atomI = 0; atomI < editor.m.atoms.length; ++atomI) {
54
+ // const aX: number = editor.m.atoms[atomI].p.x;
55
+ // const aY: number = editor.m.atoms[atomI].p.y;
56
+ // const distanceToAtomI: number = Math.sqrt((argsX - aX) ** 2 + (argsY - aY) ** 2);
57
+ // if (firstDistance === undefined || firstDistance > distanceToAtomI) {
58
+ // secondAtomI = firstAtomI;
59
+ // firstAtomI = atomI;
60
+ //
61
+ // secondDistance = firstDistance;
62
+ // firstDistance = distanceToAtomI;
63
+ // }
64
+ // }
65
+
66
+ if (firstAtomI !== undefined && firstDistance !== undefined) {
67
+ if (secondAtomI !== undefined && secondDistance !== undefined) {
68
+ if (firstDistance < secondDistance * 0.45)
69
+ hoveredMonomerSymbol = editor.m.atoms[firstAtomI].elem;
70
+ } else {
71
+ if (firstDistance < 0.35 * gridCell.bounds.height)
72
+ hoveredMonomerSymbol = editor.m.atoms[firstAtomI].elem;
73
+ }
74
+ }
75
+ return hoveredMonomerSymbol;
76
+ }
77
+
78
+ function getHoveredMonomerFallback(
79
+ argsX: number, _argsY: number, gridCell: DG.GridCell, helmPlacer: HelmMonomerPlacer
80
+ ): string | null {
81
+ let hoveredMonomerSymbol: string | null = null;
82
+ const [allParts, lengths, sumLengths] = helmPlacer.getCellAllPartsLengths(gridCell.tableRowIndex!);
83
+ const maxIndex = Object.values(lengths).length - 1;
84
+ let left = 0;
85
+ let right = maxIndex;
86
+ let found = false;
87
+ let iterCount: number = 0;
88
+
89
+ let mid = 0;
90
+ if (argsX > sumLengths[0]) {
91
+ while (!found && iterCount < sumLengths.length) {
92
+ mid = Math.floor((right + left) / 2);
93
+ if (argsX >= sumLengths[mid] && argsX <= sumLengths[mid + 1]) {
94
+ left = mid;
95
+ found = true;
96
+ } else if (argsX < sumLengths[mid])
97
+ right = mid - 1;
98
+ else if (argsX > sumLengths[mid + 1])
99
+ left = mid + 1;
100
+
101
+ if (left == right)
102
+ found = true;
103
+
104
+ iterCount++;
105
+ }
106
+ }
107
+ left = (argsX >= sumLengths[left]) ? left : left - 1; // correct left to between sumLengths
108
+ if (left >= 0)
109
+ hoveredMonomerSymbol = allParts[left];
110
+ return hoveredMonomerSymbol;
111
+ }
112
+
113
+ // window.dojox.gfx.svg.Text.prototype.getTextWidth hangs
114
+ // /** get the text width in pixels */
115
+ // // @ts-ignore
116
+ // window.dojox.gfx.svg.Text.prototype.getTextWidth = function() {
117
+ // const rawNode = this.rawNode;
118
+ // const oldParent = rawNode.parentNode;
119
+ // const _measurementNode = rawNode.cloneNode(true);
120
+ // _measurementNode.style.visibility = 'hidden';
121
+ //
122
+ // // solution to the "orphan issue" in FF
123
+ // let _width = 0;
124
+ // const _text = _measurementNode.firstChild.nodeValue;
125
+ // oldParent.appendChild(_measurementNode);
126
+ //
127
+ // // solution to the "orphan issue" in Opera
128
+ // // (nodeValue == "" hangs firefox)
129
+ // if (_text != '') {
130
+ // while (!_width) { // <-- hangs
131
+ // //Yang: work around svgweb bug 417 -- http://code.google.com/p/svgweb/issues/detail?id=417
132
+ // if (_measurementNode.getBBox)
133
+ // _width = parseInt(_measurementNode.getBBox().width);
134
+ // else
135
+ // _width = 68;
136
+ // }
137
+ // }
138
+ // oldParent.removeChild(_measurementNode);
139
+ // return _width;
140
+ // };
26
141
 
27
142
  /** Helm cell renderer in case of no missed monomer draws with JSDraw2.Editor (webeditor),
28
143
  * in case of missed monomers presented, draws linear sequences aligned in width per monomer.
@@ -43,65 +158,63 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
43
158
  try { tableCol = gridCell.tableColumn; } catch { }
44
159
  if (!tableCol) return;
45
160
 
46
- const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
47
- const [allParts, lengths, sumLengths] = helmPlacer.getCellAllPartsLengths(gridCell.tableRowIndex!);
48
-
49
- const maxIndex = Object.values(lengths).length - 1;
50
161
  const argsX = e.offsetX - gridCell.bounds.x;
51
- let left = 0;
52
- let right = maxIndex;
53
- let found = false;
54
- let iterCount: number = 0;
55
-
56
- let mid = 0;
57
- if (argsX > sumLengths[0]) {
58
- while (!found && iterCount < sumLengths.length) {
59
- mid = Math.floor((right + left) / 2);
60
- if (argsX >= sumLengths[mid] && argsX <= sumLengths[mid + 1]) {
61
- left = mid;
62
- found = true;
63
- } else if (argsX < sumLengths[mid])
64
- right = mid - 1;
65
- else if (argsX > sumLengths[mid + 1])
66
- left = mid + 1;
67
-
68
- if (left == right)
69
- found = true;
70
-
71
- iterCount++;
72
- }
162
+ const argsY = e.offsetY - gridCell.bounds.y;
163
+
164
+ const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
165
+ const editor: IEditor | null = helmPlacer.getEditor(gridCell.tableRowIndex!);
166
+ const hoveredMonomerSymbol = editor ? getHoveredMonomerFromEditor(argsX, argsY, gridCell, editor) :
167
+ getHoveredMonomerFallback(argsX, argsY, gridCell, helmPlacer);
168
+ if (!hoveredMonomerSymbol) {
169
+ ui.tooltip.hide();
170
+ return;
73
171
  }
74
- left = (argsX >= sumLengths[left]) ? left : left - 1; // correct left to between sumLengths
75
172
 
76
173
  const seq: string = !gridCell.cell.value ? '' : gridCell.cell.value
77
174
  .replaceAll(helmGapStartRe, '{').replaceAll(helmGapIntRe, '.').replaceAll(helmGapEndRe, '}')
78
175
  .replace('{*}', '{}');
79
176
  const monomerList = parseHelm(seq);
80
- const monomers = new Set<string>(monomerList);
81
177
  const missedMonomers = findMonomers(monomerList);
82
178
 
83
- const tooltipMessage: HTMLElement[] = [];
84
- for (const [part, partI] of wu.enumerate(allParts)) {
85
- if (missedMonomers.has(part)) {
86
- tooltipMessage[partI] = ui.divV([
87
- ui.divText(`Monomer ${allParts[partI]} not found.`),
88
- ui.divText('Open the Context Panel, then expand Manage Libraries')
89
- ]);
90
- } else if (monomers.has(part)) {
91
- const elList = [ui.div(part)];
92
- const monomer = helmPlacer.getMonomer(part);
93
- if (monomer) {
94
- const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
95
- const monomerSvg = grok.chem.svgMol(monomer.smiles, undefined, undefined, options);
96
- elList.push(monomerSvg);
97
- }
98
- tooltipMessage[partI] = ui.divV(elList);
179
+ if (missedMonomers.has(hoveredMonomerSymbol)) {
180
+ ui.tooltip.show(ui.divV([
181
+ ui.divText(`Monomer ${hoveredMonomerSymbol} not found.`),
182
+ ui.divText('Open the Context Panel, then expand Manage Libraries'),
183
+ ]), e.x + 16, e.y + 16);
184
+ } else {
185
+ const monomer = helmPlacer.getMonomer(hoveredMonomerSymbol);
186
+ if (monomer) {
187
+ const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
188
+ const monomerSvg = grok.chem.svgMol(monomer.smiles, undefined, undefined, options);
189
+ ui.tooltip.show(ui.divV([
190
+ ui.divText(hoveredMonomerSymbol),
191
+ monomerSvg,
192
+ ]), e.x + 16, e.y + 16);
99
193
  }
100
194
  }
101
195
 
102
- (((tooltipMessage[left]?.childNodes.length ?? 0) > 0)) ?
103
- ui.tooltip.show(tooltipMessage[left], e.x + 16, e.y + 16) :
104
- ui.tooltip.hide();
196
+ // const tooltipMessage: HTMLElement[] = [];
197
+ // for (const [part, partI] of wu.enumerate(allParts)) {
198
+ // if (missedMonomers.has(part)) {
199
+ // tooltipMessage[partI] = ui.divV([
200
+ // ui.divText(`Monomer ${allParts[partI]} not found.`),
201
+ // ui.divText('Open the Context Panel, then expand Manage Libraries')
202
+ // ]);
203
+ // } else if (monomers.has(part)) {
204
+ // const elList = [ui.div(part)];
205
+ // const monomer = helmPlacer.getMonomer(part);
206
+ // if (monomer) {
207
+ // const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
208
+ // const monomerSvg = grok.chem.svgMol(monomer.smiles, undefined, undefined, options);
209
+ // elList.push(monomerSvg);
210
+ // }
211
+ // tooltipMessage[partI] = ui.divV(elList);
212
+ // }
213
+ // }
214
+ //
215
+ // (((tooltipMessage[left]?.childNodes.length ?? 0) > 0)) ?
216
+ // ui.tooltip.show(tooltipMessage[left], e.x + 16, e.y + 16) :
217
+ // ui.tooltip.hide();
105
218
  } catch (err: any) {
106
219
  const errMsg: string = errorToConsole(err);
107
220
  console.error('Helm: HelmCellRenderer.onMouseMove() error:\n' + errMsg);
@@ -132,13 +245,16 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
132
245
  const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
133
246
 
134
247
  if (missedMonomers.size == 0) {
135
- helmPlacer.skipCell(gridCell.tableRowIndex!);
136
- const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
248
+ // Recreate host to avoid hanging in window.dojox.gfx.svg.Text.prototype.getTextWidth
249
+ const host = gridCell.element = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
137
250
  host.setAttribute('dataformat', 'helm');
138
251
  host.setAttribute('data', seq /* gaps skipped */);
139
- gridCell.element = host;
140
- //@ts-ignore
141
- const canvas = new JSDraw2.Editor(host, {width: w, height: h, skin: 'w8', viewonly: true});
252
+
253
+ // Recreate editor to avoid hanging in window.dojox.gfx.svg.Text.prototype.getTextWidth
254
+ const editor = new JSDraw2.Editor(host, {width: w, height: h, skin: 'w8', viewonly: true}) as IEditor;
255
+ helmPlacer.setEditor(gridCell.tableRowIndex!, editor);
256
+
257
+ helmPlacer.skipCell(gridCell.tableRowIndex!);
142
258
  return;
143
259
  }
144
260
 
@@ -13,9 +13,32 @@ export const enum Temps {
13
13
  helmMonomerPlacer = 'bio-helmMonomerPlacer',
14
14
  }
15
15
 
16
+ export interface IEditor {
17
+ get m(): IEditorMol;
18
+
19
+ redraw(): void;
20
+ }
21
+
22
+ export interface IEditorMol {
23
+ get atoms(): IEditorMolAtom[];
24
+ }
25
+
26
+ export interface IEditorMolAtom {
27
+ get p(): IEditorPoint;
28
+
29
+ get elem(): string;
30
+ }
31
+
32
+ export interface IEditorPoint {
33
+ get x(): number;
34
+
35
+ get y(): number;
36
+ }
37
+
16
38
  export class HelmMonomerPlacer {
17
- private _allPartsList: (string[] | null)[] | null = null;
18
- private _lengthsList: (number[] | null) [] | null = null;
39
+ private _allPartsList: (string[] | null)[];
40
+ private _lengthsList: (number[] | null)[];
41
+ private _editorList: (IEditor | null)[];
19
42
 
20
43
  private monomerLib: IMonomerLib;
21
44
 
@@ -26,25 +49,20 @@ export class HelmMonomerPlacer {
26
49
  this.col.dataFrame.onDataChanged.subscribe();
27
50
  this.monomerLib = getMonomerLib();
28
51
  this.monomerLib.onChanged.subscribe(this.monomerLibOnChanged.bind(this));
52
+
53
+ this._allPartsList = new Array<string[] | null>(this.col.length).fill(null);
54
+ this._lengthsList = new Array<number[] | null>(this.col.length).fill(null);
55
+ this._editorList = new Array<IEditor | null>(this.col.length).fill(null);
29
56
  }
30
57
 
58
+ /** Skips cell for the fallback rendering */
31
59
  public skipCell(rowIdx: number): void {
32
- if (this._allPartsList === null)
33
- this._allPartsList = new Array<string[] | null>(this.col.length).fill(null);
34
- if (this._lengthsList === null)
35
- this._lengthsList = new Array<number[] | null>(this.col.length).fill(null);
36
-
37
60
  this._allPartsList[rowIdx] = [];
38
61
  this._lengthsList[rowIdx] = [];
39
62
  }
40
63
 
41
64
  /** @param rowIdx Row index of the table {@link DG.DataFrame}, HelmMonomerPlacer is {@link DG.Column} based */
42
65
  public getCellAllPartsLengths(rowIdx: number): [string[], number[], number[]] {
43
- if (this._allPartsList === null)
44
- this._allPartsList = new Array<string[] | null>(this.col.length).fill(null);
45
- if (this._lengthsList === null)
46
- this._lengthsList = new Array<number[] | null>(this.col.length).fill(null);
47
-
48
66
  const [allParts, lengths] = this.getCellMonomerLengthsForSeq(rowIdx);
49
67
 
50
68
  const sumLengths: number[] = new Array<number>(lengths.length + 1);
@@ -89,8 +107,9 @@ export class HelmMonomerPlacer {
89
107
  // -- Handle events --
90
108
 
91
109
  private monomerLibOnChanged(_value: any): void {
92
- this._lengthsList = null;
93
- this._allPartsList = null;
110
+ this._lengthsList.fill(null);
111
+ this._allPartsList.fill(null);
112
+ this._editorList.fill(null);
94
113
  // TODO: Invalidate all grids of this.col.dataFrame
95
114
  }
96
115
 
@@ -98,4 +117,12 @@ export class HelmMonomerPlacer {
98
117
  if (!(Temps.helmMonomerPlacer in col.temp)) col.temp[Temps.helmMonomerPlacer] = new HelmMonomerPlacer(col);
99
118
  return col.temp[Temps.helmMonomerPlacer];
100
119
  }
120
+
121
+ public setEditor(tableRowIndex: number, editor: IEditor) {
122
+ this._editorList![tableRowIndex] = editor;
123
+ }
124
+
125
+ public getEditor(tableRowIndex: number): IEditor | null {
126
+ return this._editorList![tableRowIndex];
127
+ }
101
128
  }
package/src/package.ts CHANGED
@@ -3,21 +3,26 @@ import * as grok from 'datagrok-api/grok';
3
3
  import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
 
6
- import {WebEditorMonomer, RGROUP_CAP_GROUP_NAME, RGROUP_LABEL, SMILES} from './constants';
7
- import {HelmWebEditor} from './helm-web-editor';
8
- import {HelmCellRenderer} from './cell-renderer';
9
- import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types';
10
- import {GapSymbols, UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
11
- import {findMonomers, parseHelm} from './utils';
12
6
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
13
7
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
8
+ import {GapSymbols, UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
9
+ import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types';
10
+
11
+ import {findMonomers, parseHelm} from './utils';
12
+ import {HelmWebEditor} from './helm-web-editor';
13
+ import {HelmCellRenderer} from './cell-renderer';
14
14
 
15
15
  let monomerLib: IMonomerLib | null = null;
16
16
 
17
+ import {WebEditorMonomer, RGROUP_CAP_GROUP_NAME, RGROUP_LABEL, SMILES} from './constants';
18
+
17
19
  export const _package = new DG.Package();
18
20
 
19
21
  //tags: init
20
22
  export async function initHelm(): Promise<void> {
23
+ //@ts-ignore
24
+ org.helm.webeditor.kCaseSensitive = true; // GROK-13880
25
+
21
26
  return Promise.all([
22
27
  new Promise((resolve, reject) => {
23
28
  // @ts-ignore
@@ -59,6 +64,7 @@ export function getMonomerLib(): IMonomerLib {
59
64
  return monomerLib!;
60
65
  }
61
66
 
67
+ /** Fills org.helm.webeditor.Monomers dictionary for WebEditor */
62
68
  function rewriteLibraries() {
63
69
  // @ts-ignore
64
70
  org.helm.webeditor.Monomers.clear();
package/src/utils.ts CHANGED
@@ -94,9 +94,8 @@ export function parseHelm(s: string): string[] {
94
94
  // return new Set(helmPartList.filter((val) => !monomerNameList.includes(val)));
95
95
  // }
96
96
 
97
- /** Searches monomers of {@link helmString} for missed. */
97
+ /** Searches monomers of helmString for missed. */
98
98
  export function findMonomers(monomerSymbolList: string[]): Set<string> {
99
- //@ts-ignore
100
99
  const types = Object.keys(org.helm.webeditor.monomerTypeList());
101
100
  const monomers: any = [];
102
101
  const monomerNames: any = [];