@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.
@@ -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": "3",
13
- "secondLinkingGroup": "2",
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": "3",
24
- "secondLinkingGroup": "3",
25
- "code": "1"
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.9.11",
4
+ "version": "1.9.12",
5
5
  "author": {
6
6
  "name": "Davit Rizhinashvili",
7
7
  "email": "drizhinashvili@datagrok.ai"
@@ -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(): DG.DataFrame {
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
- return DG.DataFrame.fromColumns([
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
- this.synthRuleDataFrame = this.rules.getSynthesisRulesDf();
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
- return DG.DataFrame.fromColumns([
181
- initCol, helmCol
182
- ]).plot.grid();
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 reactionsGridDiv = this.createGridDiv('Rules',
245
- this.synthRuleDataFrame.plot.grid({showAddNewRowIcon: true}));
246
- const reactionExamples = this.createGridDiv('Examples', await this.getReactionExamplesGrid());
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.synthRuleDataFrame.rows.addNew();
338
+ getReactionEditor((props: ReactionEditorProps) => this.addSynthRulesFunc(props));
285
339
  });
286
- const topPanel = [saveButton, addButton];
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
+ }