@datagrok/helm 2.1.34 → 2.2.1

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,150 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as DG from 'datagrok-api/dg';
4
+
5
+ import * as org from 'org';
6
+ import * as scil from 'scil';
7
+ import * as JSDraw2 from 'JSDraw2';
8
+ import Atom = JSDraw2.Atom;
9
+
10
+ import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
11
+ import {HelmServiceBase} from '@datagrok-libraries/bio/src/viewers/helm-service';
12
+ import {IMonomerLib, HelmType} from '@datagrok-libraries/bio/src/types';
13
+ import {ILogger} from '@datagrok-libraries/bio/src/utils/logger';
14
+
15
+ import {HelmService} from './utils/helm-service';
16
+ import {GetMonomerFunc, GetMonomerResType, getWebEditorMonomer} from './utils/get-monomer';
17
+
18
+
19
+ type HelmWindowType = Window & {
20
+ $helmService?: HelmServiceBase,
21
+ }
22
+ declare const window: HelmWindowType;
23
+
24
+ export function _getHelmService(): HelmServiceBase {
25
+ let res = window.$helmService;
26
+ if (!res) res = window.$helmService = new HelmService();
27
+ return res;
28
+ }
29
+
30
+ export function initHelmPatchDojo(): void {
31
+ // patch window.dojox.gfx.svg.Text.prototype.getTextWidth hangs
32
+ /** get the text width in pixels */
33
+ // @ts-ignore
34
+ window.dojox.gfx.svg.Text.prototype.getTextWidth = function() {
35
+ const rawNode = this.rawNode;
36
+ const oldParent = rawNode.parentNode;
37
+ const _measurementNode = rawNode.cloneNode(true);
38
+ _measurementNode.style.visibility = 'hidden';
39
+
40
+ // solution to the "orphan issue" in FF
41
+ let _width = 0;
42
+ const _text = _measurementNode.firstChild.nodeValue;
43
+ oldParent.appendChild(_measurementNode);
44
+
45
+ // solution to the "orphan issue" in Opera
46
+ // (nodeValue == "" hangs firefox)
47
+ if (_text != '') {
48
+ let watchdogCounter = 100;
49
+ while (!_width && --watchdogCounter > 0) { // <-- hangs
50
+ //Yang: work around svgweb bug 417 -- http://code.google.com/p/svgweb/issues/detail?id=417
51
+ if (_measurementNode.getBBox)
52
+ _width = parseInt(_measurementNode.getBBox().width);
53
+ else
54
+ _width = 68;
55
+ }
56
+ }
57
+ oldParent.removeChild(_measurementNode);
58
+ return _width;
59
+ };
60
+ }
61
+
62
+ export const helmJsonReplacer = (key: string, value: any): any => {
63
+ switch (key) {
64
+ case '_parent': {
65
+ return `${value.toString()}`;
66
+ }
67
+ default:
68
+ return value;
69
+ }
70
+ };
71
+
72
+ export class HelmPackage extends DG.Package {
73
+ public getMonomerOriginal: GetMonomerFunc;
74
+ public alertOriginal: (s: string) => void;
75
+
76
+ /** Patches Pistoia Monomers.getMonomer method to utilize DG Bio monomer Lib */
77
+ public initHelmPatchPistoia(monomerLib: IMonomerLib, logger: ILogger): void {
78
+ const logPrefix: string = 'Helm: initHelmPatchPistoia()';
79
+ const monomers = org.helm.webeditor.Monomers;
80
+
81
+ this.getMonomerOriginal = monomers.getMonomer.bind(monomers);
82
+ this.logger.debug(`${logPrefix}, this.getMonomerOriginal stored`);
83
+
84
+ this.alertOriginal = scil.Utils.alert;
85
+
86
+ org.helm.webeditor.Monomers.getMonomer = (
87
+ a: Atom<HelmType> | HelmType, name: string
88
+ ): GetMonomerResType => {
89
+ const logPrefixInt = `${logPrefix}, org.helm.webeditor.Monomers.getMonomer()`;
90
+ try {
91
+ // logger.debug(`${logPrefixInt}, a: ${JSON.stringify(a, helmJsonReplacer)}, name: '${name}'`);
92
+
93
+ // Creates monomers in lib
94
+ const dgWem = getWebEditorMonomer(monomerLib, a, name);
95
+
96
+ // // Returns null for gap
97
+ // const oWem = this.getMonomerOriginal(a, name);
98
+ // if (!oWem)
99
+ // logger.warning(`${logPrefixInt}, getMonomerOriginal( a: ${a}, name: ${name}) returns null`);
100
+ return dgWem; //dgWem;
101
+ } catch (err) {
102
+ const [errMsg, errStack] = errInfo(err);
103
+ logger.error(`${logPrefixInt}, Error: ${errMsg}`, undefined, errStack);
104
+ throw err;
105
+ }
106
+ };
107
+ scil.Utils.alert = (s: string): void => {
108
+ logger.warning(`${logPrefix}, scil.Utils.alert() s = 's'.`);
109
+ };
110
+
111
+ // @ts-ignore, intercept with proxy to observe access and usage
112
+ org.helm.webeditor.Monomers = new class {
113
+ constructor(base: org.helm.IMonomers) {
114
+ return new Proxy(base, {
115
+ get(target: any, p: string | symbol, _receiver: any): any {
116
+ return target[p];
117
+ },
118
+ set(target: any, p: string | symbol, newValue: any, _receiver: any): boolean {
119
+ if (p != 'sugars' && p != 'linkers' && p != 'bases' && p != 'aas') {
120
+ const k = 11;
121
+ }
122
+ target[p] = newValue;
123
+ return true;
124
+ },
125
+ apply(target: any, thisArg: any, argArray: any[]): any {
126
+ return target[thisArg](...argArray);
127
+ },
128
+ });
129
+ }
130
+ }(org.helm.webeditor.Monomers) as org.helm.IMonomers;
131
+
132
+ // @ts-ignore, intercept with proxy to observe access and usage
133
+ org.helm.webeditor = new class {
134
+ constructor(base: org.helm.IOrgHelmWebEditor) {
135
+ return new Proxy(base, {
136
+ get(target: any, p: string | symbol, _receiver: any): any {
137
+ return target[p];
138
+ },
139
+ set(target: any, p: string | symbol, newValue: any, _receiver: any): boolean {
140
+ target[p] = newValue;
141
+ return true;
142
+ },
143
+ apply(target: any, thisArg: any, argArray: any[]): any {
144
+ return target[thisArg](...argArray);
145
+ },
146
+ });
147
+ }
148
+ }(org.helm.webeditor) as org.helm.IOrgHelmWebEditor;
149
+ }
150
+ }
package/src/package.ts CHANGED
@@ -3,6 +3,14 @@ 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 '@datagrok-libraries/bio/src/types/dojo';
7
+ // import * as dojo from 'DOJO';
8
+ import '@datagrok-libraries/bio/src/types/helm';
9
+ import '@datagrok-libraries/bio/src/types/jsdraw2';
10
+ import * as scil from 'scil';
11
+ import * as org from 'org';
12
+ import * as JSDraw2 from 'JSDraw2';
13
+
6
14
  import $ from 'cash-dom';
