@datagrok/sequence-translator 1.9.11 → 1.9.12
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 +30 -2
- package/src/polytool/conversion/rule-manager.ts +92 -10
- package/src/polytool/conversion/rule-reaction-editor.ts +125 -0
- package/test-console-output-1.log +89 -61
- 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
|
@@ -9,6 +9,7 @@ import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/mon
|
|
|
9
9
|
import {
|
|
10
10
|
PackageFunctions} from '../../package';
|
|
11
11
|
import {MmcrTemps} from '@datagrok-libraries/bio/src/utils/cell-renderer-consts';
|
|
12
|
+
import {ReactionEditorProps} from './rule-reaction-editor';
|
|
12
13
|
|
|
13
14
|
export const RULES_PATH = 'System:AppData/SequenceTranslator/polytool-rules/';
|
|
14
15
|
export const RULES_STORAGE_NAME = 'Polytool';
|
|
@@ -170,7 +171,7 @@ export class Rules {
|
|
|
170
171
|
return cards;
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
getSynthesisRulesDf()
|
|
174
|
+
getSynthesisRulesDf() {
|
|
174
175
|
const length = this.reactionRules.length;
|
|
175
176
|
const codeCol = DG.Column.int(NAME_CODE, length);
|
|
176
177
|
codeCol.setTag('friendlyName', 'Code');
|
|
@@ -178,6 +179,15 @@ export class Rules {
|
|
|
178
179
|
firstMonomerCol.setTag('friendlyName', 'First monomers');
|
|
179
180
|
const secondMonomerCol = DG.Column.string(NAME_SECOND_MONOMERS, length);
|
|
180
181
|
secondMonomerCol.setTag('friendlyName', 'Second monomers');
|
|
182
|
+
// set these columns as macromolecule for better visualization
|
|
183
|
+
firstMonomerCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
184
|
+
firstMonomerCol.temp[MmcrTemps.fontSize] = 16;
|
|
185
|
+
PackageFunctions.applyNotationProviderForCyclized(firstMonomerCol, ',');
|
|
186
|
+
secondMonomerCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
187
|
+
secondMonomerCol.temp[MmcrTemps.fontSize] = 16;
|
|
188
|
+
PackageFunctions.applyNotationProviderForCyclized(secondMonomerCol, ',');
|
|
189
|
+
secondMonomerCol.setTag(DG.TAGS.CELL_RENDERER, 'Sequence');
|
|
190
|
+
firstMonomerCol.setTag(DG.TAGS.CELL_RENDERER, 'Sequence');
|
|
181
191
|
const name = DG.Column.string(NAME_REACTION_NAME, length);
|
|
182
192
|
name.setTag('friendlyName', 'Name');
|
|
183
193
|
const firstReactant = DG.Column.string('firstReactant', length);
|
|
@@ -205,9 +215,27 @@ export class Rules {
|
|
|
205
215
|
product.semType = DG.SEMTYPE.MOLECULE;
|
|
206
216
|
|
|
207
217
|
|
|
208
|
-
|
|
218
|
+
const df = DG.DataFrame.fromColumns([
|
|
209
219
|
name, firstReactant, secondReactant, product, codeCol, firstMonomerCol, secondMonomerCol
|
|
210
220
|
]);
|
|
221
|
+
|
|
222
|
+
const addNewRow = (rowObj: ReactionEditorProps) => {
|
|
223
|
+
if (rowObj.rowIndex != undefined && rowObj.rowIndex < df.rowCount && rowObj.rowIndex >= 0) {
|
|
224
|
+
name.set(rowObj.rowIndex, rowObj.resultMonomerName);
|
|
225
|
+
codeCol.set(rowObj.rowIndex, rowObj.code);
|
|
226
|
+
firstMonomerCol.set(rowObj.rowIndex, rowObj.firstMonomers.join(','));
|
|
227
|
+
secondMonomerCol.set(rowObj.rowIndex, rowObj.secondMonomers.join(','));
|
|
228
|
+
firstReactant.set(rowObj.rowIndex, rowObj.firstReactantSmiles);
|
|
229
|
+
secondReactant.set(rowObj.rowIndex, rowObj.secondReactantSmiles);
|
|
230
|
+
product.set(rowObj.rowIndex, rowObj.productSmiles);
|
|
231
|
+
} else {
|
|
232
|
+
const {resultMonomerName, code, firstMonomers, secondMonomers,
|
|
233
|
+
firstReactantSmiles, secondReactantSmiles, productSmiles} = rowObj;
|
|
234
|
+
df.rows.addNew([resultMonomerName, firstReactantSmiles, secondReactantSmiles,
|
|
235
|
+
productSmiles, code, firstMonomers.join(','), secondMonomers.join(',')]);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
return {df, addNewRow};
|
|
211
239
|
}
|
|
212
240
|
|
|
213
241
|
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
|
+
}
|