@datagrok/sequence-translator 1.9.11 → 1.9.13
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/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/files/polytool-rules/rules_example.json +98 -9
- package/files/samples/cyclized.csv +6 -0
- package/package.json +1 -1
- package/src/polytool/conversion/pt-rules.ts +94 -2
- package/src/polytool/conversion/rule-manager.ts +92 -10
- package/src/polytool/conversion/rule-reaction-editor.ts +125 -0
- package/src/polytool/pt-convert-editor.ts +2 -2
- package/test-console-output-1.log +82 -55
- package/test-record-1.mp4 +0 -0
|
@@ -3,26 +3,71 @@
|
|
|
3
3
|
"heterodimerCode": "$2",
|
|
4
4
|
"linkRules": [
|
|
5
5
|
{
|
|
6
|
+
"code": 2,
|
|
6
7
|
"firstMonomers": [
|
|
7
8
|
"D"
|
|
8
9
|
],
|
|
9
10
|
"secondMonomers": [
|
|
10
11
|
"NH2"
|
|
11
12
|
],
|
|
12
|
-
"firstLinkingGroup":
|
|
13
|
-
"secondLinkingGroup":
|
|
14
|
-
"code": "2"
|
|
13
|
+
"firstLinkingGroup": 3,
|
|
14
|
+
"secondLinkingGroup": 2
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
|
+
"code": 1,
|
|
17
18
|
"firstMonomers": [
|
|
18
|
-
"C"
|
|
19
|
+
"C",
|
|
20
|
+
"dC"
|
|
19
21
|
],
|
|
20
22
|
"secondMonomers": [
|
|
21
|
-
"C"
|
|
23
|
+
"C",
|
|
24
|
+
"dC"
|
|
22
25
|
],
|
|
23
|
-
"firstLinkingGroup":
|
|
24
|
-
"secondLinkingGroup":
|
|
25
|
-
|
|
26
|
+
"firstLinkingGroup": 3,
|
|
27
|
+
"secondLinkingGroup": 3
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"code": 10,
|
|
31
|
+
"firstMonomers": [
|
|
32
|
+
"C",
|
|
33
|
+
"D",
|
|
34
|
+
"E",
|
|
35
|
+
"K"
|
|
36
|
+
],
|
|
37
|
+
"secondMonomers": [
|
|
38
|
+
"C",
|
|
39
|
+
"D",
|
|
40
|
+
"E",
|
|
41
|
+
"K"
|
|
42
|
+
],
|
|
43
|
+
"firstLinkingGroup": 3,
|
|
44
|
+
"secondLinkingGroup": 2
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"code": 11,
|
|
48
|
+
"firstMonomers": [
|
|
49
|
+
"C",
|
|
50
|
+
"E",
|
|
51
|
+
"K"
|
|
52
|
+
],
|
|
53
|
+
"secondMonomers": [
|
|
54
|
+
"NH2"
|
|
55
|
+
],
|
|
56
|
+
"firstLinkingGroup": 3,
|
|
57
|
+
"secondLinkingGroup": 2
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"code": 1,
|
|
61
|
+
"firstMonomers": [
|
|
62
|
+
"C",
|
|
63
|
+
"dC"
|
|
64
|
+
],
|
|
65
|
+
"secondMonomers": [
|
|
66
|
+
"THA",
|
|
67
|
+
"MG3"
|
|
68
|
+
],
|
|
69
|
+
"firstLinkingGroup": 3,
|
|
70
|
+
"secondLinkingGroup": 3
|
|
26
71
|
}
|
|
27
72
|
],
|
|
28
73
|
"reactionRules": [
|
|
@@ -36,6 +81,50 @@
|
|
|
36
81
|
],
|
|
37
82
|
"reaction": "[C:1]N=[N+]=[N-].[C:2]C#C>>[C:1]N1-N=NC=C1[C:2]",
|
|
38
83
|
"name": "GGaz"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"code": 8,
|
|
87
|
+
"firstMonomers": [
|
|
88
|
+
"DRR1"
|
|
89
|
+
],
|
|
90
|
+
"secondMonomers": [
|
|
91
|
+
"DRR2"
|
|
92
|
+
],
|
|
93
|
+
"reaction": "C=C[C:1].C=C/C=C/[C:2]>>C1=CC([C:2])C([C:1])CC1",
|
|
94
|
+
"name": "DARR"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"code": 8,
|
|
98
|
+
"firstMonomers": [
|
|
99
|
+
"ODAR1"
|
|
100
|
+
],
|
|
101
|
+
"secondMonomers": [
|
|
102
|
+
"ODAR2"
|
|
103
|
+
],
|
|
104
|
+
"reaction": "C=CC(=O)[C:1].C=C[C:2]>>C1CC([C:1])OC([C:2])C1",
|
|
105
|
+
"name": "ODARR"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"code": 7,
|
|
109
|
+
"firstMonomers": [
|
|
110
|
+
"PHAR1"
|
|
111
|
+
],
|
|
112
|
+
"secondMonomers": [
|
|
113
|
+
"PHAR2"
|
|
114
|
+
],
|
|
115
|
+
"reaction": "C=C[C:1].C=C[C:2]>>C1CC([C:2])C1[C:1]",
|
|
116
|
+
"name": "PHAR_CIS"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"code": 6,
|
|
120
|
+
"firstMonomers": [
|
|
121
|
+
"PHAR1"
|
|
122
|
+
],
|
|
123
|
+
"secondMonomers": [
|
|
124
|
+
"PHAR2"
|
|
125
|
+
],
|
|
126
|
+
"reaction": "C=C[C:1].C=C[C:2]>>C1C([C:1])CC1[C:2]",
|
|
127
|
+
"name": "PHAR_TRANS"
|
|
39
128
|
}
|
|
40
129
|
]
|
|
41
|
-
}
|
|
130
|
+
}
|
|
@@ -7,3 +7,9 @@ n,seqs
|
|
|
7
7
|
6,D(2)-T-G-H-F-Y-P-NH2(2)
|
|
8
8
|
7,R-F-azG(4)-T-G-H-F-Y-P-aG(4)-meI
|
|
9
9
|
8,R-F-aG(4)-T-G-H-F-Y-P-azG(4)-meI
|
|
10
|
+
9,R-F-R-I-F-D-C(1)-T-G-H-F-Y-G-H-F-Y-G-H-F-Y-P-THA(1)-meI-G-T
|
|
11
|
+
10,R-F-R-I-F-D-C(1)-T-G-H-F-Y-G-H-F-Y-G-H-F-Y-P-MG3(1)-meI-G-T
|
|
12
|
+
11,R-F-DRR1(8)-T-G-H-F-Y-P-DRR2(8)-C-C
|
|
13
|
+
12,R-F-ODAR1(8)-T-G-H-F-Y-P-ODAR2(8)-meI-R-P-NH2
|
|
14
|
+
13,R-F-PHAR1(7)-T-G-H-F-Y-P-PHAR2(7)-meI-F-K-NH2
|
|
15
|
+
14,R-F-PHAR1(6)-T-G-H-F-Y-P-PHAR2(6)-C-C
|
package/package.json
CHANGED
|
@@ -7,8 +7,10 @@ import {RulesManager} from './rule-manager';
|
|
|
7
7
|
import {RuleCards} from './pt-rule-cards';
|
|
8
8
|
import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
9
9
|
import {
|
|
10
|
+
_package,
|
|
10
11
|
PackageFunctions} from '../../package';
|
|
11
12
|
import {MmcrTemps} from '@datagrok-libraries/bio/src/utils/cell-renderer-consts';
|
|
13
|
+
import {ReactionEditorProps} from './rule-reaction-editor';
|
|
12
14
|
|
|
13
15
|
export const RULES_PATH = 'System:AppData/SequenceTranslator/polytool-rules/';
|
|
14
16
|
export const RULES_STORAGE_NAME = 'Polytool';
|
|
@@ -25,6 +27,7 @@ const NAME_FIRST_LINK = 'firstLinkingGroup';
|
|
|
25
27
|
const NAME_SECOND_LINK = 'secondLinkingGroup';
|
|
26
28
|
|
|
27
29
|
export class RuleInputs extends ActiveFiles {
|
|
30
|
+
static ruleDescriptions: Record<string, Promise<HTMLElement>> = {};
|
|
28
31
|
constructor(
|
|
29
32
|
path: string, userStorageName: string, ext: string,
|
|
30
33
|
options?: { onValueChanged: (value: string[]) => void }
|
|
@@ -47,8 +50,70 @@ export class RuleInputs extends ActiveFiles {
|
|
|
47
50
|
|
|
48
51
|
return res;
|
|
49
52
|
}
|
|
53
|
+
|
|
54
|
+
override async getForm(): Promise<HTMLDivElement> {
|
|
55
|
+
const form = await super.getForm();
|
|
56
|
+
this.processRulesForm(form);
|
|
57
|
+
return form;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async processRulesForm(rulesForm: HTMLElement) {
|
|
61
|
+
const ruleNameLabels = Array.from(rulesForm.getElementsByTagName('label') ?? [])
|
|
62
|
+
.filter((l) => l.textContent?.endsWith('.json') && l.parentElement != null);
|
|
63
|
+
for (const label of ruleNameLabels) {
|
|
64
|
+
const ruleName = label.textContent!.trim();
|
|
65
|
+
const inputRoot = label.parentElement!.getElementsByTagName('input')[0]!;
|
|
66
|
+
ui.tooltip.bind(inputRoot, ui.wait(async () => {
|
|
67
|
+
RuleInputs.ruleDescriptions[ruleName] ??= (async () => {
|
|
68
|
+
try {
|
|
69
|
+
const rule = JSON.parse(await grok.dapi.files.readAsText(`${RULES_PATH}/${ruleName}`));
|
|
70
|
+
const descDiv = ui.divV([ui.h1(ruleName)]);
|
|
71
|
+
const linkRules: RuleLink[] = rule.linkRules ?? [];
|
|
72
|
+
// link rules
|
|
73
|
+
if (linkRules.length > 0) {
|
|
74
|
+
descDiv.appendChild(ui.h3('Linkage Rules'));
|
|
75
|
+
const table = ui.table(linkRules, (item) => [
|
|
76
|
+
item.code?.toString() ?? '',
|
|
77
|
+
shortenToMaxLen((item.firstMonomers ?? []).join(', ')),
|
|
78
|
+
shortenToMaxLen((item.secondMonomers ?? []).join(', ')),
|
|
79
|
+
item.firstLinkingGroup?.toString() ?? '',
|
|
80
|
+
item.secondLinkingGroup?.toString() ?? ''
|
|
81
|
+
], ['Code', 'M1', 'M2', 'L1', 'L2']);
|
|
82
|
+
descDiv.appendChild(table);
|
|
83
|
+
}
|
|
84
|
+
// reaction rules
|
|
85
|
+
const reactionRules: RuleReaction[] = rule.reactionRules ?? [];
|
|
86
|
+
if (reactionRules.length > 0) {
|
|
87
|
+
descDiv.appendChild(ui.h3('Synthesis Rules'));
|
|
88
|
+
const table = ui.table(reactionRules, (item) => [
|
|
89
|
+
item.code?.toString() ?? '',
|
|
90
|
+
shortenToMaxLen((item.firstMonomers ?? []).join(', ')),
|
|
91
|
+
shortenToMaxLen((item.secondMonomers ?? []).join(', ')),
|
|
92
|
+
shortenToMaxLen(item.name ?? ''),
|
|
93
|
+
grok.chem.drawMolecule(item.reaction?.split('>>')[1] ?? '', 70, 70)
|
|
94
|
+
], ['Code', 'M1', 'M2', 'Name', 'Product']);
|
|
95
|
+
descDiv.appendChild(table);
|
|
96
|
+
}
|
|
97
|
+
return descDiv;
|
|
98
|
+
} catch (e: any) {
|
|
99
|
+
_package.logger.error(`Failed to load rule ${ruleName}: ${e?.toString?.()}`);
|
|
100
|
+
console.error(e);
|
|
101
|
+
return ui.divText(`Failed to load rule ${ruleName}`);
|
|
102
|
+
}
|
|
103
|
+
})();
|
|
104
|
+
return await RuleInputs.ruleDescriptions[ruleName];
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
50
108
|
}
|
|
51
109
|
|
|
110
|
+
|
|
111
|
+
function shortenToMaxLen(s: string, maxLen: number = 30): string {
|
|
112
|
+
if (s.length <= maxLen) return s;
|
|
113
|
+
return `${s.substring(0, maxLen)}...`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
52
117
|
export type RuleLink = {
|
|
53
118
|
code: number,
|
|
54
119
|
firstMonomers: string[],
|
|
@@ -170,7 +235,7 @@ export class Rules {
|
|
|
170
235
|
return cards;
|
|
171
236
|
}
|
|
172
237
|
|
|
173
|
-
getSynthesisRulesDf()
|
|
238
|
+
getSynthesisRulesDf() {
|
|
174
239
|
const length = this.reactionRules.length;
|
|
175
240
|
const codeCol = DG.Column.int(NAME_CODE, length);
|
|
176
241
|
codeCol.setTag('friendlyName', 'Code');
|
|
@@ -178,6 +243,15 @@ export class Rules {
|
|
|
178
243
|
firstMonomerCol.setTag('friendlyName', 'First monomers');
|
|
179
244
|
const secondMonomerCol = DG.Column.string(NAME_SECOND_MONOMERS, length);
|
|
180
245
|
secondMonomerCol.setTag('friendlyName', 'Second monomers');
|
|
246
|
+
// set these columns as macromolecule for better visualization
|
|
247
|
+
firstMonomerCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
248
|
+
firstMonomerCol.temp[MmcrTemps.fontSize] = 16;
|
|
249
|
+
PackageFunctions.applyNotationProviderForCyclized(firstMonomerCol, ',');
|
|
250
|
+
secondMonomerCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
251
|
+
secondMonomerCol.temp[MmcrTemps.fontSize] = 16;
|
|
252
|
+
PackageFunctions.applyNotationProviderForCyclized(secondMonomerCol, ',');
|
|
253
|
+
secondMonomerCol.setTag(DG.TAGS.CELL_RENDERER, 'Sequence');
|
|
254
|
+
firstMonomerCol.setTag(DG.TAGS.CELL_RENDERER, 'Sequence');
|
|
181
255
|
const name = DG.Column.string(NAME_REACTION_NAME, length);
|
|
182
256
|
name.setTag('friendlyName', 'Name');
|
|
183
257
|
const firstReactant = DG.Column.string('firstReactant', length);
|
|
@@ -205,9 +279,27 @@ export class Rules {
|
|
|
205
279
|
product.semType = DG.SEMTYPE.MOLECULE;
|
|
206
280
|
|
|
207
281
|
|
|
208
|
-
|
|
282
|
+
const df = DG.DataFrame.fromColumns([
|
|
209
283
|
name, firstReactant, secondReactant, product, codeCol, firstMonomerCol, secondMonomerCol
|
|
210
284
|
]);
|
|
285
|
+
|
|
286
|
+
const addNewRow = (rowObj: ReactionEditorProps) => {
|
|
287
|
+
if (rowObj.rowIndex != undefined && rowObj.rowIndex < df.rowCount && rowObj.rowIndex >= 0) {
|
|
288
|
+
name.set(rowObj.rowIndex, rowObj.resultMonomerName);
|
|
289
|
+
codeCol.set(rowObj.rowIndex, rowObj.code);
|
|
290
|
+
firstMonomerCol.set(rowObj.rowIndex, rowObj.firstMonomers.join(','));
|
|
291
|
+
secondMonomerCol.set(rowObj.rowIndex, rowObj.secondMonomers.join(','));
|
|
292
|
+
firstReactant.set(rowObj.rowIndex, rowObj.firstReactantSmiles);
|
|
293
|
+
secondReactant.set(rowObj.rowIndex, rowObj.secondReactantSmiles);
|
|
294
|
+
product.set(rowObj.rowIndex, rowObj.productSmiles);
|
|
295
|
+
} else {
|
|
296
|
+
const {resultMonomerName, code, firstMonomers, secondMonomers,
|
|
297
|
+
firstReactantSmiles, secondReactantSmiles, productSmiles} = rowObj;
|
|
298
|
+
df.rows.addNew([resultMonomerName, firstReactantSmiles, secondReactantSmiles,
|
|
299
|
+
productSmiles, code, firstMonomers.join(','), secondMonomers.join(',')]);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
return {df, addNewRow};
|
|
211
303
|
}
|
|
212
304
|
|
|
213
305
|
setLinkRules(df: DG.DataFrame) : void {
|
|
@@ -8,6 +8,12 @@ import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
|
8
8
|
import {doPolyToolConvert} from './pt-conversion';
|
|
9
9
|
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
10
10
|
import {RuleCards} from './pt-rule-cards';
|
|
11
|
+
import {getOverriddenLibrary} from './pt-synthetic';
|
|
12
|
+
import {MmcrTemps} from '@datagrok-libraries/bio/src/utils/cell-renderer-consts';
|
|
13
|
+
import {helmToMol} from './pt-atomic';
|
|
14
|
+
import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
|
|
15
|
+
import {getSeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
16
|
+
import {getReactionEditor, ReactionEditorProps} from './rule-reaction-editor';
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
const TAB_LINKS = 'Links';
|
|
@@ -26,6 +32,7 @@ export class RulesManager {
|
|
|
26
32
|
|
|
27
33
|
linkCards: RuleCards[];
|
|
28
34
|
addLinkRulesFunc: (rowObj: LinkRuleRowArgs) => void;
|
|
35
|
+
addSynthRulesFunc: (rowObj: ReactionEditorProps) => void;
|
|
29
36
|
|
|
30
37
|
// every rule set will have its editor instance
|
|
31
38
|
private static instances: Record<string, RulesManager> = {};
|
|
@@ -36,7 +43,15 @@ export class RulesManager {
|
|
|
36
43
|
this.linkRuleDataFrame = linksRes.res;
|
|
37
44
|
this.addLinkRulesFunc = linksRes.addNewRow;
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
const synthRes = this.rules.getSynthesisRulesDf();
|
|
47
|
+
this.addSynthRulesFunc = (r) => {
|
|
48
|
+
synthRes.addNewRow(r);
|
|
49
|
+
this.rules.setSynthesisRules(synthRes.df);
|
|
50
|
+
this.synthRuleDataFrame = synthRes.df;
|
|
51
|
+
this.substituteReactionGridDataFrame?.();
|
|
52
|
+
this.save();
|
|
53
|
+
};
|
|
54
|
+
this.synthRuleDataFrame = synthRes.df;
|
|
40
55
|
this.fileName = fileName;
|
|
41
56
|
|
|
42
57
|
const homoValue = this.rules.homodimerCode ? this.rules.homodimerCode : '';
|
|
@@ -170,18 +185,36 @@ export class RulesManager {
|
|
|
170
185
|
|
|
171
186
|
const initCol = DG.Column.fromStrings('Monomers', seqs);
|
|
172
187
|
const helmCol = DG.Column.fromStrings('Helm', helms);
|
|
173
|
-
|
|
188
|
+
const df = DG.DataFrame.fromColumns([
|
|
189
|
+
initCol, helmCol
|
|
190
|
+
]);
|
|
174
191
|
initCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
192
|
+
const rdKitModule = await getRdKitModule();
|
|
193
|
+
const seqHelper = await getSeqHelper();
|
|
194
|
+
// initialize seqHandler for this column
|
|
175
195
|
PackageFunctions.applyNotationProviderForCyclized(initCol, '-');
|
|
196
|
+
initCol.tags[DG.TAGS.CELL_RENDERER] = 'Sequence';
|
|
176
197
|
|
|
177
198
|
helmCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
178
199
|
helmCol.meta.units = NOTATION.HELM;
|
|
179
200
|
helmCol.setTag(DG.TAGS.CELL_RENDERER, 'helm');
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
201
|
+
|
|
202
|
+
const lib = await getOverriddenLibrary(this.rules);
|
|
203
|
+
const resHelmColTemp = helmCol.temp;
|
|
204
|
+
resHelmColTemp[MmcrTemps.overriddenLibrary] = lib;
|
|
205
|
+
helmCol.temp = resHelmColTemp;
|
|
206
|
+
|
|
207
|
+
const resMolCol = await helmToMol(helmCol, seqs,
|
|
208
|
+
isLinear, true, false, false, lib, rdKitModule, seqHelper);
|
|
209
|
+
resMolCol.name = `molfile(sequence)`;
|
|
210
|
+
resMolCol.semType = DG.SEMTYPE.MOLECULE;
|
|
211
|
+
df.columns.add(resMolCol);
|
|
212
|
+
|
|
213
|
+
return df.plot.grid();
|
|
183
214
|
}
|
|
184
215
|
|
|
216
|
+
private substituteReactionGridDataFrame: (() => void) | null = null;
|
|
217
|
+
|
|
185
218
|
async getForm() {
|
|
186
219
|
const gridOptions: Partial<DG.IGridSettings> = {showAddNewRowIcon: false, allowEdit: false, rowHeight: 60};
|
|
187
220
|
|
|
@@ -240,10 +273,31 @@ export class RulesManager {
|
|
|
240
273
|
});
|
|
241
274
|
|
|
242
275
|
const links = ui.splitH([linksGridDiv, gridDiv], null, true);
|
|
276
|
+
const synthesisGrid = this.synthRuleDataFrame.plot.grid({showAddNewRowIcon: false, allowEdit: false, rowHeight: 130});
|
|
277
|
+
synthesisGrid.onCellDoubleClick.subscribe(() => {
|
|
278
|
+
if (!synthesisGrid.dataFrame || synthesisGrid.dataFrame.currentRowIdx == -1 || synthesisGrid.dataFrame.currentRowIdx == undefined)
|
|
279
|
+
return;
|
|
280
|
+
const idx = synthesisGrid.dataFrame.currentRowIdx;
|
|
281
|
+
const editArgs: ReactionEditorProps = {
|
|
282
|
+
rowIndex: idx,
|
|
283
|
+
code: this.synthRuleDataFrame.get('code', idx),
|
|
284
|
+
firstMonomers: this.synthRuleDataFrame.get('firstMonomers', idx).split(',').map((s: string) => s.trim()).filter((s: string) => s),
|
|
285
|
+
secondMonomers: this.synthRuleDataFrame.get('secondMonomers', idx).split(',').map((s: string) => s.trim()).filter((s: string) => s),
|
|
286
|
+
resultMonomerName: this.synthRuleDataFrame.get('name', idx),
|
|
287
|
+
firstReactantSmiles: this.synthRuleDataFrame.get('firstReactant', idx),
|
|
288
|
+
secondReactantSmiles: this.synthRuleDataFrame.get('secondReactant', idx),
|
|
289
|
+
productSmiles: this.synthRuleDataFrame.get('product', idx),
|
|
290
|
+
};
|
|
291
|
+
getReactionEditor((props: ReactionEditorProps) => this.addSynthRulesFunc(props), editArgs);
|
|
292
|
+
});
|
|
293
|
+
const reactionsGridDiv = this.createGridDiv('Rules', synthesisGrid);
|
|
243
294
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
295
|
+
const reactionExamplesGrid = await this.getReactionExamplesGrid();
|
|
296
|
+
const reactionExamples = this.createGridDiv('Examples', reactionExamplesGrid);
|
|
297
|
+
this.substituteReactionGridDataFrame = async () => {
|
|
298
|
+
const newGrid = await this.getReactionExamplesGrid();
|
|
299
|
+
reactionExamplesGrid.dataFrame = newGrid.dataFrame;
|
|
300
|
+
};
|
|
247
301
|
reactionsGridDiv.style.width = '50%';
|
|
248
302
|
reactionExamples.style.width = '50%';
|
|
249
303
|
const reactions = ui.divH([reactionsGridDiv, reactionExamples]);
|
|
@@ -281,9 +335,37 @@ export class RulesManager {
|
|
|
281
335
|
if (currentTab == TAB_LINKS)
|
|
282
336
|
this.getAddNewLinkRuleDialog();
|
|
283
337
|
else if (currentTab == TAB_REACTIONS)
|
|
284
|
-
this.
|
|
338
|
+
getReactionEditor((props: ReactionEditorProps) => this.addSynthRulesFunc(props));
|
|
285
339
|
});
|
|
286
|
-
const
|
|
340
|
+
const removeButton = ui.button('Remove rule', () => {
|
|
341
|
+
const currentTab = inputsTabControl.currentPane.name;
|
|
342
|
+
if (currentTab == TAB_LINKS) {
|
|
343
|
+
if (this.linkRuleDataFrame == null || this.linkRuleDataFrame.currentRowIdx == -1 || this.linkRuleDataFrame.currentRowIdx == undefined)
|
|
344
|
+
return;
|
|
345
|
+
const idx = linksGrid.dataFrame.currentRowIdx;
|
|
346
|
+
ui.dialog('Are you sure you want to remove the rule?')
|
|
347
|
+
.add(ui.divText('This action is irreversible!'))
|
|
348
|
+
.onOK(() => {
|
|
349
|
+
this.linkRuleDataFrame.rows.removeAt(idx);
|
|
350
|
+
this.rules.setLinkRules(this.linkRuleDataFrame);
|
|
351
|
+
this.save();
|
|
352
|
+
}).show();
|
|
353
|
+
} else if (currentTab == TAB_REACTIONS) {
|
|
354
|
+
if (this.synthRuleDataFrame == null || this.synthRuleDataFrame.currentRowIdx == -1 || this.synthRuleDataFrame.currentRowIdx == undefined)
|
|
355
|
+
return;
|
|
356
|
+
const idx = synthesisGrid.dataFrame.currentRowIdx;
|
|
357
|
+
ui.dialog('Are you sure you want to remove the rule?')
|
|
358
|
+
.add(ui.divText('This action is irreversible!'))
|
|
359
|
+
.onOK(() => {
|
|
360
|
+
this.synthRuleDataFrame.rows.removeAt(idx);
|
|
361
|
+
this.rules.setSynthesisRules(this.synthRuleDataFrame);
|
|
362
|
+
this.substituteReactionGridDataFrame?.();
|
|
363
|
+
this.save();
|
|
364
|
+
}).show();
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const topPanel = [saveButton, addButton, removeButton];
|
|
287
369
|
this.v!.setRibbonPanels([topPanel]);
|
|
288
370
|
|
|
289
371
|
panel.style.height = '100%';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
3
|
+
import * as grok from 'datagrok-api/grok';
|
|
4
|
+
import * as ui from 'datagrok-api/ui';
|
|
5
|
+
|
|
6
|
+
function correctRGroups(smiles: string): string {
|
|
7
|
+
const elementRGroupRegex = /\[R[1-9]\]/g;
|
|
8
|
+
// replace all [R1] with [*:1]
|
|
9
|
+
let correctedSmiles = smiles.replaceAll(elementRGroupRegex, (match) => {
|
|
10
|
+
const rGroupNum = match[2];
|
|
11
|
+
return `[*:${rGroupNum}]`;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// in some scenarios, rgroups can be written as [2*]
|
|
15
|
+
const elementRGroupRegex2 = /\[\d\*\]/g;
|
|
16
|
+
correctedSmiles = correctedSmiles.replaceAll(elementRGroupRegex2, (match) => {
|
|
17
|
+
const rGroupNum = match[1];
|
|
18
|
+
return `[*:${rGroupNum}]`;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// in some other scenarios, rgroups can be written as [1*:1] or [1*:0]
|
|
22
|
+
const elementRGroupRegex3 = /\[\d\*\:\d\]/g;
|
|
23
|
+
return correctedSmiles.replaceAll(elementRGroupRegex3, (match) => {
|
|
24
|
+
const rGroupNum = match[1];
|
|
25
|
+
return `[*:${rGroupNum}]`;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function rgroupToRadicalForm(smiles: string): string {
|
|
30
|
+
// here we are sure that all rgroups are in the form [*:1], [*:2], ..., [*:9]
|
|
31
|
+
// replace all [*:1] with [C:1] to mark carbons
|
|
32
|
+
return smiles.replaceAll(/\[\*\:\d\]/g, (match) => {
|
|
33
|
+
const rGroupNum = match[3];
|
|
34
|
+
return `[C:${rGroupNum}]`;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function radicalToRgroupForm(smiles: string): string {
|
|
39
|
+
// here we are sure that all marked carbons are in the form [C:1], [C:2], ..., [C:9]
|
|
40
|
+
// replace all [C:1] with [*:1]
|
|
41
|
+
return smiles.replaceAll(/\[C\:\d\]/g, (match) => {
|
|
42
|
+
const rGroupNum = match[3];
|
|
43
|
+
return `[*:${rGroupNum}]`;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ReactionEditorProps = {
|
|
48
|
+
resultMonomerName: string,
|
|
49
|
+
firstReactantSmiles: string,
|
|
50
|
+
secondReactantSmiles: string,
|
|
51
|
+
productSmiles: string,
|
|
52
|
+
firstMonomers: string[],
|
|
53
|
+
secondMonomers: string[],
|
|
54
|
+
code: number,
|
|
55
|
+
rowIndex?: number,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getReactionEditor(onOk: (props: ReactionEditorProps) => void, preset?: ReactionEditorProps) {
|
|
59
|
+
// preset smiles are in radical form, need to convert to rgroup form
|
|
60
|
+
const firstReactant = preset?.firstReactantSmiles ? radicalToRgroupForm(preset.firstReactantSmiles) : '[*:1]C';
|
|
61
|
+
const secondReactant = preset?.secondReactantSmiles ? radicalToRgroupForm(preset.secondReactantSmiles) : '[*:2]C';
|
|
62
|
+
const product = preset?.productSmiles ? radicalToRgroupForm(preset.productSmiles) : '[*:1]CC[*:2]';
|
|
63
|
+
const code = preset?.code;
|
|
64
|
+
const firstMonomers = preset?.firstMonomers ?? [];
|
|
65
|
+
const secondMonomers = preset?.secondMonomers ?? [];
|
|
66
|
+
const resultMonomerName = preset?.resultMonomerName ?? '';
|
|
67
|
+
|
|
68
|
+
const codeInput = ui.input.int('Code', {value: code, nullable: false, showPlusMinus: false, showSlider: false, tooltipText: 'Reaction code'});
|
|
69
|
+
const firstMonomersInput = ui.input.string('First monomers', {value: firstMonomers.join(','), nullable: false, tooltipText: 'Comma-separated list of monomers for the first reactant'});
|
|
70
|
+
const secondMonomersInput = ui.input.string('Second monomers', {value: secondMonomers.join(','), nullable: false, tooltipText: 'Comma-separated list of monomers for the second reactant'});
|
|
71
|
+
const resultMonomerNameInput = ui.input.string('Result monomer name', {value: resultMonomerName, nullable: false, tooltipText: 'Name of the resulting pseudo-monomer generated by the reaction of the two reactants'});
|
|
72
|
+
|
|
73
|
+
const firstReactantInput = ui.input.molecule('First reactant', {value: firstReactant, nullable: false, tooltipText: 'Reactant fragment of first monomer(s). Use numbered R groups in correspondence with the rest of the molecule.'});
|
|
74
|
+
const secondReactantInput = ui.input.molecule('Second reactant', {value: secondReactant, nullable: false, tooltipText: 'Reactant fragment of second monomer(s). Use numbered R groups in correspondence with the rest of the molecule.'});
|
|
75
|
+
const productInput = ui.input.molecule('Product', {value: product, nullable: false, tooltipText: 'Product fragment of the resulting monomer from the reaction of the two reactants. Use numbered R groups in correspondence with the reactants.'});
|
|
76
|
+
|
|
77
|
+
const validate = () => {
|
|
78
|
+
if (!firstReactantInput || !secondReactantInput || !productInput)
|
|
79
|
+
return 'Inputs not initialized yet';
|
|
80
|
+
const firstSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(firstReactantInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
|
|
81
|
+
const secondSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(secondReactantInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
|
|
82
|
+
const productSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(productInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
|
|
83
|
+
if (!firstSmiles || !secondSmiles || !productSmiles)
|
|
84
|
+
return 'First/Second Reactants and Product must all be set';
|
|
85
|
+
if (firstSmiles.indexOf('[C:1]') == -1)
|
|
86
|
+
return 'First Reactant must contain R1-group';
|
|
87
|
+
if (secondSmiles.indexOf('[C:2]') == -1)
|
|
88
|
+
return 'Second Reactant must contain R2-group';
|
|
89
|
+
if (productSmiles.indexOf('[C:1]') == -1 || productSmiles.indexOf('[C:2]') == -1)
|
|
90
|
+
return 'Product must contain R1- and R2-groups';
|
|
91
|
+
return null;
|
|
92
|
+
};
|
|
93
|
+
firstReactantInput.addValidator(validate);
|
|
94
|
+
secondReactantInput.addValidator(validate);
|
|
95
|
+
productInput.addValidator(validate);
|
|
96
|
+
const dialog = ui.dialog(preset ? 'Edit Reaction Rule' : 'Add Reaction Rule')
|
|
97
|
+
.add(codeInput)
|
|
98
|
+
.add(resultMonomerNameInput)
|
|
99
|
+
.add(firstMonomersInput)
|
|
100
|
+
.add(secondMonomersInput)
|
|
101
|
+
.add(firstReactantInput)
|
|
102
|
+
.add(secondReactantInput)
|
|
103
|
+
.add(productInput);
|
|
104
|
+
dialog.addButton('Save', () => {
|
|
105
|
+
const validationError = validate();
|
|
106
|
+
if (validationError != null) {
|
|
107
|
+
grok.shell.warning(validationError);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const firstSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(firstReactantInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
|
|
111
|
+
const secondSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(secondReactantInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
|
|
112
|
+
const productSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(productInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
|
|
113
|
+
onOk({
|
|
114
|
+
code: codeInput.value!,
|
|
115
|
+
resultMonomerName: resultMonomerNameInput.value,
|
|
116
|
+
firstMonomers: firstMonomersInput.value.split(',').map((s) => s.trim()).filter((s) => s.length > 0),
|
|
117
|
+
secondMonomers: secondMonomersInput.value.split(',').map((s) => s.trim()).filter((s) => s.length > 0),
|
|
118
|
+
firstReactantSmiles: firstSmiles,
|
|
119
|
+
secondReactantSmiles: secondSmiles,
|
|
120
|
+
productSmiles: productSmiles,
|
|
121
|
+
rowIndex: preset?.rowIndex});
|
|
122
|
+
dialog.close();
|
|
123
|
+
});
|
|
124
|
+
dialog.show();
|
|
125
|
+
}
|
|
@@ -35,7 +35,7 @@ export class PolyToolConvertFuncEditor {
|
|
|
35
35
|
|
|
36
36
|
private async initInputs(): Promise<void> {
|
|
37
37
|
const getParam = (pName: string) => this.call.inputParams[pName];
|
|
38
|
-
|
|
38
|
+
const rulesForm = await this.ruleInputs.getForm();
|
|
39
39
|
this.inputs = {
|
|
40
40
|
table: (() => {
|
|
41
41
|
const p = getParam(P.table);
|
|
@@ -49,7 +49,7 @@ export class PolyToolConvertFuncEditor {
|
|
|
49
49
|
chiralityEngine: ui.input.forProperty(getParam(P.chiralityEngine).property),
|
|
50
50
|
rules: {
|
|
51
51
|
header: ui.inlineText([PT_UI_RULES_USED]),
|
|
52
|
-
form:
|
|
52
|
+
form: rulesForm,
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
}
|