@datagrok/helm 2.13.2 → 2.13.4

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.13.2",
4
+ "version": "2.13.4",
5
5
  "author": {
6
6
  "name": "Maria Dolotova",
7
7
  "email": "mdolotova@datagrok.ai"
@@ -16,7 +16,7 @@
16
16
  "css/helm.css"
17
17
  ],
18
18
  "dependencies": {
19
- "@datagrok-libraries/bio": "^5.63.4",
19
+ "@datagrok-libraries/bio": "^5.63.5",
20
20
  "@datagrok-libraries/chem-meta": "^1.2.9",
21
21
  "@datagrok-libraries/helm-web-editor": "^1.1.16",
22
22
  "@datagrok-libraries/utils": "^4.6.9",
@@ -387,46 +387,87 @@ export class HelmHelper implements IHelmHelper {
387
387
  for (let aI: number = mol.atoms.length - 1; aI >= 0; --aI) {
388
388
  const a = mol.atoms[aI];
389
389
  if (a.elem === HELM_GAP_SYMBOL /* '*' - original */) {
390
- const leftBondList: { aI: number, bI: number }[] = [];
391
- const rightBondList: { aI: number, bI: number }[] = [];
390
+ // Collect all bonds connected to this gap atom, tracking which side the gap is on
391
+ const connectedBonds: { bI: number, gapSide: 1 | 2 }[] = [];
392
392
  for (let bI: number = mol.bonds.length - 1; bI >= 0; --bI) {
393
393
  const b = mol.bonds[bI];
394
- if (b.a1 !== a && b.a2 === a) leftBondList.push({aI, bI});
395
- if (b.a1 === a && b.a2 !== a) rightBondList.push({aI, bI});
394
+ if (b.a1 === a) connectedBonds.push({bI, gapSide: 1}); // gap is a1
395
+ else if (b.a2 === a) connectedBonds.push({bI, gapSide: 2}); // gap is a2
396
396
  }
397
- if (leftBondList.length > 1 || rightBondList.length > 1)
398
- throw new HelmNotSupportedError(`Removing a gap monomer #${aI} with more than two bonds is unsupported.`);
399
-
400
- const lb = leftBondList[0];
401
- const rb = rightBondList[0];
402
- if (lb && !rb) {
403
- mol.bonds.splice(lb.bI, 1);
404
- mol.atoms.splice(lb.aI, 1);
405
- } else if (!lb && rb) {
406
- const rb = rightBondList[0];
407
- mol.bonds.splice(rb.bI, 1);
408
- mol.atoms.splice(rb.aI, 1);
409
- } else {
410
- if (lb.aI !== rb.aI)
411
- throw new Error('Something is really wrong here.');
412
-
413
- // is not enough, breaks the simple polymer
414
- //mol.delAtom(a, true);
415
-
416
- const leftBond = mol.bonds[lb.bI];
417
- const rightBond = mol.bonds[rb.bI];
418
- const a2 = leftBond.a2 = rightBond.a2; // right atom
419
- leftBond.r2 = rightBond.r2;
420
- leftBond.apo2 = rightBond.apo2;
421
-
422
- if (a2.bonds)
423
- throw new Error('Bond list of the atom is not corrected.');
424
- // a2.bonds!.splice(a2.bonds!.indexOf(rightBond), 1); // remove the right bond from links of the right atom
425
- // a2.bonds!.push(leftBond); // put the left bond to links of the right atom
426
-
427
- mol.bonds.splice(rb.bI, 1); // remove right bond
428
- mol.atoms.splice(lb.aI, 1);
397
+
398
+ if (connectedBonds.length > 2) {
399
+ throw new HelmNotSupportedError(
400
+ `Removing gap monomer #${aI} with ${connectedBonds.length} bonds is unsupported (max 2).`);
401
+ }
402
+
403
+ // Separate into backbone bonds (R1/R2) and other bonds (R3+ cross-polymer)
404
+ // A backbone bond connects to the gap's R1 or R2 attachment point
405
+ let backboneR1Bond: { bI: number, gapSide: 1 | 2 } | null = null; // bond using gap's R1
406
+ let backboneR2Bond: { bI: number, gapSide: 1 | 2 } | null = null; // bond using gap's R2
407
+ const otherBonds: number[] = []; // bond indices to just remove
408
+
409
+ for (const cb of connectedBonds) {
410
+ const bond = mol.bonds[cb.bI];
411
+ // The R-group on the gap's side tells us which attachment point is used
412
+ const gapR = cb.gapSide === 1 ? bond.r1 : bond.r2;
413
+ const gapRNum = typeof gapR === 'string' ? parseInt(gapR.replace(/\D/g, '')) : gapR;
414
+ if (gapRNum === 1)
415
+ backboneR1Bond = cb;
416
+ else if (gapRNum === 2)
417
+ backboneR2Bond = cb;
418
+ else
419
+ otherBonds.push(cb.bI);
429
420
  }
421
+
422
+ // Remove other (non-backbone) bonds first (iterate in reverse to keep indices valid)
423
+ otherBonds.sort((a, b) => b - a);
424
+ for (const bI of otherBonds)
425
+ mol.bonds.splice(bI, 1);
426
+
427
+ // Recalculate bond indices after splice (otherBonds were removed)
428
+ // We need to adjust backboneR1Bond/R2Bond indices
429
+ const adjustIdx = (origBi: number): number => {
430
+ let adj = origBi;
431
+ for (const removedBi of otherBonds)
432
+ if (removedBi < origBi) adj--;
433
+ return adj;
434
+ };
435
+
436
+ const r1 = backboneR1Bond ? {...backboneR1Bond, bI: adjustIdx(backboneR1Bond.bI)} : null;
437
+ const r2 = backboneR2Bond ? {...backboneR2Bond, bI: adjustIdx(backboneR2Bond.bI)} : null;
438
+
439
+ if (r1 && r2) {
440
+ // Re-link: the neighbor on the R1 side connects directly to the neighbor on the R2 side
441
+ const r1Bond = mol.bonds[r1.bI];
442
+ const r2Bond = mol.bonds[r2.bI];
443
+ // The "other" atom (not the gap) on each bond
444
+ const r1Neighbor = r1.gapSide === 1 ? r1Bond.a2 : r1Bond.a1;
445
+ const r2Neighbor = r2.gapSide === 1 ? r2Bond.a2 : r2Bond.a1;
446
+ const r1NeighborR = r1.gapSide === 1 ? r1Bond.r2 : r1Bond.r1;
447
+ const r1NeighborApo = r1.gapSide === 1 ? r1Bond.apo2 : r1Bond.apo1;
448
+ const r2NeighborR = r2.gapSide === 1 ? r2Bond.r2 : r2Bond.r1;
449
+ const r2NeighborApo = r2.gapSide === 1 ? r2Bond.apo2 : r2Bond.apo1;
450
+
451
+ // Rewrite the R1 bond to connect r1Neighbor ↔ r2Neighbor directly
452
+ r1Bond.a1 = r1Neighbor;
453
+ r1Bond.r1 = r1NeighborR;
454
+ r1Bond.apo1 = r1NeighborApo;
455
+ r1Bond.a2 = r2Neighbor;
456
+ r1Bond.r2 = r2NeighborR;
457
+ r1Bond.apo2 = r2NeighborApo;
458
+
459
+ // Remove the R2 bond (no longer needed)
460
+ mol.bonds.splice(r2.bI, 1);
461
+ } else if (r1 && !r2) {
462
+ // Gap at the end (only R1 bond) — just remove
463
+ mol.bonds.splice(r1.bI, 1);
464
+ } else if (!r1 && r2) {
465
+ // Gap at the start (only R2 bond) — just remove
466
+ mol.bonds.splice(r2.bI, 1);
467
+ }
468
+ // else: isolated gap, no backbone bonds — just remove atom
469
+
470
+ mol.atoms.splice(aI, 1);
430
471
  }
431
472
  }
432
473
 
package/src/package.g.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {PackageFunctions} from './package';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
 
4
+ //tags: init
4
5
  //meta.role: init
5
6
  export async function initHelm() : Promise<void> {
6
7
  await PackageFunctions.initHelm();
@@ -12,6 +13,7 @@ export async function getHelmService() : Promise<any> {
12
13
  return await PackageFunctions.getHelmService();
13
14
  }
14
15
 
16
+ //tags: cellRenderer
15
17
  //output: grid_cell_renderer result
16
18
  //meta.columnTags: quality=Macromolecule, units=helm
17
19
  //meta.cellType: helm
@@ -21,6 +23,7 @@ export function helmCellRenderer() : any {
21
23
  }
22
24
 
23
25
  //description: Macromolecule
26
+ //tags: cellEditor
24
27
  //input: grid_cell cell
25
28
  //meta.columnTags: quality=Macromolecule, units=helm
26
29
  //meta.role: cellEditor
@@ -37,6 +40,7 @@ export function openEditor(mol: DG.SemanticValue) : void {
37
40
  }
38
41
 
39
42
  //name: Properties
43
+ //tags: panel, widgets, bio
40
44
  //input: semantic_value sequence { semType: Macromolecule }
41
45
  //output: widget result
42
46
  //meta.role: widgets,panel
@@ -51,6 +55,7 @@ export function getMolfiles(col: DG.Column<any>) : any {
51
55
  return PackageFunctions.getMolfiles(col);
52
56
  }
53
57
 
58
+ //tags: valueEditor
54
59
  //input: string name { optional: true }
55
60
  //input: object options { optional: true }
56
61
  //output: object result
package/src/package.ts CHANGED
@@ -125,7 +125,7 @@ function checkMonomersAndOpenWebEditor(cell: DG.GridCell, value?: string, units?
125
125
  }
126
126
 
127
127
  export class PackageFunctions {
128
- @grok.decorators.init()
128
+ @grok.decorators.init({tags: ['init']})
129
129
  static async initHelm(): Promise<void> {
130
130
  if (initHelmPromise === null)
131
131
  initHelmPromise = initHelmInt();
@@ -147,6 +147,7 @@ export class PackageFunctions {
147
147
  'cellType': 'helm',
148
148
  'role': 'cellRenderer'
149
149
  },
150
+ 'tags': ['cellRenderer'],
150
151
  'outputs': [{name: 'result', type: 'grid_cell_renderer'}]
151
152
  })
152
153
  static helmCellRenderer(): DG.GridCellRenderer {
@@ -162,6 +163,7 @@ export class PackageFunctions {
162
163
  'columnTags': 'quality=Macromolecule, units=helm',
163
164
  'role': 'cellEditor'
164
165
  },
166
+ 'tags': ['cellEditor'],
165
167
  'description': 'Macromolecule'
166
168
  })
167
169
  static editMoleculeCell(
@@ -197,6 +199,7 @@ export class PackageFunctions {
197
199
  @grok.decorators.panel({
198
200
  'name': 'Properties',
199
201
  'meta': {role: 'widgets', domain: 'bio'},
202
+ 'tags': ['panel', 'widgets', 'bio']
200
203
  })
201
204
  static propertiesWidget(
202
205
  @grok.decorators.param({'options': {'semType': 'Macromolecule'}}) sequence: DG.SemanticValue): DG.Widget {
@@ -219,6 +222,7 @@ export class PackageFunctions {
219
222
  'semType': 'Macromolecule',
220
223
  'role': 'valueEditor'
221
224
  },
225
+ 'tags': ['valueEditor'],
222
226
  'outputs': [{'type': 'object', 'name': 'result'}]
223
227
  })
224
228
  static helmInput(