@datagrok/sequence-translator 1.6.4 → 1.8.0

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/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.6.4",
4
+ "version": "1.8.0",
5
5
  "author": {
6
6
  "name": "Leonid Stolbov",
7
7
  "email": "lstolbov@datagrok.ai"
@@ -22,13 +22,13 @@
22
22
  }
23
23
  ],
24
24
  "dependencies": {
25
- "@datagrok-libraries/bio": "^5.46.0",
26
- "@datagrok-libraries/chem-meta": "^1.2.7",
25
+ "@datagrok-libraries/bio": "^5.50.0",
26
+ "@datagrok-libraries/chem-meta": "^1.2.8",
27
27
  "@datagrok-libraries/tutorials": "^1.4.3",
28
28
  "@datagrok-libraries/utils": "^4.3.7",
29
29
  "@types/react": "^18.0.15",
30
30
  "cash-dom": "^8.1.0",
31
- "datagrok-api": "^1.21.1",
31
+ "datagrok-api": "^1.24.0",
32
32
  "lodash": "^4.17.21",
33
33
  "object-hash": "^3.0.0",
34
34
  "openchemlib": "6.0.1",
@@ -41,10 +41,10 @@
41
41
  "devDependencies": {
42
42
  "@datagrok-libraries/helm-web-editor": "^1.1.13",
43
43
  "@datagrok-libraries/js-draw-lite": "^0.0.10",
44
- "@datagrok/bio": "^2.17.2",
45
- "@datagrok/helm": "^2.5.8",
46
- "@datagrok/chem": "^1.12.4",
47
- "@types/jquery": "^3.5.14",
44
+ "@datagrok/bio": "^2.18.0",
45
+ "@datagrok/chem": "^1.13.0",
46
+ "@datagrok/helm": "^2.7.0",
47
+ "@types/jquery": "^3.5.32",
48
48
  "@types/js-yaml": "^4.0.5",
49
49
  "@types/lodash": "^4.14.202",
50
50
  "@types/node-fetch": "^2.6.2",
@@ -0,0 +1,70 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+ import * as grok from 'datagrok-api/grok';
3
+
4
+ import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
5
+ import {getSeqHelper, ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
6
+ import {_toAtomicLevel} from '@datagrok-libraries/bio/src/monomer-works/to-atomic-level';
7
+ import {ALPHABET, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
8
+ import {IMonomerLibBase} from '@datagrok-libraries/bio/src/types';
9
+ import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
10
+
11
+ export function dealGroups(col: DG.Column<string>): void {
12
+ for (let i = 0; i < col.length; i++) {
13
+ col.set(i, col.get(i)!.replaceAll('undefined', 'H'));
14
+ col.set(i, col.get(i)!.replaceAll('Oh', 'O'));
15
+ col.set(i, col.get(i)!.replaceAll('0.000000 3', '0.000000 0'));
16
+ col.set(i, col.get(i)!.replaceAll('?', 'O'));
17
+ col.set(i, col.get(i)!.replaceAll('0 3\n', '0 0\n'));
18
+ col.set(i, col.get(i)!.replaceAll('RGROUPS=(1 1)', ''));
19
+ }
20
+ }
21
+
22
+ export async function helmToMol(resHelmCol: DG.Column, resList: string[],
23
+ isLinear: boolean[], chiralityEngine: boolean, highlight: boolean, linearize: boolean,
24
+ lib: IMonomerLibBase, rdkit: RDModule, seqHelper: ISeqHelper) {
25
+ const getUnusedName = (df: DG.DataFrame | undefined, colName: string): string => {
26
+ if (!df) return colName;
27
+ return df.columns.getUnusedName(colName);
28
+ };
29
+
30
+ const toAtomicLevelRes =
31
+ await seqHelper.helmToAtomicLevel(resHelmCol, chiralityEngine, highlight, lib);
32
+
33
+ const resMolCol = toAtomicLevelRes.molCol!;
34
+
35
+ const allLinear = isLinear.filter((l) => l).length;
36
+ if (linearize && allLinear > 0) {
37
+ const lin = new Array<string>(allLinear);
38
+ let counter = 0;
39
+ for (let i = 0; i < isLinear.length; i++) {
40
+ if (isLinear[i]) {
41
+ lin[counter] = resList[i];
42
+ counter++;
43
+ }
44
+ }
45
+
46
+ const linCol = DG.Column.fromStrings('helm', lin);
47
+ linCol.semType = DG.SEMTYPE.MACROMOLECULE;
48
+ linCol.meta.units = NOTATION.HELM;
49
+ linCol.setTag(DG.TAGS.CELL_RENDERER, 'helm');
50
+
51
+ const monomerLibHelper = await getMonomerLibHelper();
52
+ const systemMonomerLib = monomerLibHelper.getMonomerLib();
53
+ try {
54
+ const linear = await _toAtomicLevel(DG.DataFrame.create(0), linCol, systemMonomerLib, seqHelper, rdkit);
55
+ counter = 0;
56
+ for (let i = 0; i < isLinear.length; i++) {
57
+ if (isLinear[i]) {
58
+ resMolCol.set(i, linear!.molCol!.get(counter));
59
+ counter++;
60
+ }
61
+ }
62
+ } catch (e: any) {
63
+ grok.shell.warning('PolyTool was not able to linearize sequences');
64
+ }
65
+ }
66
+
67
+ dealGroups(resMolCol);
68
+
69
+ return resMolCol;
70
+ }
@@ -16,6 +16,7 @@ export class Chain {
16
16
  linkagesUnderRules: Linkage[];
17
17
  monomersUnderRules: string[][];
18
18
  molUnderRules: HelmMol;
19
+ posToPosUnderRules: number[][] = [];
19
20
 
20
21
  constructor(monomers: string[][], linkages: Linkage[], protected helmHelper: IHelmHelper) {
21
22
  this.linkages = linkages;
@@ -52,9 +53,28 @@ export class Chain {
52
53
  applyRules(rules: Rules): void {
53
54
  const sequence = this.getNotation();
54
55
 
55
- const [linkages, mainFragments] = handleDuplicated(sequence, rules);
56
+ const [linkages, mainFragments, isDuplicated] = handleDuplicated(sequence, rules);
56
57
  const monomers = new Array<Array<string>>(mainFragments.length);
57
- handleLinkRules(mainFragments, monomers, linkages, rules);
58
+
59
+ let counter = 0;
60
+ for (let i = 0; i < mainFragments.length; i++) {
61
+ monomers[i] = mainFragments[i].split('-');
62
+ if (!isDuplicated[i]) {
63
+ for (let j = 0; j < monomers[i].length; j++) {
64
+ this.posToPosUnderRules.push([counter]);
65
+ counter++;
66
+ }
67
+ } else {
68
+ const start = this.posToPosUnderRules.length - monomers[i].length;
69
+ for (let j = 0; j < monomers[i].length; j++) {
70
+ this.posToPosUnderRules[start + j].push(counter);
71
+ counter++;
72
+ }
73
+ }
74
+ }
75
+
76
+ //his.posToPosUnderRules
77
+ handleLinkRules(monomers, linkages, rules);
58
78
  handleReactionRules(monomers, linkages, rules);
59
79
 
60
80
  this.underRules = true;
@@ -6,9 +6,11 @@ import {Chain} from './pt-chain';
6
6
  import {_package} from '../../package';
7
7
 
8
8
  /** The main PolyTool convert engine. Returns list of Helms. Covered with tests. */
9
- export function doPolyToolConvert(sequences: string[], rules: Rules, helmHelper: IHelmHelper): [string[], boolean[]] {
9
+ export function doPolyToolConvert(sequences: string[], rules: Rules, helmHelper: IHelmHelper):
10
+ [string[], boolean[], number[][][]] {
10
11
  const helms = new Array<string>(sequences.length);
11
12
  const isLinear = new Array<boolean>(sequences.length);
13
+ const positionMaps = new Array<number[][]>(sequences.length);
12
14
  for (let i = 0; i < sequences.length; i++) {
13
15
  try {
14
16
  if (sequences[i] == null) { helms[i] = ''; } else {
@@ -16,6 +18,7 @@ export function doPolyToolConvert(sequences: string[], rules: Rules, helmHelper:
16
18
  chain.applyRules(rules);
17
19
  isLinear[i] = chain.monomersUnderRules.length > 1 || chain.linkagesUnderRules.length > 0 ? false : true;
18
20
  helms[i] = chain.getHelm();
21
+ positionMaps[i] = chain.posToPosUnderRules;
19
22
  }
20
23
  } catch (err: any) {
21
24
  const [errMsg, errStack] = errInfo(err);
@@ -23,5 +26,5 @@ export function doPolyToolConvert(sequences: string[], rules: Rules, helmHelper:
23
26
  helms[i] = '';
24
27
  }
25
28
  }
26
- return [helms, isLinear];
29
+ return [helms, isLinear, positionMaps];
27
30
  }
@@ -0,0 +1,160 @@
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
+ import './style.css';
5
+
6
+ import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types';
7
+ import {Rules} from './pt-rules';
8
+ import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
9
+ import {doPolyToolConvert} from './pt-conversion';
10
+ import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
11
+ import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
12
+ import {getSeqHelper, ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
13
+ import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
14
+ import {getOverriddenLibrary} from './pt-synthetic';
15
+ import {helmToMol} from './pt-atomic';
16
+
17
+ class MonomerCard {
18
+ root: HTMLElement = ui.divV([], {classes: 'monomer-card-rule-root'});
19
+
20
+ private _selected: boolean = false;
21
+ get selected(): boolean { return this._selected; }
22
+ set selected(value: boolean) {
23
+ this._selected = value;
24
+ this.root.style.border = value ? '2px solid var(--green-2)' : '2px solid var(--grey-2)';
25
+ }
26
+
27
+ constructor(public monomer: Monomer | null) {}
28
+
29
+ render() {
30
+ if (this.monomer) {
31
+ ui.empty(this.root);
32
+ const monomerMolSvg = this.monomer.smiles && grok.chem.checkSmiles(this.monomer.smiles) ?
33
+ grok.chem.drawMolecule(this.monomer.smiles, 150, 120) :
34
+ grok.chem.drawMolecule(this.monomer.molfile ?? '', 150, 120);
35
+ this.root.appendChild(monomerMolSvg);
36
+ const monomerName =
37
+ ui.divH([ui.divText('Monomer Name: '), ui.divText(this.monomer.name)], {classes: 'monomer-card-info-rules'});
38
+
39
+ this.root.appendChild(monomerName);
40
+ ui.tooltip.bind(monomerName, this.monomer.name);
41
+ if (this.monomer.lib?.source) {
42
+ const monomerSource =
43
+ ui.divH([ui.divText('Source: '), ui.divText(this.monomer.lib.source)], {classes: 'monomer-card-info-rules'});
44
+ this.root.appendChild(monomerSource);
45
+ ui.tooltip.bind(monomerSource, this.monomer.lib.source);
46
+ }
47
+ const monomerType = ui.divH([
48
+ ui.divText('Polymer Type: '),
49
+ ui.divText(this.monomer.polymerType)
50
+ ], {classes: 'monomer-card-info-rules'});
51
+ this.root.appendChild(monomerType);
52
+ ui.tooltip.bind(monomerType, this.monomer.polymerType);
53
+
54
+ ui.tooltip.bind(this.root, 'Select Monomer');
55
+ }
56
+ }
57
+ }
58
+
59
+ export class RuleCards {
60
+ root: HTMLElement = ui.divH([],
61
+ {style: {
62
+ alignItems: 'center',
63
+ width: '100%',
64
+ overflow: 'hidden',
65
+ visibility: 'visible',
66
+ }, classes: 'monomer-cards'}
67
+ );
68
+ cardsFirst: MonomerCard[];
69
+ cardsSecond: MonomerCard[];
70
+ firstCard: MonomerCard;
71
+ secondCard: MonomerCard;
72
+ resulting: HTMLElement;
73
+ actionable: boolean;
74
+
75
+ constructor(public firstMonomers: string[], public secondMonomers: string[],
76
+ lib: IMonomerLib, public code: number, public rules: Rules) {
77
+ if (firstMonomers.length > 0) {
78
+ this.actionable = true;
79
+ const monomerGalleryFirst = ui.divH([], {style: {overflowX: 'auto', width: '100%'}});
80
+ const monomerGallerySecond = ui.divH([], {style: {overflowX: 'auto', width: '100%'}});
81
+ this.resulting = ui.divH([], {style: {overflowX: 'auto', width: '100%', minHeight: '150px'}});
82
+ const galleries = ui.divV([monomerGalleryFirst, monomerGallerySecond, this.resulting]);
83
+ this.cardsFirst = firstMonomers.map((symbol) => new MonomerCard(lib.getMonomer('PEPTIDE', symbol)));
84
+ this.cardsSecond = secondMonomers.map((symbol) => new MonomerCard(lib.getMonomer('PEPTIDE', symbol)));
85
+
86
+ this.cardsFirst.forEach((card) => {
87
+ card.root.onclick = () => {
88
+ this.cardsFirst.forEach((c) => c.selected = false);
89
+ card.selected = true;
90
+ this.firstCard = card;
91
+ this.reset();
92
+ //onMonomerSelected(card.monomer);
93
+ };
94
+ monomerGalleryFirst.appendChild(card.root);
95
+ });
96
+
97
+ this.cardsSecond.forEach((card) => {
98
+ card.root.onclick = () => {
99
+ this.cardsSecond.forEach((c) => c.selected = false);
100
+ card.selected = true;
101
+ this.secondCard = card;
102
+ this.reset();
103
+ //onMonomerSelected(card.monomer);
104
+ };
105
+ monomerGallerySecond.appendChild(card.root);
106
+ });
107
+
108
+ this.cardsFirst[0].selected = true;
109
+ this.cardsSecond[0].selected = true;
110
+ this.firstCard = this.cardsFirst[0];
111
+ this.secondCard = this.cardsSecond[0];
112
+ this.root.appendChild(galleries);
113
+ } else {
114
+ const es = ui.divV(['Rule is active for all monomers, no examples to show']);
115
+ this.root.appendChild(es);
116
+ this.actionable = false;
117
+ }
118
+ }
119
+
120
+ async reset() {
121
+ if (this.actionable) {
122
+ const seqs: string[] = [
123
+ `${this.firstCard.monomer?.symbol}(${this.code})-A-A-A-A-${this.secondCard.monomer?.symbol}(${this.code})`
124
+ ];
125
+
126
+ const helmHelper = await getHelmHelper();
127
+ const [helms, isLinear, positionMaps] = doPolyToolConvert(seqs, this.rules, helmHelper);
128
+
129
+ const resHelmCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'helm', helms.length)
130
+ .init((rowIdx: number) => { return helms[rowIdx]; });
131
+ resHelmCol.semType = DG.SEMTYPE.MACROMOLECULE;
132
+ resHelmCol.meta.units = NOTATION.HELM;
133
+ resHelmCol.setTag(DG.TAGS.CELL_RENDERER, 'helm');
134
+
135
+ const rdKitModule: RDModule = await getRdKitModule();
136
+ const seqHelper: ISeqHelper = await getSeqHelper();
137
+
138
+ const lib = await getOverriddenLibrary(this.rules);
139
+ const resHelmColTemp = resHelmCol.temp;
140
+ resHelmCol.temp = resHelmColTemp;
141
+ const resMolCol = await helmToMol(resHelmCol, helms,
142
+ isLinear, true, false, false, lib, rdKitModule, seqHelper);
143
+
144
+ const mol = resMolCol.get(0);
145
+ const monomerMolSvg = mol && grok.chem.checkSmiles(mol) ?
146
+ grok.chem.drawMolecule(mol, 150, 120) :
147
+ grok.chem.drawMolecule(mol ?? '', 150, 120);
148
+
149
+ ui.empty(this.resulting);
150
+ this.resulting.append(ui.divH([monomerMolSvg], {style: {overflowX: 'auto', width: '100%', minHeight: '150px'}}));
151
+ }
152
+ }
153
+
154
+ render() {
155
+ if (this.actionable) {
156
+ this.cardsFirst.forEach((card) => card.render());
157
+ this.cardsSecond.forEach((card) => card.render());
158
+ }
159
+ }
160
+ }
@@ -3,6 +3,9 @@ import * as grok from 'datagrok-api/grok';
3
3
  import * as ui from 'datagrok-api/ui';
4
4
  import {ActiveFiles} from '@datagrok-libraries/utils/src/settings/active-files-base';
5
5
  import {RulesManager} from './rule-manager';
6
+ import {RuleCards} from './pt-rule-cards';
7
+ import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
8
+ import {applyNotationProviderForCyclized} from '../../package';
6
9
 
7
10
  export const RULES_PATH = 'System:AppData/SequenceTranslator/polytool-rules/';
8
11
  export const RULES_STORAGE_NAME = 'Polytool';
@@ -95,10 +98,17 @@ export class Rules {
95
98
  const length = this.linkRules.length;
96
99
  const codeCol = DG.Column.int(NAME_CODE, length);
97
100
  codeCol.setTag('friendlyName', 'Code');
101
+ //ui.tooltip.bind(codeCol.root, 'Click to zoom');
98
102
  const firstMonomerCol = DG.Column.string(NAME_FIRST_MONOMERS, length);
99
103
  firstMonomerCol.setTag('friendlyName', 'First monomers');
104
+ firstMonomerCol.semType = DG.SEMTYPE.MACROMOLECULE;
105
+ applyNotationProviderForCyclized(firstMonomerCol, ',');
106
+
100
107
  const secondMonomerCol = DG.Column.string(NAME_SECOND_MONOMERS, length);
101
108
  secondMonomerCol.setTag('friendlyName', 'Second monomers');
109
+ secondMonomerCol.semType = DG.SEMTYPE.MACROMOLECULE;
110
+ applyNotationProviderForCyclized(secondMonomerCol, ',');
111
+
102
112
  const firstLinkingGroup = DG.Column.int(NAME_FIRST_LINK, length);
103
113
  firstLinkingGroup.setTag('friendlyName', 'First group');
104
114
  const secondLinkingGroup = DG.Column.int(NAME_SECOND_LINK, length);
@@ -119,6 +129,20 @@ export class Rules {
119
129
  return res;
120
130
  }
121
131
 
132
+ async getLinkCards(): Promise<RuleCards[]> {
133
+ const length = this.linkRules.length;
134
+ const cards: RuleCards[] = new Array<RuleCards>(length);
135
+ const monomerLibHelper = await getMonomerLibHelper();
136
+ const systemMonomerLib = monomerLibHelper.getMonomerLib();
137
+
138
+ for (let i = 0; i < length; i++) {
139
+ cards[i] = new RuleCards(this.linkRules[i].firstMonomers, this.linkRules[i].secondMonomers,
140
+ systemMonomerLib, this.linkRules[i].code, this);
141
+ }
142
+
143
+ return cards;
144
+ }
145
+
122
146
  getSynthesisRulesDf(): DG.DataFrame {
123
147
  const length = this.reactionRules.length;
124
148
  const codeCol = DG.Column.int(NAME_CODE, length);
@@ -121,9 +121,10 @@ export function fromObjectsToHelm(linkages: Linkage[], monomers: string[][]): st
121
121
  return helm;
122
122
  }
123
123
 
124
- //homo and hetero dimers
125
- export function handleDuplicated(sequence: string, rules: Rules): [Linkage[], string[]] {
124
+ //homo and hetero dimers/ also fills map from initial positions to positions arrays
125
+ export function handleDuplicated(sequence: string, rules: Rules): [Linkage[], string[], boolean[]] {
126
126
  const mainFragments: string[] = [];
127
+ const isDuplicated: boolean[] = [];
127
128
  const linkages: Linkage[] = [];
128
129
  const heterodimerCode = rules.heterodimerCode;
129
130
  const homodimerCode = rules.homodimerCode;
@@ -134,8 +135,11 @@ export function handleDuplicated(sequence: string, rules: Rules): [Linkage[], st
134
135
  linkages.push({fChain: 0, sChain: 1, fMonomer: 1, sMonomer: 1, fR: 1, sR: 1});
135
136
  mainFragments.push(heterodimeric[1].replaceAll('{', '').replaceAll('}', ''));
136
137
  mainFragments.push(heterodimeric[2].replaceAll('{', '').replaceAll('}', ''));
138
+ isDuplicated.push(false);
139
+ isDuplicated.push(false);
137
140
  } else {
138
141
  mainFragments.push(sequence);
142
+ isDuplicated.push(false);
139
143
  }
140
144
 
141
145
  //NOTICE: this works only with simple single dimers
@@ -151,30 +155,16 @@ export function handleDuplicated(sequence: string, rules: Rules): [Linkage[], st
151
155
 
152
156
  mainFragments[i] = linker + body;
153
157
  mainFragments.push(body);
158
+ isDuplicated.push(true);
154
159
  }
155
160
  }
156
161
 
157
- for (let i = 0; i < mainFragments.length; i++) {
158
- if (homodimerCode !== null && mainFragments[i].includes(`(${homodimerCode!})`)) {
159
- const idxSequence = mainFragments.length;
160
-
161
- linkages.push({fChain: i, sChain: idxSequence, fMonomer: 1, sMonomer: 1, fR: 1, sR: 1});
162
- const rawDimer = mainFragments[i].replace(`(${homodimerCode!})`, '');
163
- const idx = rawDimer.indexOf('{');
164
- const linker = rawDimer.slice(0, idx);
165
- const body = rawDimer.replace(linker, '').replaceAll('{', '').replaceAll('}', '');
166
-
167
- mainFragments[i] = linker + body;
168
- mainFragments.push(body);
169
- }
170
- }
171
-
172
- return [linkages, mainFragments];
162
+ return [linkages, mainFragments, isDuplicated];
173
163
  }
174
164
 
175
- export function handleLinkRules(mf: string[], monomers: string[][], linkages: Linkage[], rules: Rules): void {
176
- for (let i = 0; i < mf.length; i++) {
177
- const rawMonomers = mf[i].split('-');
165
+ export function handleLinkRules(monomers: string[][], linkages: Linkage[], rules: Rules): void {
166
+ for (let i = 0; i < monomers.length; i++) {
167
+ const rawMonomers = monomers[i];
178
168
  const linkedPositions = getLinkedPositions(rawMonomers, rules.linkRules);
179
169
  const [allPos1, allPos2, allAttaches1, allAttaches2] =
180
170
  getAllCycles(rules.linkRules, rawMonomers, linkedPositions);
@@ -6,6 +6,7 @@ import {_package, applyNotationProviderForCyclized} from '../../package';
6
6
  import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
7
7
  import {doPolyToolConvert} from './pt-conversion';
8
8
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
9
+ import {RuleCards} from './pt-rule-cards';
9
10
 
10
11
 
11
12
  const TAB_LINKS = 'Links';
@@ -22,12 +23,15 @@ export class RulesManager {
22
23
  homoDimerInput: DG.InputBase;
23
24
  heteroDimerInput: DG.InputBase;
24
25
 
26
+ linkCards: RuleCards[];
27
+
25
28
  // every rule set will have its editor instance
26
29
  private static instances: Record<string, RulesManager> = {};
27
30
 
28
31
  protected constructor(rules: Rules, fileName: string) {
29
32
  this.rules = rules;
30
33
  this.linkRuleDataFrame = this.rules.getLinkRulesDf();
34
+
31
35
  this.synthRuleDataFrame = this.rules.getSynthesisRulesDf();
32
36
  this.fileName = fileName;
33
37
 
@@ -96,8 +100,9 @@ export class RulesManager {
96
100
  grok.shell.info(`Polytool rules at ${this.fileName} was updated`);
97
101
  }
98
102
 
99
- private createGridDiv(name: string, grid: DG.Grid) {
103
+ private createGridDiv(name: string, grid: DG.Grid, tooltipMsg?: string) {
100
104
  const header = ui.h1(name, 'polytool-grid-header');
105
+ ui.tooltip.bind(header, tooltipMsg);
101
106
  header.style.marginTop = '10px';
102
107
  header.style.marginRight = '10px';
103
108
  grid.root.style.height = '100%';
@@ -128,7 +133,7 @@ export class RulesManager {
128
133
  }
129
134
  const helmHelper = await getHelmHelper();
130
135
 
131
- const [helms, isLinear] = doPolyToolConvert(seqs, this.rules, helmHelper);
136
+ const [helms, isLinear, positionMaps] = doPolyToolConvert(seqs, this.rules, helmHelper);
132
137
 
133
138
  const initCol = DG.Column.fromStrings('Monomers', seqs);
134
139
  const helmCol = DG.Column.fromStrings('Helm', helms);
@@ -157,7 +162,7 @@ export class RulesManager {
157
162
  }
158
163
  const helmHelper = await getHelmHelper();
159
164
 
160
- const [helms, isLinear] = doPolyToolConvert(seqs, this.rules, helmHelper);
165
+ const [helms, isLinear, positionMaps] = doPolyToolConvert(seqs, this.rules, helmHelper);
161
166
 
162
167
  const initCol = DG.Column.fromStrings('Monomers', seqs);
163
168
  const helmCol = DG.Column.fromStrings('Helm', helms);
@@ -177,11 +182,45 @@ export class RulesManager {
177
182
  inputsTabControl: DG.TabControl;
178
183
 
179
184
  const linksGridDiv = this.createGridDiv('Rules',
180
- this.linkRuleDataFrame.plot.grid({showAddNewRowIcon: true}));
181
- const linkExamples = this.createGridDiv('Examples', await this.getLinkExamplesGrid());
185
+ this.linkRuleDataFrame.plot.grid({showAddNewRowIcon: true}),
186
+ 'specification for monomers to link and linking positions');
187
+ const linkExamples = this.createGridDiv('Examples', await this.getLinkExamplesGrid(),
188
+ 'specification for monomers to link and linking positions');
182
189
  linksGridDiv.style.width = '50%';
183
190
  linkExamples.style.width = '50%';
184
- const links = ui.divH([linksGridDiv, linkExamples]);
191
+ const header = ui.h1('Monomers', 'polytool-grid-header');
192
+ ui.tooltip.bind(header, 'Click different cobination to see how monomers will link');
193
+ this.linkCards = await this.rules.getLinkCards();
194
+ const gridDiv: HTMLElement = ui.splitV([
195
+ ui.box(
196
+ header,
197
+ {style: {maxHeight: '30px'}},
198
+ ),
199
+ this.linkCards[0].root,
200
+ ]);
201
+
202
+ this.linkCards[0].render();
203
+ await this.linkCards[0].reset();
204
+
205
+ this.linkRuleDataFrame.currentRowIdx = 0;
206
+ this.linkRuleDataFrame.onCurrentRowChanged.subscribe(async () => {
207
+ const idx = this.linkRuleDataFrame.currentRowIdx;
208
+ if (idx !== -1) {
209
+ ui.empty(gridDiv);
210
+ gridDiv.append(ui.splitV([
211
+ ui.box(
212
+ header,
213
+ {style: {maxHeight: '30px'}},
214
+ ),
215
+ this.linkCards[idx].root,
216
+ ]));
217
+ }
218
+
219
+ this.linkCards[idx].render();
220
+ await this.linkCards[idx].reset();
221
+ });
222
+
223
+ const links = ui.splitH([linksGridDiv, gridDiv], null, true);
185
224
 
186
225
  const reactionsGridDiv = this.createGridDiv('Rules',
187
226
  this.synthRuleDataFrame.plot.grid({showAddNewRowIcon: true}));
@@ -201,6 +240,13 @@ export class RulesManager {
201
240
  'Dimers': dimerInputsDiv,
202
241
  }, false);
203
242
 
243
+ ui.tooltip.bind(inputsTabControl.getPane(TAB_LINKS).header,
244
+ 'Specify rules to link monomers based on HELM notation');
245
+ ui.tooltip.bind(inputsTabControl.getPane(TAB_REACTIONS).header,
246
+ 'Specify rules to perform reactions within monomers');
247
+ ui.tooltip.bind(inputsTabControl.getPane(TAB_DIMERS).header,
248
+ 'Specify symbols for homodimeric and heterodimeric codes');
249
+
204
250
  inputsTabControl.root.style.height = '100%';
205
251
  inputsTabControl.root.style.width = '100%';
206
252
  inputsTabControl.root.classList.add('rules-manager-form-tab-control');
@@ -0,0 +1,32 @@
1
+ .monomer-card-rule-root {
2
+ border: 2px solid var(--grey-2);
3
+ border-radius: 5px;
4
+ padding: 5px;
5
+ margin: 5px;
6
+ overflow: hidden;
7
+ align-items: center;
8
+ cursor: pointer;
9
+ width: 200px;
10
+ height: 210px;
11
+ min-width: 200px;
12
+ min-height: 210px;
13
+ }
14
+
15
+ .monomer-card-info-rules {
16
+ width: 100%;
17
+ overflow: hidden;
18
+ text-overflow: ellipsis;
19
+ margin-bottom: 10px;
20
+ }
21
+
22
+ .monomer-card-info-rules > div:nth-child(2) {
23
+ white-space: nowrap;
24
+ overflow: hidden;
25
+ text-overflow: ellipsis;
26
+ }
27
+
28
+ .monomer-card-info-rules > div:nth-child(1) {
29
+ font-weight: bold;
30
+ margin-right: 5px;
31
+ text-wrap: nowrap;
32
+ }