Skip to content

Commit 6ae6abd

Browse files
targoslpatiny
andauthored
feat: add OCL Reaction editor module (#1177)
* feat: add OCL Reaction editor module * finish initial implementation * test: check in place modification of RXN V2000 and V3000 * do not condition event and improve state management --------- Co-authored-by: Luc Patiny <luc@patiny.com>
1 parent 514f1d5 commit 6ae6abd

14 files changed

Lines changed: 2858 additions & 31 deletions

File tree

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
"mf-parser": "^1.5.0",
8383
"mime-types": "^2.1.35",
8484
"node-jsgraph": "2.4.15",
85-
"openchemlib": "^9.6.0",
85+
"openchemlib": "^9.14.0",
8686
"quill": "2.0.2",
8787
"quill-resize-module": "^2.0.4",
8888
"quill-table-better": "^1.2.1",
@@ -92,6 +92,6 @@
9292
"twig": "^1.17.1"
9393
},
9494
"volta": {
95-
"node": "20.19.1"
95+
"node": "20.19.5"
9696
}
9797
}

src/modules/types/science/chemistry/folder.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
"moduleName": "OCL Molecule editor",
1313
"url": "modules/types/science/chemistry/ocl_editor/"
1414
},
15+
{
16+
"moduleName": "OCL Reaction editor",
17+
"url": "modules/types/science/chemistry/ocl_reaction_editor/"
18+
},
1519
{
1620
"moduleName": "Periodic table",
1721
"url": "modules/types/science/chemistry/periodic_table/"

src/modules/types/science/chemistry/ocl_editor/controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ define([
2121
'modules/types/science/chemistry/ocl_editor/help.html',
2222
);
2323
ui.dialog(
24-
`<iframe src=${url} width="100%", height="100%" frameBorder="0"></iframe>`,
24+
`<iframe src=${url} width="100%" height="100%" frameBorder="0"></iframe>`,
2525
{
2626
width: Math.min(w - 40, 800),
2727
height: h - 70,
@@ -103,7 +103,7 @@ define([
103103

104104
Controller.prototype.moduleInformation = {
105105
name: 'OCL Molecule editor',
106-
description: 'Molecule editor using the openchemlib javascript library',
106+
description: 'Molecule editor using the OpenChemLib JavaScript library',
107107
author: 'Michael Zasso',
108108
date: '11.05.2015',
109109
license: 'BSD',

src/modules/types/science/chemistry/ocl_editor/view.js

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,10 @@ define(['modules/default/defaultview', 'src/util/ui', 'openchemlib'], function (
2727
let pastedData = clipboardData.getData('text');
2828
if (!pastedData) return;
2929

30-
let molecule;
31-
try {
32-
if (/M {2}END/.test(pastedData)) {
33-
molecule = OCL.Molecule.fromMolfile(pastedData);
34-
} else {
35-
try {
36-
molecule = OCL.Molecule.fromSmiles(pastedData.trim());
37-
} catch {
38-
molecule = OCL.Molecule.fromIDCode(pastedData.trim());
39-
}
40-
}
41-
if (molecule) {
42-
setCurrentValue(this, molecule);
43-
event.preventDefault();
44-
}
45-
} catch (error) {
46-
// eslint-disable-next-line no-console
47-
console.error(error);
30+
const molecule = OCL.Molecule.fromText(pastedData);
31+
if (molecule) {
32+
setCurrentValue(this, molecule);
33+
event.preventDefault();
4834
}
4935
});
5036
},
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
'use strict';
2+
3+
define([
4+
'modules/default/defaultcontroller',
5+
'openchemlib',
6+
'src/util/ui',
7+
], function (Default, OCL, ui) {
8+
function Controller() {}
9+
10+
$.extend(true, Controller.prototype, Default);
11+
12+
Controller.prototype.getToolbar = function () {
13+
const base = Default.getToolbar.call(this);
14+
base.unshift({
15+
onClick() {
16+
const w = $(window).width();
17+
const h = $(window).height();
18+
const url = require.toUrl(
19+
'modules/types/science/chemistry/ocl_editor/help.html',
20+
);
21+
ui.dialog(
22+
`<iframe src=${url} width="100%" height="100%" frameBorder="0"></iframe>`,
23+
{
24+
width: Math.min(w - 40, 800),
25+
height: h - 70,
26+
title: 'OpenChemLib editor Help',
27+
},
28+
);
29+
},
30+
title: 'Help',
31+
cssClass: 'fa fa-question',
32+
ifLocked: true,
33+
});
34+
base.unshift({
35+
onClick: () => {
36+
if (navigator.clipboard) {
37+
navigator.clipboard.readText().then((text) => {
38+
this.module.view.onActionReceive.addProduct.call(
39+
this.module.view,
40+
text,
41+
);
42+
});
43+
}
44+
},
45+
title: 'Add product from clipboard (RXN, SMILES or ID code)',
46+
cssClass: 'fa fa-paste',
47+
ifLocked: true,
48+
});
49+
base.unshift({
50+
onClick: () => {
51+
if (navigator.clipboard) {
52+
navigator.clipboard.readText().then((text) => {
53+
this.module.view.onActionReceive.addReactant.call(
54+
this.module.view,
55+
text,
56+
);
57+
});
58+
}
59+
},
60+
title: 'Add reactant from clipboard (RXN, SMILES or ID code)',
61+
cssClass: 'fa fa-paste',
62+
ifLocked: true,
63+
});
64+
base.unshift({
65+
onClick: () => {
66+
const rxnV3 = this.module.view.editor.getReaction().toRxnV3();
67+
ui.copyToClipboard(rxnV3, {
68+
successMessage: 'RXN V3000 copied to the clipboard',
69+
});
70+
},
71+
title: 'Copy RXN V3000 to clipboard',
72+
cssClass: 'fa fa-copy',
73+
ifLocked: true,
74+
});
75+
return base;
76+
};
77+
78+
Controller.prototype.moduleInformation = {
79+
name: 'OCL Reaction Editor',
80+
description: 'Reaction editor using the OpenChemLib JavaScript library',
81+
author: 'Michael Zasso',
82+
date: '29.10.2025',
83+
license: 'BSD',
84+
cssClass: 'ocl_reaction_editor',
85+
};
86+
87+
Controller.prototype.references = {
88+
rxn: { label: 'RXN V2000' },
89+
rxnV3: { label: 'RXN V3000' },
90+
smiles: { label: 'SMILES' },
91+
reactionIdCode: { label: 'OCL reaction ID code' },
92+
};
93+
94+
Controller.prototype.variablesIn = [
95+
'rxn',
96+
'rxnV3',
97+
'smiles',
98+
'reactionIdCode',
99+
];
100+
101+
Controller.prototype.events = {
102+
onReactionChange: {
103+
label: 'Reaction has changed',
104+
refVariable: ['rxn', 'rxnV3', 'smiles', 'reactionIdCode'],
105+
},
106+
};
107+
108+
Controller.prototype.actionsIn = $.extend({}, Default.actionsIn, {
109+
addReactant: 'Add a reactant from text (molfile, SMILES, or ID code)',
110+
addProduct: 'Add a product from text (molfile, SMILES, or ID code)',
111+
});
112+
113+
Controller.prototype.configurationStructure = function () {
114+
return {
115+
groups: {
116+
group: {
117+
options: { type: 'list' },
118+
fields: {
119+
prefs: {
120+
type: 'checkbox',
121+
title: 'Options',
122+
options: {
123+
queryFeatures: 'Enable query features',
124+
inPlace: 'Modify input variable',
125+
},
126+
},
127+
},
128+
},
129+
},
130+
};
131+
};
132+
133+
Controller.prototype.configAliases = {
134+
prefs: ['groups', 'group', 0, 'prefs', 0],
135+
};
136+
137+
Controller.prototype.onChange = function (event, reaction) {
138+
const inPlace = this.module.getConfigurationCheckbox('prefs', 'inPlace');
139+
const idCode = OCL.ReactionEncoder.encode(reaction) || '';
140+
const rxn = reaction.toRxn();
141+
const rxnV3 = reaction.toRxnV3();
142+
const smiles = reaction.toSmiles();
143+
this.createDataFromEvent('onReactionChange', 'rxn', rxn);
144+
this.createDataFromEvent('onReactionChange', 'rxnV3', rxnV3);
145+
this.createDataFromEvent('onReactionChange', 'smiles', smiles);
146+
this.createDataFromEvent('onReactionChange', 'reactionIdCode', idCode);
147+
148+
if (inPlace && this.module.view._currentType) {
149+
const currentValue = this.module.view._currentValue;
150+
switch (this.module.view._currentType) {
151+
case 'rxn':
152+
currentValue.setValue(rxn);
153+
break;
154+
case 'rxnV3':
155+
currentValue.setValue(rxnV3);
156+
break;
157+
case 'smiles':
158+
currentValue.setValue(smiles);
159+
break;
160+
case 'reactionIdCode':
161+
currentValue.setValue(idCode);
162+
break;
163+
default:
164+
throw new Error('invalid reaction value type');
165+
}
166+
this.module.model.dataTriggerChange(currentValue);
167+
}
168+
};
169+
170+
return Controller;
171+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
define(['modules/default/defaultmodel'], function (Default) {
4+
function Model() {}
5+
6+
$.extend(true, Model.prototype, Default);
7+
8+
return Model;
9+
});

src/modules/types/science/chemistry/ocl_reaction_editor/style.css

Whitespace-only changes.

0 commit comments

Comments
 (0)