@datagrok/helm 2.0.3 → 2.0.6

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/src/package.ts CHANGED
@@ -2,34 +2,34 @@
2
2
  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
- import {NotationConverter} from '@datagrok-libraries/bio/src/utils/notation-converter';
5
+ import * as bio from '@datagrok-libraries/bio';
6
+
6
7
  import {createJsonMonomerLibFromSdf} from './utils';
7
8
  import {MONOMER_MANAGER_MAP, RGROUPS, RGROUP_CAP_GROUP_NAME, RGROUP_LABEL, SMILES} from './constants';
8
- import {printLeftOrCentered} from '@datagrok-libraries/bio/src/utils/cell-renderer';
9
+ import {HelmWebEditor} from './helm-web-editor';
10
+ import {Observable, Subject} from 'rxjs';
9
11
 
10
12
  export const _package = new DG.Package();
11
13
 
12
- const lru = new DG.LruCache<any, any>();
13
- const molfiles = new DG.LruCache<any, any>();
14
14
  const STORAGE_NAME = 'Libraries';
15
- let i = 0;
16
15
  const LIB_PATH = 'libraries/';
17
16
 
18
-
19
17
  //tags: init
20
18
  export async function initHelm(): Promise<void> {
21
- let grid = grok.shell.tv.grid;
22
- for (let j = 0; j <= i; j++) {
23
- await monomerManager(await grok.dapi.userDataStorage.getValue(STORAGE_NAME, j.toString(), true));
24
- }
25
- grid.invalidate();
26
-
27
- return new Promise((resolve, reject) => {
19
+ return new Promise(async (resolve, reject) => {
28
20
  // @ts-ignore
29
21
  dojo.ready(function() { resolve(null); });
22
+ await loadLibraries();
30
23
  });
31
24
  }
32
25
 
26
+
27
+ async function loadLibraries() {
28
+ let uploadedLibraries: string[] = Object.values(await grok.dapi.userDataStorage.get(STORAGE_NAME, true));
29
+ for (let i = 0; i < uploadedLibraries.length; ++i)
30
+ await monomerManager(uploadedLibraries[i]);
31
+ }
32
+
33
33
  //name: helmCellRenderer
34
34
  //tags: cellRenderer
35
35
  //meta.cellType: helm
@@ -40,7 +40,7 @@ export function helmCellRenderer(): HelmCellRenderer {
40
40
  }
41
41
 
42
42
 
43
- export function webEditor(value: string) {
43
+ function webEditor(cell?: DG.GridCell, value?: string) {
44
44
  let view = ui.div();
45
45
  org.helm.webeditor.MolViewer.molscale = 0.8;
46
46
  let app = new scil.helm.App(view, {
@@ -66,15 +66,20 @@ export function webEditor(value: string) {
66
66
  app.structureview.resize(sizes.rightwidth, sizes.bottomheight + app.toolbarheight);
67
67
  app.mex.resize(sizes.topheight - 80);
68
68
  setTimeout(function() {
69
- app.canvas.helm.setSequence(value, 'HELM');
69
+ if (typeof cell !== 'undefined') {
70
+ app.canvas.helm.setSequence(cell.cell.value, 'HELM');
71
+ } else {
72
+ app.canvas.helm.setSequence(value, 'HELM');
73
+ }
70
74
  }, 200);
71
75
  //@ts-ignore
72
76
  ui.dialog({showHeader: false, showFooter: true})
73
- .add(view)
74
- .onOK(() => {
75
- const val = app.canvas.getHelm(true).replace(/<\/span>/g, '').replace(/<span style='background:#bbf;'>/g, '');
76
- value = val;
77
- }).show({modal: true, fullScreen: true});
77
+ .add(view)
78
+ .onOK(() => {
79
+ if (typeof cell !== 'undefined') {
80
+ cell.cell.value = app.canvas.getHelm(true).replace(/<\/span>/g, '').replace(/<span style='background:#bbf;'>/g, '');
81
+ }
82
+ }).show({modal: true, fullScreen: true});
78
83
  }
79
84
 
80
85
 
@@ -83,9 +88,7 @@ export function webEditor(value: string) {
83
88
  //input: grid_cell cell
84
89
  export function editMoleculeCell(cell: DG.GridCell): void {
85
90
  if (cell.gridColumn.column.tags[DG.TAGS.UNITS] === 'helm') {
86
- let grid = grok.shell.tv.grid;
87
- webEditor(cell.cell.value);
88
- grid.invalidate();
91
+ webEditor(cell);
89
92
  }
90
93
  }
91
94
 
@@ -95,9 +98,9 @@ export function editMoleculeCell(cell: DG.GridCell): void {
95
98
  //input: string mol { semType: Macromolecule }
96
99
  export function openEditor(mol: string): void {
97
100
  let df = grok.shell.tv.grid.dataFrame;
98
- let converter = new NotationConverter(df.columns.bySemType('Macromolecule'));
101
+ let converter = new bio.NotationConverter(df.columns.bySemType('Macromolecule'));
99
102
  const resStr = converter.convertStringToHelm(mol, '/');
100
- webEditor(resStr);
103
+ webEditor(undefined, resStr);
101
104
  }
102
105
 
103
106
  //name: Properties
@@ -105,73 +108,58 @@ export function openEditor(mol: string): void {
105
108
  //input: string helmString {semType: Macromolecule}
106
109
  //output: widget result
107
110
  export async function propertiesPanel(helmString: string) {
108
- //const lru = await getLru();
109
- const result = lru.get(helmString).split(',');
111
+ let grid = grok.shell.tv.grid;
112
+ let parent = grid.root.parentElement;
113
+ const host = ui.div([]);
114
+ parent.appendChild(host);
115
+ let editor = new JSDraw2.Editor(host, {viewonly: true});
116
+ host.style.width = '0px';
117
+ host.style.height = '0px';
118
+ editor.setHelm(helmString);
119
+ let formula = editor.getFormula(true);
120
+ let molWeight = Math.round(editor.getMolWeight() * 100) / 100;
121
+ let coef = Math.round(editor.getExtinctionCoefficient(true) * 100) / 100;
122
+ parent.lastChild.remove();
110
123
  return new DG.Widget(
111
124
  ui.tableFromMap({
112
- 'formula': result[0].replace(/<sub>/g, '').replace(/<\/sub>/g, ''),
113
- 'molecular weight': result[1],
114
- 'extinction coefficient': result[2],
115
- /*'fasta': ui.wait(async () => ui.divText(await helmToFasta(helmString))),
116
- 'rna analogue sequence': ui.wait(async () => ui.divText(await helmToRNA(helmString))),
117
- 'smiles': ui.wait(async () => ui.divText(await helmToSmiles(helmString))),
118
- //'peptide analogue sequence': ui.wait(async () => ui.divText(await helmToPeptide(helmString))),*/
125
+ 'formula': formula.replace(/<sub>/g, '').replace(/<\/sub>/g, ''),
126
+ 'molecular weight': molWeight,
127
+ 'extinction coefficient': coef,
119
128
  })
120
129
  );
121
130
  }
122
131
 
123
- //name: Molfile
124
- //tags: panel, widgets
125
- //input: string helmString {semType: Macromolecule}
126
- //output: widget result
127
- export async function molfilePanel(helmString: string) {
128
- return ui.textInput('', await getMolfile(helmString));
129
- }
130
-
131
-
132
- //name: loadDialog
133
- export async function loadDialog() {
134
- let res = (await _package.files.list(`${LIB_PATH}`, false, '')).map(it => it.fileName);
135
- let FilesList = await ui.choiceInput('Monomer Libraries', ' ', res);
136
- let grid = grok.shell.tv.grid;
137
- ui.dialog('Load library from file')
138
- .add(FilesList)
139
- .onOK(async () => {
140
- await monomerManager(FilesList.value);
141
- grok.dapi.userDataStorage.postValue(STORAGE_NAME, i.toString(), FilesList.value, true);
142
- i += 1;
143
- grid.invalidate();
144
- }).show();
145
- };
146
132
 
147
133
  //name: Manage Libraries
148
134
  //tags: panel, widgets
149
135
  //input: column helmColumn {semType: Macromolecule}
150
136
  //output: widget result
151
- export async function libraryPanel(helmColumn: DG.Column) {
152
- //@ts-ignore
153
- let loadButton = ui.button('Load Library');
154
- loadButton.addEventListener('click', loadDialog);
137
+ export async function libraryPanel(helmColumn: DG.Column): Promise<DG.Widget> {
155
138
  //@ts-ignore
156
- let filesButton = ui.button('Manage');
157
- filesButton.addEventListener('click', manageFiles);
158
- let list = (await _package.files.list(`${LIB_PATH}`, false, '')).map(it => it.fileName);
159
- let usedLibraries = [];
160
- for (let j = 0; j <= i; j++) {
161
- usedLibraries.push(await grok.dapi.userDataStorage.getValue(STORAGE_NAME, j.toString(), true));
139
+ let filesButton: HTMLButtonElement = ui.button('Manage', manageFiles);
140
+ let divInputs: HTMLDivElement = ui.div();
141
+ let librariesList: string[] = (await _package.files.list(`${LIB_PATH}`, false, '')).map(it => it.fileName);
142
+ let uploadedLibraries: string[] = Object.values(await grok.dapi.userDataStorage.get(STORAGE_NAME, true));
143
+ for (let i = 0; i < uploadedLibraries.length; ++i) {
144
+ let libraryName: string = uploadedLibraries[i];
145
+ divInputs.append(ui.boolInput(libraryName, true, async v => {
146
+ org.helm.webeditor.Monomers.clear();
147
+ grok.dapi.userDataStorage.remove(STORAGE_NAME, libraryName, true);
148
+ await loadLibraries();
149
+ grok.shell.tv.grid.invalidate();
150
+ }).root);
151
+ }
152
+ let unusedLibraries: string[] = librariesList.filter(x => !uploadedLibraries.includes(x));
153
+ for (let i = 0; i < unusedLibraries.length; ++i) {
154
+ let libraryName: string = unusedLibraries[i];
155
+ divInputs.append(ui.boolInput(libraryName, false, v => {
156
+ monomerManager(libraryName);
157
+ grok.dapi.userDataStorage.postValue(STORAGE_NAME, libraryName, libraryName, true);
158
+ }).root);
162
159
  }
163
- let unusedLibraries = list.filter(x => !usedLibraries.includes(x));
164
160
  return new DG.Widget(ui.splitV([
165
- ui.splitH([
166
- ui.tableFromMap({
167
- 'Uploaded libraries': ui.divText(usedLibraries.toString()),
168
- 'Not used libraries': ui.divText(unusedLibraries.toString()),
169
- })
170
- ]),
171
- ui.splitH([
172
- ui.divH([loadButton]),
173
- ui.divH([filesButton]),
174
- ])
161
+ divInputs,
162
+ ui.divV([filesButton])
175
163
  ]));
176
164
  }
177
165
 
@@ -185,7 +173,6 @@ export async function manageFiles() {
185
173
  }
186
174
 
187
175
 
188
-
189
176
  async function accessServer(url: string, key: string) {
190
177
  const params: RequestInit = {
191
178
  method: 'GET',
@@ -271,7 +258,7 @@ export async function monomerManager(value: string) {
271
258
  dfSdf = await grok.functions.call('Chem:importSdf', {bytes: file});
272
259
  df = createJsonMonomerLibFromSdf(dfSdf[0]);
273
260
  } else {
274
- grok.shell.warning("Chem package is not installed");
261
+ grok.shell.warning('Chem package is not installed');
275
262
  }
276
263
  } else {
277
264
  const file = await _package.files.readAsText(`${LIB_PATH}${value}`);
@@ -296,6 +283,14 @@ export async function monomerManager(value: string) {
296
283
  }
297
284
  org.helm.webeditor.Monomers.addOneMonomer(m);
298
285
  });
286
+
287
+ // Now after updating org.helm.webeditor.Monomers we update MonomerLib window singleton (fires onChanged event)
288
+ (getMonomerLibObj() as MonomerLib).update(getAllLibsData());
289
+ //grok.events.fireCustomEvent('monomersLibChanged', null);
290
+
291
+ // Obsolete
292
+ let grid: DG.Grid = grok.shell.tv.grid;
293
+ grid.invalidate();
299
294
  }
300
295
 
301
296
  //name: helmColumnToSmiles
@@ -304,52 +299,45 @@ export function helmColumnToSmiles(helmColumn: DG.Column) {
304
299
  //todo: add column with smiles to col.dataFrame.
305
300
  }
306
301
 
307
- //name: getMolfile
308
- //input: string helmString {semType: Macromolecule}
309
- export async function getMolfile(helmString) {
310
- return molfiles.get(helmString);
311
- }
312
302
 
313
303
  function split(s: string, sep: string) {
314
304
  var ret = [];
315
- var frag = "";
305
+ var frag = '';
316
306
  var parentheses = 0;
317
307
  var bracket = 0;
318
308
  var braces = 0;
319
309
  var quote = 0;
320
310
  for (var i = 0; i < s.length; ++i) {
321
- var c = s.substring(i, i + 1);
322
- if (c == sep && bracket == 0 && parentheses == 0 && braces == 0 && quote == 0) {
323
- ret.push(frag);
324
- frag = "";
325
- }
326
- else {
327
- frag += c;
328
- if (quote > 0) {
329
- if (c == '\\' && i + 1 < s.length) {
330
- ++i;
331
- var c2 = s.substring(i, i + 1);
332
- frag += c2;
333
- c += c2;
334
- }
335
- }
336
- if (c == '\"') {
337
- if (!(i > 0 && s.substring(i - 1, i) == '\\'))
338
- quote = quote == 0 ? 1 : 0;
339
- }
340
- else if (c == '[')
341
- ++bracket;
342
- else if (c == ']')
343
- --bracket;
344
- else if (c == '(')
345
- ++parentheses;
346
- else if (c == ')')
347
- --parentheses;
348
- else if (c == '{')
349
- ++braces;
350
- else if (c == '}')
351
- --braces;
311
+ var c = s.substring(i, i + 1);
312
+ if (c == sep && bracket == 0 && parentheses == 0 && braces == 0 && quote == 0) {
313
+ ret.push(frag);
314
+ frag = '';
315
+ } else {
316
+ frag += c;
317
+ if (quote > 0) {
318
+ if (c == '\\' && i + 1 < s.length) {
319
+ ++i;
320
+ var c2 = s.substring(i, i + 1);
321
+ frag += c2;
322
+ c += c2;
323
+ }
352
324
  }
325
+ if (c == '\"') {
326
+ if (!(i > 0 && s.substring(i - 1, i) == '\\'))
327
+ quote = quote == 0 ? 1 : 0;
328
+ } else if (c == '[')
329
+ ++bracket;
330
+ else if (c == ']')
331
+ --bracket;
332
+ else if (c == '(')
333
+ ++parentheses;
334
+ else if (c == ')')
335
+ --parentheses;
336
+ else if (c == '{')
337
+ ++braces;
338
+ else if (c == '}')
339
+ --braces;
340
+ }
353
341
  }
354
342
  ret.push(frag);
355
343
  return ret;
@@ -358,81 +346,86 @@ function split(s: string, sep: string) {
358
346
  function detachAnnotation(s: string) {
359
347
  var ret = _detachAppendix(s, '\"');
360
348
  if (ret.tag != null)
361
- return ret;
349
+ return ret;
362
350
 
363
351
  var r = _detachAppendix(s, '\'');
364
- return { tag: ret.tag, repeat: r.tag, str: r.str };
352
+ return {tag: ret.tag, repeat: r.tag, str: r.str};
365
353
  }
366
354
 
367
355
  function _detachAppendix(s: string, c: string) {
368
356
  var tag = null;
369
357
  //@ts-ignore
370
358
  if (scil.Utils.endswith(s, c)) {
371
- var p = s.length - 1;
372
- while (p > 0) {
373
- p = s.lastIndexOf(c, p - 1);
374
- if (p <= 0 || s.substring(p - 1, p) != '\\')
375
- break;
376
- }
359
+ var p = s.length - 1;
360
+ while (p > 0) {
361
+ p = s.lastIndexOf(c, p - 1);
362
+ if (p <= 0 || s.substring(p - 1, p) != '\\')
363
+ break;
364
+ }
377
365
 
378
- if (p > 0 && p < s.length - 1) {
379
- tag = s.substring(p + 1, s.length - 1);
380
- s = s.substring(0, p);
381
- }
366
+ if (p > 0 && p < s.length - 1) {
367
+ tag = s.substring(p + 1, s.length - 1);
368
+ s = s.substring(0, p);
369
+ }
382
370
  }
383
371
  if (tag != null)
384
- tag = tag.replace(new RegExp("\\" + c, "g"), c);
385
- return { tag: unescape(tag), str: s };
372
+ tag = tag.replace(new RegExp('\\' + c, 'g'), c);
373
+ return {tag: unescape(tag), str: s};
386
374
  }
387
375
 
388
376
  function unescape(s: string) {
389
377
  //@ts-ignore
390
378
  if (scil.Utils.isNullOrEmpty(s))
391
- return s;
392
-
393
- return s.replace(/[\\]./g, function (m) {
394
- switch (m) {
395
- case "\\r":
396
- return "\r";
397
- case "\\n":
398
- return "\n";
399
- case "\\t":
400
- return "\t";
401
- default:
402
- return m.substring(1);
403
- }
379
+ return s;
380
+
381
+ return s.replace(/[\\]./g, function(m) {
382
+ switch (m) {
383
+ case '\\r':
384
+ return '\r';
385
+ case '\\n':
386
+ return '\n';
387
+ case '\\t':
388
+ return '\t';
389
+ default:
390
+ return m.substring(1);
391
+ }
404
392
  });
405
393
  }
406
394
 
407
- function parseHelm(s: string) {
395
+ //name: parseHelm
396
+ //input: string s
397
+ export function parseHelm(s: string) {
408
398
  var sections = split(s, '$');
409
399
  s = sections[0];
410
400
  var monomers = [];
411
401
  //@ts-ignore
412
402
  if (!scil.Utils.isNullOrEmpty(s)) {
413
- var seqs = split(s, '|');
414
- for (var i = 0; i < seqs.length; ++i) {
415
- var e = detachAnnotation(seqs[i]);
416
- s = e.str;
417
-
418
- var p = s.indexOf("{");
419
-
420
- s = s.substring(p + 1);
421
- p = s.indexOf('}');
422
- s = s.substring(0, p);
423
-
424
- var ss = split(s, '.');
425
- for (var monomer of ss) {
426
- if (monomer.includes('(') && monomer.includes(')')) {
427
- var elements = monomer.replace(/[()]/g, "").split("");
428
- for (var el of elements) {
429
- monomers.push(el);
430
- }
431
- } else {
432
- monomers.push(monomer);
433
- }
403
+ var seqs = split(s, '|');
404
+ for (var i = 0; i < seqs.length; ++i) {
405
+ var e = detachAnnotation(seqs[i]);
406
+ s = e.str;
407
+
408
+ var p = s.indexOf('{');
409
+
410
+ s = s.substring(p + 1);
411
+ p = s.indexOf('}');
412
+ s = s.substring(0, p);
413
+
414
+ var ss = split(s, '.');
415
+ for (var monomer of ss) {
416
+ if (monomer.includes('(') && monomer.includes(')')) {
417
+ var elements = monomer.replace(/[()]/g, '').split('');
418
+ for (var el of elements) {
419
+ monomers.push(el);
434
420
  }
421
+ } else if (monomer.includes('[') && monomer.includes(']')) {
422
+ var element = monomer.match(/(?<=\[).*?(?=\])/g, '');
423
+ monomers.push(element[0]);
424
+ } else {
425
+ monomers.push(monomer);
426
+ }
435
427
  }
428
+ }
436
429
  }
437
430
  return monomers;
438
431
  }
@@ -455,6 +448,89 @@ export function findMonomers(helmString: string) {
455
448
  return new Set(split_string.filter(val => !monomer_names.includes(val)));
456
449
  }
457
450
 
451
+
452
+ class MonomerLib implements bio.IMonomerLib {
453
+ private _monomers: { [type: string]: { [name: string]: bio.Monomer } };
454
+ private _onChanged = new Subject<any>();
455
+
456
+ get(monomerType: string, monomerName: string): bio.Monomer | null {
457
+ if (monomerType in this._monomers && monomerName in this._monomers[monomerType])
458
+ return this._monomers[monomerType][monomerName];
459
+ else
460
+ return null;
461
+ }
462
+
463
+ get onChanged(): Observable<any> {
464
+ return this._onChanged;
465
+ }
466
+
467
+ public update(monomers: { [type: string]: { [name: string]: bio.Monomer } }): void {
468
+ this._monomers = monomers;
469
+ this._onChanged.next();
470
+ }
471
+ }
472
+
473
+ function getAllLibsData(): { [type: string]: { [name: string]: bio.Monomer } } {
474
+ //@ts-ignore
475
+ const types = Object.keys(org.helm.webeditor.monomerTypeList());
476
+ //const monomers: any = [];
477
+ const monomers: { [type: string]: { [name: string]: bio.Monomer } } = {};
478
+ for (let i = 0; i < types.length; i++) {
479
+ monomers[types[i]] = new scil.helm.Monomers.getMonomerSet(types[i]);
480
+ //@ts-ignore
481
+ //monomers.push(new scil.helm.Monomers.getMonomerSet(types[i]));
482
+ }
483
+
484
+ return monomers;
485
+ }
486
+
487
+ //name: getMonomerLibObj
488
+ //description: Get object of IMonomerLib interface (singleton)
489
+ //output: object result
490
+ export function getMonomerLibObj(): bio.IMonomerLib {
491
+ if (!('monomerLib' in window)) {
492
+ window['monomerLib'] = new MonomerLib();
493
+ window['monomerLib'].update(getAllLibsData()); // TODO: refactor
494
+ }
495
+
496
+ return window['monomerLib'];
497
+ }
498
+
499
+ //name: getMolfiles
500
+ //input: column col {semType: Macromolecule}
501
+ //output: column res
502
+ export function getMolfiles(col: DG.Column): DG.Column {
503
+ let grid = grok.shell.tv.grid;
504
+ let parent = grid.root.parentElement;
505
+ let res = DG.Column.string('mols', col.length);
506
+ const host = ui.div([]);
507
+ parent.appendChild(host);
508
+ let editor = new JSDraw2.Editor(host, {viewonly: true});
509
+ host.style.width = '0px';
510
+ host.style.height = '0px';
511
+ res.init((i) => {
512
+ editor.setHelm(col.get(i));
513
+ let mol = editor.getMolfile();
514
+ return mol;
515
+ });
516
+ parent.lastChild.remove();
517
+ return res;
518
+ }
519
+
520
+ function getParts(subParts: string[], s: string): string[] {
521
+ let j = 0;
522
+ let allParts: string[] = [];
523
+ for (let k = 0; k < subParts.length; ++k) {
524
+ let indexOfMonomer = s.indexOf(subParts[k]);
525
+ let helmBeforeMonomer = s.slice(j, indexOfMonomer);
526
+ allParts.push(helmBeforeMonomer);
527
+ allParts.push(subParts[k]);
528
+ s = s.substring(indexOfMonomer + subParts[k].length);
529
+ }
530
+ allParts.push(s);
531
+ return allParts;
532
+ }
533
+
458
534
  class HelmCellRenderer extends DG.GridCellRenderer {
459
535
  get name() { return 'helm'; }
460
536
 
@@ -464,6 +540,49 @@ class HelmCellRenderer extends DG.GridCellRenderer {
464
540
 
465
541
  get defaultHeight(): number | null { return 100; }
466
542
 
543
+ onMouseMove(gridCell: DG.GridCell, e: MouseEvent): void {
544
+ const maxLengthWordsSum = gridCell.cell.column.temp['helm-sum-maxLengthWords'];
545
+ const maxIndex = Object.values(gridCell.cell.column.temp['helm-maxLengthWords']).length - 1;
546
+ const argsX = e.offsetX - gridCell.gridColumn.left + (gridCell.gridColumn.left - gridCell.bounds.x);
547
+ let left = 0;
548
+ let right = maxIndex;
549
+ let found = false;
550
+ maxLengthWordsSum[maxIndex + 1] = argsX + 1;
551
+ let mid = 0;
552
+ if (argsX > maxLengthWordsSum[0]) {
553
+ while (!found) {
554
+ mid = Math.floor((right + left) / 2);
555
+ if (argsX >= maxLengthWordsSum[mid] && argsX <= maxLengthWordsSum[mid + 1]) {
556
+ left = mid;
557
+ found = true;
558
+ } else if (argsX < maxLengthWordsSum[mid]) {
559
+ right = mid - 1;
560
+ } else if (argsX > maxLengthWordsSum[mid + 1]) {
561
+ left = mid + 1;
562
+ }
563
+ if (left == right) {
564
+ found = true;
565
+ }
566
+ }
567
+ }
568
+ left = (argsX >= maxLengthWordsSum[left]) ? left + 1 : left;
569
+ const monomers = findMonomers(gridCell.cell.value);
570
+ let s: string = gridCell.cell.value ?? '';
571
+ let subParts: string[] = parseHelm(s);
572
+ let allParts: string[] = getParts(subParts, s);
573
+ let tooltipMessage: HTMLElement[] = [];
574
+ for (let i = 0; i < allParts.length; ++i) {
575
+ if (monomers.has(allParts[i]))
576
+ tooltipMessage[i] = ui.divV([
577
+ ui.divText(`Monomer ${allParts[i]} not found.`),
578
+ ui.divText('Open the Property Panel, then expand Manage Libraries')
579
+ ]);
580
+ }
581
+ (((tooltipMessage[left]?.childNodes.length ?? 0) > 0))
582
+ ? ui.tooltip.show(ui.div(tooltipMessage[left]), e.x + 16, e.y + 16)
583
+ : ui.tooltip.hide();
584
+ }
585
+
467
586
  render(g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number,
468
587
  gridCell: DG.GridCell, cellStyle: DG.GridCellStyle
469
588
  ) {
@@ -471,45 +590,47 @@ class HelmCellRenderer extends DG.GridCellRenderer {
471
590
  const undefinedColor = 'rgb(100,100,100)';
472
591
  const grayColor = '#808080';
473
592
  const monomers = findMonomers(gridCell.cell.value);
593
+ let s: string = gridCell.cell.value ?? '';
594
+ let subParts: string[] = parseHelm(s);
474
595
  if (monomers.size == 0) {
475
596
  const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
476
597
  host.setAttribute('dataformat', 'helm');
477
598
  host.setAttribute('data', gridCell.cell.value);
478
599
  gridCell.element = host;
479
- //@ts-ignore
480
- const canvas = new JSDraw2.Editor(host, {width: w, height: h, skin: 'w8', viewonly: true});
481
- const formula = canvas.getFormula(true);
482
- if (!formula) {
483
- gridCell.element = ui.divText(gridCell.cell.value, {style: {color: 'red'}});
484
- }
485
- const molWeight = Math.round(canvas.getMolWeight() * 100) / 100;
486
- const coef = Math.round(canvas.getExtinctionCoefficient(true) * 100) / 100;
487
- const molfile = canvas.getMolfile();
488
- const result = formula + ', ' + molWeight + ', ' + coef;
489
- lru.set(gridCell.cell.value, result);
490
- molfiles.set(gridCell.cell.value, molfile);
491
- return;
600
+ //@ts-ignore
601
+ const canvas = new JSDraw2.Editor(host, {width: w, height: h, skin: 'w8', viewonly: true});
602
+ return;
603
+ }
604
+ if (monomers.size > 0) {
605
+ w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
606
+ g.save();
607
+ g.beginPath();
608
+ g.rect(x, y, w, h);
609
+ g.clip();
610
+ g.font = '12px monospace';
611
+ g.textBaseline = 'top';
612
+ let x1 = x;
613
+ let maxLengthWords: any = {};
614
+ let maxLengthWordSum: any = {};
615
+ let allParts: string[] = getParts(subParts, s);
616
+ for (let i = 0; i < allParts.length; ++i) {
617
+ maxLengthWords[i] = allParts[i].length * 7;
618
+ let color = monomers.has(allParts[i]) ? 'red' : grayColor;
619
+ g.fillStyle = undefinedColor;
620
+ x1 = bio.printLeftOrCentered(x1, y, w, h, g, allParts[i], color, 0, true, 1.0);
492
621
  }
493
- if (monomers.size > 0) {
494
- w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
495
- g.save();
496
- g.beginPath();
497
- g.rect(x, y, w, h);
498
- g.clip();
499
- g.font = '12px monospace';
500
- g.textBaseline = 'top';
501
- let x1 = x;
502
- const s: string = gridCell.cell.value ?? '';
503
- let subParts: string[] = parseHelm(s);
504
- subParts.forEach((amino, index) => {
505
- let color = monomers.has(amino) ? 'red' : grayColor;
506
- g.fillStyle = undefinedColor;
507
- let last = index === subParts.length - 1;
508
- x1 = printLeftOrCentered(x1, y, w, h, g, amino, color, 0, true, 1.0, '/', last);
509
- });
510
- g.restore();
511
- return;
622
+
623
+ maxLengthWordSum[0] = maxLengthWords[0];
624
+ for (let i = 1; i < allParts.length; i++) {
625
+ maxLengthWordSum[i] = maxLengthWordSum[i - 1] + maxLengthWords[i];
512
626
  }
627
+ gridCell.cell.column.temp = {
628
+ 'helm-sum-maxLengthWords': maxLengthWordSum,
629
+ 'helm-maxLengthWords': maxLengthWords
630
+ };
631
+ g.restore();
632
+ return;
633
+ }
513
634
  }
514
635
  }
515
636
 
@@ -517,5 +638,13 @@ class HelmCellRenderer extends DG.GridCellRenderer {
517
638
  //input: string type
518
639
  //output: string monomerSetJSON
519
640
  export function getMonomerLib(type: string): string {
641
+ // TODO: deprecate
520
642
  return JSON.stringify(scil.helm.Monomers.getMonomerSet(type));
521
643
  }
644
+
645
+
646
+ //name: helmWebEditor
647
+ //output: object
648
+ export function helmWebEditor(): HelmWebEditor {
649
+ return new HelmWebEditor();
650
+ }