7
15
 
8
16
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
@@ -10,78 +18,61 @@ import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
18
  import {GapOriginals, SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
11
19
  import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types';
12
20
  import {IHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
21
+ import {HelmServiceBase} from '@datagrok-libraries/bio/src/viewers/helm-service';
13
22
 
14
- import {findMonomers, parseHelm} from './utils';
15
23
  import {HelmCellRenderer} from './cell-renderer';
16
24
  import {HelmHelper} from './helm-helper';
17
25
  import {getPropertiesWidget} from './widgets/properties-widget';
26
+ import {HelmGridCellRenderer, HelmGridCellRendererBack} from './utils/helm-grid-cell-renderer';
27
+ import {_getHelmService, HelmPackage, initHelmPatchDojo} from './package-utils';
18
28
 
19
29
  let monomerLib: IMonomerLib | null = null;
20
30
 
21
- import {WebEditorMonomer, RGROUP_CAP_GROUP_NAME, RGROUP_LABEL, SMILES} from './constants';
22
-
23
- export const _package = new DG.Package();
24
-
25
- function initHelmPatchDojo(): void {
26
- // patch window.dojox.gfx.svg.Text.prototype.getTextWidth hangs
27
- /** get the text width in pixels */
28
- // @ts-ignore
29
- window.dojox.gfx.svg.Text.prototype.getTextWidth = function() {
30
- const rawNode = this.rawNode;
31
- const oldParent = rawNode.parentNode;
32
- const _measurementNode = rawNode.cloneNode(true);
33
- _measurementNode.style.visibility = 'hidden';
34
-
35
- // solution to the "orphan issue" in FF
36
- let _width = 0;
37
- const _text = _measurementNode.firstChild.nodeValue;
38
- oldParent.appendChild(_measurementNode);
39
-
40
- // solution to the "orphan issue" in Opera
41
- // (nodeValue == "" hangs firefox)
42
- if (_text != '') {
43
- let watchdogCounter = 100;
44
- while (!_width && --watchdogCounter > 0) { // <-- hangs
45
- //Yang: work around svgweb bug 417 -- http://code.google.com/p/svgweb/issues/detail?id=417
46
- if (_measurementNode.getBBox)
47
- _width = parseInt(_measurementNode.getBBox().width);
48
- else
49
- _width = 68;
50
- }
51
- }
52
- oldParent.removeChild(_measurementNode);
53
- return _width;
54
- };
55
- }
31
+ export const _package = new HelmPackage();
56
32
 
57
33
  //tags: init
58
34
  export async function initHelm(): Promise<void> {
59
- //@ts-ignore
35
+ const logPrefix: string = 'Helm: initHelm()';
36
+ _package.logger.debug(`${logPrefix}, start`);
60
37
  org.helm.webeditor.kCaseSensitive = true; // GROK-13880
61
38
 
62
- return Promise.all([
39
+ await Promise.all([
63
40
  new Promise((resolve, reject) => {
64
41
  // @ts-ignore
65
42
  dojo.ready(function() { resolve(null); });
66
43
  }).then(() => {
67
44
  initHelmPatchDojo();
68
45
  }),
69
- grok.functions.call('Bio:getBioLib'),
46
+ (async () => {
47
+ const libHelper = await getMonomerLibHelper();
48
+ return libHelper.getBioLib();
49
+ })()
70
50
  ])
71
51
  .then(([_, lib]: [unknown, IMonomerLib]) => {
52
+ _package.logger.debug(`${logPrefix}, then(), lib loaded`);
72
53
  monomerLib = lib;
73
54
  rewriteLibraries(); // initHelm()
55
+ _package.initHelmPatchPistoia(monomerLib, _package.logger);
56
+
74
57
  monomerLib.onChanged.subscribe((_) => {
75
58
  try {
76
- rewriteLibraries(); // initHelm()
59
+ const libSummary = monomerLib!.getSummary();
60
+ const isLibEmpty = Object.keys(libSummary).length == 0;
61
+ const libSummaryLog = isLibEmpty ? 'empty' : Object.entries(libSummary)
62
+ .map(([pt, count]) => `${pt}: ${count}`)
63
+ .join(', ');
64
+ const logPrefixInt = `${logPrefix} monomerLib.onChanged()`;
65
+ _package.logger.debug(`${logPrefixInt}, start, lib: { ${libSummaryLog} }`);
77
66
 
78
- const monTypeList: string[] = monomerLib!.getPolymerTypes();
79
- const msgStr: string = 'Monomer lib updated:<br />' + (
80
- monTypeList.length == 0 ? 'empty' : monTypeList.map((monType) => {
81
- return `${monType} ${monomerLib!.getMonomerSymbolsByType(monType).length}`;
82
- }).join('<br />'));
67
+ const libSummaryHtml = isLibEmpty ? 'empty' : Object.entries(libSummary)
68
+ .map(([pt, count]) => `${pt} ${count}`)
69
+ .join('<br />');
70
+ const libMsg: string = `Monomer lib updated:<br /> ${libSummaryHtml}`;
71
+ grok.shell.info(libMsg);
83
72
 
84
- grok.shell.info(msgStr);
73
+ _package.logger.debug(`${logPrefixInt}, org,helm.webeditor.Monomers updating ...`);
74
+ rewriteLibraries(); // initHelm()
75
+ _package.logger.debug(`${logPrefixInt}, end, org.helm.webeditor.Monomers completed`);
85
76
  } catch (err: any) {
86
77
  const errMsg = errorToConsole(err);
87
78
  console.error('Helm: initHelm monomerLib.onChanged() error:\n' + errMsg);
@@ -96,22 +87,22 @@ export async function initHelm(): Promise<void> {
96
87
  errRes.stack = err.stack;
97
88
  throw errRes;
98
89
  });
90
+ _package.logger.debug(`${logPrefix}, end`);
99
91
  }
100
92
 
101
- export function getMonomerLib(): IMonomerLib {
102
- return monomerLib!;
93
+ export function getMonomerLib(): IMonomerLib | null {
94
+ return monomerLib;
103
95
  }
104
96
 
105
97
  /** Fills org.helm.webeditor.Monomers dictionary for WebEditor */
106
98
  function rewriteLibraries() {
107
- // @ts-ignore
108
99
  org.helm.webeditor.Monomers.clear();
109
100
  monomerLib!.getPolymerTypes().forEach((polymerType) => {
110
101
  const monomerSymbols = monomerLib!.getMonomerSymbolsByType(polymerType);
111
102
  monomerSymbols.forEach((monomerSymbol) => {
112
103
  let isBroken = false;
113
104
  const monomer: Monomer = monomerLib!.getMonomer(polymerType, monomerSymbol)!;
114
- const webEditorMonomer: WebEditorMonomer = {
105
+ const webEditorMonomer: org.helm.WebEditorMonomer = {
115
106
  id: monomerSymbol,
116
107
  m: monomer.molfile,
117
108
  n: monomer.name,
@@ -123,6 +114,7 @@ function rewriteLibraries() {
123
114
  };
124
115
 
125
116
  if (monomer.rgroups.length > 0) {
117
+ // @ts-ignore
126
118
  webEditorMonomer.rs = monomer.rgroups.length;
127
119
  const at: { [prop: string]: any } = {};
128
120
  monomer.rgroups.forEach((it) => {
@@ -130,13 +122,13 @@ function rewriteLibraries() {
130
122
  });
131
123
  webEditorMonomer.at = at;
132
124
  } else if (monomer[SMILES] != null) {
125
+ // @ts-ignore
133
126
  webEditorMonomer.rs = Object.keys(getRS(monomer[SMILES].toString())).length;
134
127
  webEditorMonomer.at = getRS(monomer[SMILES].toString());
135
128
  } else
136
129
  isBroken = true;
137
130
 
138
131
  if (!isBroken) {
139
- // @ts-ignore
140
132
  org.helm.webeditor.Monomers.addOneMonomer(webEditorMonomer);
141
133
  }
142
134
  });
@@ -147,29 +139,24 @@ function rewriteLibraries() {
147
139
  if (grid) grid.invalidate();
148
140
  }
149
141
 
142
+ //name: getHelmService
143
+ //output: object result
144
+ export function getHelmService(): HelmServiceBase {
145
+ return _getHelmService();
146
+ }
147
+
150
148
  //name: helmCellRenderer
151
149
  //tags: cellRenderer
152
150
  //meta.cellType: helm
153
151
  //meta.columnTags: quality=Macromolecule, units=helm
154
152
  //output: grid_cell_renderer result
155
153
  export function helmCellRenderer(): HelmCellRenderer {
156
- return new HelmCellRenderer();
154
+ // return new HelmCellRenderer(); // old
155
+ return new HelmGridCellRenderer(); // new
157
156
  }
158
157
 
159
158
  function checkMonomersAndOpenWebEditor(cell: DG.Cell, value?: string, units?: string) {
160
- const cellValue: string = !!cell && units === undefined ? cell.value : value;
161
- const monomerList: string[] = parseHelm(cellValue);
162
- const missedMonomerSet = findMonomers(monomerList);
163
- if (missedMonomerSet.size === 0)
164
- webEditor(cell, value, units);
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
- else {
168
- grok.shell.warning(
169
- `Monomers ${Array.from(missedMonomerSet).map((m) => `'${m}'`).join(', ')} are absent! <br/>` +
170
- `Please, upload the monomer library! <br/>` +
171
- `<a href="https://datagrok.ai/help/domains/bio/macromolecules" target="_blank">Learn more</a>`);
172
- }
159
+ openWebEditor(cell, value, units);
173
160
  }
174
161
 
175
162
  //tags: cellEditor
@@ -186,7 +173,7 @@ export function editMoleculeCell(cell: DG.GridCell): void {
186
173
  //input: string mol { semType: Macromolecule }
187
174
  export function openEditor(mol: string): void {
188
175
  const df = grok.shell.tv.grid.dataFrame;
189
- const col = df.columns.bySemType('Macromolecule')!;
176
+ const col = df.columns.bySemType('Macromolecule')! as DG.Column<string>;
190
177
  const colSh = SeqHandler.forColumn(col);
191
178
  const colUnits = col.getTag(DG.TAGS.UNITS);
192
179
  if (colUnits === NOTATION.HELM)
@@ -204,17 +191,15 @@ export function propertiesWidget(sequence: DG.SemanticValue): DG.Widget {
204
191
  return getPropertiesWidget(sequence);
205
192
  }
206
193
 
207
- function webEditor(cell: DG.Cell, value?: string, units?: string) {
194
+ function openWebEditor(cell: DG.Cell, value?: string, units?: string) {
208
195
  const view = ui.div();
209
196
  // const df = grok.shell.tv.grid.dataFrame;
210
197
  // const col = df.columns.bySemType('Macromolecule')!;
211
- const col = cell.column;
198
+ const col = cell.column as DG.Column<string>;
212
199
  const sh = SeqHandler.forColumn(col);
213
200
  const rowIdx = cell.rowIndex;
214
- // @ts-ignore
215
201
  org.helm.webeditor.MolViewer.molscale = 0.8;
216
- // @ts-ignore
217
- const app = new scil.helm.App(view, {
202
+ const app = new org.helm.webeditor.App(view, {
218
203
  showabout: false,
219
204
  mexfontsize: '90%',
220
205
  mexrnapinontab: true,
@@ -227,12 +212,9 @@ function webEditor(cell: DG.Cell, value?: string, units?: string) {
227
212
  const sizes = app.calculateSizes();
228
213
  app.canvas.resize(sizes.rightwidth - 100, sizes.topheight - 210);
229
214
  let s = {width: sizes.rightwidth - 100 + 'px', height: sizes.bottomheight + 'px'};
230
- //@ts-ignore
231
215
  scil.apply(app.sequence.style, s);
232
- //@ts-ignore
233
216
  scil.apply(app.notation.style, s);
234
217
  s = {width: sizes.rightwidth + 'px', height: (sizes.bottomheight + app.toolbarheight) + 'px'};
235
- //@ts-ignore
236
218
  scil.apply(app.properties.parent.style, s);
237
219
  app.structureview.resize(sizes.rightwidth, sizes.bottomheight + app.toolbarheight);
238
220
  app.mex.resize(sizes.topheight - 80);
@@ -240,9 +222,8 @@ function webEditor(cell: DG.Cell, value?: string, units?: string) {
240
222
  if (!!cell && units === undefined)
241
223
  app.canvas.helm.setSequence(cell.value, 'HELM');
242
224
  else
243
- app.canvas.helm.setSequence(value, 'HELM');
225
+ app.canvas.helm.setSequence(value!, 'HELM');
244
226
  }, 200);
245
- //@ts-ignore
246
227
  ui.dialog({showHeader: false, showFooter: true})
247
228
  .add(view)
248
229
  .onOK(() => {
@@ -259,30 +240,6 @@ function webEditor(cell: DG.Cell, value?: string, units?: string) {
259
240
  }).show({modal: true, fullScreen: true});
260
241
  }
261
242
 
262
- function getRS(smiles: string) {
263
- const newS = smiles.match(/(?<=\[)[^\][]*(?=])/gm);
264
- const res: { [name: string]: string } = {};
265
- let el = '';
266
- let digit;
267
- if (!!newS) {
268
- for (let i = 0; i < newS.length; i++) {
269
- if (newS[i] != null) {
270
- if (/\d/.test(newS[i])) {
271
- digit = newS[i][newS[i].length - 1];
272
- newS[i] = newS[i].replace(/[0-9]/g, '');
273
- for (let j = 0; j < newS[i].length; j++) {
274
- if (newS[i][j] != ':')
275
- el += newS[i][j];
276
- }
277
- res['R' + digit] = el;
278
- el = '';
279
- }
280
- }
281
- }
282
- }
283
- return res;
284
- }
285
-
286
243
  //name: getMolfiles
287
244
  //input: column col {semType: Macromolecule}
288
245
  //output: column res
@@ -312,3 +269,42 @@ export function getMolfiles(col: DG.Column): DG.Column {
312
269
  export async function getHelmHelper(): Promise<IHelmHelper> {
313
270
  return HelmHelper.getInstance();
314
271
  }
272
+
273
+ import {testEvent} from '@datagrok-libraries/utils/src/test';
274
+ import {CellRendererBackAsyncBase} from '@datagrok-libraries/bio/src/utils/cell-renderer-async-base';
275
+ import {RGROUP_CAP_GROUP_NAME, RGROUP_LABEL, SMILES} from './constants';
276
+ import {getRS} from './utils/dummy-monomer';
277
+ import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
278
+
279
+ //name: measureCellRenderer
280
+ export async function measureCellRenderer(): Promise<void> {
281
+ const grid = grok.shell.tv.grid;
282
+ const gridCol = grid.columns.byName('sequence')!;
283
+ const back = gridCol.temp['rendererBack'] as HelmGridCellRendererBack;
284
+
285
+ let etSum: number = 0;
286
+ let etCount: number = 0;
287
+ for (let i = 0; i < 20; ++i) {
288
+ const t1 = window.performance.now();
289
+ let t2: number;
290
+ if (!back.cacheEnabled) {
291
+ await testEvent(back.onRendered, () => {
292
+ t2 = window.performance.now();
293
+ _package.logger.info(`measureCellRenderer() cache disabled , ET: ${t2 - t1} ms`);
294
+ }, () => {
295
+ back.invalidate(); // grid.invalidate();
296
+ }, 5000);
297
+ } else {
298
+ await testEvent(grid.onAfterDrawContent, () => {
299
+ t2 = window.performance.now();
300
+ _package.logger.info(`measureCellRenderer() cache enabled, ET: ${t2! - t1} ms`);
301
+ }, () => {
302
+ grid.invalidate();
303
+ }, 5000);
304
+ }
305
+
306
+ etSum += (t2! - t1);
307
+ etCount++;
308
+ }
309
+ _package.logger.info(`measureCellRenderer(), avg ET: ${etSum / etCount} ms`);
310
+ }