From bac7287ef8bdaa6de86b9821a54fd2f6eaef6519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Fri, 9 Feb 2024 16:23:09 +0100 Subject: [PATCH 01/64] feat: add JS API to create a generic editor Closes: https://github.com/cheminfo/openchemlib-js/issues/136 --- examples/.eslintrc.yml | 4 + examples/GenericEditor.html | 35 +++ examples/generic-editor.js | 206 +++++++++++++++ .../generic-editor/CanvasClipboardHandler.js | 11 + examples/generic-editor/CanvasDrawContext.js | 142 +++++++++++ examples/generic-editor/CanvasEditor.js | 73 ++++++ examples/generic-editor/CanvasEditorArea.js | 97 ++++++++ examples/generic-editor/CanvasEditorDialog.js | 234 ++++++++++++++++++ examples/generic-editor/CanvasEditorImage.js | 33 +++ examples/generic-editor/CanvasToolbar.js | 27 ++ examples/generic-editor/events.js | 104 ++++++++ examples/generic-editor/index.js | 1 + examples/generic-editor/utils.js | 46 ++++ full.js | 2 + full.pretty.js | 2 + package-lock.json | 30 +++ package.json | 1 + scripts/openchemlib/classes.js | 2 + scripts/openchemlib/generateImageData.js | 50 ++++ .../research/gui/hidpi/HiDPIHelper.java | 14 +- .../research/gui/hidpi/HiDPIIcon.java | 40 +++ src/com/actelion/research/gwt/Full.gwt.xml | 1 + src/com/actelion/research/gwt/Generic.gwt.xml | 12 + .../research/gui/hidpi/HiDPIHelper.java | 14 +- .../research/gui/hidpi/HiDPIIcon.java | 40 +++ .../research/gwt/gui/generic/Generic.java | 11 + .../research/gwt/gui/generic/ImageData.java | 21 ++ .../research/gwt/gui/generic/JSCheckBox.java | 24 ++ .../gwt/gui/generic/JSClipboardHandler.java | 90 +++++++ .../research/gwt/gui/generic/JSComboBox.java | 59 +++++ .../research/gwt/gui/generic/JSComponent.java | 57 +++++ .../research/gwt/gui/generic/JSDialog.java | 120 +++++++++ .../gwt/gui/generic/JSDrawContext.java | 216 ++++++++++++++++ .../gwt/gui/generic/JSEditorArea.java | 179 ++++++++++++++ .../gwt/gui/generic/JSEditorToolbar.java | 98 ++++++++ .../research/gwt/gui/generic/JSImage.java | 58 +++++ .../gwt/gui/generic/JSKeyHandler.java | 9 + .../research/gwt/gui/generic/JSLabel.java | 17 ++ .../gwt/gui/generic/JSMouseHandler.java | 9 + .../research/gwt/gui/generic/JSPolygon.java | 27 ++ .../research/gwt/gui/generic/JSRectangle.java | 23 ++ .../research/gwt/gui/generic/JSTextField.java | 24 ++ .../research/gwt/gui/generic/JSUIHelper.java | 106 ++++++++ .../gwt/gui/generic/package-info.java | 3 + .../research/gwt/jre/java/awt/Color.java | 19 ++ .../research/gwt/jre/java/awt/Font.java | 2 + .../research/gwt/jre/java/awt/Image.java | 36 +++ .../research/gwt/jre/java/lang/Character.java | 4 + .../gwt/jre/javax/swing/UIManager.java | 2 +- 49 files changed, 2432 insertions(+), 3 deletions(-) create mode 100644 examples/.eslintrc.yml create mode 100644 examples/GenericEditor.html create mode 100644 examples/generic-editor.js create mode 100644 examples/generic-editor/CanvasClipboardHandler.js create mode 100644 examples/generic-editor/CanvasDrawContext.js create mode 100644 examples/generic-editor/CanvasEditor.js create mode 100644 examples/generic-editor/CanvasEditorArea.js create mode 100644 examples/generic-editor/CanvasEditorDialog.js create mode 100644 examples/generic-editor/CanvasEditorImage.js create mode 100644 examples/generic-editor/CanvasToolbar.js create mode 100644 examples/generic-editor/events.js create mode 100644 examples/generic-editor/index.js create mode 100644 examples/generic-editor/utils.js create mode 100644 scripts/openchemlib/generateImageData.js create mode 100644 scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIIcon.java create mode 100644 src/com/actelion/research/gwt/Generic.gwt.xml create mode 100644 src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIIcon.java create mode 100644 src/com/actelion/research/gwt/gui/generic/Generic.java create mode 100644 src/com/actelion/research/gwt/gui/generic/ImageData.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSCheckBox.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSClipboardHandler.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSComboBox.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSComponent.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSDialog.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSDrawContext.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSEditorArea.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSImage.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSLabel.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSPolygon.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSRectangle.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSTextField.java create mode 100644 src/com/actelion/research/gwt/gui/generic/JSUIHelper.java create mode 100644 src/com/actelion/research/gwt/gui/generic/package-info.java create mode 100644 src/com/actelion/research/gwt/jre/java/awt/Image.java diff --git a/examples/.eslintrc.yml b/examples/.eslintrc.yml new file mode 100644 index 00000000..a481cc7b --- /dev/null +++ b/examples/.eslintrc.yml @@ -0,0 +1,4 @@ +parserOptions: + sourceType: module +rules: + no-undef: off diff --git a/examples/GenericEditor.html b/examples/GenericEditor.html new file mode 100644 index 00000000..94a593b0 --- /dev/null +++ b/examples/GenericEditor.html @@ -0,0 +1,35 @@ + + + + + + + + +
+ + + +
+
+
+
+
+
Change count
+
+
ID Code
+
+
Molfile
+
+
+ + diff --git a/examples/generic-editor.js b/examples/generic-editor.js new file mode 100644 index 00000000..f4f80bbc --- /dev/null +++ b/examples/generic-editor.js @@ -0,0 +1,206 @@ +import { CanvasEditor } from './generic-editor/index.js'; + +const { Reaction, Molecule, EditorArea } = OCL; + +const rxn = `$RXN + + +JME Molecular Editor + 2 3 +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 6 6 0 0 0 0 0 0 0 0999 V2000 + 2.4249 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.4249 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 2.8000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 2 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 2 0 0 0 0 + 5 6 1 0 0 0 0 + 6 1 2 0 0 0 0 +M END +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M END +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 6 6 0 0 0 0 0 0 0 0999 V2000 + 2.4249 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.4249 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 2.8000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 2 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 2 0 0 0 0 + 5 6 1 0 0 0 0 + 6 1 2 0 0 0 0 +M END +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 3 2 0 0 0 0 0 0 0 0999 V2000 + 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 1.4000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 1 0 0 0 0 +M END +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M END`; + +const molfile = `446220 +-OEChem-02022300542D + + 43 45 0 1 0 0 0 0 0999 V2000 + 5.5851 -0.3586 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5407 -2.1051 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 8.2717 -2.0446 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.9927 1.2690 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 8.0971 2.2384 0.0000 N 0 0 3 0 0 0 0 0 0 0 0 0 + 8.5749 0.2899 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 + 8.8337 1.2558 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 + 7.3538 -0.5758 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 + 9.9738 -0.2189 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 10.2326 0.7470 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.8004 0.9447 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5248 -0.0166 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 + 7.5971 3.1045 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.3888 -1.5752 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.8191 0.2842 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5756 -3.1045 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.8794 -0.0578 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.7057 -1.0426 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.1133 0.5850 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.7660 -1.3846 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.1736 0.2429 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.0000 -0.7419 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 8.3549 -0.5311 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 9.0537 2.0769 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.8116 -0.8763 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 9.7789 -0.8075 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 10.5678 -0.3967 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 10.8266 0.5692 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 10.4276 1.3356 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.7954 1.5647 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.1861 1.0285 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.4493 -0.6319 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 8.1340 3.4145 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 7.2871 3.6414 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 7.0601 2.7945 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 5.9560 -3.1261 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5972 -3.7241 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 7.1952 -3.0828 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.1807 -1.4412 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.2210 1.1955 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.6584 -1.9952 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.6987 0.6415 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.4174 -0.9539 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 12 1 1 1 0 0 0 + 1 15 1 0 0 0 0 + 2 14 1 0 0 0 0 + 2 16 1 0 0 0 0 + 3 14 2 0 0 0 0 + 4 15 2 0 0 0 0 + 5 6 1 0 0 0 0 + 5 7 1 0 0 0 0 + 5 13 1 0 0 0 0 + 6 8 1 0 0 0 0 + 6 9 1 0 0 0 0 + 6 23 1 1 0 0 0 + 7 10 1 0 0 0 0 + 7 11 1 0 0 0 0 + 7 24 1 6 0 0 0 + 8 12 1 0 0 0 0 + 8 14 1 1 0 0 0 + 8 25 1 0 0 0 0 + 9 10 1 0 0 0 0 + 9 26 1 0 0 0 0 + 9 27 1 0 0 0 0 + 10 28 1 0 0 0 0 + 10 29 1 0 0 0 0 + 11 12 1 0 0 0 0 + 11 30 1 0 0 0 0 + 11 31 1 0 0 0 0 + 12 32 1 0 0 0 0 + 13 33 1 0 0 0 0 + 13 34 1 0 0 0 0 + 13 35 1 0 0 0 0 + 15 17 1 0 0 0 0 + 16 36 1 0 0 0 0 + 16 37 1 0 0 0 0 + 16 38 1 0 0 0 0 + 17 18 2 0 0 0 0 + 17 19 1 0 0 0 0 + 18 20 1 0 0 0 0 + 18 39 1 0 0 0 0 + 19 21 2 0 0 0 0 + 19 40 1 0 0 0 0 + 20 22 2 0 0 0 0 + 20 41 1 0 0 0 0 + 21 22 1 0 0 0 0 + 21 42 1 0 0 0 0 + 22 43 1 0 0 0 0 +M END`; + +const changeCountDiv = document.getElementById('changeCount'); +const idcodeDiv = document.getElementById('idcode'); +const molfileDiv = document.getElementById('molfile'); +let changeCount = 0; + +const editor = new CanvasEditor(document.getElementById('editor'), { + onChange({ what, isUserEvent }) { + if (isUserEvent && what === EditorArea.EDITOR_EVENT_MOLECULE_CHANGED) { + changeCountDiv.innerText = ++changeCount; + const idcodeAndCoords = editor.getMolecule().getIDCodeAndCoordinates(); + idcodeDiv.innerText = `${idcodeAndCoords.idCode} ${idcodeAndCoords.coordinates}`; + const molfile = editor.getMolecule().toMolfileV3(); + molfileDiv.innerText = molfile; + } + }, +}); + +document.getElementById('loadMolecule').onclick = () => { + // const molecule = Molecule.fromMolfile(molfile); + const molecule = Molecule.fromSmiles('c1ccccc1CO'); + editor.setMolecule(molecule); +}; + +document.getElementById('loadFragment').onclick = () => { + // const molecule = Molecule.fromMolfile(molfile); + const molecule = Molecule.fromSmiles('CCC'); + molecule.setFragment(true); + editor.setMolecule(molecule); +}; + +document.getElementById('loadReaction').onclick = () => { + const reaction = Reaction.fromSmiles('c1ccccc1..CC>CO>c1ccccc1..CCC..CC'); + // const reaction = Reaction.fromRxn(rxn); + // reaction.addCatalyst(Molecule.fromSmiles('CO')); + console.log(reaction.toSmiles()); + editor.setReaction(reaction); +}; diff --git a/examples/generic-editor/CanvasClipboardHandler.js b/examples/generic-editor/CanvasClipboardHandler.js new file mode 100644 index 00000000..ad8ec6f6 --- /dev/null +++ b/examples/generic-editor/CanvasClipboardHandler.js @@ -0,0 +1,11 @@ +export class CanvasClipboardHandler { + copyMolecule(molecule) { + const data = molecule.getIDCodeAndCoordinates(); + navigator.clipboard.writeText(`${data.idCode} ${data.coordinates}`); + } + + pasteMolecule() { + // TODO: find a way to implement this in a synchronous way. + return null; + } +} diff --git a/examples/generic-editor/CanvasDrawContext.js b/examples/generic-editor/CanvasDrawContext.js new file mode 100644 index 00000000..7d3fa143 --- /dev/null +++ b/examples/generic-editor/CanvasDrawContext.js @@ -0,0 +1,142 @@ +import { toHex } from './utils.js'; + +export class CanvasDrawContext { + /** + * + * @param {CanvasRenderingContext2D} ctx + */ + constructor(ctx) { + this.ctx = ctx; + + this.ctx.textAlign = 'left'; + this.ctx.textBaseline = 'top'; + + this.currentFontSize = 12; + this.currentFont = '12px sans-serif'; + this.ctx.font = this.currentFont; + + this.currentColor = '#000000'; + this.currentLineWidth = 1; + } + + clearRect(x, y, w, h) { + this.ctx.clearRect(x, y, w, h); + } + + getBackgroundRGB() { + return 0xffffff; + } + + getForegroundRGB() { + return 0x000000; + } + + getSelectionBackgroundRGB() { + return 0xeb8282; + } + + getLineWidth() { + return this.currentLineWidth; + } + + setRGB(rgb) { + const r = (rgb >>> 16) & 0xff; + const g = (rgb >>> 8) & 0xff; + const b = (rgb >>> 0) & 0xff; + this.currentColor = `#${toHex(r)}${toHex(g)}${toHex(b)}`; + this.ctx.fillStyle = this.currentColor; + this.ctx.strokeStyle = this.currentColor; + } + + setFont(size, isBold, isItalic) { + this.currentFontSize = size; + this.currentFont = `${isBold ? 'bold' : ''} ${ + isItalic ? 'italic' : '' + } ${size}px sans-serif`; + this.ctx.font = this.currentFont; + } + + getFontSize() { + return this.currentFontSize; + } + + getBounds(s, rect) { + const metrics = this.ctx.measureText(s); + rect.set( + metrics.actualBoundingBoxLeft, + metrics.actualBoundingBoxAscent, + metrics.actualBoundingBoxRight, + metrics.actualBoundingBoxAscent, + ); + } + + drawString(x, y, s) { + this.ctx.fillText(s, x, y); + } + + drawCenteredString(x, y, s) { + this.ctx.textAlign = 'center'; + this.ctx.textBaseline = 'middle'; + this.ctx.fillText(s, x, y); + this.ctx.textAlign = 'left'; + this.ctx.textBaseline = 'top'; + } + + setLineWidth(lineWidth) { + this.currentLineWidth = lineWidth; + this.ctx.lineWidth = lineWidth; + } + + fillRectangle(x, y, w, h) { + this.ctx.fillRect(x, y, w, h); + } + + fillCircle(x, y, d) { + const r = d / 2; + this.ctx.beginPath(); + this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI); + this.ctx.fill(); + } + + drawLine(x1, y1, x2, y2) { + this.ctx.beginPath(); + this.ctx.moveTo(x1, y1); + this.ctx.lineTo(x2, y2); + this.ctx.stroke(); + } + + drawPolygon(p) { + this.ctx.beginPath(); + this.ctx.moveTo(p.getX(0), p.getY(0)); + for (let i = 1; i < p.getSize(); i++) { + this.ctx.lineTo(p.getX(i), p.getY(i)); + } + this.ctx.stroke(); + } + + fillPolygon(p) { + this.ctx.beginPath(); + this.ctx.moveTo(p.getX(0), p.getY(0)); + for (let i = 1; i < p.getSize(); i++) { + this.ctx.lineTo(p.getX(i), p.getY(i)); + } + this.ctx.fill(); + } + + drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) { + if (arguments.length !== 9) { + throw new Error( + `drawImage call with ${arguments.length} arguments unimplemented`, + ); + } + const fullScaleCanvas = document.createElement('canvas'); + /** @type {ImageData} */ + const imageData = image.imageData; + fullScaleCanvas.width = imageData.width; + fullScaleCanvas.height = imageData.height; + const fullScaleContext = fullScaleCanvas.getContext('2d'); + fullScaleContext.globalAlpha = 0; + fullScaleContext.putImageData(imageData, 0, 0); + this.ctx.drawImage(fullScaleCanvas, sx, sy, sw, sh, dx, dy, dw, dh); + } +} diff --git a/examples/generic-editor/CanvasEditor.js b/examples/generic-editor/CanvasEditor.js new file mode 100644 index 00000000..a3813008 --- /dev/null +++ b/examples/generic-editor/CanvasEditor.js @@ -0,0 +1,73 @@ +import { CanvasEditorArea } from './CanvasEditorArea.js'; +import { CanvasToolbar } from './CanvasToolbar.js'; +import { addMouseListeners, addKeyboardListeners } from './events.js'; + +const { EditorArea } = OCL; + +export class CanvasEditor { + /** + * + * @param {HTMLElement} rootElement + */ + constructor(rootElement, options = {}) { + const { onChange } = options; + + const childElement = document.createElement('div'); + childElement.setAttribute( + 'style', + 'width: 100%; height: 100%; display: flex; flex-direction: row; align-items: start; background-color: white;', + ); + rootElement.appendChild(childElement); + + const toolbarCanvas = document.createElement('canvas'); + childElement.appendChild(toolbarCanvas); + + const editorContainer = document.createElement('div'); + editorContainer.setAttribute('style', 'width: 100%; height: 100%;'); + childElement.appendChild(editorContainer); + + const editorCanvas = document.createElement('canvas'); + editorCanvas.tabIndex = '0'; + editorCanvas.style = 'outline: none'; + editorContainer.appendChild(editorCanvas); + + const containerSize = editorContainer.getBoundingClientRect(); + editorCanvas.width = containerSize.width; + editorCanvas.height = containerSize.height; + + const editorArea = new EditorArea( + new CanvasEditorArea(editorCanvas, onChange), + ); + this.editorArea = editorArea; + + editorArea.draw(); + + this.toolbar = new CanvasToolbar(toolbarCanvas, editorArea); + + const resizeObserver = new ResizeObserver(([entry]) => { + editorCanvas.width = entry.contentRect.width; + editorCanvas.height = entry.contentRect.height; + editorArea.repaint(); + }); + resizeObserver.observe(editorContainer); + + addMouseListeners(editorCanvas, editorArea); + addKeyboardListeners(editorCanvas, editorArea); + } + + getMolecule() { + return this.editorArea.getMolecule(); + } + + setMolecule(molecule) { + this.editorArea.setMolecule(molecule); + } + + getReaction() { + return this.editorArea.getReaction(); + } + + setReaction(reaction) { + this.editorArea.setReaction(reaction); + } +} diff --git a/examples/generic-editor/CanvasEditorArea.js b/examples/generic-editor/CanvasEditorArea.js new file mode 100644 index 00000000..e6cb28db --- /dev/null +++ b/examples/generic-editor/CanvasEditorArea.js @@ -0,0 +1,97 @@ +import { CanvasClipboardHandler } from './CanvasClipboardHandler.js'; +import { CanvasDrawContext } from './CanvasDrawContext.js'; +import { CanvasEditorDialog } from './CanvasEditorDialog.js'; +import { CanvasEditorImage } from './CanvasEditorImage.js'; +import { decodeBase64 } from './utils.js'; + +const { EditorArea } = OCL; + +export class CanvasEditorArea { + constructor(canvasElement, onChange) { + this.canvasElement = canvasElement; + this.changeListener = onChange; + } + // JSEditorArea methods + getBackgroundRGB() { + return 0xffffff; + } + getCanvasWidth() { + return this.canvasElement.width; + } + getCanvasHeight() { + return this.canvasElement.height; + } + getDrawContext() { + return new CanvasDrawContext(this.canvasElement.getContext('2d')); + } + onChange(what, isUserEvent) { + this.changeListener?.({ what, isUserEvent }); + } + getClipboardHandler() { + return new CanvasClipboardHandler(); + } + // JSUIHelper methods + grabFocus() { + this.canvasElement.focus(); + } + setCursor(cursor) { + this.canvasElement.style.cursor = getCursor(cursor); + } + showHelpDialog(url, title) { + console.log({ url, title }); + alert('Help dialog is not implemented yet'); + } + createImageFromBase64(width, height, base64) { + base64 = base64.replaceAll('%', 'A'.repeat(20)); + const decoded = decodeBase64(base64); + const typedArray = new Uint8ClampedArray(decoded); + const imageData = new ImageData(typedArray, width, height); + return new CanvasEditorImage(imageData); + } + createDialog(title) { + return new CanvasEditorDialog(title); + } +} + +const customCursors = { + [EditorArea.cChainCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACOElEQVRIx+3VzUsUcRzH8c/MOLszdlFwY8H6E6JjIARFBlGt7KEsbelgj65ZayJYf0GtERSVFnQOOnTp1j9gRSIlBh7sEhn5uLpbu+3Tu8Os7qqrYpc6+P0df5/vi/k9zIy0U/++DMmQZInNIhsUkmokU0XVbAgYm8CoKHPLB9yiqgKnlXEyDRljs/a0/ashbZ2RJEuGHPlXRkT4f9x+NZwOYziqNoLCmu55OZw5h1kFwJnpvzonHk1kwlQFsGZjIk58hOZ1AO5MX+esEOLxeLYKgT3fLYTF0HsOrgIiwpnrjU6L3QgLmycfcy2rF4K90OXx90YKpzAqgIjwJ25Gv4sAItUvLMTgaJkICmsx6rXHR/NtWAcqNxFnqadzStQhikfxL13wokMf8iFKa091CmFy51O+Heu8fJKU8nJuIhb9JnYh8scwJHypbmEgnr4rhhB26ooQdTwfyZ3Fkhz5JEQgeXFy4MuDrq+elG3BbJQjCV/ymhA2z4YLJ5OXhahlcDR3AkNyS0D9xP2OxXoCCBtRCGFely3HI5zEDY998VYIPw/HsmHMZrkrwOFbU15EiEILkvyVu+4uxMrzA59/t2JeklsBhJrS5UAT5ppTd4WT6PVm747l27FUal8GjsRK18ZC0GivaXcVEf75PhEf/92GWW5fBgJjQ+FMLUGCJDswnXWAq4hwfh7PHsJy5a4BDKQ9c62T+/25fa/NN7Ppvdt4nVHRS+Alcbf5LSgB3nX+u2Lnp/Bf1B//ZTKHt1UOnwAAAABJRU5ErkJggg==)', + [EditorArea.cDeleteCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADc0lEQVRIx42VW29UVRiGn7XWbr0QRhM81GCwHDxEuDDRUShUagy1ETTqBdNy4eEXaADDpbbwF6AmxhvTGJWku9paewCbdGYqJRhqG4WUBE12gyad0VjnQOewPy/26sy0M9Nxrbu11vt87/vtw1JCkiyNxqNg2Mo/S7JxRyVYJIlGodBoNKBRGLsSrD5rMpHll3Z8TvSmXQEHjcbJkuQODgqNwWBQGDQOGoWDwqFDpU64Zz/bfu6RQ+rJ6V/RtoQADmgce1DbLQeNCfi045hUz1DfycdytB+NykHZF1sUB0UTCo0DatPpmFS3e/Zka44iIdqPxaSNx6O/l/dBY2wEjcZY4waDpkOle9y+M605VhAUIQ69Fpc21TrtWYcOFQ0zFhCAnqZFp0+4fad2rrKCgEUcfD3OAdkR+1MCRCm5tog1Ly0m3e32frAzb+WsR/Bg9O9yBFMybWwTD+t0t9v34a5KeSVixj+gtk2ncGBjGzVbCOtMz1Dv6V3ZDfIyou2NGdkvW2PWQXPJuMMWwiYTcT9+f3e+hryM6Hpz3N+f1qyrrVGEdSYy2HtqTz352niAlYcw9ino8oPT2W73o9N7spvIFSFW+GKu4xzX8IgywhjjXOYPxGR6Bha3SUiQegMJCTJyPfdKCh884owywSRTFFntGrjx8P+S3+1MU0TK34LmsOI5eeLTp+5CA/Mj8y+fuWfSLnlcYZI8YlYjE5dn3b/OIyEJbVb959VOoYAgCHhcZQoxueODC0ooxMaS55F7ayDW5NnOIr6VC3hcQ1Qu4s7hK1FCIT6WqOEiOD88nzkiqliSCwhicpGh60rWJoX498kL6xFB9eH5bKdQKRcQkzvuVsgtYjTZXw4SyL9ZyB7zK8yvAV4cjgbm1yNmRpf7Axf3S0iQbxfSR0QLUgXYG71UDVBCYea7RD9yn4QEGfwlc7SWXECa/Le/nlGihGrESOITAvmrteVBE5uL71yMVXtQQv7H4cSF2YlUVz25gIcgTfn3LsZrB5n7UvaJXqIewIFF8vm9A2/5X+nIC0pVvbuGf31/e/2v2uMWN2yQoBcV9f3ZHwphv251G+EWN7ltg1QiKF6ZyocFaQT4jdt4LHEHaSq8Ox4nQYIEy7OXCs8naARQHj5iL5YWaOYZdtt0i/zU8NpGeQAVv/UQqvTr1I31/AcQPMokc/81uwAAAABJRU5ErkJggg==)', + [EditorArea.cHandCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACXUlEQVRIx6WVT2sTURTFfzOTtNESGy1ScS3uhFTERSHa2k0WdeOHcKmiFvwKIoL7foDiphvBdtF/WhRqhXTlzlWhWGjF2oJNM+8dFzOTzCQvE8H7FvPmzrvn3TPn3fvgP83L/arB6/xe15NU+DzzPIjmSsENNCVjX1ZLQqgeeV79QwYIatQAKCKaQJ33LADc7F5c6I0f5zrr+NxlEwOUYn/VmawD4JgRhGUE8PHb1MsAF5nCB9ZyANwyQAgwwXpWFd+tbLKvSfmLwBRvo7+zmgMQhQbtoMRaQIn7LA5SoZhym1RexRi4NAjAZLir6xAGXQczARhlBqE+WqWoNbs2SAA2WBmlBB/7Qygm6DkpVKf5xCJXyjSiJC33eIjFowC8aFPx+9SVHqulE70W8lSXlXSiQ1m1dKYDNWW0J1SXdCTk98q4TUiJWS4hQFiGqeDhETBGgMdlfrBAiAfYHgpzX/iMzxjTMV+Lj48hwAd8DIcMUyFAGfGSWSNkC0OZ27E7wGuLBh4Fxqk4lE/evrKzxiEFbgBw1rd7KP7FvbaOlmV1og/6JslKMuqYbT9/C3VqoWMTaFanasmmlncDWEk/hQJHMTVoLLNFQOhM38uU9ZCzFp6GrPKHIl5OszYY4IITYIO1JQ5o5VSDxRIC+/2qcZt3FJwUhLB4BLwBmOm7yTXtymWRKkZGtcwN0d0P5vZYzDSydAZRk4VzOV20yq9JfXfIaNSUZLSrqwPuqBX0TEfxIWplIEKdqqYh8Tz/cm0MVV9yixIGk+JoOc8jNuGYO+zk5VCm0bkdHSPTs/4Cu8J6qPZw7lkAAAAASUVORK5CYII=)', + [EditorArea.cHandPlusCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACaUlEQVRIx6WVP2sUQRjGf7N7l1wMZ6JBItZiJ1xELITTxDRXxMYPYamiBvwKIoJ9PkCwSSOYFPlrUNCIl8rOKhCMJGJMwNzdzjwWu3vZvdvbE3yn2Jl3Z555n3nmfQf+00zuX/Wf53W7HiWWzzHHvbCvBFxfU9z25LQohGqh58U/RICgShWAIqIB1HjLPMC1zsmF7vXjXGENj9tsYoFS5K9kBpsBcMQwwjEMeHht6mWAc0ziAas5ANkyQAAwwVpaFS9b2Xhfm/AXgUleh6ezkgMQLvXbi2JrASXustBPhWLCbRNxFSPgUj8Am+Kujkvod1zMGGCEaYR6aJWg1ujYIAZYZ3mEErzrDaGIoMmkUJniPQtcLFMPg3Tc4T4OQwF41qbi9cgrPVRLx3opZFSTk3SsAzm11NS+GrLaFapJOhTyumXcIqDEDOcRIByDjGIw+IzhY7jAd+YJMIDrojD7iQ94jDEV8XV4eFgMHuBhOWCQUXyUEi/u1QM+YilzI3L7GMCPvoYC44xmKB+PPrO9ygEFrgLQ7Fk9FB1xt62hJTkda0NfJTlJVhJCisbh97fQaS6c2gSa0YlaconpnQBO0k8hfyUrui8FbcipKScnMloI8ENoKDMbHwes8IciJqdYWyxwNjOZ1lldZJ9Wqq7GfYtDOBwBsNcrG7d4QyFTgfD6GHxeAUz3zJjL2lHSkodoZWVVTb0QnfVgdpeFVCFLPhVhkYWhnCpa4ddNfUvIGJtVQ5LVji71eaOW0RMdykqSWimIQCeqakA8zX9c6wOV51ynhMUmODrO8IBNOOIW23kxlKmjnJaqWX8BrBWTpFVweJUAAAAASUVORK5CYII=)', + [EditorArea.cFistCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACC0lEQVRIx+2UsWsTYRjGf3eXFktqS20pUqQVdQgUJCkKDi4mckJRKIj/guDgoGRzFzWQRZycnKSTe42zoNhkKAGXKtSlimItanK9+x6HXtKL3ncER/F5h+Pu+77ne9/nfZ+D//gH4GSslahjgMrfEih+NrmNC3yhNVxWL+LQQ71TXdNiP75RHOr4lHytyNW43ipUSyNCd3VHE6I6JMGGNuVrTpsK1daofO3qh5bVLyoB97f3MmWYZx4QDuARAHlynMYhLqZqJ4iVdQHh4hL0vwWIAivMQO1gby69jogqG7g4TFFnEUOOEeABV7jEGpkEZ3FwqHABgDlu4uASscsMpxCQI0wlKB/jw9J+XV6sQYSHBxg8bnGNk0SAlyA4QA3lNa5R+fqpdBiFknzllSZi6RyPuE4AmFRdTNyXhYFu9giKnClwmRsct863E3dneWC9p0GLN9vliKPcZ9rSmt6xxQEFEnvX+USBq4CX6bCAbuokrm/TwLXNVjwdAr7byYvakh1GoYxCPRvwRPK6Sos1ummOiTXwAJf3WWZa5SvGQqF4uNocsRZRQ48VWUqIZBTqs0qyJgloQW3tWTQwMnqpsUw7XzzEUyI6RAhhEiEMe7xiFpr2DCZpzuq5pK6MOv17jYxCRdrSeZ1oMmm38w6Nj8V7BEwwRgcn4QsPeMJrug12sv+KVWSLw38I+Auc4F9yeHFVDQAAAABJRU5ErkJggg==)', + [EditorArea.cLassoCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAEfklEQVRIx3WVfUzVZRTHP/fKSz8GaN6WV7GbKOpQYANUXDqV5WJO6c5S/miyzJmlrAicbm3gWtisbC5f2npZw2qNJNORC3xbqFOuQsAEkkAlXvK+IAiXd+7l/k5//C7cC17O88/znOec73l5zjmPDkLR46OhaJZiwuie2fOcJwgdIDPGnu0KdmKnnaawf32yKqPoQqnkeQCMM0hmDYbWuLrEGmNxSKMfbCwZriR7Qu2CBrq5RbXdA9DJKlCwIQgyX7I8RyxVuz3aMfDa7bFUeY5IlswXBBsKOoUWjLCKTY/ivzR/EeSzmox51DAC3c+UhFb7ebN/LKckqp4yKu0s9AK8RNqd9K2JNi0U8vtT6pbWh3fjwgbMJWTA0BR/J+GriHsAzOV8bcoFLtkrFqJTGFpOxq3X1sZp+Ke7zSWzOrjPPR7k92u8gghiWMai3hdLzDsNGu9mw5pzFIf9rROF9xs2xa8H2CZH/1hQQx3l9ifQQhn+VDCbVBJakw5sPqsDqL8eV8YJJHX4uFlFkEz1SaEclOhIFBQUigNlMVoOPinMVBHErA4fl1Qk+1IzgqwUR5HsF0MJVd7VjiA4OePdeZdB9juKVgqCXGqWbKRg7xiClD6UPDF+wqdTbNpQsDDqzzNKXulDBNk7JofpKtS4g6dk48t04AoAkMyOyRAbB09pm65CvSsEYIeE9WB5h0iCeZqqaaSGDh/DEtbzlgrgCvG2gQcgYjuRBCYjGVh8xyAYrzi9MgBQpLMvIg3dNPpkcp1qDtFNN/mwrm/Od3oAZUg/y5HnBih6VRaTNh2AnmhycPGIxxQsIfniFq2oZ9mRd2tuagkpaZOP5RUJkES8NeFC9LJC8psuzxUEKW+QrCAsifOPJh1QwGz6PTNdRzhtXrNO/mPE34/gcMwSW5W2b4UN2KWuLeOuTmFoZ39K9p5CPcBPXVsuWKP6w8DQ+0JLqBMrFWF1wxSzPZhEUgdNP2RmRWgNdeNizBVOoiCRktt9+u0AcyBvtPaGFMhWiZT1kuv+/PZfWtEj29X7pfKRLFa8A8UgOdbiwCPk58fqETnc983Ve7smTBwbcH4rByXGhjIxkdIv/+Ov9ovjdmVh7zpBkLWTAPeN1V+TAnlTZnoTPA5wxoogjt9KW6ME2aZaz0p+Rd1kb3Ldlir3Z5Itcb4XmhhhehXg2rqUimL3mpizutbXP1x9zajdLeONkdUty+uMD3BQSS1uv6fxerDZVrQiYA72eNp/dX4th2S3rJd5AWrECzBP8tvOfTA2VX2j9HwvOWKSmYHQ/UOw8qOp91hjTuzdBGe4MxSaw08EwVVcoXTQPu7vHVZO+oZ8TQVtnNKZTEtM4SiAYWRR/3uFeijdtLOZcAY0oaQp6vh9LAhCOeXaTpHcu9cRJFYGT0q0T0JFnRTCFMANbNA2w1yJv5Uu0IjDRJRPopNW+viTsYk+nY4adH0ZjwFao/0CZTYGYDkzxnOg0hkYQIxNYUMA7iCwP3XtADpRCRpl1TRuDI1sPm+dA7MdsDCghMoo/wN5WLFDBuWEyAAAAABJRU5ErkJggg==)', + [EditorArea.cLassoPlusCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAEk0lEQVRIx42Vf0zUZRzHX3ec0JcAyzM5hS5B01BggT+HTmJzmineNLWtxTJn2mSFwLS5iZ6dy5WNqdXsxxpqcxSZSk6Z5lJbyq+U8UMIdIQ47wd2yi8ROO776Y8vB3eEW8/zx/M8n+fzeX9+Pp9HByHoGR49MUzHjMkz9uF4rwEdIEEDz/4zpgMnrTSG/j3Mq9KHLoQKJgBgCmIWCzC2xNck3TAVBTf4wcaxrj/ZmVg1uQ43V7nu9AK0MRcUHAiCREumd19p5Uavdhx9bvSWVnr3SaZEC4IDBZ1CMyaYy7J7CQcsnxmGtc7C0mfsBfdTxSHX/azJHcgujqqlhAonsYMAKSwtT1+V5NBcIa9rXs302jA3/TiAiQR3GxsTyhO/DK8HYCKnquad4bzzWiw6hZ6ZrLu6emG8hn/EbSl+5i63qOd2XpdGs4UzlRlMaX+h2LLeqNH+qFtwkqLQmzpR+KBuWUIqwBrZf3byDWq45HwAzaRYNeY8K4BtHGkktiRvW35CB1B7Jb6EQ0ja44MWFUEy1AcFsl1iIlBQUChCrAgiVr8oxsj2BwUZKoJY1McHJQ3JOt+EIHPEVSi5YiymcnC2DgG0+ifCKLmuwjmCIOebJMvA+NOxAHuaJ9zk+MfuIFYyctwjkmDfwc3xCcqe5tdi4XTskucM7ujDQQCpJZQtdh4hEsAaCDDfd9ZWJ2WpJjLhcJAtytAfDPCWhD6kdDMRjAFAt3tY3LeXPUOk0tDUd9QCPfQHDxaOFyB87SP+3zCAr+IMSjdAoS5/imkpx5BAbbrd/pp3kQUcwLaoM/JbPYDSg3y0sx9B8rvVvfLqiNIfzAIUITj4kGoakGli/dGOILkesemxr64AyHn6TAYpLHmy3SZsJOpfms0bTSlbJwKsaMRpoDQpen/yNgUs5l8y0nWEcWdQogNDIMSYMCwSV7l0y2wHsEFdWEK1IbS6J2nz0fpNBXpYOfn791acsUd1hYKx/fnmkJflLHd90iSR9sh8NCMzXHtQOy4YHJSjIBGS4z7y7ih9YGdf1e9ik1USIamS4/m07E+t6JG16q1zYpUXlcGGYpRse9HoLeT4fXWf7O38+mL9hiEV+d0d38h2mepAGepI6Rf+8hf7wVVWUdC+SBBkYQDgloHay2KTt2Ws1pGGALTEuH4+1xIlyBrVfkLyrtUEWpPjKa30fCJZEq8RHCjDcdarAJcXzbtW5Fkw9YSu5fUd8y+btLsZvNk7v3lmjek2LiqowuOXmkELljsKZ48ag03e1p86vpJdslFSZVLgpb8LkyTvzsmtAyPFF8vD7yRbzDJ2NHR/F+wcM7fnN2THVSd2hHWEQFPYIQNcpD+Eu7T67C1nTsA3hF+t3eELndk8zRyGAhh7p3S9X6CHc8vWNxFGt8aUPEIcv48FQbjEJW2nSE71FQSJk0efS8wwh4oa4MIIwFd4Rds85teEq+kCDbjMRA1ztNFCJ78x4MveEx9fna5z3X2Alhj/RzUOIzCTIF8MVNpGBxBTY2gPgMcAzv9cu4A2VAx9zH2CGT29y0/ZI2GcC2JH5VDp418SVoR6q/CpzAAAAABJRU5ErkJggg==)', + [EditorArea.cSelectRectCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAC3klEQVRIx6XVz29UVRQH8M9781qhnYpYbCut1dpKo1SCEjttiBpNLCoIhjRqokv9C1yZaKZEd2rCygVqcGlC/EFIQdAQaRe0EeMCW6CQgFLFWmwQC5SxdFz08ToDHVPg3MW953vP/d6Tc849N/jUSldlRqfTzhp10nnnjMuhXI0lbnefe9ylwUFhcmJItFIG5OWM2etnhw36xSXFUqFRu1XaPKhWuSDGo9npX385ZsA3+swgUO8hdypHzoQhZxx1VOgJz+rQqib2IxiQocfNSc+giJxyQfbGT+e35BAy4WZlAlFGr/Ux440S/Gi9aFhfTICey/b4KFay1gowJXU11ond7HRAk3BEb4L/aoM6u+LREaMvOlB0ejpZ7TYiPO5IAvyh2zK3xSMVoz856ZipxCpCkA2yHDEiOm0m2er3ZrwKCm4c80ZcatfKjFHheAGw+X9DtmseinFRVYHaNJfjAk9qjdkkbad1IqErUknG0sL6BSXsEdus9aq92F6ALxfda/lCqk6F11Qad0o6QavVC1d5foFlU+YlGZ/IJFl4UpuwRVdBZdHr64KU5Y0XkbR6z9+J1qVRdNiaBNhhUq1/9AtVqlMtLIp8zqQf7LM61ts1iCKLE4MPnJA2ISdlqTot2jR7zmdxV/jdITvt8WGchYcNirg7IbjiC1AudFlehU02e992pxwy7KBeYwXvIjXXkWalUZUhvKzdBRft87nzfsNZw/Y7hw4bEGTJbwmuJXhHjf0+NuIZG6W0eV2v3XFhlenSaY22orAWESzWqlmDrba6ZLU/TaNa2mNa3K/JCneoLE3wtnVaVMn40rs69btooxekNVtiRVE7m5dgyrYkbaN2SHlFt6ctlS96nyU9OFOyOwcl6zO6zjQ75+JC1qFblFv+WAzIzzumjXgLtb7SV7TznQu+9628vIHrYzBXprWWKTNl0qNFO08JPT73uQ6V9O8BdboFFllksITNkP8AkhXgo0QeBZEAAAAASUVORK5CYII=)', + [EditorArea.cSelectRectPlusCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAC50lEQVRIx6XV309XdRgH8Nc53wMpQmQakBBFkqwkV7n4wly12sJKs9ZYtdVl/QGtq7baF1Z31eZVF1azrXXl+uEcGeVcwAWw6bowUKkNS5YRxoylIiGnC47n+/0ibKjPc3E+n/fned7n2ef58Qk+tdkVmdfmtLPGjZl2zqRZlKpS6WZ3ucNt6gwIU49h0WZZEJs1ocfPjhnym4uKpUy9Fls0u1e1UkGCRwuf//ztpEHf6TOPQK373KoUs6YMO+OEE0KPekqrJlVJHMGgLJ2uTzqHRMwqFeSu3TvumkXIlOuVKURZ3XYkjNdKcNQO0Yi+hACdlxz0UbLJ2SbAjMyVu07tFj69GoSjulP8dzvVOJBoa4I+r7fIey5dfWtU+IvjKfCnDuvdlGgmQX8y5qSZ1CpCkAtyHDcqOm0+Per3ZrIKCv444fWk1BbLvHHhZAHwwhK5yvsdWIJiUlRRsG0o8MtHUm3Cc8rtt10kdFkmzVi5sHZFCXvQHtu8ogd7C/ANojttWEnVKfOqNSadUp6i69QKt3hmCYcgXlCYiMVdcRCXeFHWJ7JpFh7TLGzUXlBZdPumIGWLpcl7/kl37eqFx2xNgX16VavU71AwEIwF0wFUB4JcEAcLjd3j89S+RZ0osjoFPvCrclNmZaxVoxE87bNkKvzhiP0O+jDJwv2GRNyeElz2JSgVuiRWBt631ylHjBjQbaKgLzL5ibQg9SoM4yUtzrvge4P4AmeNOOwcWu1EkCPuChYTvKPKYR8b9aRdMpq9Fkx7IymsEu3abNVcdK1FBKs12ajObrtd9IC/zGGdcg9rdLcGm9xizfIEb9uuUYWsr7yrTb8LdnlWuY0qbSoaZ0sSzNiTdsG4fTJe1uEJa8VF/blsBGeWnc7BsqUVXWWay4e4knXoBuWGHxaD4iV1zqi3UO1rfUUnh5z3ox/EYoNX30G+TKutV2LGvx4qOnlc6JH84zq8bHz3qNEhsMoqQ8vYDPsfzFvqVWmuitgAAAAASUVORK5CYII=)', + [EditorArea.cZoomCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADoElEQVRIx6WVW2xUVRSGv73PmaHTHopTiI1kaGlDGjHW2IQWL1SSeosVoYmODfZBQ3ygqGhMY7Q+GBLShBdjokkTbdQEGpBaLWg1ESTBjo1cpJbBRGg7WhgHS9ILMDMtcznLh5mxpJ22MfOvt3X+vW77/Gurc0xgo1AoNAoNKDQ67VNo8vGgmMRFITeZwE5/1WjMCa6mHQYaA4Wmwck6yinGwgLChBkjUDDcHXuQVSSZIJFOB6aNjQ1oBMHGm08tVefvP1UVKA66wgZYSc90+djGgft+e3aAvlB0FZopQGOiUMf4BxuNgaYRanjsyJauDZ0O5qEp7j277VuOczpGhBuAicJ4kWi6/EYHjQHvu6+0lPkNssBvHFpz7aH1TneBcdG0jUwbJxhDUsebfM+9WX9GpeibpXayIuSagem8S6v73CfT/mp5/7tNX9IZj88wjUKJg60UcZk7fY3NT18AoCG545enfjRHCTIOrMSTKP3+0U8f6DEA7qW9d1MnB+Pc4hZIpb9zd/jguV+7qm0EQfaFIm1SL9bt5Ysl9ZG2faEUo9oe6ZCav4kRBak89XXKnbL9g/KelGebgZRIy/7BFKs5Kq2SHyIOYsX3PGzPBtgd/v2AVJIVcoe0ZKro6ZcnDxNC+8Lm1Ueuz5KOFVxfjpk9gJri8KufNyQBujZQ5XUGwIfUdZ/O5K+zz3TLDlnGgpD6o74Ud/CQ3DMIPyNrr7QvFwR5IRb4RBrEwSIQK75ns40gH1+ULTfQGi57RrfG4bVI+wdlB+hR8cUCqLA5WjsJECim2Imp+MF+IvCMf2PRrs+Mr7igWBLBihBFEHRhmZgaE0405gHHVWh+xcDcmOOuGYCwgWWgFU5ygalxQd0Xr18r2rVW5reQraOV03kAVpIwmPncrSn/prLTMfTWXquwV04uOQXPpdUAnmnCYK6BkmDpUQd8WDD+xl53mVt6F7sHsRKlfW6A8jHGgGhOP5LAeWTnO5OzWlgv/UekasHjJZG2hgSCNMXkbXEKutJK3PXTilnK45EVN0ksJCae/+il1FbwnmWAGLnJWZBcFkoqYw4r7T+Ja0yuUPE/lyqZqx5iiChx/kAcsn2kozl6+0TmWnN0pEO2i2PWxTBDTJFMhUBqpLWnvymW7XBTrKdfWjO9Z0yNYOOkiGXY/EUfLy/ytDFAH9E5YvkTwcTEoBCDIH5C7Mz6uDJMLMuCGQU0Gk0heSRJkARA48BE0Eso619gHCw05MD3YAAAAABJRU5ErkJggg==)', + [EditorArea.cInvisibleCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADkklEQVRIx5XVX2iWdRQH8M/zZ5oO5krUV11DUwvcZtAS6R8NtOgfgtJF5m1kRiVqYajkJt5EqOsiuilC8KabTciCigjCufUHi0oZM8t615R0FrK9a++7vU8X77PXzW1KvwPP8+Oc3/n+vs855zknSPQbcrNVk757J1mCy3r0CwUCoVCIUCBKNSVto5xLatGdaoiFQvGQfn1igVAkEglEQrFQIBaINRnQ7n0HPOguZ4TpFQmCrB9dTA+GqSkWikr4HhIbcMxew2oc9oBAj1igYoxLcEOJDWi317ARvV5zUmLFOHtIKBKX749E4vQZazKo3QHD7nbQHOft1CmwJD1bUWIwFrAoldJuZeq+z1X1DtrsLdV+t81J1F4LejhOonH7jEHt9hjSoFWdyOPeNE+vnTol5o3/hIl3XyO/X94qh9XhhNgTDqh23qu6ROaKRDv0GyrnuyRV7pdzTLMBdVrVoVulvDmWWeorF3VoUGOmaIe/DZshTkNXocpqOe12+1e9VvUSPSrEQr9YbJklvnVBlwY1YhNSEgqsltOmRV6DQ+rRk1p+NYhItVtQcJUSQJRWViTUZEi7FgPqyu5j8dmlU+Kk7frMt19TCSAsA6yUMaTNbnkrHVZvUFaFUOQnvTrRYZs+i7V4VL5UB9eCN19em33yGrRqQG85vT/bgg479anRYp2CWYISg1IdPozEeTlkLEJOhUCs23s+lOi0XdZCzdaaOfY/Z3X5XEFi2Ge+dsUbFsjY4pLEqHPOOCqR6NAo417HDUuMSCQSsr7xpURemw02OaFfs7kytvpLIme/ROKENTLWOG7IqGLqnpD1nUReu43WW2+TDpftk7HAVpckEkUd1sho9JGcxGjZPREdskjBJ44YBQWnLPekglO6XXCf2Tptk1Wj2TqzFIXjWlrUrOBjRxTLqhE/uMNTRnU566LIbn9Yao9HzGSCO0HiuA+MXNcqK71shXe8KxAqqrXXWrOnaLch1ZJJ6kFvO+slzwsU1XrduindiZrdLnJ6EsiI7y33pBFX7PKYWdM0/CBBUZujU/Co9IoVzqlTOe3EiJ5TJXCnGVOwKDhllUYz/KlqGoCQHqfFNnpGNMlcKtiixdMySFsaoadtEkww3uZF9ZLrEjcJgMBvCG2wedzhW73gHtF1oJOCmDUiSafMQqO+8Kk8Is9q9I+5Nxm7QVZRks7GjP+/YuPm8RWhqjLlG3/72PoPdU0hakaQf9YAAAAASUVORK5CYII=)', + [EditorArea.cPointedHandCursor]: + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFYUlEQVRYw72XXUgUaxjH//POzOZ+4ULrLARZe5GbuMWK3UhZHt2bxSBWMiy8iboo6MpKuorywkDpSF3XbZFRlIUR+IVSGNVZ6WvDoMg0O6WB4NrOvvO+z7k47JCezF1P+cALM8w77/PjP8/XADnY8ePHCQCVlJSQrut0+PDhRFdXVyFWw1pbWzsAUGtrKyWTSWpubqZIJEIHDx6sXRWAxsbGPq/XS4lEgoiI7t+/T2vWrCEA1b/ifLbchunpafj9fvj9fhAR1q9fD845zp079+eqAAAAEUEIAQBQVRUAoChK+aoAmKYJVVW/dwwppQ302wF0XYcQAkQERVFARAAAIQRu3Ljhu3nzpu//AGgreamwsBD9/f3o6ekZ4Jyjqqqqd3h4+OSqAXDOMTw8jPr6+gjnHD09PZGtW7f+vXnz5r9GR0cRjUaVmpqaJ3v37p39bQDxeBwXLlwAADQ0NODt27cdQgh8/foVjx8/RjAYHAVQ/kuy4EcAW7ZsgWEYMAwDpaWlmJqaQm1tLS5evAi/34+2trbInj17akpLS2uqq6trjx07VvjLFAAAKSVUVYUQAg6HA5qm4ciRIwiFQvB6vbh37x5ev37dNzMzg1QqBVVVf6jIYgW87e3tiZaWFjp69OiJbA3QNA2c8wX1wOl0QggBVVVhmiYCgQAMwwBjDMFgEAAQCATQ2dmJpqYmDA4ORk6dOtX3UwX8fv/QmTNnIkIIuFyujk2bNhV5PB6ZhcgCAYBlWbYSiqJgfn4eUkoQESzLgsfjQV1dHRobGzE5OYmBgQF0dXX9PAamp6cjbW1tSCaT2L9/PyzLavn48WOUiGzHmqahsrISTqcTiqJACIGioiJUVFTAsiwoigLGGEzThGEY0DQNLpcLgUAAExMTy1fdsbExymQy9Pz5c/J4PKTrOgWDQRofHyfOOZmmSRMTEzQzM0OWZZFlWfTlyxeanJwkzjkJIejFixdUUFBAly9fJiklffr0ieLxODmdzr5ls+Du3bvQNA2hUAgHDhwA59yWloigqioMw4DP5wNjDIwx+Hw+rFu3DlJKSCmxYcMG9Pf3Y/fu3f86YcxW8KcAO3fuxK1bt5BOp8EYQ319vV3/FUWx40DXdSiKYj/L9gmHwwHGGNxuNyorK2EYBqSUdupm42ZJgOrq6sTQ0BAGBwehqipCoRDKy8uh6zo45xBCgDG2ACp7bR/I2H/uF+9fEuDVq1fNZWVlGBkZQSqVQlFREXbt2oVMJrNAgRV1vUVgS1pxcXHf9u3b6cOHD2RZFj179oy6u7tpbm6OTNMkKSXlY1JK+vz5M9XV1ZHX610+CBsaGvDgwQM8evQIiqIgHA4jFotB07QVKZBNVc45XC7X8r3g/PnztWVlZbh69SoymQzm5+fBGIPD4VgwD+Rq2cKUTqfhcDhya0Zr1649efv2bTx8+BButxuWZdkRnNN3/M45EUFKiampKRiGkRvAxo0bE+FwGL29vUin09B13Y7mfOUnInz79g3v379HOBzODSAWiz1xu92jd+7cwbt372zZl0ql5ezNmzfIZDIoL89jjj179mwHAGpvbyfOORFR3lmQ3Xv69GlSFIXyJo/H41RcXExjY2MkpbRXPjY3N0cVFRW0Y8cOynsiSqVSUU3TcO3aNZimaU/HS63FAwsAjIyM4OnTp4hGo4m8Aaqqqp4UFBSMXrlyBclkEpZl2f1/8freKRGBMYbZ2Vl0d3dj27ZtGB8fb/5hoObyKSKRCLndbnR2dsLpdNrNZ3HKSSmh67r9M/Py5UscOnQIsVhs8Pr163+suI43NTWdKCkpIU3TssGU89q3bx9dunRpyfD/B4mqU48lcruHAAAAAElFTkSuQmCC)', +}; + +function getCursor(cursor) { + if (customCursors[cursor]) { + return `${customCursors[cursor]} ${EditorArea.HOTSPOT_32[cursor * 2]} ${ + EditorArea.HOTSPOT_32[cursor * 2 + 1] + }, default`; + } + switch (cursor) { + case EditorArea.cPointerCursor: + return 'default'; + case EditorArea.cTextCursor: + return 'text'; + default: + throw new Error(`Unknown cursor: ${cursor}`); + } +} diff --git a/examples/generic-editor/CanvasEditorDialog.js b/examples/generic-editor/CanvasEditorDialog.js new file mode 100644 index 00000000..21a8423f --- /dev/null +++ b/examples/generic-editor/CanvasEditorDialog.js @@ -0,0 +1,234 @@ +export class CanvasEditorDialog { + /** + * + * @param {string} title + */ + constructor(title) { + this.title = title; + this.elements = []; + this.dialogElement = null; + } + + setLayout(hLayout, vLayout) { + this.hLayout = generateLayout(hLayout); + this.vLayout = generateLayout(vLayout); + } + + add(component, x, y, x2, y2) { + this.elements.push({ component: component.getJsComponent(), x, y, x2, y2 }); + } + + createTextField(width, height) { + return new TextField(width, height); + } + + createLabel(text) { + return new Label(text); + } + + createComboBox() { + return new ComboBox(); + } + + createCheckBox(text) { + return new CheckBox(text); + } + + setEventConsumer(consumer) { + this.consumer = consumer; + } + + showMessage(message) { + alert(message); + } + + showDialog() { + const dialog = document.createElement('dialog'); + this.dialogElement = dialog; + document.body.append(dialog); + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = this.hLayout; + grid.style.gridTemplateRows = this.vLayout; + dialog.append(grid); + for (const { component, x, y, x2, y2 } of this.elements) { + const div = document.createElement('div'); + if (x2 === undefined) { + div.style.gridColumn = `${x + 1} / ${x + 2}`; + div.style.gridRow = `${y + 1} / ${y + 2}`; + } else { + div.style.gridColumn = `${x + 1} / ${x2 + 2}`; + div.style.gridRow = `${y + 1} / ${y2 + 2}`; + } + div.appendChild(component.getElement()); + grid.appendChild(div); + } + const buttons = document.createElement('div'); + buttons.style.display = 'flex'; + buttons.style.justifyContent = 'flex-end'; + buttons.style.gap = '15px'; + const okButton = document.createElement('button'); + okButton.textContent = 'OK'; + okButton.addEventListener('click', () => { + this.consumer.fireOk(); + }); + buttons.appendChild(okButton); + const cancelButton = document.createElement('button'); + cancelButton.textContent = 'Cancel'; + cancelButton.addEventListener('click', () => { + this.consumer.fireCancel(); + }); + buttons.appendChild(cancelButton); + dialog.append(buttons); + dialog.showModal(); + dialog.addEventListener('cancel', () => { + this.consumer.fireCancel(); + }); + } + + disposeDialog() { + if (this.dialogElement !== null) { + this.dialogElement.remove(); + this.dialogElement = null; + } + } +} + +class Component { + setEventHandler(eventHandler) { + this.eventHandler = eventHandler; + } + + fireEvent(what, value) { + this.eventHandler(what, value); + } +} + +class Label extends Component { + constructor(text) { + super(); + this.element = document.createElement('label'); + this.setText(text); + } + + setText(text) { + this.element.textContent = text; + } + + getElement() { + return this.element; + } +} + +class TextField extends Component { + constructor() { + super(); + this.element = document.createElement('input'); + this.element.type = 'text'; + } + + setText(text) { + this.element.value = text; + } + + getText() { + return this.element.value; + } + + getElement() { + return this.element; + } +} + +class ComboBox extends Component { + constructor() { + super(); + this.element = document.createElement('select'); + this.element.addEventListener('change', () => { + this.fireEvent(2, this.element.selectedIndex); + }); + } + + setEnabled(enabled) { + this.element.disabled = !enabled; + } + + addItem(item) { + const option = document.createElement('option'); + option.textContent = item; + this.element.append(option); + } + + getSelectedIndex() { + return this.element.selectedIndex; + } + + setSelectedIndex(index) { + this.element.selectedIndex = index; + } + + setSelectedItem(item) { + const options = this.element.options; + for (let i = 0; i < options.length; i++) { + if (options[i].textContent === item) { + this.element.selectedIndex = i; + } + } + } + + getSelectedItem() { + return this.element.options[this.element.selectedIndex].textContent; + } + + removeAllItems() { + this.element.innerHTML = ''; + } + + getElement() { + return this.element; + } +} + +class CheckBox extends Component { + constructor(text) { + super(); + const label = document.createElement('label'); + const input = document.createElement('input'); + input.type = 'checkbox'; + input.addEventListener('change', () => { + this.fireEvent(3, input.checked ? 1 : 0); + }); + label.append(input); + label.append(text); + this.element = label; + this.checkBox = input; + } + + setEnabled(enabled) { + this.checkBox.disabled = !enabled; + } + + isSelected() { + return this.checkBox.checked; + } + + setSelected(selected) { + this.checkBox.checked = selected; + } + + getElement() { + return this.element; + } +} + +function generateLayout(layout) { + return layout + .map((dim) => { + if (dim > 0) { + return `${dim}px`; + } else { + return 'auto'; + } + }) + .join(' '); +} diff --git a/examples/generic-editor/CanvasEditorImage.js b/examples/generic-editor/CanvasEditorImage.js new file mode 100644 index 00000000..75ee45ee --- /dev/null +++ b/examples/generic-editor/CanvasEditorImage.js @@ -0,0 +1,33 @@ +export class CanvasEditorImage { + /** + * + * @param {ImageData} imageData + */ + constructor(imageData) { + this.imageData = imageData; + this.dataView = new DataView(imageData.data.buffer); + } + + getWidth() { + return this.imageData.width; + } + + getHeight() { + return this.imageData.height; + } + + getRGB(x, y) { + const color = this.dataView.getInt32( + (y * this.imageData.width + x) * 4, + false, + ); + const alpha = color & 0xff; + return (alpha << 24) | (color >>> 8); + } + + setRGB(x, y, argb) { + const alpha = (argb >>> 24) & 0xff; + const rgb = (argb << 8) | alpha; + this.dataView.setInt32((y * this.imageData.width + x) * 4, rgb, false); + } +} diff --git a/examples/generic-editor/CanvasToolbar.js b/examples/generic-editor/CanvasToolbar.js new file mode 100644 index 00000000..aa4e4d8e --- /dev/null +++ b/examples/generic-editor/CanvasToolbar.js @@ -0,0 +1,27 @@ +import { CanvasDrawContext } from './CanvasDrawContext.js'; +import { addMouseListeners } from './events.js'; + +const { EditorToolbar } = OCL; + +export class CanvasToolbar { + constructor(canvasElement, editorArea) { + this.toolbar = new EditorToolbar(editorArea, { + setDimensions(width, height) { + canvasElement.width = width; + canvasElement.height = height; + }, + getDrawContext() { + return new CanvasDrawContext(canvasElement.getContext('2d')); + }, + getBackgroundRGB() { + return 0xffffff; + }, + + getForegroundRGB() { + return 0x000000; + }, + }); + + addMouseListeners(canvasElement, this.toolbar); + } +} diff --git a/examples/generic-editor/events.js b/examples/generic-editor/events.js new file mode 100644 index 00000000..3f0c96db --- /dev/null +++ b/examples/generic-editor/events.js @@ -0,0 +1,104 @@ +const { EditorArea } = OCL; + +export function addMouseListeners(canvasElement, drawArea) { + let isMouseDown = false; + + function fireMouseEvent(what, ev, clickCount = 0) { + if (ev.button !== 0) { + // TODO: remove this to implement popup menu. + return; + } + drawArea.fireMouseEvent( + what, + ev.button + 1, + clickCount, + ev.offsetX, + ev.offsetY, + ev.shiftKey, + ev.ctrlKey, + ev.altKey, + ev.button === 2, + ); + } + + canvasElement.addEventListener('mousedown', (ev) => { + isMouseDown = true; + fireMouseEvent(EditorArea.MOUSE_EVENT_PRESSED, ev); + }); + canvasElement.addEventListener('mouseup', (ev) => { + isMouseDown = false; + fireMouseEvent(EditorArea.MOUSE_EVENT_RELEASED, ev); + }); + canvasElement.addEventListener('click', (ev) => { + fireMouseEvent(EditorArea.MOUSE_EVENT_CLICKED, ev, ev.detail); + }); + canvasElement.addEventListener('mouseenter', (ev) => { + fireMouseEvent(EditorArea.MOUSE_EVENT_ENTERED, ev); + }); + canvasElement.addEventListener('mouseleave', (ev) => { + fireMouseEvent(EditorArea.MOUSE_EVENT_EXITED, ev); + }); + canvasElement.addEventListener('mousemove', (ev) => { + if (isMouseDown) { + fireMouseEvent(EditorArea.MOUSE_EVENT_DRAGGED, ev); + } else { + fireMouseEvent(EditorArea.MOUSE_EVENT_MOVED, ev); + } + }); +} + +export function addKeyboardListeners(canvasElement, editorArea) { + const isMac = + typeof navigator !== 'undefined' && navigator.platform === 'MacIntel'; + + function fireKeyEvent(what, ev) { + const key = getKeyFromEvent(ev); + if (key === null) return; + ev.stopPropagation(); + ev.preventDefault(); + editorArea.fireKeyEvent( + what, + key, + ev.altKey, + ev.ctrlKey, + ev.shiftKey, + (isMac && ev.metaKey) || (!isMac && ev.ctrlKey), + ); + } + + canvasElement.addEventListener('keydown', (ev) => { + fireKeyEvent(EditorArea.KEY_EVENT_PRESSED, ev); + }); + canvasElement.addEventListener('keyup', (ev) => { + fireKeyEvent(EditorArea.KEY_EVENT_RELEASED, ev); + }); +} + +/** + * + * @param {KeyboardEvent} ev + */ +function getKeyFromEvent(ev) { + switch (ev.key) { + case 'Control': + return EditorArea.KEY_CTRL; + case 'Alt': + return EditorArea.KEY_ALT; + case 'Shift': + return EditorArea.KEY_SHIFT; + case 'Delete': + return EditorArea.KEY_DELETE; + case 'Backspace': + return EditorArea.KEY_BACKSPACE; + case 'F1': + return EditorArea.KEY_HELP; + case 'Escape': + return EditorArea.KEY_ESCAPE; + default: + if (ev.key.length === 1) { + return ev.key.charCodeAt(0); + } else { + return null; + } + } +} diff --git a/examples/generic-editor/index.js b/examples/generic-editor/index.js new file mode 100644 index 00000000..a37f85b2 --- /dev/null +++ b/examples/generic-editor/index.js @@ -0,0 +1 @@ +export { CanvasEditor } from './CanvasEditor.js'; diff --git a/examples/generic-editor/utils.js b/examples/generic-editor/utils.js new file mode 100644 index 00000000..fd937be2 --- /dev/null +++ b/examples/generic-editor/utils.js @@ -0,0 +1,46 @@ +// https://github.com/niklasvh/base64-arraybuffer/blob/master/LICENSE +const chars = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +// Use a lookup table to find the index. +const lookup = new Uint8Array(256); +for (let i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; +} + +export function decodeBase64(base64) { + let bufferLength = base64.length * 0.75; + let len = base64.length; + let i; + let p = 0; + let encoded1; + let encoded2; + let encoded3; + let encoded4; + + if (base64[base64.length - 1] === '=') { + bufferLength--; + if (base64[base64.length - 2] === '=') { + bufferLength--; + } + } + + const arraybuffer = new ArrayBuffer(bufferLength); + const bytes = new Uint8Array(arraybuffer); + + for (i = 0; i < len; i += 4) { + encoded1 = lookup[base64.charCodeAt(i)]; + encoded2 = lookup[base64.charCodeAt(i + 1)]; + encoded3 = lookup[base64.charCodeAt(i + 2)]; + encoded4 = lookup[base64.charCodeAt(i + 3)]; + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + + return arraybuffer; +} + +export function toHex(v) { + return v.toString(16).padStart(2, '0'); +} diff --git a/full.js b/full.js index b5ea3455..dbd7a77d 100644 --- a/full.js +++ b/full.js @@ -7,6 +7,8 @@ exports.CanonizerUtil = OCL.CanonizerUtil; exports.ConformerGenerator = OCL.ConformerGenerator; exports.DrugScoreCalculator = OCL.DrugScoreCalculator; exports.DruglikenessPredictor = OCL.DruglikenessPredictor; +exports.EditorArea = OCL.EditorArea; +exports.EditorToolbar = OCL.EditorToolbar; exports.ForceFieldMMFF94 = OCL.ForceFieldMMFF94; exports.Molecule = OCL.Molecule; exports.MoleculeProperties = OCL.MoleculeProperties; diff --git a/full.pretty.js b/full.pretty.js index 0032628d..2ad3bce5 100644 --- a/full.pretty.js +++ b/full.pretty.js @@ -7,6 +7,8 @@ exports.CanonizerUtil = OCL.CanonizerUtil; exports.ConformerGenerator = OCL.ConformerGenerator; exports.DrugScoreCalculator = OCL.DrugScoreCalculator; exports.DruglikenessPredictor = OCL.DruglikenessPredictor; +exports.EditorArea = OCL.EditorArea; +exports.EditorToolbar = OCL.EditorToolbar; exports.ForceFieldMMFF94 = OCL.ForceFieldMMFF94; exports.Molecule = OCL.Molecule; exports.MoleculeProperties = OCL.MoleculeProperties; diff --git a/package-lock.json b/package-lock.json index f4ee573c..e5bfe4d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "benchmark": "^2.1.4", "eslint": "^8.56.0", "eslint-config-cheminfo": "^9.1.1", + "fast-png": "^6.2.0", "gwt-api-exporter": "^2.0.0", "jest": "^29.7.0", "openchemlib-utils": "^5.8.0", @@ -1378,6 +1379,12 @@ "dev": true, "peer": true }, + "node_modules/@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -2887,6 +2894,17 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-png": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.2.0.tgz", + "integrity": "sha512-fO4DewoEd9WwuP8DQcfj8Tlc88Jno6lJAjlDYzvJSqMIZwxUpRT4zuzPXgqygjJqngBdCbeQRaL/FVz3InExhA==", + "dev": true, + "dependencies": { + "@types/pako": "^2.0.0", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -3425,6 +3443,12 @@ "node": ">= 0.4" } }, + "node_modules/iobuffer": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.3.2.tgz", + "integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw==", + "dev": true + }, "node_modules/is-any-array": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz", @@ -5034,6 +5058,12 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "dev": true + }, "node_modules/papaparse": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", diff --git a/package.json b/package.json index db9273d6..903a819d 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "benchmark": "^2.1.4", "eslint": "^8.56.0", "eslint-config-cheminfo": "^9.1.1", + "fast-png": "^6.2.0", "gwt-api-exporter": "^2.0.0", "jest": "^29.7.0", "openchemlib-utils": "^5.8.0", diff --git a/scripts/openchemlib/classes.js b/scripts/openchemlib/classes.js index b51dac70..b9621d95 100644 --- a/scripts/openchemlib/classes.js +++ b/scripts/openchemlib/classes.js @@ -12,6 +12,7 @@ const modified = [ 'chem/prediction/ToxicityPredictor', 'gui/hidpi/HiDPIHelper', + 'gui/hidpi/HiDPIIcon', 'util/ConstantsDWAR', ]; @@ -215,6 +216,7 @@ exports.removed = removed.map(getFolderName); const generated = [ ['chem/conf/TorsionDBData', require('./generateTorsionDBData')], ['chem/forcefield/mmff/CsvData', require('./generateCsvData')], + ['@gwt/gui/generic/ImageData', require('./generateImageData')], ]; exports.generated = generated.map((file) => [getFilename(file[0]), file[1]]); diff --git a/scripts/openchemlib/generateImageData.js b/scripts/openchemlib/generateImageData.js new file mode 100644 index 00000000..8a2046e5 --- /dev/null +++ b/scripts/openchemlib/generateImageData.js @@ -0,0 +1,50 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const { decode } = require('fast-png'); + +const resourcesDir = path.join( + __dirname, + '../../openchemlib/src/main/resources/images', +); + +const images = ['editorButtons.png', 'esrButtons.png']; + +const start = `package com.actelion.research.gwt.gui.generic; + +public class ImageData { +`; + +const end = ` +} +`; + +function generateImageData() { + const imageData = [start]; + for (const image of images) { + const contents = fs.readFileSync(path.join(resourcesDir, image)); + const png = decode(contents); + const name = image.replace('.png', ''); + const base64 = Buffer.from(png.data).toString('base64'); + const compressed = base64.replaceAll('A'.repeat(20), '%'); + const compressedSplit = []; + for (let i = 0; i < compressed.length; i += 50_000) { + compressedSplit.push(compressed.substring(i, i + 50_000)); + } + imageData.push( + ` +public static final int ${name}Width = ${png.width}; +public static final int ${name}Height = ${png.height}; +${compressedSplit + .map((s, i) => `public static final String ${name}Data${i} = "${s}";`) + .join('\n')} +`, + ); + } + imageData.push(end); + return imageData.join('\n'); +} + +module.exports = generateImageData; diff --git a/scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIHelper.java b/scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIHelper.java index 80cb7367..4d272130 100644 --- a/scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIHelper.java +++ b/scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIHelper.java @@ -1,6 +1,10 @@ package com.actelion.research.gui.hidpi; +import com.actelion.research.gui.LookAndFeelHelper; import com.actelion.research.gui.generic.GenericImage; +import com.actelion.research.util.ColorHelper; + +import java.awt.Color; public class HiDPIHelper { public static int scale(float value) { @@ -16,6 +20,14 @@ public static void adaptForLookAndFeel(GenericImage image) { } public static void disableImage(GenericImage image) { - return; + Color gray = ColorHelper.brighter(new Color(238, 238, 238), 0.8f); + int grayRGB = 0x00FFFFFF & gray.getRGB(); + + for (int x=0; x + diff --git a/src/com/actelion/research/gwt/Generic.gwt.xml b/src/com/actelion/research/gwt/Generic.gwt.xml new file mode 100644 index 00000000..7b25342c --- /dev/null +++ b/src/com/actelion/research/gwt/Generic.gwt.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIHelper.java b/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIHelper.java index 80cb7367..4d272130 100644 --- a/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIHelper.java +++ b/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIHelper.java @@ -1,6 +1,10 @@ package com.actelion.research.gui.hidpi; +import com.actelion.research.gui.LookAndFeelHelper; import com.actelion.research.gui.generic.GenericImage; +import com.actelion.research.util.ColorHelper; + +import java.awt.Color; public class HiDPIHelper { public static int scale(float value) { @@ -16,6 +20,14 @@ public static void adaptForLookAndFeel(GenericImage image) { } public static void disableImage(GenericImage image) { - return; + Color gray = ColorHelper.brighter(new Color(238, 238, 238), 0.8f); + int grayRGB = 0x00FFFFFF & gray.getRGB(); + + for (int x=0; x pasteMolecules() { + Object test = null; + System.out.println("test: " + test.toString()); + return null; + } + + public Reaction pasteReaction() { + Object test = null; + System.out.println("test: " + test.toString()); + return null; + } + + public boolean copyMolecule(String molfile) { + Object test = null; + System.out.println("test: " + test.toString()); + StereoMolecule m = new StereoMolecule(); + MolfileParser p = new MolfileParser(); + p.parse(m, molfile); + return copyMolecule(m); + } + + public native boolean copyMolecule(StereoMolecule mol) + /*-{ + var handler = this.@com.actelion.research.gwt.gui.generic.JSClipboardHandler::getJsHandler()(); + var jsMolecule = @com.actelion.research.gwt.minimal.JSMolecule::new(Lcom/actelion/research/chem/StereoMolecule;)(mol); + return handler.copyMolecule(jsMolecule); + }-*/; + + public boolean copyReaction(Reaction r) { + Object test = null; + System.out.println("test: " + test.toString()); + return true; + } + + public boolean copyReaction(String ctab) { + Object test = null; + System.out.println("test: " + test.toString()); + return true; + } + + public boolean copyImage(java.awt.Image img) { + Object test = null; + System.out.println("test: " + test.toString()); + return true; + } + + public java.awt.Image pasteImage() { + Object test = null; + System.out.println("test: " + test.toString()); + return null; + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSComboBox.java b/src/com/actelion/research/gwt/gui/generic/JSComboBox.java new file mode 100644 index 00000000..43335b65 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSComboBox.java @@ -0,0 +1,59 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.*; +import com.google.gwt.core.client.JavaScriptObject; + +public class JSComboBox extends JSComponent implements GenericComboBox { + public JSComboBox(JavaScriptObject jsComboBox) { + super(jsComboBox); + } + + @Override + public native void addItem(String item) + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); + return component.addItem(item); + }-*/; + + @Override + public native void removeAllItems() + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); + return component.removeAllItems(); + }-*/; + + @Override + public native int getSelectedIndex() + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); + return component.getSelectedIndex(); + }-*/; + + @Override + public native String getSelectedItem() + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); + return component.getSelectedItem(); + }-*/; + + @Override + public native void setSelectedIndex(int index) + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); + return component.setSelectedIndex(index); + }-*/; + + @Override + public native void setSelectedItem(String item) + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); + return component.setSelectedItem(item); + }-*/; + + @Override + public native void setEditable(boolean b) + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); + return component.setEditable(b); + }-*/; +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSComponent.java b/src/com/actelion/research/gwt/gui/generic/JSComponent.java new file mode 100644 index 00000000..edaefb06 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSComponent.java @@ -0,0 +1,57 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.*; +import com.google.gwt.core.client.JavaScriptObject; + +import java.util.ArrayList; + +public class JSComponent implements GenericComponent { + private JavaScriptObject mJsComponent; + private ArrayList> mConsumerList; + + public JSComponent(JavaScriptObject jsComponent) { + mJsComponent = jsComponent; + mConsumerList = new ArrayList<>(); + setEventHandler(jsComponent); + } + + private native void setEventHandler(JavaScriptObject jsComponent) + /*-{ + var component = this; + jsComponent.setEventHandler(function(what, value) { + component.@com.actelion.research.gwt.gui.generic.JSComponent::fireEventFromJs(II)(what, value); + }); + }-*/; + + private void fireEventFromJs(int what, int value) { + fireEvent(new GenericActionEvent(this, what, value)); + } + + public JavaScriptObject getJsComponent() { + return mJsComponent; + } + + @Override + public native void setEnabled(boolean b) + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSComponent::getJsComponent()(); + return component.setEnabled(b); + }-*/; + + @Override + public void addEventConsumer(GenericEventListener consumer) { + mConsumerList.add(consumer); + } + + @Override + public void removeEventConsumer(GenericEventListener consumer) { + mConsumerList.remove(consumer); + } + + @Override + public void fireEvent(GenericActionEvent event) { + for (GenericEventListener consumer:mConsumerList) { + consumer.eventHappened(event); + } + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSDialog.java b/src/com/actelion/research/gwt/gui/generic/JSDialog.java new file mode 100644 index 00000000..19b7e267 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSDialog.java @@ -0,0 +1,120 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.*; +import com.google.gwt.core.client.JavaScriptObject; + +public class JSDialog implements GenericDialog { + private JavaScriptObject mJsDialog; + private GenericEventListener mConsumer; + + public JSDialog(JavaScriptObject jsDialog) { + mJsDialog = jsDialog; + } + + private JavaScriptObject getJsDialog() { + return mJsDialog; + } + + @Override + public native void setLayout(int[] hLayout, int[] vLayout) + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + return dialog.setLayout(hLayout, vLayout); + }-*/; + + @Override + public native void add(GenericComponent c, int x, int y) + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + return dialog.add(c, x, y); + }-*/; + + @Override + public native void add(GenericComponent c, int x1, int y1, int x2, int y2) + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + return dialog.add(c, x1, y1, x2, y2); + }-*/; + + @Override + public native GenericCheckBox createCheckBox(String text) + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var checkBox = dialog.createCheckBox(text); + return @com.actelion.research.gwt.gui.generic.JSCheckBox::new(Lcom/google/gwt/core/client/JavaScriptObject;)(checkBox); + }-*/; + + @Override + public native GenericComboBox createComboBox() + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var comboBox = dialog.createComboBox(); + return @com.actelion.research.gwt.gui.generic.JSComboBox::new(Lcom/google/gwt/core/client/JavaScriptObject;)(comboBox); + }-*/; + + @Override + public native GenericLabel createLabel(String text) + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var label = dialog.createLabel(text); + return @com.actelion.research.gwt.gui.generic.JSLabel::new(Lcom/google/gwt/core/client/JavaScriptObject;)(label); + }-*/; + + @Override + public native GenericTextField createTextField(int width, int height) + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var textField = dialog.createTextField(width, height); + return @com.actelion.research.gwt.gui.generic.JSTextField::new(Lcom/google/gwt/core/client/JavaScriptObject;)(textField); + }-*/; + + @Override + public native void setEventConsumer(GenericEventListener consumer) + /*-{ + this.@com.actelion.research.gwt.gui.generic.JSDialog::mConsumer = consumer; + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var that = this; + var jsConsumer = { + fireOk: function ok() { + that.@com.actelion.research.gwt.gui.generic.JSDialog::fireOk()(); + }, + fireCancel: function cancel() { + that.@com.actelion.research.gwt.gui.generic.JSDialog::fireCancel()(); + } + }; + dialog.setEventConsumer(jsConsumer); + }-*/; + + @Override + public native void showDialog() + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + dialog.showDialog(); + }-*/; + + @Override + public native void disposeDialog() + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + dialog.disposeDialog(); + }-*/; + + @Override + public native void showMessage(String message) + /*-{ + var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + dialog.showMessage(message); + }-*/; + + private void fireOk() { + if (mConsumer != null) { + mConsumer.eventHappened(new GenericActionEvent(this, GenericActionEvent.WHAT_OK, 0)); + } + } + + private void fireCancel() { + if (mConsumer != null) { + mConsumer.eventHappened(new GenericActionEvent(this, GenericActionEvent.WHAT_CANCEL, 0)); + } + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java b/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java new file mode 100644 index 00000000..8a5f75b9 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java @@ -0,0 +1,216 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.*; +import com.google.gwt.core.client.JavaScriptObject; + +public class JSDrawContext implements GenericDrawContext { + private JavaScriptObject mJsContext; + + public JSDrawContext(JavaScriptObject jsContext) { + mJsContext = jsContext; + } + + private JavaScriptObject getJsContext() { + return mJsContext; + } + + public native void clearRect(double x, double y, double w, double h) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.clearRect(x, y, w, h); + }-*/; + + @Override + public native GenericImage createARGBImage(int width, int height) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + var image = ctx.createARGBImage(width, height); + return @com.actelion.research.gwt.gui.generic.JSImage::new(Lcom/google/gwt/core/client/JavaScriptObject;)(image); + }-*/; + + @Override + public native void drawCenteredString(double x, double y, String s) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.drawCenteredString(x, y, s); + }-*/; + + @Override + public native void drawCircle(double x, double y, double d) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.drawCircle(x, y, d); + }-*/; + + @Override + public native void drawDottedLine(double x1, double y1, double x2, double y2) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.drawDottedLine(x1, y1, x2, y2); + }-*/; + + @Override + public native void drawImage(GenericImage image, double x, double y) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()(); + return ctx.drawImage(jsImage, x, y); + }-*/; + + @Override + public native void drawImage(GenericImage image, double sx, double sy, double dx, double dy, double w, double h) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()(); + return ctx.drawImage(jsImage, sx, sy, dx, dy, w, h); + }-*/; + + @Override + public native void drawImage(GenericImage image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()(); + return ctx.drawImage(jsImage, sx, sy, sw, sh, dx, dy, dw, dh); + }-*/; + + @Override + public native void drawLine(double x1, double y1, double x2, double y2) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.drawLine(x1, y1, x2, y2); + }-*/; + + @Override + public void drawPolygon(GenericPolygon p) { + drawPolygon(new JSPolygon(p)); + } + + public native void drawPolygon(JSPolygon jsPolygon) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.drawPolygon(jsPolygon); + }-*/; + + @Override + public native void drawRectangle(double x, double y, double w, double h) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.drawRectangle(x, y, w, h); + }-*/; + + @Override + public native void drawString(double x, double y, String s) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.drawString(x, y, s); + }-*/; + + @Override + public native void fillCircle(double x, double y, double d) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.fillCircle(x, y, d); + }-*/; + + @Override + public void fillPolygon(GenericPolygon p) { + fillPolygon(new JSPolygon(p)); + } + + public native void fillPolygon(JSPolygon jsPolygon) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.fillPolygon(jsPolygon); + }-*/; + + @Override + public native void fillRectangle(double x, double y, double w, double h) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.fillRectangle(x, y, w, h); + }-*/; + + @Override + public native int getBackgroundRGB() + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.getBackgroundRGB(); + }-*/; + + @Override + public GenericRectangle getBounds(String s) { + JSRectangle jsRect = new JSRectangle(); + getBounds(s, jsRect); + return jsRect.getRectangle(); + } + + public native GenericRectangle getBounds(String s, JSRectangle jsRect) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.getBounds(s, jsRect); + }-*/; + + @Override + public native int getFontSize() + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.getFontSize(); + }-*/; + + @Override + public native int getForegroundRGB() + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.getForegroundRGB(); + }-*/; + + @Override + public native float getLineWidth() + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.getLineWidth(); + }-*/; + + @Override + public native int getRGB() + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.getRGB(); + }-*/; + + @Override + public native int getSelectionBackgroundRGB() + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.getSelectionBackgroundRGB(); + }-*/; + + @Override + public native boolean isDarkBackground() + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.isDarkBackground(); + }-*/; + + @Override + public native void setFont(int size, boolean isBold, boolean isItalic) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.setFont(size, isBold, isItalic); + }-*/; + + @Override + public native void setLineWidth(float lineWidth) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.setLineWidth(lineWidth); + }-*/; + + @Override + public native void setRGB(int rgb) + /*-{ + var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return ctx.setRGB(rgb); + }-*/; + +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java new file mode 100644 index 00000000..01011fbe --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java @@ -0,0 +1,179 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.chem.StereoMolecule; +import com.actelion.research.gui.editor.EditorEvent; +import com.actelion.research.gui.editor.GenericEditorArea; +import com.actelion.research.gui.generic.*; +import com.actelion.research.gwt.minimal.JSMolecule; +import com.actelion.research.gwt.minimal.JSReaction; +import com.google.gwt.core.client.JavaScriptObject; + +import info.clearthought.layout.TableLayout; + +import jsinterop.annotations.*; + +@JsType(name = "EditorArea") +public class JSEditorArea implements GenericCanvas { + private GenericEditorArea mDrawArea; + private JavaScriptObject mOptions; + private JSMouseHandler mMouseHandler; + private JSKeyHandler mKeyHandler; + + public static final int EDITOR_EVENT_MOLECULE_CHANGED = EditorEvent.WHAT_MOLECULE_CHANGED; + public static final int EDITOR_EVENT_SELECTION_CHANGED = EditorEvent.WHAT_SELECTION_CHANGED; + public static final int EDITOR_EVENT_HIGHLIGHT_ATOM_CHANGED = EditorEvent.WHAT_HILITE_ATOM_CHANGED; + public static final int EDITOR_EVENT_HIGHLIGHT_BOND_CHANGED = EditorEvent.WHAT_HILITE_BOND_CHANGED; + + public static final int MOUSE_EVENT_PRESSED = GenericMouseEvent.MOUSE_PRESSED; + public static final int MOUSE_EVENT_RELEASED = GenericMouseEvent.MOUSE_RELEASED; + public static final int MOUSE_EVENT_CLICKED = GenericMouseEvent.MOUSE_CLICKED; + public static final int MOUSE_EVENT_ENTERED = GenericMouseEvent.MOUSE_ENTERED; + public static final int MOUSE_EVENT_EXITED = GenericMouseEvent.MOUSE_EXITED; + public static final int MOUSE_EVENT_MOVED = GenericMouseEvent.MOUSE_MOVED; + public static final int MOUSE_EVENT_DRAGGED = GenericMouseEvent.MOUSE_DRAGGED; + + public static final int KEY_CTRL = GenericKeyEvent.KEY_CTRL; + public static final int KEY_ALT = GenericKeyEvent.KEY_ALT; + public static final int KEY_SHIFT = GenericKeyEvent.KEY_SHIFT; + public static final int KEY_DELETE = GenericKeyEvent.KEY_DELETE; + public static final int KEY_BACK_SPACE = GenericKeyEvent.KEY_BACK_SPACE; + public static final int KEY_HELP = GenericKeyEvent.KEY_HELP; + public static final int KEY_ESCAPE = GenericKeyEvent.KEY_ESCAPE; + + public static final int KEY_EVENT_PRESSED = GenericKeyEvent.KEY_PRESSED; + public static final int KEY_EVENT_RELEASED = GenericKeyEvent.KEY_RELEASED; + + public static final int cChainCursor = GenericCursorHelper.cChainCursor; + public static final int cDeleteCursor = GenericCursorHelper.cDeleteCursor; + public static final int cHandCursor = GenericCursorHelper.cHandCursor; + public static final int cHandPlusCursor = GenericCursorHelper.cHandPlusCursor; + public static final int cFistCursor = GenericCursorHelper.cFistCursor; + public static final int cLassoCursor = GenericCursorHelper.cLassoCursor; + public static final int cLassoPlusCursor = GenericCursorHelper.cLassoPlusCursor; + public static final int cSelectRectCursor = GenericCursorHelper.cSelectRectCursor; + public static final int cSelectRectPlusCursor = GenericCursorHelper.cSelectRectPlusCursor; + public static final int cZoomCursor = GenericCursorHelper.cZoomCursor; + public static final int cInvisibleCursor = GenericCursorHelper.cInvisibleCursor; + public static final int cPointerCursor = GenericCursorHelper.cPointerCursor; + public static final int cTextCursor = GenericCursorHelper.cTextCursor; + public static final int cPointedHandCursor = GenericCursorHelper.cPointedHandCursor; + public static final int[] HOTSPOT_32 = GenericCursorHelper.HOTSPOT_32; + + public static final int TableLayoutPreferred = (int)TableLayout.PREFERRED; + public static final int TableLayoutFill = (int)TableLayout.FILL; + + public JSEditorArea(JavaScriptObject options) { + mDrawArea = new GenericEditorArea(new StereoMolecule(), 0, new JSUIHelper(options), this); + mDrawArea.addDrawAreaListener(new GenericEventListener() { + @Override + public void eventHappened(EditorEvent e) { + callJsEventListener(e.getWhat(), e.isUserChange()); + } + }); + mOptions = options; + + mMouseHandler = new JSMouseHandler(mDrawArea); + mMouseHandler.addListener(mDrawArea); + + mKeyHandler = new JSKeyHandler(mDrawArea); + mKeyHandler.addListener(mDrawArea); + + JavaScriptObject clipboardHandler = getClipboardHandler(); + mDrawArea.setClipboardHandler(new JSClipboardHandler(clipboardHandler)); + } + + public void setMolecule(JSMolecule molecule) { + mDrawArea.setMolecule(molecule.getStereoMolecule()); + } + + public JSMolecule getMolecule() { + return new JSMolecule(mDrawArea.getMolecule()); + } + + public void setReaction(JSReaction reaction) { + mDrawArea.setReaction(reaction.getReaction()); + } + + public JSReaction getReaction() { + return new JSReaction(mDrawArea.getReaction()); + } + + private native JavaScriptObject getClipboardHandler() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); + return options.getClipboardHandler(); + }-*/; + + private native JavaScriptObject callJsEventListener(int what, boolean isUserChange) + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); + return options.onChange(what, isUserChange); + }-*/; + + public void draw() { + mDrawArea.paintContent(getDrawContext()); + } + + public GenericEditorArea getGenericEditorArea() { + return mDrawArea; + } + + public void fireMouseEvent(int what, int button, int clickCount, int x, int y, boolean shiftDown, boolean ctrlDown, boolean altDown, boolean isPopupTrigger) { + GenericMouseEvent gme = new GenericMouseEvent(what, button, clickCount, x, y, shiftDown, ctrlDown, altDown, isPopupTrigger, mDrawArea); + mMouseHandler.fireEvent(gme); + } + + public void fireKeyEvent(int what, int key, boolean isAltDown, boolean isCtrlDown, boolean isShiftDown, boolean isMenuShortcut) { + GenericKeyEvent gke = new GenericKeyEvent(what, key, isAltDown, isCtrlDown, isShiftDown, isMenuShortcut, mDrawArea); + mKeyHandler.fireEvent(gke); + } + + public void toolChanged(int newTool) { + mDrawArea.toolChanged(newTool); + } + + private JavaScriptObject getOptions() { + return mOptions; + } + + @Override + @JsIgnore + public native int getBackgroundRGB() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); + return options.getBackgroundRGB(); + }-*/; + + @Override + @JsIgnore + public native double getCanvasHeight() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); + return options.getCanvasHeight(); + }-*/; + + @Override + @JsIgnore + public native double getCanvasWidth() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); + return options.getCanvasWidth(); + }-*/; + + @Override + @JsIgnore + public GenericDrawContext getDrawContext() { + return new JSDrawContext(getDrawContextFromOptions()); + } + + private native JavaScriptObject getDrawContextFromOptions() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); + return options.getDrawContext(); + }-*/; + + @Override + public void repaint() { + draw(); + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java new file mode 100644 index 00000000..1bb037a2 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java @@ -0,0 +1,98 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.editor.GenericEditorArea; +import com.actelion.research.gui.editor.GenericEditorToolbar; +import com.actelion.research.gui.generic.*; +import com.actelion.research.gwt.minimal.JSMolecule; +import com.google.gwt.core.client.JavaScriptObject; + +import jsinterop.annotations.*; + +@JsType(name = "EditorToolbar") +public class JSEditorToolbar implements GenericCanvas { + private GenericEditorToolbar mGenericToolbar; + private JavaScriptObject mOptions; + private JSMouseHandler mMouseHandler; + + public JSEditorToolbar(JSEditorArea jsEditorArea, JavaScriptObject options) { + mGenericToolbar = new GenericEditorToolbar(this, jsEditorArea.getGenericEditorArea()); + mOptions = options; + + mMouseHandler = new JSMouseHandler(mGenericToolbar); + mMouseHandler.addListener(mGenericToolbar); + + setDimensions(mGenericToolbar.getWidth(), mGenericToolbar.getHeight()); + draw(); + } + + public int getWidth() { + return mGenericToolbar.getWidth(); + } + + public int getHeight() { + return mGenericToolbar.getHeight(); + } + + public void draw() { + JSDrawContext ctx = getDrawContext(); + ctx.clearRect(0, 0, getWidth(), getHeight()); + mGenericToolbar.paintContent(getDrawContext()); + } + + public void fireMouseEvent(int what, int button, int clickCount, int x, int y, boolean shiftDown, boolean ctrlDown, boolean altDown, boolean isPopupTrigger) { + GenericMouseEvent gme = new GenericMouseEvent(what, button, clickCount, x, y, shiftDown, ctrlDown, altDown, isPopupTrigger, mGenericToolbar); + mMouseHandler.fireEvent(gme); + } + + private JavaScriptObject getOptions() { + return mOptions; + } + + public native void setDimensions(int width, int height) + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); + return options.setDimensions(width, height); + }-*/; + + @Override + @JsIgnore + public native int getBackgroundRGB() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); + return options.getBackgroundRGB(); + }-*/; + + @Override + @JsIgnore + public native double getCanvasHeight() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); + return options.getCanvasHeight(); + }-*/; + + @Override + @JsIgnore + public native double getCanvasWidth() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); + return options.getCanvasWidth(); + }-*/; + + @Override + @JsIgnore + public JSDrawContext getDrawContext() { + return new JSDrawContext(getDrawContextFromOptions()); + } + + private native JavaScriptObject getDrawContextFromOptions() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); + return options.getDrawContext(); + }-*/; + + @Override + @JsIgnore + public void repaint() { + draw(); + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSImage.java b/src/com/actelion/research/gwt/gui/generic/JSImage.java new file mode 100644 index 00000000..e54511db --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSImage.java @@ -0,0 +1,58 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.GenericImage; +import com.google.gwt.core.client.JavaScriptObject; + +public class JSImage implements GenericImage { + private JavaScriptObject mJsImage; + + public JSImage(JavaScriptObject jsImage) { + mJsImage = jsImage; + } + + private JavaScriptObject getJsImage() { + return mJsImage; + } + + @Override + public native void scale(int width, int height) + /*-{ + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + jsImage.scale(width, height); + }-*/; + + @Override + public native Object get() + /*-{ + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + return jsImage; + }-*/; + + @Override + public native int getWidth() + /*-{ + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + return jsImage.getWidth(); + }-*/; + + @Override + public native int getHeight() + /*-{ + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + return jsImage.getHeight(); + }-*/; + + @Override + public native int getRGB(int x, int y) + /*-{ + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + return jsImage.getRGB(x, y); + }-*/; + + @Override + public native void setRGB(int x, int y, int argb) + /*-{ + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + jsImage.setRGB(x, y, argb); + }-*/; +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java b/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java new file mode 100644 index 00000000..ffa79bc5 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java @@ -0,0 +1,9 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.*; + +public class JSKeyHandler extends GenericEventHandler { + public JSKeyHandler(Object source) { + super(source); + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSLabel.java b/src/com/actelion/research/gwt/gui/generic/JSLabel.java new file mode 100644 index 00000000..81517813 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSLabel.java @@ -0,0 +1,17 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.*; +import com.google.gwt.core.client.JavaScriptObject; + +public class JSLabel extends JSComponent implements GenericLabel { + public JSLabel(JavaScriptObject jsLabel) { + super(jsLabel); + } + + @Override + public native void setText(String text) + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSLabel::getJsComponent()(); + return component.setText(text); + }-*/; +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java b/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java new file mode 100644 index 00000000..5b8e4f0d --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java @@ -0,0 +1,9 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.*; + +public class JSMouseHandler extends GenericEventHandler { + public JSMouseHandler(Object source) { + super(source); + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSPolygon.java b/src/com/actelion/research/gwt/gui/generic/JSPolygon.java new file mode 100644 index 00000000..87903005 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSPolygon.java @@ -0,0 +1,27 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.GenericPolygon; + +import jsinterop.annotations.*; + +@JsType(name = "Polygon") +public class JSPolygon { + private GenericPolygon mPolygon; + + @JsIgnore + JSPolygon(GenericPolygon polygon) { + mPolygon = polygon; + } + + public int getSize() { + return mPolygon.getSize(); + } + + public double getX(int i) { + return mPolygon.getX(i); + } + + public double getY(int i) { + return mPolygon.getY(i); + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSRectangle.java b/src/com/actelion/research/gwt/gui/generic/JSRectangle.java new file mode 100644 index 00000000..cb1f6a97 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSRectangle.java @@ -0,0 +1,23 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.GenericRectangle; + +import jsinterop.annotations.*; + +@JsType(name = "Rectangle") +public class JSRectangle { + private GenericRectangle mRectangle; + + JSRectangle() { + mRectangle = new GenericRectangle(); + } + + public void set(double x, double y, double w, double h) { + mRectangle.set(x, y, w, h); + } + + @JsIgnore() + public GenericRectangle getRectangle() { + return mRectangle; + } +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSTextField.java b/src/com/actelion/research/gwt/gui/generic/JSTextField.java new file mode 100644 index 00000000..06340a24 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSTextField.java @@ -0,0 +1,24 @@ +package com.actelion.research.gwt.gui.generic; + +import com.actelion.research.gui.generic.*; +import com.google.gwt.core.client.JavaScriptObject; + +public class JSTextField extends JSComponent implements GenericTextField { + public JSTextField(JavaScriptObject jsTextField) { + super(jsTextField); + } + + @Override + public native String getText() + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSTextField::getJsComponent()(); + return component.getText(); + }-*/; + + @Override + public native void setText(String text) + /*-{ + var component = this.@com.actelion.research.gwt.gui.generic.JSTextField::getJsComponent()(); + return component.setText(text); + }-*/; +} diff --git a/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java b/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java new file mode 100644 index 00000000..de17fb2a --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java @@ -0,0 +1,106 @@ +package com.actelion.research.gwt.gui.generic; + +import java.io.File; + +import com.actelion.research.gui.generic.*; +import com.google.gwt.core.client.JavaScriptObject; + +public class JSUIHelper implements GenericUIHelper { + private JavaScriptObject mOptions; + + public JSUIHelper(JavaScriptObject options) { + mOptions = options; + } + + private JavaScriptObject getOptions() { + return mOptions; + } + + @Override + public GenericDialog createDialog(String title, GenericEventListener consumer) { + JSDialog dialog = new JSDialog(createNativeDialog(title)); + dialog.setEventConsumer(consumer); + return dialog; + } + + public native JavaScriptObject createNativeDialog(String title) + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); + return options.createDialog(title); + }-*/; + + @Override + public GenericImage createImage(String name) { + if (name.equals("editorButtons.png")) { + String data = ImageData.editorButtonsData0 + ImageData.editorButtonsData1 + ImageData.editorButtonsData2 + ImageData.editorButtonsData3 + ImageData.editorButtonsData4 + ImageData.editorButtonsData5; + return new JSImage(createImageFromBase64(ImageData.editorButtonsWidth, ImageData.editorButtonsHeight, data)); + } else if (name.equals("esrButtons.png")) { + return new JSImage(createImageFromBase64(ImageData.esrButtonsWidth, ImageData.esrButtonsHeight, ImageData.esrButtonsData0)); + } else { + return null; + } + } + + private native JavaScriptObject createImageFromBase64(int width, int height, String base64) + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); + return options.createImageFromBase64(width, height, base64); + }-*/; + + @Override + public native GenericImage createImage(int width, int height) + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); + var image = options.createImage(width, height); + return @com.actelion.research.gwt.gui.generic.JSImage::new(Lcom/google/gwt/core/client/JavaScriptObject;)(image); + }-*/; + + @Override + public GenericPopupMenu createPopupMenu(GenericEventListener consumer) { + // TODO: implement popup menu. + return null; + } + + @Override + public native void grabFocus() + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); + return options.grabFocus(); + }-*/; + + @Override + public File openChemistryFile(boolean isReaction) { + return null; + } + + @Override + public native void runLater(Runnable r) + /*-{ + var that = this; + $wnd.requestAnimationFrame(function(){ + that.@com.actelion.research.gwt.gui.generic.JSUIHelper::runNow(Ljava/lang/Runnable;)(r); + }); + }-*/; + + private void runNow(Runnable r) { + r.run(); + } + + @Override + public native void setCursor(int cursor) + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); + return options.setCursor(cursor); + }-*/; + + @Override + public native void showHelpDialog(String url, String title) + /*-{ + var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); + return options.showHelpDialog(url, title); + }-*/; + + @Override + public void showMessage(String message) {} + +} diff --git a/src/com/actelion/research/gwt/gui/generic/package-info.java b/src/com/actelion/research/gwt/gui/generic/package-info.java new file mode 100644 index 00000000..0eb34f94 --- /dev/null +++ b/src/com/actelion/research/gwt/gui/generic/package-info.java @@ -0,0 +1,3 @@ +@jsinterop.annotations.JsPackage(namespace = "OCL") + +package com.actelion.research.gwt.gui.generic; diff --git a/src/com/actelion/research/gwt/jre/java/awt/Color.java b/src/com/actelion/research/gwt/jre/java/awt/Color.java index a365a341..7bb9b6ab 100644 --- a/src/com/actelion/research/gwt/jre/java/awt/Color.java +++ b/src/com/actelion/research/gwt/jre/java/awt/Color.java @@ -159,4 +159,23 @@ public float[] getRGBComponents(float[] compArray) { return f; } + public float[] getRGBColorComponents(float[] compArray) { + float[] f; + if (compArray == null) { + f = new float[3]; + } else { + f = compArray; + } + if (realRGBValues == null) { + f[0] = ((float) getRed()) / 255f; + f[1] = ((float) getGreen()) / 255f; + f[2] = ((float) getBlue()) / 255f; + } else { + f[0] = realRGBValues[0]; + f[1] = realRGBValues[1]; + f[2] = realRGBValues[2]; + } + return f; + } + } diff --git a/src/com/actelion/research/gwt/jre/java/awt/Font.java b/src/com/actelion/research/gwt/jre/java/awt/Font.java index 2e9b4b32..b4f54780 100644 --- a/src/com/actelion/research/gwt/jre/java/awt/Font.java +++ b/src/com/actelion/research/gwt/jre/java/awt/Font.java @@ -38,6 +38,8 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING public class Font { public static final int PLAIN = 0; + public static final int BOLD = 1; + public static final int ITALIC = 2; private String name; private int size; diff --git a/src/com/actelion/research/gwt/jre/java/awt/Image.java b/src/com/actelion/research/gwt/jre/java/awt/Image.java new file mode 100644 index 00000000..781055f4 --- /dev/null +++ b/src/com/actelion/research/gwt/jre/java/awt/Image.java @@ -0,0 +1,36 @@ +/* + +Copyright (c) 2015-2017, cheminfo + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of {{ project }} nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +package java.awt; + +public abstract class Image { +} diff --git a/src/com/actelion/research/gwt/jre/java/lang/Character.java b/src/com/actelion/research/gwt/jre/java/lang/Character.java index 714ff4a2..d17710d5 100644 --- a/src/com/actelion/research/gwt/jre/java/lang/Character.java +++ b/src/com/actelion/research/gwt/jre/java/lang/Character.java @@ -309,6 +309,10 @@ public static boolean isValidCodePoint(int codePoint) { return codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT; } + public static native boolean isWhitespace(char c)/*-{ + return String.fromCodePoint(c).trim() === ''; + }-*/; + public static int offsetByCodePoints(char[] a, int start, int count, int index, int codePointOffset) { return offsetByCodePoints(new CharSequenceAdapter(a, start, count), index, codePointOffset); } diff --git a/src/com/actelion/research/gwt/jre/javax/swing/UIManager.java b/src/com/actelion/research/gwt/jre/javax/swing/UIManager.java index 3f2b0e91..360df6af 100644 --- a/src/com/actelion/research/gwt/jre/javax/swing/UIManager.java +++ b/src/com/actelion/research/gwt/jre/javax/swing/UIManager.java @@ -3,7 +3,7 @@ public class UIManager { private final static LookAndFeel lookAndFeel = new LookAndFeel(); - static LookAndFeel getLookAndFeel() { + public static LookAndFeel getLookAndFeel() { return lookAndFeel; } } From 4e5f760d069aadc8c755b7253c9fec3f7e5ec21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Sat, 10 Feb 2024 12:09:03 +0100 Subject: [PATCH 02/64] chore: add Prettier scripts --- .github/workflows/nodejs.yml | 2 ++ __tests__/molecule.js | 18 ++++++---------- __tests__/molfileAndAtomMapNo.test.js | 31 +++++++++++++-------------- benchmark/.eslintrc.yml | 4 ++-- examples/CopyIdCode.html | 20 ++++++++++++----- examples/Editor.html | 27 +++++++++++++++++------ examples/GenericEditor.html | 2 +- examples/SVG.html | 12 +++++------ examples/ShowStructures.html | 31 +++++++++++++++++---------- examples/SimpleEditor.html | 4 ++-- package.json | 4 +++- scripts/build.js | 4 +++- scripts/extend/core.js | 2 +- 13 files changed, 98 insertions(+), 63 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 99103483..e1367c45 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -18,6 +18,8 @@ jobs: run: npm ci - name: Run ESLint run: npm run eslint + - name: Run Prettier + run: npm run prettier test: runs-on: ubuntu-latest strategy: diff --git a/__tests__/molecule.js b/__tests__/molecule.js index 8343bf83..4dbcc894 100644 --- a/__tests__/molecule.js +++ b/__tests__/molecule.js @@ -156,22 +156,22 @@ describe('Molecule', () => { it('getFinalRanks', () => { const molecule = Molecule.fromSmiles('CCC'); - molecule.setAtomicNo(0, 8) + molecule.setAtomicNo(0, 8); const atoms = []; const ranks = [...molecule.getFinalRanks()]; for (let i = 0; i < molecule.getAllAtoms(); i++) { atoms.push(molecule.getAtomLabel(i), ranks[i]); } - expect(atoms).toStrictEqual(['O', 3, 'C', 2, 'C', 1]) + expect(atoms).toStrictEqual(['O', 3, 'C', 2, 'C', 1]); const molecule2 = Molecule.fromSmiles('CCC'); - molecule2.setAtomicNo(2, 8) + molecule2.setAtomicNo(2, 8); const atoms2 = []; const ranks2 = [...molecule2.getFinalRanks()]; for (let i = 0; i < molecule2.getAllAtoms(); i++) { atoms2.push(molecule2.getAtomLabel(i), ranks2[i]); } - expect(atoms2).toStrictEqual(['C', 1, 'C', 2, 'O', 3]) - }) + expect(atoms2).toStrictEqual(['C', 1, 'C', 2, 'O', 3]); + }); it('getFinalRanks of xMolecule', () => { const molecule = Molecule.fromSmiles('CCCO'); @@ -188,12 +188,8 @@ describe('Molecule', () => { } const ranks = [...molecule.getFinalRanks()]; - expect(ranks).toStrictEqual([ - 3, 1, 2, 4, 11, - 10, 9, 5, 6, 7, - 8, 12 - ]) - }) + expect(ranks).toStrictEqual([3, 1, 2, 4, 11, 10, 9, 5, 6, 7, 8, 12]); + }); it('should have a method that returns the OCL object', () => { const molecule = Molecule.fromSmiles('C'); diff --git a/__tests__/molfileAndAtomMapNo.test.js b/__tests__/molfileAndAtomMapNo.test.js index 5a7a1512..bba97c5e 100644 --- a/__tests__/molfileAndAtomMapNo.test.js +++ b/__tests__/molfileAndAtomMapNo.test.js @@ -1,27 +1,26 @@ 'use strict'; -const { readFileSync } = require('fs'); +const { readFileSync } = require('node:fs'); const { Molecule } = require('../minimal'); - - test('molfile with atomMapNo', () => { - const molfile = readFileSync( - `${__dirname}/data/atomMapNo.mol`, - 'utf8', - ); + const molfile = readFileSync(`${__dirname}/data/atomMapNo.mol`, 'utf8'); const molecule = Molecule.fromMolfile(molfile); const newMolfile = molecule.toMolfile(); - const atomMapNo = newMolfile.split(/\r?\n/).filter((line) => line.match(/ [OCH] /)) - .map(line => line.replace(/.* ([OCH]) .*(.) {2}0 {2}0$/, '$1 $2')); - expect(atomMapNo).toStrictEqual(['O 5', 'C 1', 'C 3', 'C 4', 'H 2']) + const atomMapNo = newMolfile + .split(/\r?\n/) + .filter((line) => line.match(/ [OCH] /)) + .map((line) => + line.replace(/.* (?[OCH]) .*(?.) {2}0 {2}0$/, '$ $'), + ); + expect(atomMapNo).toStrictEqual(['O 5', 'C 1', 'C 3', 'C 4', 'H 2']); const svg = molecule.toSVG(300, 200); - const mapNos = svg.split(/\r?\n/).filter((line) => line.includes('data-atom-map')).map(line => line.replace(/.*atom-map-no="(.).*/, '$1')); - expect(mapNos).toStrictEqual(["5", "1", "3", "4", "2"]) - - - -}) \ No newline at end of file + const mapNos = svg + .split(/\r?\n/) + .filter((line) => line.includes('data-atom-map')) + .map((line) => line.replace(/.*atom-map-no="(?.).*/, '$')); + expect(mapNos).toStrictEqual(['5', '1', '3', '4', '2']); +}); diff --git a/benchmark/.eslintrc.yml b/benchmark/.eslintrc.yml index 4e8c81c4..30d99f6d 100644 --- a/benchmark/.eslintrc.yml +++ b/benchmark/.eslintrc.yml @@ -1,3 +1,3 @@ rules: - import/no-unresolved: [error, { ignore: ['./distold']}] - no-console: off \ No newline at end of file + import/no-unresolved: [error, { ignore: ['./distold'] }] + no-console: off diff --git a/examples/CopyIdCode.html b/examples/CopyIdCode.html index 142b29ef..91115d0a 100644 --- a/examples/CopyIdCode.html +++ b/examples/CopyIdCode.html @@ -1,4 +1,4 @@ - + @@ -44,7 +44,12 @@
-

+

Display of Static Structures

-

Structure Rendering Example

+

Structure Rendering Example

-
+
@@ -26,8 +26,8 @@

Structure Rendering Example

- - -
42 C6H7N 93.1286 -
+
+
Structure Rendering Example
43 C8H9NO 135.1655 -
+
+
Structure Rendering Example
43 C8H9NO 135.1655 -
+
+
Structure Rendering Example @@ -94,7 +103,7 @@

Structure Rendering Example

var el = document.getElementById('idcode42'); var btn = document.getElementById('ok'); var c = document.getElementById('cancel'); - btn.onclick = function() { + btn.onclick = function () { console.log('Changing attributes'); el.setAttribute('data-idcode', 'deVH@AAIfuneh@`@@'); console.log('Click'); diff --git a/examples/SimpleEditor.html b/examples/SimpleEditor.html index fa88bbdc..35e44a06 100644 --- a/examples/SimpleEditor.html +++ b/examples/SimpleEditor.html @@ -1,4 +1,4 @@ - + + + + + Redirecting... + + + +

Redirecting to examples/...

+ + diff --git a/package-lock.json b/package-lock.json index a524d47c..71acfd15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,14 +11,17 @@ "devDependencies": { "@types/jest": "^29.5.12", "benchmark": "^2.1.4", + "esbuild": "^0.23.0", "eslint": "^8.56.0", - "eslint-config-cheminfo": "^11.0.3", + "eslint-config-cheminfo-typescript": "^14.0.0", "fast-png": "^6.2.0", "globals": "^15.8.0", "gwt-api-exporter": "^2.0.0", "jest": "^29.7.0", "openchemlib-utils": "^6.3.0", "prettier": "^3.3.3", + "typescript": "^5.5.3", + "vite": "^5.3.4", "yargs": "^17.7.2" } }, @@ -694,6 +697,414 @@ "node": ">=16" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1373,20 +1784,244 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz", + "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz", + "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz", + "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz", + "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz", + "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz", + "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz", + "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz", + "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz", + "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz", + "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz", + "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz", + "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz", + "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz", + "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz", + "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz", + "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { "type-detect": "4.0.8" } }, @@ -1445,6 +2080,13 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1541,6 +2183,69 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", + "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.16.1", + "@typescript-eslint/type-utils": "7.16.1", + "@typescript-eslint/utils": "7.16.1", + "@typescript-eslint/visitor-keys": "7.16.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz", + "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.16.1", + "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/typescript-estree": "7.16.1", + "@typescript-eslint/visitor-keys": "7.16.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "7.16.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", @@ -1559,6 +2264,34 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz", + "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.16.1", + "@typescript-eslint/utils": "7.16.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/types": { "version": "7.16.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", @@ -2448,6 +3181,46 @@ "dev": true, "license": "MIT" }, + "node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -2544,6 +3317,22 @@ "eslint": "^8.57.0" } }, + "node_modules/eslint-config-cheminfo-typescript": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-cheminfo-typescript/-/eslint-config-cheminfo-typescript-14.0.0.tgz", + "integrity": "sha512-3BTNRlkasdOO7qSZST7gX88QjVj6SfNmvNsCvlol0wGZZ58PU2TiEVbrDZSdvtvuqtPe4o05Iea3+VnV9ocztg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-cheminfo": "^11.0.2", + "eslint-plugin-deprecation": "^3.0.0", + "typescript-eslint": "^7.15.0" + }, + "peerDependencies": { + "eslint": "^8.57.0", + "typescript": "^5.4.5" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -2566,6 +3355,22 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-deprecation": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-deprecation/-/eslint-plugin-deprecation-3.0.0.tgz", + "integrity": "sha512-JuVLdNg/uf0Adjg2tpTyYoYaMbwQNn/c78P1HcccokvhtRphgnRjZDKmhlxbxYptppex03zO76f97DD/yQHv7A==", + "dev": true, + "license": "LGPL-3.0-or-later", + "dependencies": { + "@typescript-eslint/utils": "^7.0.0", + "ts-api-utils": "^1.3.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "eslint": "^8.0.0", + "typescript": "^4.2.4 || ^5.0.0" + } + }, "node_modules/eslint-plugin-import-x": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-0.5.3.tgz", @@ -4591,6 +5396,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4995,6 +5819,35 @@ "node": ">=4" } }, + "node_modules/postcss": { + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5369,6 +6222,42 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz", + "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.1", + "@rollup/rollup-android-arm64": "4.18.1", + "@rollup/rollup-darwin-arm64": "4.18.1", + "@rollup/rollup-darwin-x64": "4.18.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.1", + "@rollup/rollup-linux-arm-musleabihf": "4.18.1", + "@rollup/rollup-linux-arm64-gnu": "4.18.1", + "@rollup/rollup-linux-arm64-musl": "4.18.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1", + "@rollup/rollup-linux-riscv64-gnu": "4.18.1", + "@rollup/rollup-linux-s390x-gnu": "4.18.1", + "@rollup/rollup-linux-x64-gnu": "4.18.1", + "@rollup/rollup-linux-x64-musl": "4.18.1", + "@rollup/rollup-win32-arm64-msvc": "4.18.1", + "@rollup/rollup-win32-ia32-msvc": "4.18.1", + "@rollup/rollup-win32-x64-msvc": "4.18.1", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5481,6 +6370,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -5821,7 +6720,6 @@ "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5830,14 +6728,41 @@ "node": ">=14.17" } }, - "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "node_modules/typescript-eslint": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.16.1.tgz", + "integrity": "sha512-889oE5qELj65q/tGeOSvlreNKhimitFwZqQ0o7PcWC7/lgRkAMknznsCsV8J8mZGTP/Z+cIbX8accf2DE33hrA==", "dev": true, - "license": "MIT" - }, - "node_modules/undici-types": { + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "7.16.1", + "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/utils": "7.16.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", @@ -5922,6 +6847,492 @@ "spdx-license-ids": "^3.0.0" } }, + "node_modules/vite": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz", + "integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.39", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index aac00a3f..7969364c 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,17 @@ "build-debug-minimal": "npm run build:pretty -- -m minimal --verbose", "build-minimal": "npm run build:min -- -m minimal", "build-full-pretty": "npm run build:pretty -- -m full", + "build-esm": "npm run build-full-pretty && npm run build-esm-bundle", + "build-esm-bundle": "esbuild full.pretty.js --bundle --format=esm --outfile=distesm/full.pretty.js && echo \"export * from '../full.pretty';\" > distesm/full.pretty.d.ts", "eslint": "eslint .", "eslint-fix": "npm run eslint -- --fix", "prettier": "prettier --check .", "prettier-write": "prettier --write .", "test": "npm run build && npm run test-only && npm run eslint && npm run prettier", - "test-only": "jest" + "test-only": "jest", + "vite": "vite --open", + "vite-build": "vite build", + "vite-preview": "vite preview" }, "main": "./core.js", "exports": { @@ -38,14 +43,8 @@ "./core.js": "./core.js", "./full": "./full.js", "./full.js": "./full.js", - "./full.pretty": { - "types": "./full.d.ts", - "default": "./full.pretty.js" - }, - "./full.pretty.js": { - "types": "./full.d.ts", - "default": "./full.pretty.js" - } + "./full.pretty": "./full.pretty.js", + "./full.pretty.js": "./full.pretty.js" }, "files": [ "dist", @@ -56,7 +55,8 @@ "core.d.ts", "full.js", "full.d.ts", - "full.pretty.js" + "full.pretty.js", + "full.pretty.d.ts" ], "repository": { "type": "git", @@ -74,14 +74,17 @@ "devDependencies": { "@types/jest": "^29.5.12", "benchmark": "^2.1.4", + "esbuild": "^0.23.0", "eslint": "^8.56.0", - "eslint-config-cheminfo": "^11.0.3", + "eslint-config-cheminfo-typescript": "^14.0.0", "fast-png": "^6.2.0", "globals": "^15.8.0", "gwt-api-exporter": "^2.0.0", "jest": "^29.7.0", "openchemlib-utils": "^6.3.0", "prettier": "^3.3.3", + "typescript": "^5.5.3", + "vite": "^5.3.4", "yargs": "^17.7.2" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..b04d56ac --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true + }, + "include": ["examples", "vite.config.mts"] +} diff --git a/vite.config.mts b/vite.config.mts new file mode 100644 index 00000000..866dc975 --- /dev/null +++ b/vite.config.mts @@ -0,0 +1,26 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { defineConfig } from 'vite'; + +// @ts-expect-error globSync is not defined in the types. +const examples: string[] = fs.globSync('examples/**/*.html', { + cwd: import.meta.dirname, +}); + +export default defineConfig({ + build: { + outDir: 'distbuild', + rollupOptions: { + input: { + main: path.join(import.meta.dirname, 'index.html'), + ...Object.fromEntries( + examples.map((example) => [ + example, + path.join(import.meta.dirname, example), + ]), + ), + }, + }, + }, +}); From e23ca05634d3a2186fd5bc9f5861447a216ed7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Fri, 19 Jul 2024 15:32:47 +0200 Subject: [PATCH 05/64] chore: add get-java script --- scripts/build.js | 2 +- scripts/get-java.sh | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100755 scripts/get-java.sh diff --git a/scripts/build.js b/scripts/build.js index 023e25e7..4f2399c8 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -159,7 +159,7 @@ function compile(mode) { let min = mode === 'min'; let PATH = process.env.PATH; if (config.jdk) { - PATH = `${path.join(config.jdk, 'bin')};${PATH}`; + PATH = `${path.resolve(config.jdk, 'bin')};${PATH}`; } for (let i = 0; i < modules.length; i++) { log(`Compiling module ${modules[i].name}`); diff --git a/scripts/get-java.sh b/scripts/get-java.sh new file mode 100755 index 00000000..0872bf0d --- /dev/null +++ b/scripts/get-java.sh @@ -0,0 +1,6 @@ +#/bin/bash + +wget -q https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.4%2B7/OpenJDK21U-jdk_x64_linux_hotspot_21.0.4_7.tar.gz -O jdk.tar.gz +tar xf jdk.tar.gz +mv jdk-21* jdk +echo '{"gwt": "./gwt", "jdk": "./jdk"}' > config.json From b69716003f0f6a384549b3946248d3092d0fef0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Fri, 19 Jul 2024 15:38:35 +0200 Subject: [PATCH 06/64] chore: fix jdk PATH syntax --- .gitignore | 2 ++ scripts/build.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ff582068..20100cfb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ war/** *.log /gwt/ gwt.zip +/jdk/ +jdk.tar.gz /dist/ /distbuild/ /distold/ diff --git a/scripts/build.js b/scripts/build.js index 4f2399c8..f47d1a2b 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -159,7 +159,7 @@ function compile(mode) { let min = mode === 'min'; let PATH = process.env.PATH; if (config.jdk) { - PATH = `${path.resolve(config.jdk, 'bin')};${PATH}`; + PATH = `${path.resolve(config.jdk, 'bin')}:${PATH}`; } for (let i = 0; i < modules.length; i++) { log(`Compiling module ${modules[i].name}`); From 2a5f763f4187df18215a60c045d90a7d4993cf7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Fri, 19 Jul 2024 15:40:30 +0200 Subject: [PATCH 07/64] chore: use Node.js 22 for build --- .node-version | 2 +- package.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.node-version b/.node-version index 209e3ef4..c2aa6d76 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20 +22.4.1 diff --git a/package.json b/package.json index 7969364c..75e1c13f 100644 --- a/package.json +++ b/package.json @@ -86,5 +86,8 @@ "typescript": "^5.5.3", "vite": "^5.3.4", "yargs": "^17.7.2" + }, + "volta": { + "node": "22.4.1" } } From d6a206be9a1384dfb7ab6bf8a9e2b3e91598fe59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Fri, 19 Jul 2024 16:53:10 +0200 Subject: [PATCH 08/64] wip: start working on CanvasEditor extension --- .node-version | 2 +- eslint.config.mjs | 2 +- examples/css/generic_editor.css | 5 +++++ examples/generic-editor.ts | 3 --- ...{generic-editor.html => generic_editor.html} | 3 ++- examples/generic_editor.ts | 12 ++++++++++++ examples/index.html | 2 +- full.d.ts | 1 + full.js | 3 +++ full.pretty.js | 3 +++ index.html | 4 ++-- lib/canvas_editor/index.js | 12 ++++++++++++ package.json | 8 +++++--- types.d.ts | 17 +++++++++++++++++ 14 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 examples/css/generic_editor.css delete mode 100644 examples/generic-editor.ts rename examples/{generic-editor.html => generic_editor.html} (67%) create mode 100644 examples/generic_editor.ts create mode 100644 lib/canvas_editor/index.js diff --git a/.node-version b/.node-version index c2aa6d76..2bd5a0a9 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.4.1 +22 diff --git a/eslint.config.mjs b/eslint.config.mjs index 6265eb54..8ba41258 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -18,7 +18,7 @@ export default [ ...cheminfo, ...unicorn, { - files: ['*.js'], + files: ['*.js', 'lib/**'], languageOptions: { sourceType: 'commonjs', }, diff --git a/examples/css/generic_editor.css b/examples/css/generic_editor.css new file mode 100644 index 00000000..b14913df --- /dev/null +++ b/examples/css/generic_editor.css @@ -0,0 +1,5 @@ +#editor-area { + width: 600px; + height: 600px; + background-color: #b5e2ff; +} diff --git a/examples/generic-editor.ts b/examples/generic-editor.ts deleted file mode 100644 index 4920d677..00000000 --- a/examples/generic-editor.ts +++ /dev/null @@ -1,3 +0,0 @@ -import OCL from '../distesm/full.pretty'; - -console.log(OCL); diff --git a/examples/generic-editor.html b/examples/generic_editor.html similarity index 67% rename from examples/generic-editor.html rename to examples/generic_editor.html index ab12de5c..9f0f27f4 100644 --- a/examples/generic-editor.html +++ b/examples/generic_editor.html @@ -4,10 +4,11 @@ Generic editor - OpenChemLib JS examples +

Generic editor

- + diff --git a/examples/generic_editor.ts b/examples/generic_editor.ts new file mode 100644 index 00000000..6e3025f9 --- /dev/null +++ b/examples/generic_editor.ts @@ -0,0 +1,12 @@ +import OCL from '../distesm/full.pretty'; + +const editorArea = document.getElementById('editor-area') as HTMLElement; + +const editor = new OCL.CanvasEditor(editorArea, { + width: 500, + height: 500, +}); + +const molecule = OCL.Molecule.fromSmiles('COCO'); + +editor.setMolecule(molecule); diff --git a/examples/index.html b/examples/index.html index c05cdf53..866aa354 100644 --- a/examples/index.html +++ b/examples/index.html @@ -8,7 +8,7 @@

OpenChemLib JS examples

diff --git a/full.d.ts b/full.d.ts index 8de1d83a..ab51543b 100644 --- a/full.d.ts +++ b/full.d.ts @@ -6,4 +6,5 @@ export { AtomHighlightCallback, BondHighlightCallback, ChangeListenerCallback, + CanvasEditor, } from './types'; diff --git a/full.js b/full.js index dbd7a77d..30503871 100644 --- a/full.js +++ b/full.js @@ -1,6 +1,9 @@ 'use strict'; const OCL = require('./dist/openchemlib-full.js'); +const createCanvasEditor = require('./lib/canvas_editor'); + +exports.CanvasEditor = createCanvasEditor(OCL.EditorArea, OCL.EditorToolbar); exports.default = OCL; exports.CanonizerUtil = OCL.CanonizerUtil; diff --git a/full.pretty.js b/full.pretty.js index 2ad3bce5..00f0fca3 100644 --- a/full.pretty.js +++ b/full.pretty.js @@ -1,6 +1,9 @@ 'use strict'; const OCL = require('./dist/openchemlib-full.pretty.js'); +const createCanvasEditor = require('./lib/canvas_editor'); + +exports.CanvasEditor = createCanvasEditor(OCL.EditorArea, OCL.EditorToolbar); exports.default = OCL; exports.CanonizerUtil = OCL.CanonizerUtil; diff --git a/index.html b/index.html index ed9183d5..2950728c 100644 --- a/index.html +++ b/index.html @@ -3,9 +3,9 @@ Redirecting... - + -

Redirecting to examples/...

+

Redirecting to /examples/...

diff --git a/lib/canvas_editor/index.js b/lib/canvas_editor/index.js new file mode 100644 index 00000000..d1737a16 --- /dev/null +++ b/lib/canvas_editor/index.js @@ -0,0 +1,12 @@ +'use strict'; + +function createCanvasEditor(EditorArea, EditorToolbar) { + return class CanvasEditor { + setMolecule(molecule) { + console.log('WITH WATCH'); + this.molecule = molecule; + } + }; +} + +module.exports = createCanvasEditor; diff --git a/package.json b/package.json index 75e1c13f..861b0bf5 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,10 @@ "build-debug-minimal": "npm run build:pretty -- -m minimal --verbose", "build-minimal": "npm run build:min -- -m minimal", "build-full-pretty": "npm run build:pretty -- -m full", - "build-esm": "npm run build-full-pretty && npm run build-esm-bundle", - "build-esm-bundle": "esbuild full.pretty.js --bundle --format=esm --outfile=distesm/full.pretty.js && echo \"export * from '../full.pretty';\" > distesm/full.pretty.d.ts", + "build-esm": "npm run build-full-pretty && npm run build-esm-bundle-export", + "build-esm-bundle": "esbuild full.pretty.js --bundle --format=esm --outfile=distesm/full.pretty.js", + "build-esm-bundle-watch": "npm run build-esm-bundle -- --watch", + "build-esm-bundle-export": "npm run build-esm-bundle && echo \"export * from '../full.pretty';\" > distesm/full.pretty.d.ts", "eslint": "eslint .", "eslint-fix": "npm run eslint -- --fix", "prettier": "prettier --check .", @@ -88,6 +90,6 @@ "yargs": "^17.7.2" }, "volta": { - "node": "22.4.1" + "node": "22.5.1" } } diff --git a/types.d.ts b/types.d.ts index 3ff32c7f..2bc7b268 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3679,3 +3679,20 @@ export declare namespace SVGRenderer { height: number, ): string; } + +export interface CanvasEditorOptions { + /** + * The width of the canvas. + */ + width: number; + /** + * The height of the canvas. + */ + height: number; +} + +export declare class CanvasEditor { + constructor(element: HTMLElement, options?: CanvasEditorOptions); + + setMolecule(molecule: Molecule): void; +} From 7d44b490cd3f41d8045d079d0a6465c496d9d39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Mon, 22 Jul 2024 15:42:16 +0200 Subject: [PATCH 09/64] feat: move poc code from examples to lib and install CanvasEditor in "full" distribution --- .github/workflows/nodejs.yml | 2 +- __tests__/__snapshots__/library.js.snap | 4 + __tests__/library.js | 7 +- eslint.config.mjs | 15 +++ examples/css/generic_editor.css | 1 + examples/generic-editor/CanvasEditor.js | 73 ---------- examples/generic-editor/CanvasToolbar.js | 27 ---- examples/generic-editor/index.js | 1 - examples/generic_editor.html | 1 + examples/generic_editor.ts | 12 +- full.js | 12 +- full.pretty.js | 12 +- .../canvas_editor/clipboard_handler.js | 6 +- lib/canvas_editor/create_editor.js | 62 +++++++++ .../canvas_editor/cursor_manager.js | 127 +++++++----------- .../canvas_editor/draw_context.js | 8 +- lib/canvas_editor/editor_area.js | 58 ++++++++ .../canvas_editor/editor_dialog.js | 17 ++- .../canvas_editor/editor_image.js | 6 +- .../canvas_editor}/events.js | 53 ++++---- lib/canvas_editor/index.js | 66 ++++++++- lib/canvas_editor/toolbar.js | 29 ++++ .../canvas_editor}/utils.js | 17 ++- package.json | 11 +- scripts/build.js | 22 --- scripts/build_esm_bundle_types.js | 9 ++ types.d.ts | 54 ++++++-- 27 files changed, 437 insertions(+), 275 deletions(-) delete mode 100644 examples/generic-editor/CanvasEditor.js delete mode 100644 examples/generic-editor/CanvasToolbar.js delete mode 100644 examples/generic-editor/index.js rename examples/generic-editor/CanvasClipboardHandler.js => lib/canvas_editor/clipboard_handler.js (77%) create mode 100644 lib/canvas_editor/create_editor.js rename examples/generic-editor/CanvasEditorArea.js => lib/canvas_editor/cursor_manager.js (86%) rename examples/generic-editor/CanvasDrawContext.js => lib/canvas_editor/draw_context.js (96%) create mode 100644 lib/canvas_editor/editor_area.js rename examples/generic-editor/CanvasEditorDialog.js => lib/canvas_editor/editor_dialog.js (94%) rename examples/generic-editor/CanvasEditorImage.js => lib/canvas_editor/editor_image.js (90%) rename {examples/generic-editor => lib/canvas_editor}/events.js (54%) create mode 100644 lib/canvas_editor/toolbar.js rename {examples/generic-editor => lib/canvas_editor}/utils.js (82%) create mode 100644 scripts/build_esm_bundle_types.js diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 7a9aa9f0..2545c0f7 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -49,7 +49,7 @@ jobs: run: npm run build-debug-full - name: Upload debug build and logs if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debug-build path: | diff --git a/__tests__/__snapshots__/library.js.snap b/__tests__/__snapshots__/library.js.snap index d6f96255..539c95b1 100644 --- a/__tests__/__snapshots__/library.js.snap +++ b/__tests__/__snapshots__/library.js.snap @@ -2,6 +2,8 @@ exports[`prototype properties of CanonizerUtil 1`] = `[]`; +exports[`prototype properties of CanvasEditor 1`] = `[]`; + exports[`prototype properties of ConformerGenerator 1`] = ` [ "getConformerCount", @@ -416,6 +418,8 @@ exports[`static properties of CanonizerUtil 1`] = ` ] `; +exports[`static properties of CanvasEditor 1`] = `[]`; + exports[`static properties of ConformerGenerator 1`] = ` [ "STRATEGY_ADAPTIVE_RANDOM", diff --git a/__tests__/library.js b/__tests__/library.js index 4e0197a7..06bc5af3 100644 --- a/__tests__/library.js +++ b/__tests__/library.js @@ -31,7 +31,12 @@ const coreAPI = [ 'Transformer', ]; -const fullAPI = ['StructureView', 'StructureEditor', 'SVGRenderer']; +const fullAPI = [ + 'CanvasEditor', + 'StructureView', + 'StructureEditor', + 'SVGRenderer', +]; const allAPI = [...minimalAPI, ...coreAPI, ...fullAPI]; diff --git a/eslint.config.mjs b/eslint.config.mjs index 8ba41258..a65e6c98 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -23,6 +23,15 @@ export default [ sourceType: 'commonjs', }, }, + { + files: ['lib/**'], + languageOptions: { + sourceType: 'commonjs', + globals: { + ...globals.browser, + }, + }, + }, { files: ['__tests__/**'], languageOptions: { @@ -62,4 +71,10 @@ export default [ 'no-console': 'off', }, }, + { + files: ['types.d.ts'], + rules: { + '@typescript-eslint/naming-convention': 'off', + }, + }, ]; diff --git a/examples/css/generic_editor.css b/examples/css/generic_editor.css index b14913df..bd64573a 100644 --- a/examples/css/generic_editor.css +++ b/examples/css/generic_editor.css @@ -2,4 +2,5 @@ width: 600px; height: 600px; background-color: #b5e2ff; + border: 1px solid #000; } diff --git a/examples/generic-editor/CanvasEditor.js b/examples/generic-editor/CanvasEditor.js deleted file mode 100644 index a3813008..00000000 --- a/examples/generic-editor/CanvasEditor.js +++ /dev/null @@ -1,73 +0,0 @@ -import { CanvasEditorArea } from './CanvasEditorArea.js'; -import { CanvasToolbar } from './CanvasToolbar.js'; -import { addMouseListeners, addKeyboardListeners } from './events.js'; - -const { EditorArea } = OCL; - -export class CanvasEditor { - /** - * - * @param {HTMLElement} rootElement - */ - constructor(rootElement, options = {}) { - const { onChange } = options; - - const childElement = document.createElement('div'); - childElement.setAttribute( - 'style', - 'width: 100%; height: 100%; display: flex; flex-direction: row; align-items: start; background-color: white;', - ); - rootElement.appendChild(childElement); - - const toolbarCanvas = document.createElement('canvas'); - childElement.appendChild(toolbarCanvas); - - const editorContainer = document.createElement('div'); - editorContainer.setAttribute('style', 'width: 100%; height: 100%;'); - childElement.appendChild(editorContainer); - - const editorCanvas = document.createElement('canvas'); - editorCanvas.tabIndex = '0'; - editorCanvas.style = 'outline: none'; - editorContainer.appendChild(editorCanvas); - - const containerSize = editorContainer.getBoundingClientRect(); - editorCanvas.width = containerSize.width; - editorCanvas.height = containerSize.height; - - const editorArea = new EditorArea( - new CanvasEditorArea(editorCanvas, onChange), - ); - this.editorArea = editorArea; - - editorArea.draw(); - - this.toolbar = new CanvasToolbar(toolbarCanvas, editorArea); - - const resizeObserver = new ResizeObserver(([entry]) => { - editorCanvas.width = entry.contentRect.width; - editorCanvas.height = entry.contentRect.height; - editorArea.repaint(); - }); - resizeObserver.observe(editorContainer); - - addMouseListeners(editorCanvas, editorArea); - addKeyboardListeners(editorCanvas, editorArea); - } - - getMolecule() { - return this.editorArea.getMolecule(); - } - - setMolecule(molecule) { - this.editorArea.setMolecule(molecule); - } - - getReaction() { - return this.editorArea.getReaction(); - } - - setReaction(reaction) { - this.editorArea.setReaction(reaction); - } -} diff --git a/examples/generic-editor/CanvasToolbar.js b/examples/generic-editor/CanvasToolbar.js deleted file mode 100644 index aa4e4d8e..00000000 --- a/examples/generic-editor/CanvasToolbar.js +++ /dev/null @@ -1,27 +0,0 @@ -import { CanvasDrawContext } from './CanvasDrawContext.js'; -import { addMouseListeners } from './events.js'; - -const { EditorToolbar } = OCL; - -export class CanvasToolbar { - constructor(canvasElement, editorArea) { - this.toolbar = new EditorToolbar(editorArea, { - setDimensions(width, height) { - canvasElement.width = width; - canvasElement.height = height; - }, - getDrawContext() { - return new CanvasDrawContext(canvasElement.getContext('2d')); - }, - getBackgroundRGB() { - return 0xffffff; - }, - - getForegroundRGB() { - return 0x000000; - }, - }); - - addMouseListeners(canvasElement, this.toolbar); - } -} diff --git a/examples/generic-editor/index.js b/examples/generic-editor/index.js deleted file mode 100644 index a37f85b2..00000000 --- a/examples/generic-editor/index.js +++ /dev/null @@ -1 +0,0 @@ -export { CanvasEditor } from './CanvasEditor.js'; diff --git a/examples/generic_editor.html b/examples/generic_editor.html index 9f0f27f4..510b01bc 100644 --- a/examples/generic_editor.html +++ b/examples/generic_editor.html @@ -9,6 +9,7 @@

Generic editor

+
diff --git a/examples/generic_editor.ts b/examples/generic_editor.ts index 6e3025f9..b758fa1c 100644 --- a/examples/generic_editor.ts +++ b/examples/generic_editor.ts @@ -1,12 +1,14 @@ import OCL from '../distesm/full.pretty'; const editorArea = document.getElementById('editor-area') as HTMLElement; +const eventsArea = document.getElementById('events-area') as HTMLElement; -const editor = new OCL.CanvasEditor(editorArea, { - width: 500, - height: 500, -}); +const editor = new OCL.CanvasEditor(editorArea); const molecule = OCL.Molecule.fromSmiles('COCO'); - editor.setMolecule(molecule); + +editor.setOnChangeListener((event) => { + console.log('change', event); + eventsArea.innerText = `Type: ${event.type}; User event: ${event.isUserEvent}`; +}); diff --git a/full.js b/full.js index 30503871..18884b9d 100644 --- a/full.js +++ b/full.js @@ -1,17 +1,21 @@ 'use strict'; const OCL = require('./dist/openchemlib-full.js'); -const createCanvasEditor = require('./lib/canvas_editor'); -exports.CanvasEditor = createCanvasEditor(OCL.EditorArea, OCL.EditorToolbar); +OCL.CanvasEditor = require('./lib/canvas_editor')( + OCL.EditorArea, + OCL.EditorToolbar, +); + +delete OCL.EditorArea; +delete OCL.EditorToolbar; exports.default = OCL; exports.CanonizerUtil = OCL.CanonizerUtil; +exports.CanvasEditor = OCL.CanvasEditor; exports.ConformerGenerator = OCL.ConformerGenerator; exports.DrugScoreCalculator = OCL.DrugScoreCalculator; exports.DruglikenessPredictor = OCL.DruglikenessPredictor; -exports.EditorArea = OCL.EditorArea; -exports.EditorToolbar = OCL.EditorToolbar; exports.ForceFieldMMFF94 = OCL.ForceFieldMMFF94; exports.Molecule = OCL.Molecule; exports.MoleculeProperties = OCL.MoleculeProperties; diff --git a/full.pretty.js b/full.pretty.js index 00f0fca3..00a41fc0 100644 --- a/full.pretty.js +++ b/full.pretty.js @@ -1,17 +1,21 @@ 'use strict'; const OCL = require('./dist/openchemlib-full.pretty.js'); -const createCanvasEditor = require('./lib/canvas_editor'); -exports.CanvasEditor = createCanvasEditor(OCL.EditorArea, OCL.EditorToolbar); +OCL.CanvasEditor = require('./lib/canvas_editor')( + OCL.EditorArea, + OCL.EditorToolbar, +); + +delete OCL.EditorArea; +delete OCL.EditorToolbar; exports.default = OCL; exports.CanonizerUtil = OCL.CanonizerUtil; +exports.CanvasEditor = OCL.CanvasEditor; exports.ConformerGenerator = OCL.ConformerGenerator; exports.DrugScoreCalculator = OCL.DrugScoreCalculator; exports.DruglikenessPredictor = OCL.DruglikenessPredictor; -exports.EditorArea = OCL.EditorArea; -exports.EditorToolbar = OCL.EditorToolbar; exports.ForceFieldMMFF94 = OCL.ForceFieldMMFF94; exports.Molecule = OCL.Molecule; exports.MoleculeProperties = OCL.MoleculeProperties; diff --git a/examples/generic-editor/CanvasClipboardHandler.js b/lib/canvas_editor/clipboard_handler.js similarity index 77% rename from examples/generic-editor/CanvasClipboardHandler.js rename to lib/canvas_editor/clipboard_handler.js index ad8ec6f6..35364f5c 100644 --- a/examples/generic-editor/CanvasClipboardHandler.js +++ b/lib/canvas_editor/clipboard_handler.js @@ -1,4 +1,6 @@ -export class CanvasClipboardHandler { +'use strict'; + +class ClipboardHandler { copyMolecule(molecule) { const data = molecule.getIDCodeAndCoordinates(); navigator.clipboard.writeText(`${data.idCode} ${data.coordinates}`); @@ -9,3 +11,5 @@ export class CanvasClipboardHandler { return null; } } + +module.exports = ClipboardHandler; diff --git a/lib/canvas_editor/create_editor.js b/lib/canvas_editor/create_editor.js new file mode 100644 index 00000000..05a6a831 --- /dev/null +++ b/lib/canvas_editor/create_editor.js @@ -0,0 +1,62 @@ +'use strict'; + +const EditorArea = require('./editor_area'); +const { addMouseListeners, addKeyboardListeners } = require('./events'); +const Toolbar = require('./toolbar'); + +function createEditor( + rootElement, + onChange, + JavaEditorArea, + JavaEditorToolbar, +) { + const childElement = document.createElement('div'); + childElement.setAttribute( + 'style', + 'width: 100%; height: 100%; display: flex; flex-direction: row; align-items: start; background-color: white;', + ); + rootElement.append(childElement); + + const toolbarCanvas = document.createElement('canvas'); + childElement.append(toolbarCanvas); + + const editorContainer = document.createElement('div'); + editorContainer.setAttribute('style', 'width: 100%; height: 100%;'); + childElement.append(editorContainer); + + const editorCanvas = document.createElement('canvas'); + editorCanvas.tabIndex = '0'; + editorCanvas.style = 'outline: none'; + editorContainer.append(editorCanvas); + + const containerSize = editorContainer.getBoundingClientRect(); + editorCanvas.width = containerSize.width; + editorCanvas.height = containerSize.height; + + const editorArea = new JavaEditorArea( + new EditorArea(editorCanvas, onChange, JavaEditorArea), + ); + + editorArea.draw(); + + const toolbar = new Toolbar( + toolbarCanvas, + editorArea, + JavaEditorArea, + JavaEditorToolbar, + ); + + const resizeObserver = new ResizeObserver(([entry]) => { + editorCanvas.width = entry.contentRect.width; + editorCanvas.height = entry.contentRect.height; + editorArea.repaint(); + }); + resizeObserver.observe(editorContainer); + + addMouseListeners(editorCanvas, editorArea, JavaEditorArea); + addKeyboardListeners(editorCanvas, editorArea, JavaEditorArea); + + return { editorArea, toolbar }; +} + +module.exports = createEditor; diff --git a/examples/generic-editor/CanvasEditorArea.js b/lib/canvas_editor/cursor_manager.js similarity index 86% rename from examples/generic-editor/CanvasEditorArea.js rename to lib/canvas_editor/cursor_manager.js index e6cb28db..7f0a07cc 100644 --- a/examples/generic-editor/CanvasEditorArea.js +++ b/lib/canvas_editor/cursor_manager.js @@ -1,97 +1,68 @@ -import { CanvasClipboardHandler } from './CanvasClipboardHandler.js'; -import { CanvasDrawContext } from './CanvasDrawContext.js'; -import { CanvasEditorDialog } from './CanvasEditorDialog.js'; -import { CanvasEditorImage } from './CanvasEditorImage.js'; -import { decodeBase64 } from './utils.js'; - -const { EditorArea } = OCL; - -export class CanvasEditorArea { - constructor(canvasElement, onChange) { - this.canvasElement = canvasElement; - this.changeListener = onChange; - } - // JSEditorArea methods - getBackgroundRGB() { - return 0xffffff; - } - getCanvasWidth() { - return this.canvasElement.width; - } - getCanvasHeight() { - return this.canvasElement.height; - } - getDrawContext() { - return new CanvasDrawContext(this.canvasElement.getContext('2d')); - } - onChange(what, isUserEvent) { - this.changeListener?.({ what, isUserEvent }); - } - getClipboardHandler() { - return new CanvasClipboardHandler(); - } - // JSUIHelper methods - grabFocus() { - this.canvasElement.focus(); - } - setCursor(cursor) { - this.canvasElement.style.cursor = getCursor(cursor); - } - showHelpDialog(url, title) { - console.log({ url, title }); - alert('Help dialog is not implemented yet'); - } - createImageFromBase64(width, height, base64) { - base64 = base64.replaceAll('%', 'A'.repeat(20)); - const decoded = decodeBase64(base64); - const typedArray = new Uint8ClampedArray(decoded); - const imageData = new ImageData(typedArray, width, height); - return new CanvasEditorImage(imageData); - } - createDialog(title) { - return new CanvasEditorDialog(title); - } -} +'use strict'; const customCursors = { - [EditorArea.cChainCursor]: + cChainCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACOElEQVRIx+3VzUsUcRzH8c/MOLszdlFwY8H6E6JjIARFBlGt7KEsbelgj65ZayJYf0GtERSVFnQOOnTp1j9gRSIlBh7sEhn5uLpbu+3Tu8Os7qqrYpc6+P0df5/vi/k9zIy0U/++DMmQZInNIhsUkmokU0XVbAgYm8CoKHPLB9yiqgKnlXEyDRljs/a0/ashbZ2RJEuGHPlXRkT4f9x+NZwOYziqNoLCmu55OZw5h1kFwJnpvzonHk1kwlQFsGZjIk58hOZ1AO5MX+esEOLxeLYKgT3fLYTF0HsOrgIiwpnrjU6L3QgLmycfcy2rF4K90OXx90YKpzAqgIjwJ25Gv4sAItUvLMTgaJkICmsx6rXHR/NtWAcqNxFnqadzStQhikfxL13wokMf8iFKa091CmFy51O+Heu8fJKU8nJuIhb9JnYh8scwJHypbmEgnr4rhhB26ooQdTwfyZ3Fkhz5JEQgeXFy4MuDrq+elG3BbJQjCV/ymhA2z4YLJ5OXhahlcDR3AkNyS0D9xP2OxXoCCBtRCGFely3HI5zEDY998VYIPw/HsmHMZrkrwOFbU15EiEILkvyVu+4uxMrzA59/t2JeklsBhJrS5UAT5ppTd4WT6PVm747l27FUal8GjsRK18ZC0GivaXcVEf75PhEf/92GWW5fBgJjQ+FMLUGCJDswnXWAq4hwfh7PHsJy5a4BDKQ9c62T+/25fa/NN7Ppvdt4nVHRS+Alcbf5LSgB3nX+u2Lnp/Bf1B//ZTKHt1UOnwAAAABJRU5ErkJggg==)', - [EditorArea.cDeleteCursor]: + cDeleteCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADc0lEQVRIx42VW29UVRiGn7XWbr0QRhM81GCwHDxEuDDRUShUagy1ETTqBdNy4eEXaADDpbbwF6AmxhvTGJWku9paewCbdGYqJRhqG4WUBE12gyad0VjnQOewPy/26sy0M9Nxrbu11vt87/vtw1JCkiyNxqNg2Mo/S7JxRyVYJIlGodBoNKBRGLsSrD5rMpHll3Z8TvSmXQEHjcbJkuQODgqNwWBQGDQOGoWDwqFDpU64Zz/bfu6RQ+rJ6V/RtoQADmgce1DbLQeNCfi045hUz1DfycdytB+NykHZF1sUB0UTCo0DatPpmFS3e/Zka44iIdqPxaSNx6O/l/dBY2wEjcZY4waDpkOle9y+M605VhAUIQ69Fpc21TrtWYcOFQ0zFhCAnqZFp0+4fad2rrKCgEUcfD3OAdkR+1MCRCm5tog1Ly0m3e32frAzb+WsR/Bg9O9yBFMybWwTD+t0t9v34a5KeSVixj+gtk2ncGBjGzVbCOtMz1Dv6V3ZDfIyou2NGdkvW2PWQXPJuMMWwiYTcT9+f3e+hryM6Hpz3N+f1qyrrVGEdSYy2HtqTz352niAlYcw9ino8oPT2W73o9N7spvIFSFW+GKu4xzX8IgywhjjXOYPxGR6Bha3SUiQegMJCTJyPfdKCh884owywSRTFFntGrjx8P+S3+1MU0TK34LmsOI5eeLTp+5CA/Mj8y+fuWfSLnlcYZI8YlYjE5dn3b/OIyEJbVb959VOoYAgCHhcZQoxueODC0ooxMaS55F7ayDW5NnOIr6VC3hcQ1Qu4s7hK1FCIT6WqOEiOD88nzkiqliSCwhicpGh60rWJoX498kL6xFB9eH5bKdQKRcQkzvuVsgtYjTZXw4SyL9ZyB7zK8yvAV4cjgbm1yNmRpf7Axf3S0iQbxfSR0QLUgXYG71UDVBCYea7RD9yn4QEGfwlc7SWXECa/Le/nlGihGrESOITAvmrteVBE5uL71yMVXtQQv7H4cSF2YlUVz25gIcgTfn3LsZrB5n7UvaJXqIewIFF8vm9A2/5X+nIC0pVvbuGf31/e/2v2uMWN2yQoBcV9f3ZHwphv251G+EWN7ltg1QiKF6ZyocFaQT4jdt4LHEHaSq8Ox4nQYIEy7OXCs8naARQHj5iL5YWaOYZdtt0i/zU8NpGeQAVv/UQqvTr1I31/AcQPMokc/81uwAAAABJRU5ErkJggg==)', - [EditorArea.cHandCursor]: + cHandCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACXUlEQVRIx6WVT2sTURTFfzOTtNESGy1ScS3uhFTERSHa2k0WdeOHcKmiFvwKIoL7foDiphvBdtF/WhRqhXTlzlWhWGjF2oJNM+8dFzOTzCQvE8H7FvPmzrvn3TPn3fvgP83L/arB6/xe15NU+DzzPIjmSsENNCVjX1ZLQqgeeV79QwYIatQAKCKaQJ33LADc7F5c6I0f5zrr+NxlEwOUYn/VmawD4JgRhGUE8PHb1MsAF5nCB9ZyANwyQAgwwXpWFd+tbLKvSfmLwBRvo7+zmgMQhQbtoMRaQIn7LA5SoZhym1RexRi4NAjAZLir6xAGXQczARhlBqE+WqWoNbs2SAA2WBmlBB/7Qygm6DkpVKf5xCJXyjSiJC33eIjFowC8aFPx+9SVHqulE70W8lSXlXSiQ1m1dKYDNWW0J1SXdCTk98q4TUiJWS4hQFiGqeDhETBGgMdlfrBAiAfYHgpzX/iMzxjTMV+Lj48hwAd8DIcMUyFAGfGSWSNkC0OZ27E7wGuLBh4Fxqk4lE/evrKzxiEFbgBw1rd7KP7FvbaOlmV1og/6JslKMuqYbT9/C3VqoWMTaFanasmmlncDWEk/hQJHMTVoLLNFQOhM38uU9ZCzFp6GrPKHIl5OszYY4IITYIO1JQ5o5VSDxRIC+/2qcZt3FJwUhLB4BLwBmOm7yTXtymWRKkZGtcwN0d0P5vZYzDSydAZRk4VzOV20yq9JfXfIaNSUZLSrqwPuqBX0TEfxIWplIEKdqqYh8Tz/cm0MVV9yixIGk+JoOc8jNuGYO+zk5VCm0bkdHSPTs/4Cu8J6qPZw7lkAAAAASUVORK5CYII=)', - [EditorArea.cHandPlusCursor]: + cHandPlusCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACaUlEQVRIx6WVP2sUQRjGf7N7l1wMZ6JBItZiJ1xELITTxDRXxMYPYamiBvwKIoJ9PkCwSSOYFPlrUNCIl8rOKhCMJGJMwNzdzjwWu3vZvdvbE3yn2Jl3Z555n3nmfQf+00zuX/Wf53W7HiWWzzHHvbCvBFxfU9z25LQohGqh58U/RICgShWAIqIB1HjLPMC1zsmF7vXjXGENj9tsYoFS5K9kBpsBcMQwwjEMeHht6mWAc0ziAas5ANkyQAAwwVpaFS9b2Xhfm/AXgUleh6ezkgMQLvXbi2JrASXustBPhWLCbRNxFSPgUj8Am+Kujkvod1zMGGCEaYR6aJWg1ujYIAZYZ3mEErzrDaGIoMmkUJniPQtcLFMPg3Tc4T4OQwF41qbi9cgrPVRLx3opZFSTk3SsAzm11NS+GrLaFapJOhTyumXcIqDEDOcRIByDjGIw+IzhY7jAd+YJMIDrojD7iQ94jDEV8XV4eFgMHuBhOWCQUXyUEi/u1QM+YilzI3L7GMCPvoYC44xmKB+PPrO9ygEFrgLQ7Fk9FB1xt62hJTkda0NfJTlJVhJCisbh97fQaS6c2gSa0YlaconpnQBO0k8hfyUrui8FbcipKScnMloI8ENoKDMbHwes8IciJqdYWyxwNjOZ1lldZJ9Wqq7GfYtDOBwBsNcrG7d4QyFTgfD6GHxeAUz3zJjL2lHSkodoZWVVTb0QnfVgdpeFVCFLPhVhkYWhnCpa4ddNfUvIGJtVQ5LVji71eaOW0RMdykqSWimIQCeqakA8zX9c6wOV51ynhMUmODrO8IBNOOIW23kxlKmjnJaqWX8BrBWTpFVweJUAAAAASUVORK5CYII=)', - [EditorArea.cFistCursor]: + cFistCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACC0lEQVRIx+2UsWsTYRjGf3eXFktqS20pUqQVdQgUJCkKDi4mckJRKIj/guDgoGRzFzWQRZycnKSTe42zoNhkKAGXKtSlimItanK9+x6HXtKL3ncER/F5h+Pu+77ne9/nfZ+D//gH4GSslahjgMrfEih+NrmNC3yhNVxWL+LQQ71TXdNiP75RHOr4lHytyNW43ipUSyNCd3VHE6I6JMGGNuVrTpsK1daofO3qh5bVLyoB97f3MmWYZx4QDuARAHlynMYhLqZqJ4iVdQHh4hL0vwWIAivMQO1gby69jogqG7g4TFFnEUOOEeABV7jEGpkEZ3FwqHABgDlu4uASscsMpxCQI0wlKB/jw9J+XV6sQYSHBxg8bnGNk0SAlyA4QA3lNa5R+fqpdBiFknzllSZi6RyPuE4AmFRdTNyXhYFu9giKnClwmRsct863E3dneWC9p0GLN9vliKPcZ9rSmt6xxQEFEnvX+USBq4CX6bCAbuokrm/TwLXNVjwdAr7byYvakh1GoYxCPRvwRPK6Sos1ummOiTXwAJf3WWZa5SvGQqF4uNocsRZRQ48VWUqIZBTqs0qyJgloQW3tWTQwMnqpsUw7XzzEUyI6RAhhEiEMe7xiFpr2DCZpzuq5pK6MOv17jYxCRdrSeZ1oMmm38w6Nj8V7BEwwRgcn4QsPeMJrug12sv+KVWSLw38I+Auc4F9yeHFVDQAAAABJRU5ErkJggg==)', - [EditorArea.cLassoCursor]: + cLassoCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAEfklEQVRIx3WVfUzVZRTHP/fKSz8GaN6WV7GbKOpQYANUXDqV5WJO6c5S/miyzJmlrAicbm3gWtisbC5f2npZw2qNJNORC3xbqFOuQsAEkkAlXvK+IAiXd+7l/k5//C7cC17O88/znOec73l5zjmPDkLR46OhaJZiwuie2fOcJwgdIDPGnu0KdmKnnaawf32yKqPoQqnkeQCMM0hmDYbWuLrEGmNxSKMfbCwZriR7Qu2CBrq5RbXdA9DJKlCwIQgyX7I8RyxVuz3aMfDa7bFUeY5IlswXBBsKOoUWjLCKTY/ivzR/EeSzmox51DAC3c+UhFb7ebN/LKckqp4yKu0s9AK8RNqd9K2JNi0U8vtT6pbWh3fjwgbMJWTA0BR/J+GriHsAzOV8bcoFLtkrFqJTGFpOxq3X1sZp+Ke7zSWzOrjPPR7k92u8gghiWMai3hdLzDsNGu9mw5pzFIf9rROF9xs2xa8H2CZH/1hQQx3l9ifQQhn+VDCbVBJakw5sPqsDqL8eV8YJJHX4uFlFkEz1SaEclOhIFBQUigNlMVoOPinMVBHErA4fl1Qk+1IzgqwUR5HsF0MJVd7VjiA4OePdeZdB9juKVgqCXGqWbKRg7xiClD6UPDF+wqdTbNpQsDDqzzNKXulDBNk7JofpKtS4g6dk48t04AoAkMyOyRAbB09pm65CvSsEYIeE9WB5h0iCeZqqaaSGDh/DEtbzlgrgCvG2gQcgYjuRBCYjGVh8xyAYrzi9MgBQpLMvIg3dNPpkcp1qDtFNN/mwrm/Od3oAZUg/y5HnBih6VRaTNh2AnmhycPGIxxQsIfniFq2oZ9mRd2tuagkpaZOP5RUJkES8NeFC9LJC8psuzxUEKW+QrCAsifOPJh1QwGz6PTNdRzhtXrNO/mPE34/gcMwSW5W2b4UN2KWuLeOuTmFoZ39K9p5CPcBPXVsuWKP6w8DQ+0JLqBMrFWF1wxSzPZhEUgdNP2RmRWgNdeNizBVOoiCRktt9+u0AcyBvtPaGFMhWiZT1kuv+/PZfWtEj29X7pfKRLFa8A8UgOdbiwCPk58fqETnc983Ve7smTBwbcH4rByXGhjIxkdIv/+Ov9ovjdmVh7zpBkLWTAPeN1V+TAnlTZnoTPA5wxoogjt9KW6ME2aZaz0p+Rd1kb3Ldlir3Z5Itcb4XmhhhehXg2rqUimL3mpizutbXP1x9zajdLeONkdUty+uMD3BQSS1uv6fxerDZVrQiYA72eNp/dX4th2S3rJd5AWrECzBP8tvOfTA2VX2j9HwvOWKSmYHQ/UOw8qOp91hjTuzdBGe4MxSaw08EwVVcoXTQPu7vHVZO+oZ8TQVtnNKZTEtM4SiAYWRR/3uFeijdtLOZcAY0oaQp6vh9LAhCOeXaTpHcu9cRJFYGT0q0T0JFnRTCFMANbNA2w1yJv5Uu0IjDRJRPopNW+viTsYk+nY4adH0ZjwFao/0CZTYGYDkzxnOg0hkYQIxNYUMA7iCwP3XtADpRCRpl1TRuDI1sPm+dA7MdsDCghMoo/wN5WLFDBuWEyAAAAABJRU5ErkJggg==)', - [EditorArea.cLassoPlusCursor]: + cLassoPlusCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAEk0lEQVRIx42Vf0zUZRzHX3ec0JcAyzM5hS5B01BggT+HTmJzmineNLWtxTJn2mSFwLS5iZ6dy5WNqdXsxxpqcxSZSk6Z5lJbyq+U8UMIdIQ47wd2yi8ROO776Y8vB3eEW8/zx/M8n+fzeX9+Pp9HByHoGR49MUzHjMkz9uF4rwEdIEEDz/4zpgMnrTSG/j3Mq9KHLoQKJgBgCmIWCzC2xNck3TAVBTf4wcaxrj/ZmVg1uQ43V7nu9AK0MRcUHAiCREumd19p5Uavdhx9bvSWVnr3SaZEC4IDBZ1CMyaYy7J7CQcsnxmGtc7C0mfsBfdTxSHX/azJHcgujqqlhAonsYMAKSwtT1+V5NBcIa9rXs302jA3/TiAiQR3GxsTyhO/DK8HYCKnquad4bzzWiw6hZ6ZrLu6emG8hn/EbSl+5i63qOd2XpdGs4UzlRlMaX+h2LLeqNH+qFtwkqLQmzpR+KBuWUIqwBrZf3byDWq45HwAzaRYNeY8K4BtHGkktiRvW35CB1B7Jb6EQ0ja44MWFUEy1AcFsl1iIlBQUChCrAgiVr8oxsj2BwUZKoJY1McHJQ3JOt+EIHPEVSi5YiymcnC2DgG0+ifCKLmuwjmCIOebJMvA+NOxAHuaJ9zk+MfuIFYyctwjkmDfwc3xCcqe5tdi4XTskucM7ujDQQCpJZQtdh4hEsAaCDDfd9ZWJ2WpJjLhcJAtytAfDPCWhD6kdDMRjAFAt3tY3LeXPUOk0tDUd9QCPfQHDxaOFyB87SP+3zCAr+IMSjdAoS5/imkpx5BAbbrd/pp3kQUcwLaoM/JbPYDSg3y0sx9B8rvVvfLqiNIfzAIUITj4kGoakGli/dGOILkesemxr64AyHn6TAYpLHmy3SZsJOpfms0bTSlbJwKsaMRpoDQpen/yNgUs5l8y0nWEcWdQogNDIMSYMCwSV7l0y2wHsEFdWEK1IbS6J2nz0fpNBXpYOfn791acsUd1hYKx/fnmkJflLHd90iSR9sh8NCMzXHtQOy4YHJSjIBGS4z7y7ih9YGdf1e9ik1USIamS4/m07E+t6JG16q1zYpUXlcGGYpRse9HoLeT4fXWf7O38+mL9hiEV+d0d38h2mepAGepI6Rf+8hf7wVVWUdC+SBBkYQDgloHay2KTt2Ws1pGGALTEuH4+1xIlyBrVfkLyrtUEWpPjKa30fCJZEq8RHCjDcdarAJcXzbtW5Fkw9YSu5fUd8y+btLsZvNk7v3lmjek2LiqowuOXmkELljsKZ48ag03e1p86vpJdslFSZVLgpb8LkyTvzsmtAyPFF8vD7yRbzDJ2NHR/F+wcM7fnN2THVSd2hHWEQFPYIQNcpD+Eu7T67C1nTsA3hF+t3eELndk8zRyGAhh7p3S9X6CHc8vWNxFGt8aUPEIcv48FQbjEJW2nSE71FQSJk0efS8wwh4oa4MIIwFd4Rds85teEq+kCDbjMRA1ztNFCJ78x4MveEx9fna5z3X2Alhj/RzUOIzCTIF8MVNpGBxBTY2gPgMcAzv9cu4A2VAx9zH2CGT29y0/ZI2GcC2JH5VDp418SVoR6q/CpzAAAAABJRU5ErkJggg==)', - [EditorArea.cSelectRectCursor]: + cSelectRectCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAC3klEQVRIx6XVz29UVRQH8M9781qhnYpYbCut1dpKo1SCEjttiBpNLCoIhjRqokv9C1yZaKZEd2rCygVqcGlC/EFIQdAQaRe0EeMCW6CQgFLFWmwQC5SxdFz08ToDHVPg3MW953vP/d6Tc849N/jUSldlRqfTzhp10nnnjMuhXI0lbnefe9ylwUFhcmJItFIG5OWM2etnhw36xSXFUqFRu1XaPKhWuSDGo9npX385ZsA3+swgUO8hdypHzoQhZxx1VOgJz+rQqib2IxiQocfNSc+giJxyQfbGT+e35BAy4WZlAlFGr/Ux440S/Gi9aFhfTICey/b4KFay1gowJXU11ond7HRAk3BEb4L/aoM6u+LREaMvOlB0ejpZ7TYiPO5IAvyh2zK3xSMVoz856ZipxCpCkA2yHDEiOm0m2er3ZrwKCm4c80ZcatfKjFHheAGw+X9DtmseinFRVYHaNJfjAk9qjdkkbad1IqErUknG0sL6BSXsEdus9aq92F6ALxfda/lCqk6F11Qad0o6QavVC1d5foFlU+YlGZ/IJFl4UpuwRVdBZdHr64KU5Y0XkbR6z9+J1qVRdNiaBNhhUq1/9AtVqlMtLIp8zqQf7LM61ts1iCKLE4MPnJA2ISdlqTot2jR7zmdxV/jdITvt8WGchYcNirg7IbjiC1AudFlehU02e992pxwy7KBeYwXvIjXXkWalUZUhvKzdBRft87nzfsNZw/Y7hw4bEGTJbwmuJXhHjf0+NuIZG6W0eV2v3XFhlenSaY22orAWESzWqlmDrba6ZLU/TaNa2mNa3K/JCneoLE3wtnVaVMn40rs69btooxekNVtiRVE7m5dgyrYkbaN2SHlFt6ctlS96nyU9OFOyOwcl6zO6zjQ75+JC1qFblFv+WAzIzzumjXgLtb7SV7TznQu+9628vIHrYzBXprWWKTNl0qNFO08JPT73uQ6V9O8BdboFFllksITNkP8AkhXgo0QeBZEAAAAASUVORK5CYII=)', - [EditorArea.cSelectRectPlusCursor]: + cSelectRectPlusCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAC50lEQVRIx6XV309XdRgH8Nc53wMpQmQakBBFkqwkV7n4wly12sJKs9ZYtdVl/QGtq7baF1Z31eZVF1azrXXl+uEcGeVcwAWw6bowUKkNS5YRxoylIiGnC47n+/0ibKjPc3E+n/fned7n2ef58Qk+tdkVmdfmtLPGjZl2zqRZlKpS6WZ3ucNt6gwIU49h0WZZEJs1ocfPjhnym4uKpUy9Fls0u1e1UkGCRwuf//ztpEHf6TOPQK373KoUs6YMO+OEE0KPekqrJlVJHMGgLJ2uTzqHRMwqFeSu3TvumkXIlOuVKURZ3XYkjNdKcNQO0Yi+hACdlxz0UbLJ2SbAjMyVu07tFj69GoSjulP8dzvVOJBoa4I+r7fIey5dfWtU+IvjKfCnDuvdlGgmQX8y5qSZ1CpCkAtyHDcqOm0+Per3ZrIKCv444fWk1BbLvHHhZAHwwhK5yvsdWIJiUlRRsG0o8MtHUm3Cc8rtt10kdFkmzVi5sHZFCXvQHtu8ogd7C/ANojttWEnVKfOqNSadUp6i69QKt3hmCYcgXlCYiMVdcRCXeFHWJ7JpFh7TLGzUXlBZdPumIGWLpcl7/kl37eqFx2xNgX16VavU71AwEIwF0wFUB4JcEAcLjd3j89S+RZ0osjoFPvCrclNmZaxVoxE87bNkKvzhiP0O+jDJwv2GRNyeElz2JSgVuiRWBt631ylHjBjQbaKgLzL5ibQg9SoM4yUtzrvge4P4AmeNOOwcWu1EkCPuChYTvKPKYR8b9aRdMpq9Fkx7IymsEu3abNVcdK1FBKs12ajObrtd9IC/zGGdcg9rdLcGm9xizfIEb9uuUYWsr7yrTb8LdnlWuY0qbSoaZ0sSzNiTdsG4fTJe1uEJa8VF/blsBGeWnc7BsqUVXWWay4e4knXoBuWGHxaD4iV1zqi3UO1rfUUnh5z3ox/EYoNX30G+TKutV2LGvx4qOnlc6JH84zq8bHz3qNEhsMoqQ8vYDPsfzFvqVWmuitgAAAAASUVORK5CYII=)', - [EditorArea.cZoomCursor]: + cZoomCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADoElEQVRIx6WVW2xUVRSGv73PmaHTHopTiI1kaGlDGjHW2IQWL1SSeosVoYmODfZBQ3ygqGhMY7Q+GBLShBdjokkTbdQEGpBaLWg1ESTBjo1cpJbBRGg7WhgHS9ILMDMtcznLh5mxpJ22MfOvt3X+vW77/Gurc0xgo1AoNAoNKDQ67VNo8vGgmMRFITeZwE5/1WjMCa6mHQYaA4Wmwck6yinGwgLChBkjUDDcHXuQVSSZIJFOB6aNjQ1oBMHGm08tVefvP1UVKA66wgZYSc90+djGgft+e3aAvlB0FZopQGOiUMf4BxuNgaYRanjsyJauDZ0O5qEp7j277VuOczpGhBuAicJ4kWi6/EYHjQHvu6+0lPkNssBvHFpz7aH1TneBcdG0jUwbJxhDUsebfM+9WX9GpeibpXayIuSagem8S6v73CfT/mp5/7tNX9IZj88wjUKJg60UcZk7fY3NT18AoCG545enfjRHCTIOrMSTKP3+0U8f6DEA7qW9d1MnB+Pc4hZIpb9zd/jguV+7qm0EQfaFIm1SL9bt5Ysl9ZG2faEUo9oe6ZCav4kRBak89XXKnbL9g/KelGebgZRIy/7BFKs5Kq2SHyIOYsX3PGzPBtgd/v2AVJIVcoe0ZKro6ZcnDxNC+8Lm1Ueuz5KOFVxfjpk9gJri8KufNyQBujZQ5XUGwIfUdZ/O5K+zz3TLDlnGgpD6o74Ud/CQ3DMIPyNrr7QvFwR5IRb4RBrEwSIQK75ns40gH1+ULTfQGi57RrfG4bVI+wdlB+hR8cUCqLA5WjsJECim2Imp+MF+IvCMf2PRrs+Mr7igWBLBihBFEHRhmZgaE0405gHHVWh+xcDcmOOuGYCwgWWgFU5ygalxQd0Xr18r2rVW5reQraOV03kAVpIwmPncrSn/prLTMfTWXquwV04uOQXPpdUAnmnCYK6BkmDpUQd8WDD+xl53mVt6F7sHsRKlfW6A8jHGgGhOP5LAeWTnO5OzWlgv/UekasHjJZG2hgSCNMXkbXEKutJK3PXTilnK45EVN0ksJCae/+il1FbwnmWAGLnJWZBcFkoqYw4r7T+Ja0yuUPE/lyqZqx5iiChx/kAcsn2kozl6+0TmWnN0pEO2i2PWxTBDTJFMhUBqpLWnvymW7XBTrKdfWjO9Z0yNYOOkiGXY/EUfLy/ytDFAH9E5YvkTwcTEoBCDIH5C7Mz6uDJMLMuCGQU0Gk0heSRJkARA48BE0Eso619gHCw05MD3YAAAAABJRU5ErkJggg==)', - [EditorArea.cInvisibleCursor]: + cInvisibleCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADkklEQVRIx5XVX2iWdRQH8M/zZ5oO5krUV11DUwvcZtAS6R8NtOgfgtJF5m1kRiVqYajkJt5EqOsiuilC8KabTciCigjCufUHi0oZM8t615R0FrK9a++7vU8X77PXzW1KvwPP8+Oc3/n+vs855zknSPQbcrNVk757J1mCy3r0CwUCoVCIUCBKNSVto5xLatGdaoiFQvGQfn1igVAkEglEQrFQIBaINRnQ7n0HPOguZ4TpFQmCrB9dTA+GqSkWikr4HhIbcMxew2oc9oBAj1igYoxLcEOJDWi317ARvV5zUmLFOHtIKBKX749E4vQZazKo3QHD7nbQHOft1CmwJD1bUWIwFrAoldJuZeq+z1X1DtrsLdV+t81J1F4LejhOonH7jEHt9hjSoFWdyOPeNE+vnTol5o3/hIl3XyO/X94qh9XhhNgTDqh23qu6ROaKRDv0GyrnuyRV7pdzTLMBdVrVoVulvDmWWeorF3VoUGOmaIe/DZshTkNXocpqOe12+1e9VvUSPSrEQr9YbJklvnVBlwY1YhNSEgqsltOmRV6DQ+rRk1p+NYhItVtQcJUSQJRWViTUZEi7FgPqyu5j8dmlU+Kk7frMt19TCSAsA6yUMaTNbnkrHVZvUFaFUOQnvTrRYZs+i7V4VL5UB9eCN19em33yGrRqQG85vT/bgg479anRYp2CWYISg1IdPozEeTlkLEJOhUCs23s+lOi0XdZCzdaaOfY/Z3X5XEFi2Ge+dsUbFsjY4pLEqHPOOCqR6NAo417HDUuMSCQSsr7xpURemw02OaFfs7kytvpLIme/ROKENTLWOG7IqGLqnpD1nUReu43WW2+TDpftk7HAVpckEkUd1sho9JGcxGjZPREdskjBJ44YBQWnLPekglO6XXCf2Tptk1Wj2TqzFIXjWlrUrOBjRxTLqhE/uMNTRnU566LIbn9Yao9HzGSCO0HiuA+MXNcqK71shXe8KxAqqrXXWrOnaLch1ZJJ6kFvO+slzwsU1XrduindiZrdLnJ6EsiI7y33pBFX7PKYWdM0/CBBUZujU/Co9IoVzqlTOe3EiJ5TJXCnGVOwKDhllUYz/KlqGoCQHqfFNnpGNMlcKtiixdMySFsaoadtEkww3uZF9ZLrEjcJgMBvCG2wedzhW73gHtF1oJOCmDUiSafMQqO+8Kk8Is9q9I+5Nxm7QVZRks7GjP+/YuPm8RWhqjLlG3/72PoPdU0hakaQf9YAAAAASUVORK5CYII=)', - [EditorArea.cPointedHandCursor]: + cPointedHandCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFYUlEQVRYw72XXUgUaxjH//POzOZ+4ULrLARZe5GbuMWK3UhZHt2bxSBWMiy8iboo6MpKuorywkDpSF3XbZFRlIUR+IVSGNVZ6WvDoMg0O6WB4NrOvvO+z7k47JCezF1P+cALM8w77/PjP8/XADnY8ePHCQCVlJSQrut0+PDhRFdXVyFWw1pbWzsAUGtrKyWTSWpubqZIJEIHDx6sXRWAxsbGPq/XS4lEgoiI7t+/T2vWrCEA1b/ifLbchunpafj9fvj9fhAR1q9fD845zp079+eqAAAAEUEIAQBQVRUAoChK+aoAmKYJVVW/dwwppQ302wF0XYcQAkQERVFARAAAIQRu3Ljhu3nzpu//AGgreamwsBD9/f3o6ekZ4Jyjqqqqd3h4+OSqAXDOMTw8jPr6+gjnHD09PZGtW7f+vXnz5r9GR0cRjUaVmpqaJ3v37p39bQDxeBwXLlwAADQ0NODt27cdQgh8/foVjx8/RjAYHAVQ/kuy4EcAW7ZsgWEYMAwDpaWlmJqaQm1tLS5evAi/34+2trbInj17akpLS2uqq6trjx07VvjLFAAAKSVUVYUQAg6HA5qm4ciRIwiFQvB6vbh37x5ev37dNzMzg1QqBVVVf6jIYgW87e3tiZaWFjp69OiJbA3QNA2c8wX1wOl0QggBVVVhmiYCgQAMwwBjDMFgEAAQCATQ2dmJpqYmDA4ORk6dOtX3UwX8fv/QmTNnIkIIuFyujk2bNhV5PB6ZhcgCAYBlWbYSiqJgfn4eUkoQESzLgsfjQV1dHRobGzE5OYmBgQF0dXX9PAamp6cjbW1tSCaT2L9/PyzLavn48WOUiGzHmqahsrISTqcTiqJACIGioiJUVFTAsiwoigLGGEzThGEY0DQNLpcLgUAAExMTy1fdsbExymQy9Pz5c/J4PKTrOgWDQRofHyfOOZmmSRMTEzQzM0OWZZFlWfTlyxeanJwkzjkJIejFixdUUFBAly9fJiklffr0ieLxODmdzr5ls+Du3bvQNA2hUAgHDhwA59yWloigqioMw4DP5wNjDIwx+Hw+rFu3DlJKSCmxYcMG9Pf3Y/fu3f86YcxW8KcAO3fuxK1bt5BOp8EYQ319vV3/FUWx40DXdSiKYj/L9gmHwwHGGNxuNyorK2EYBqSUdupm42ZJgOrq6sTQ0BAGBwehqipCoRDKy8uh6zo45xBCgDG2ACp7bR/I2H/uF+9fEuDVq1fNZWVlGBkZQSqVQlFREXbt2oVMJrNAgRV1vUVgS1pxcXHf9u3b6cOHD2RZFj179oy6u7tpbm6OTNMkKSXlY1JK+vz5M9XV1ZHX610+CBsaGvDgwQM8evQIiqIgHA4jFotB07QVKZBNVc45XC7X8r3g/PnztWVlZbh69SoymQzm5+fBGIPD4VgwD+Rq2cKUTqfhcDhya0Zr1649efv2bTx8+BButxuWZdkRnNN3/M45EUFKiampKRiGkRvAxo0bE+FwGL29vUin09B13Y7mfOUnInz79g3v379HOBzODSAWiz1xu92jd+7cwbt372zZl0ql5ezNmzfIZDIoL89jjj179mwHAGpvbyfOORFR3lmQ3Xv69GlSFIXyJo/H41RcXExjY2MkpbRXPjY3N0cVFRW0Y8cOynsiSqVSUU3TcO3aNZimaU/HS63FAwsAjIyM4OnTp4hGo4m8Aaqqqp4UFBSMXrlyBclkEpZl2f1/8freKRGBMYbZ2Vl0d3dj27ZtGB8fb/5hoObyKSKRCLndbnR2dsLpdNrNZ3HKSSmh67r9M/Py5UscOnQIsVhs8Pr163+suI43NTWdKCkpIU3TssGU89q3bx9dunRpyfD/B4mqU48lcruHAAAAAElFTkSuQmCC)', }; -function getCursor(cursor) { - if (customCursors[cursor]) { - return `${customCursors[cursor]} ${EditorArea.HOTSPOT_32[cursor * 2]} ${ - EditorArea.HOTSPOT_32[cursor * 2 + 1] - }, default`; +class CursorManager { + constructor(JavaEditorArea) { + this.HOTSPOT_32 = JavaEditorArea.HOTSPOT_32; + this.cPointerCursor = JavaEditorArea.cPointerCursor; + this.cTextCursor = JavaEditorArea.cTextCursor; + this.customCursorMap = { + [JavaEditorArea.cChainCursor]: 'cChainCursor', + [JavaEditorArea.cDeleteCursor]: 'cDeleteCursor', + [JavaEditorArea.cHandCursor]: 'cHandCursor', + [JavaEditorArea.cHandPlusCursor]: 'cHandPlusCursor', + [JavaEditorArea.cFistCursor]: 'cFistCursor', + [JavaEditorArea.cLassoCursor]: 'cLassoCursor', + [JavaEditorArea.cLassoPlusCursor]: 'cLassoPlusCursor', + [JavaEditorArea.cSelectRectCursor]: 'cSelectRectCursor', + [JavaEditorArea.cSelectRectPlusCursor]: 'cSelectRectPlusCursor', + [JavaEditorArea.cZoomCursor]: 'cZoomCursor', + [JavaEditorArea.cInvisibleCursor]: 'cInvisibleCursor', + [JavaEditorArea.cPointedHandCursor]: 'cPointedHandCursor', + }; } - switch (cursor) { - case EditorArea.cPointerCursor: - return 'default'; - case EditorArea.cTextCursor: - return 'text'; - default: - throw new Error(`Unknown cursor: ${cursor}`); + + getCursor(cursor) { + if (this.customCursorMap[cursor]) { + return `${customCursors[this.customCursorMap[cursor]]} ${this.HOTSPOT_32[cursor * 2]} ${ + this.HOTSPOT_32[cursor * 2 + 1] + }, default`; + } + switch (cursor) { + case this.cPointerCursor: + return 'default'; + case this.cTextCursor: + return 'text'; + default: + throw new Error(`Unknown cursor: ${cursor}`); + } } } + +module.exports = CursorManager; diff --git a/examples/generic-editor/CanvasDrawContext.js b/lib/canvas_editor/draw_context.js similarity index 96% rename from examples/generic-editor/CanvasDrawContext.js rename to lib/canvas_editor/draw_context.js index 7d3fa143..fd74dcad 100644 --- a/examples/generic-editor/CanvasDrawContext.js +++ b/lib/canvas_editor/draw_context.js @@ -1,6 +1,8 @@ -import { toHex } from './utils.js'; +'use strict'; -export class CanvasDrawContext { +const { toHex } = require('./utils'); + +class DrawContext { /** * * @param {CanvasRenderingContext2D} ctx @@ -140,3 +142,5 @@ export class CanvasDrawContext { this.ctx.drawImage(fullScaleCanvas, sx, sy, sw, sh, dx, dy, dw, dh); } } + +module.exports = DrawContext; diff --git a/lib/canvas_editor/editor_area.js b/lib/canvas_editor/editor_area.js new file mode 100644 index 00000000..c91112bc --- /dev/null +++ b/lib/canvas_editor/editor_area.js @@ -0,0 +1,58 @@ +'use strict'; + +const ClipboardHandler = require('./clipboard_handler'); +const CursorManager = require('./cursor_manager'); +const DrawContext = require('./draw_context'); +const EditorDialog = require('./editor_dialog'); +const EditorImage = require('./editor_image'); +const { decodeBase64 } = require('./utils'); + +class EditorArea { + constructor(canvasElement, onChange, JavaEditorArea) { + this.canvasElement = canvasElement; + this.changeListener = onChange; + this.cursorManager = new CursorManager(JavaEditorArea); + } + // JSEditorArea methods + getBackgroundRGB() { + return 0xffffff; + } + getCanvasWidth() { + return this.canvasElement.width; + } + getCanvasHeight() { + return this.canvasElement.height; + } + getDrawContext() { + return new DrawContext(this.canvasElement.getContext('2d')); + } + onChange(what, isUserEvent) { + this.changeListener?.({ what, isUserEvent }); + } + getClipboardHandler() { + return new ClipboardHandler(); + } + // JSUIHelper methods + grabFocus() { + this.canvasElement.focus(); + } + setCursor(cursor) { + this.canvasElement.style.cursor = this.cursorManager.getCursor(cursor); + } + showHelpDialog(/* url, title */) { + // TODO: implement help dialog? + // console.log({ url, title }); + } + createImageFromBase64(width, height, base64) { + base64 = base64.replaceAll('%', 'A'.repeat(20)); + const decoded = decodeBase64(base64); + const typedArray = new Uint8ClampedArray(decoded); + const imageData = new ImageData(typedArray, width, height); + return new EditorImage(imageData); + } + createDialog(title) { + return new EditorDialog(title); + } +} + +module.exports = EditorArea; diff --git a/examples/generic-editor/CanvasEditorDialog.js b/lib/canvas_editor/editor_dialog.js similarity index 94% rename from examples/generic-editor/CanvasEditorDialog.js rename to lib/canvas_editor/editor_dialog.js index 21a8423f..d3ace6b9 100644 --- a/examples/generic-editor/CanvasEditorDialog.js +++ b/lib/canvas_editor/editor_dialog.js @@ -1,4 +1,6 @@ -export class CanvasEditorDialog { +'use strict'; + +class EditorDialog { /** * * @param {string} title @@ -39,7 +41,8 @@ export class CanvasEditorDialog { } showMessage(message) { - alert(message); + // eslint-disable-next-line no-alert + window.alert(message); } showDialog() { @@ -60,8 +63,8 @@ export class CanvasEditorDialog { div.style.gridColumn = `${x + 1} / ${x2 + 2}`; div.style.gridRow = `${y + 1} / ${y2 + 2}`; } - div.appendChild(component.getElement()); - grid.appendChild(div); + div.append(component.getElement()); + grid.append(div); } const buttons = document.createElement('div'); buttons.style.display = 'flex'; @@ -72,13 +75,13 @@ export class CanvasEditorDialog { okButton.addEventListener('click', () => { this.consumer.fireOk(); }); - buttons.appendChild(okButton); + buttons.append(okButton); const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; cancelButton.addEventListener('click', () => { this.consumer.fireCancel(); }); - buttons.appendChild(cancelButton); + buttons.append(cancelButton); dialog.append(buttons); dialog.showModal(); dialog.addEventListener('cancel', () => { @@ -232,3 +235,5 @@ function generateLayout(layout) { }) .join(' '); } + +module.exports = EditorDialog; diff --git a/examples/generic-editor/CanvasEditorImage.js b/lib/canvas_editor/editor_image.js similarity index 90% rename from examples/generic-editor/CanvasEditorImage.js rename to lib/canvas_editor/editor_image.js index 75ee45ee..2b11ca13 100644 --- a/examples/generic-editor/CanvasEditorImage.js +++ b/lib/canvas_editor/editor_image.js @@ -1,4 +1,6 @@ -export class CanvasEditorImage { +'use strict'; + +class EditorImage { /** * * @param {ImageData} imageData @@ -31,3 +33,5 @@ export class CanvasEditorImage { this.dataView.setInt32((y * this.imageData.width + x) * 4, rgb, false); } } + +module.exports = EditorImage; diff --git a/examples/generic-editor/events.js b/lib/canvas_editor/events.js similarity index 54% rename from examples/generic-editor/events.js rename to lib/canvas_editor/events.js index 3f0c96db..78c65104 100644 --- a/examples/generic-editor/events.js +++ b/lib/canvas_editor/events.js @@ -1,6 +1,6 @@ -const { EditorArea } = OCL; +'use strict'; -export function addMouseListeners(canvasElement, drawArea) { +function addMouseListeners(canvasElement, drawArea, JavaEditorArea) { let isMouseDown = false; function fireMouseEvent(what, ev, clickCount = 0) { @@ -23,36 +23,37 @@ export function addMouseListeners(canvasElement, drawArea) { canvasElement.addEventListener('mousedown', (ev) => { isMouseDown = true; - fireMouseEvent(EditorArea.MOUSE_EVENT_PRESSED, ev); + fireMouseEvent(JavaEditorArea.MOUSE_EVENT_PRESSED, ev); }); - canvasElement.addEventListener('mouseup', (ev) => { + // Listen on document to capture mouse release outside the canvas. + document.addEventListener('mouseup', (ev) => { isMouseDown = false; - fireMouseEvent(EditorArea.MOUSE_EVENT_RELEASED, ev); + fireMouseEvent(JavaEditorArea.MOUSE_EVENT_RELEASED, ev); }); canvasElement.addEventListener('click', (ev) => { - fireMouseEvent(EditorArea.MOUSE_EVENT_CLICKED, ev, ev.detail); + fireMouseEvent(JavaEditorArea.MOUSE_EVENT_CLICKED, ev, ev.detail); }); canvasElement.addEventListener('mouseenter', (ev) => { - fireMouseEvent(EditorArea.MOUSE_EVENT_ENTERED, ev); + fireMouseEvent(JavaEditorArea.MOUSE_EVENT_ENTERED, ev); }); canvasElement.addEventListener('mouseleave', (ev) => { - fireMouseEvent(EditorArea.MOUSE_EVENT_EXITED, ev); + fireMouseEvent(JavaEditorArea.MOUSE_EVENT_EXITED, ev); }); canvasElement.addEventListener('mousemove', (ev) => { if (isMouseDown) { - fireMouseEvent(EditorArea.MOUSE_EVENT_DRAGGED, ev); + fireMouseEvent(JavaEditorArea.MOUSE_EVENT_DRAGGED, ev); } else { - fireMouseEvent(EditorArea.MOUSE_EVENT_MOVED, ev); + fireMouseEvent(JavaEditorArea.MOUSE_EVENT_MOVED, ev); } }); } -export function addKeyboardListeners(canvasElement, editorArea) { +function addKeyboardListeners(canvasElement, editorArea, JavaEditorArea) { const isMac = typeof navigator !== 'undefined' && navigator.platform === 'MacIntel'; function fireKeyEvent(what, ev) { - const key = getKeyFromEvent(ev); + const key = getKeyFromEvent(ev, JavaEditorArea); if (key === null) return; ev.stopPropagation(); ev.preventDefault(); @@ -67,38 +68,44 @@ export function addKeyboardListeners(canvasElement, editorArea) { } canvasElement.addEventListener('keydown', (ev) => { - fireKeyEvent(EditorArea.KEY_EVENT_PRESSED, ev); + fireKeyEvent(JavaEditorArea.KEY_EVENT_PRESSED, ev); }); canvasElement.addEventListener('keyup', (ev) => { - fireKeyEvent(EditorArea.KEY_EVENT_RELEASED, ev); + fireKeyEvent(JavaEditorArea.KEY_EVENT_RELEASED, ev); }); } /** * * @param {KeyboardEvent} ev + * @param JavaEditorArea */ -function getKeyFromEvent(ev) { +function getKeyFromEvent(ev, JavaEditorArea) { switch (ev.key) { case 'Control': - return EditorArea.KEY_CTRL; + return JavaEditorArea.KEY_CTRL; case 'Alt': - return EditorArea.KEY_ALT; + return JavaEditorArea.KEY_ALT; case 'Shift': - return EditorArea.KEY_SHIFT; + return JavaEditorArea.KEY_SHIFT; case 'Delete': - return EditorArea.KEY_DELETE; + return JavaEditorArea.KEY_DELETE; case 'Backspace': - return EditorArea.KEY_BACKSPACE; + return JavaEditorArea.KEY_BACKSPACE; case 'F1': - return EditorArea.KEY_HELP; + return JavaEditorArea.KEY_HELP; case 'Escape': - return EditorArea.KEY_ESCAPE; + return JavaEditorArea.KEY_ESCAPE; default: if (ev.key.length === 1) { - return ev.key.charCodeAt(0); + return ev.key.codePointAt(0); } else { return null; } } } + +module.exports = { + addMouseListeners, + addKeyboardListeners, +}; diff --git a/lib/canvas_editor/index.js b/lib/canvas_editor/index.js index d1737a16..ce9b4a2b 100644 --- a/lib/canvas_editor/index.js +++ b/lib/canvas_editor/index.js @@ -1,10 +1,70 @@ 'use strict'; -function createCanvasEditor(EditorArea, EditorToolbar) { +const createEditor = require('./create_editor'); + +function createCanvasEditor(JavaEditorArea, JavaEditorToolbar) { return class CanvasEditor { + #editorArea; + // Can be useful for debugging. + // eslint-disable-next-line no-unused-private-class-members + #toolbar; + #onChange; + #changeEventMap; + + constructor(rootElement) { + const { editorArea, toolbar } = createEditor( + rootElement, + (event) => this.#handleChange(event), + JavaEditorArea, + JavaEditorToolbar, + ); + this.#editorArea = editorArea; + this.#toolbar = toolbar; + this.#onChange = null; + this.#changeEventMap = { + [JavaEditorArea.EDITOR_EVENT_MOLECULE_CHANGED]: 'molecule', + [JavaEditorArea.EDITOR_EVENT_SELECTION_CHANGED]: 'selection', + [JavaEditorArea.EDITOR_EVENT_HIGHLIGHT_ATOM_CHANGED]: 'highlight-atom', + [JavaEditorArea.EDITOR_EVENT_HIGHLIGHT_BOND_CHANGED]: 'highlight-bond', + }; + } + setMolecule(molecule) { - console.log('WITH WATCH'); - this.molecule = molecule; + this.#editorArea.setMolecule(molecule); + } + + getMolecule() { + return this.#editorArea.getMolecule(); + } + + setReaction(reaction) { + this.#editorArea.setReaction(reaction); + } + + getReaction() { + this.#editorArea.getReaction(); + } + + setOnChangeListener(onChange) { + this.#onChange = onChange; + } + + removeOnChangeListner() { + this.#onChange = null; + } + + /** + * @param {{ what: number; isUserEvent: boolean; }} event + */ + #handleChange(event) { + if (!this.#onChange) { + return; + } + const { what, isUserEvent } = event; + this.#onChange({ + type: this.#changeEventMap[what], + isUserEvent, + }); } }; } diff --git a/lib/canvas_editor/toolbar.js b/lib/canvas_editor/toolbar.js new file mode 100644 index 00000000..05401fb0 --- /dev/null +++ b/lib/canvas_editor/toolbar.js @@ -0,0 +1,29 @@ +'use strict'; + +const DrawContext = require('./draw_context'); +const { addMouseListeners } = require('./events'); + +class Toolbar { + constructor(canvasElement, editorArea, JavaEditorArea, JavaEditorToolbar) { + this.toolbar = new JavaEditorToolbar(editorArea, { + setDimensions(width, height) { + canvasElement.width = width; + canvasElement.height = height; + }, + getDrawContext() { + return new DrawContext(canvasElement.getContext('2d')); + }, + getBackgroundRGB() { + return 0xffffff; + }, + + getForegroundRGB() { + return 0x000000; + }, + }); + + addMouseListeners(canvasElement, this.toolbar, JavaEditorArea); + } +} + +module.exports = Toolbar; diff --git a/examples/generic-editor/utils.js b/lib/canvas_editor/utils.js similarity index 82% rename from examples/generic-editor/utils.js rename to lib/canvas_editor/utils.js index fd937be2..bb7c169d 100644 --- a/examples/generic-editor/utils.js +++ b/lib/canvas_editor/utils.js @@ -1,3 +1,7 @@ +'use strict'; + +/* eslint-disable unicorn/prefer-code-point */ + // https://github.com/niklasvh/base64-arraybuffer/blob/master/LICENSE const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; @@ -7,7 +11,7 @@ for (let i = 0; i < chars.length; i++) { lookup[chars.charCodeAt(i)] = i; } -export function decodeBase64(base64) { +function decodeBase64(base64) { let bufferLength = base64.length * 0.75; let len = base64.length; let i; @@ -17,9 +21,9 @@ export function decodeBase64(base64) { let encoded3; let encoded4; - if (base64[base64.length - 1] === '=') { + if (base64.at(-1) === '=') { bufferLength--; - if (base64[base64.length - 2] === '=') { + if (base64.at(-2) === '=') { bufferLength--; } } @@ -41,6 +45,11 @@ export function decodeBase64(base64) { return arraybuffer; } -export function toHex(v) { +function toHex(v) { return v.toString(16).padStart(2, '0'); } + +module.exports = { + decodeBase64, + toHex, +}; diff --git a/package.json b/package.json index 861b0bf5..71b1cf63 100644 --- a/package.json +++ b/package.json @@ -17,15 +17,16 @@ "export": "node scripts/build.js export", "build": "npm run build:min && npm run build-full-pretty", "build-core": "npm run build:min -- -m core", - "build-debug-full": "npm run build:pretty -- -m full --verbose", "build-debug-core": "npm run build:pretty -- -m core --verbose", + "build-debug-full": "npm run build:pretty -- -m full --verbose", "build-debug-minimal": "npm run build:pretty -- -m minimal --verbose", - "build-minimal": "npm run build:min -- -m minimal", - "build-full-pretty": "npm run build:pretty -- -m full", "build-esm": "npm run build-full-pretty && npm run build-esm-bundle-export", - "build-esm-bundle": "esbuild full.pretty.js --bundle --format=esm --outfile=distesm/full.pretty.js", + "build-esm-bundle": "esbuild full.pretty.js --bundle --sourcemap --format=esm --outfile=distesm/full.pretty.js", + "build-esm-bundle-export": "npm run build-esm-bundle && node scripts/build_esm_bundle_types.js", + "build-esm-bundle-types": "node scripts/esm_bundle_types.js", "build-esm-bundle-watch": "npm run build-esm-bundle -- --watch", - "build-esm-bundle-export": "npm run build-esm-bundle && echo \"export * from '../full.pretty';\" > distesm/full.pretty.d.ts", + "build-full-pretty": "npm run build:pretty -- -m full", + "build-minimal": "npm run build:min -- -m minimal", "eslint": "eslint .", "eslint-fix": "npm run eslint -- --fix", "prettier": "prettier --check .", diff --git a/scripts/build.js b/scripts/build.js index f47d1a2b..4e40e51e 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -231,28 +231,6 @@ async function build() { ); } await Promise.all(prom); - - log('Creating ESM-compatible entry points'); - for (const mod of modules) { - log(`Creating ESM-compatible entry point for module ${mod.name}${suffix}`); - const moduleInstance = require( - `../dist/openchemlib-${mod.name}${suffix}.js`, - ); - const moduleExports = Object.keys(moduleInstance).map( - (moduleExport) => `exports.${moduleExport} = OCL.${moduleExport};`, - ); - const facade = `'use strict'; - -const OCL = require('./dist/openchemlib-${mod.name}${suffix}.js'); - -exports.default = OCL; -${moduleExports.join('\n')} -`; - fs.writeFileSync( - path.join(__dirname, `../${mod.name}${suffix}.js`), - facade, - ); - } } function log(value) { diff --git a/scripts/build_esm_bundle_types.js b/scripts/build_esm_bundle_types.js new file mode 100644 index 00000000..e92de57f --- /dev/null +++ b/scripts/build_esm_bundle_types.js @@ -0,0 +1,9 @@ +'use strict'; + +const fs = require('node:fs'); +const path = require('node:path'); + +fs.writeFileSync( + path.join(__dirname, '../distesm/full.pretty.d.ts'), + "export * from '../full.pretty';\n", +); diff --git a/types.d.ts b/types.d.ts index 2bc7b268..dfd5f120 100644 --- a/types.d.ts +++ b/types.d.ts @@ -504,6 +504,7 @@ export declare class Molecule { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; toSmiles(): string; @@ -1452,7 +1453,7 @@ export declare class Molecule { * @param feature - one of cAtomQF... Because of long it could be an internal object * @param value - if true, the feature is set, otherwise it is removed */ - setAtomQueryFeature(atom: number, feature: any, value: boolean): void; + setAtomQueryFeature(atom: number, feature: number, value: boolean): void; /** * Sets an atom's radical state as singulet,dublet,triplet or none @@ -2767,6 +2768,7 @@ export declare class RingCollection { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; getAtomRingSize(atom: number): number; @@ -2824,6 +2826,7 @@ export declare class RingCollection { * console.log(OCL.CanonizerUtil.getIDCode(molecule, OCL.CanonizerUtil.NOSTEREO_TAUTOMER)); * ``` */ +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export declare class CanonizerUtil { static NORMAL: 0; static NOSTEREO: 1; @@ -2902,6 +2905,7 @@ export declare class Reaction { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; /** @@ -3085,6 +3089,7 @@ export declare class Reaction { getMergedCopy(): Reaction; } +// eslint-disable-next-line @typescript-eslint/no-extraneous-class,unicorn/no-static-only-class export declare class ReactionEncoder { static encode(reaction: Reaction): string; static decode(reaction: string): Reaction; @@ -3108,6 +3113,7 @@ export declare class SDFileParser { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; /** @@ -3164,6 +3170,7 @@ export declare class SSSearcher { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; /** @@ -3204,6 +3211,7 @@ export declare class SSSearcherWithIndex { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; /** @@ -3315,6 +3323,7 @@ export declare class DruglikenessPredictor { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; /** @@ -3359,6 +3368,7 @@ export declare class ToxicityPredictor { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; /** @@ -3406,6 +3416,7 @@ export declare class ConformerGenerator { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; /** @@ -3512,6 +3523,7 @@ export declare class ForceFieldMMFF94 { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; /** @@ -3583,6 +3595,7 @@ export declare class StructureEditor { /** * Returns the OCL object. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any getOCL(): any; getMolecule(): Molecule; @@ -3663,14 +3676,14 @@ export declare class StructureEditor { hasFocus(): boolean; } -export type AtomHighlightCallback = (atom: number, selected: boolean) => any; +export type AtomHighlightCallback = (atom: number, selected: boolean) => void; -export type BondHighlightCallback = (bond: number, selected: boolean) => any; +export type BondHighlightCallback = (bond: number, selected: boolean) => void; export type ChangeListenerCallback = ( idcode: string, molecule: Molecule, -) => any; +) => void; export declare namespace SVGRenderer { export function renderMolecule( @@ -3680,19 +3693,32 @@ export declare namespace SVGRenderer { ): string; } -export interface CanvasEditorOptions { - /** - * The width of the canvas. - */ - width: number; - /** - * The height of the canvas. - */ - height: number; +export type OnChangeEventType = + | 'molecule' + | 'selection' + | 'highlight-atom' + | 'highlight-bond'; + +export interface OnChangeEvent { + type: OnChangeEventType; + isUserEvent: boolean; } +export type OnChangeListenerCallback = (event: OnChangeEvent) => void; + export declare class CanvasEditor { - constructor(element: HTMLElement, options?: CanvasEditorOptions); + /** + * Create a new canvas-based editor. + * @param element - The DOM element in which to create the editor. + */ + constructor(element: HTMLElement); setMolecule(molecule: Molecule): void; + getMolecule(): Molecule; + + setReaction(reaction: Reaction): void; + getReaction(): Reaction; + + setOnChangeListener(callback: OnChangeListenerCallback): void; + removeOnChangeListener(): void; } From 416a4eeb938677770ad0c0f93b55ec8784af251c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Mon, 22 Jul 2024 16:11:09 +0200 Subject: [PATCH 10/64] chore: remove unused imports --- src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java index 1bb037a2..a9708865 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java @@ -1,9 +1,7 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.editor.GenericEditorArea; import com.actelion.research.gui.editor.GenericEditorToolbar; import com.actelion.research.gui.generic.*; -import com.actelion.research.gwt.minimal.JSMolecule; import com.google.gwt.core.client.JavaScriptObject; import jsinterop.annotations.*; From a470dcef233fbc1026a8ceb8c7fe9dc281ee2db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Mon, 22 Jul 2024 16:25:40 +0200 Subject: [PATCH 11/64] chore: migrate poc page to ts --- ...ricEditor.html => generic_editor_poc.html} | 6 +-- ...eneric-editor.js => generic_editor_poc.ts} | 46 +++++++++++-------- examples/index.html | 1 + full.d.ts | 3 ++ 4 files changed, 34 insertions(+), 22 deletions(-) rename examples/{GenericEditor.html => generic_editor_poc.html} (86%) rename examples/{generic-editor.js => generic_editor_poc.ts} (85%) diff --git a/examples/GenericEditor.html b/examples/generic_editor_poc.html similarity index 86% rename from examples/GenericEditor.html rename to examples/generic_editor_poc.html index b2811028..40b1676b 100644 --- a/examples/GenericEditor.html +++ b/examples/generic_editor_poc.html @@ -1,9 +1,9 @@ - + - - + Generic editor - PoC +
{ + if (isUserEvent && type === 'molecule') { + changeCountDiv.innerText = String(++changeCount); + const idcodeAndCoords = editor.getMolecule().getIDCodeAndCoordinates(); + idcodeDiv.innerText = `${idcodeAndCoords.idCode} ${idcodeAndCoords.coordinates}`; + molfileDiv.innerText = editor.getMolecule().toMolfileV3(); + } }); -document.getElementById('loadMolecule').onclick = () => { +const loadMolecule = document.getElementById( + 'loadMolecule', +) as HTMLButtonElement; +loadMolecule.onclick = () => { // const molecule = Molecule.fromMolfile(molfile); const molecule = Molecule.fromSmiles('c1ccccc1CO'); editor.setMolecule(molecule); }; -document.getElementById('loadFragment').onclick = () => { +const loadFragment = document.getElementById( + 'loadFragment', +) as HTMLButtonElement; +loadFragment.onclick = () => { // const molecule = Molecule.fromMolfile(molfile); const molecule = Molecule.fromSmiles('CCC'); molecule.setFragment(true); editor.setMolecule(molecule); }; -document.getElementById('loadReaction').onclick = () => { +const loadReaction = document.getElementById( + 'loadReaction', +) as HTMLButtonElement; +loadReaction.onclick = () => { const reaction = Reaction.fromSmiles('c1ccccc1..CC>CO>c1ccccc1..CCC..CC'); // const reaction = Reaction.fromRxn(rxn); // reaction.addCatalyst(Molecule.fromSmiles('CO')); diff --git a/examples/index.html b/examples/index.html index 866aa354..42332487 100644 --- a/examples/index.html +++ b/examples/index.html @@ -9,6 +9,7 @@

OpenChemLib JS examples

diff --git a/full.d.ts b/full.d.ts index ab51543b..6b3669e8 100644 --- a/full.d.ts +++ b/full.d.ts @@ -6,5 +6,8 @@ export { AtomHighlightCallback, BondHighlightCallback, ChangeListenerCallback, + OnChangeEventType, + OnChangeEvent, + OnChangeListenerCallback, CanvasEditor, } from './types'; From 2fb50129ade58889da44b0faee3c4349029c2c19 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:56:36 +0200 Subject: [PATCH 12/64] fix: grabFocus from java preventScroll now Refs: https://github.com/cheminfo/openchemlib-js/issues/136 --- lib/canvas_editor/editor_area.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/canvas_editor/editor_area.js b/lib/canvas_editor/editor_area.js index c91112bc..31bbf672 100644 --- a/lib/canvas_editor/editor_area.js +++ b/lib/canvas_editor/editor_area.js @@ -34,7 +34,7 @@ class EditorArea { } // JSUIHelper methods grabFocus() { - this.canvasElement.focus(); + this.canvasElement.focus({preventScroll: true}); } setCursor(cursor) { this.canvasElement.style.cursor = this.cursorManager.getCursor(cursor); From cf7709498e357b4e29d180a1b785bf3b16ed7b6d Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:01:57 +0200 Subject: [PATCH 13/64] fix(Dialog): button ok on the right, cancel on the left Refs: https://github.com/cheminfo/openchemlib-js/issues/136 --- lib/canvas_editor/editor_dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/canvas_editor/editor_dialog.js b/lib/canvas_editor/editor_dialog.js index d3ace6b9..5058a24d 100644 --- a/lib/canvas_editor/editor_dialog.js +++ b/lib/canvas_editor/editor_dialog.js @@ -68,7 +68,7 @@ class EditorDialog { } const buttons = document.createElement('div'); buttons.style.display = 'flex'; - buttons.style.justifyContent = 'flex-end'; + buttons.style.flexDirection = 'row-reverse'; buttons.style.gap = '15px'; const okButton = document.createElement('button'); okButton.textContent = 'OK'; From e0ed3407757ce2c158051de627bd9723644bb8ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Tue, 23 Jul 2024 13:05:37 +0200 Subject: [PATCH 14/64] docs: add jsdoc to all CanvasEditor methods --- types.d.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/types.d.ts b/types.d.ts index dfd5f120..2e503b07 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3713,12 +3713,38 @@ export declare class CanvasEditor { */ constructor(element: HTMLElement); + /** + * Set the molecule to be edited. + * Actions in the editor will mutate the molecule object directly. + * @param molecule + */ setMolecule(molecule: Molecule): void; + + /** + * Get the molecule being edited. + */ getMolecule(): Molecule; + /** + * Set the reaction to be edited. + * Actions in the editor will mutate the reaction object directly. + * @param reaction + */ setReaction(reaction: Reaction): void; + + /** + * Get the reaction being edited. + */ getReaction(): Reaction; + /** + * Set a callback to be notified when the editor state changes. + * @param callback + */ setOnChangeListener(callback: OnChangeListenerCallback): void; + + /** + * Remove the change listener callback. + */ removeOnChangeListener(): void; } From 7728439d5604bb080e06f8b4347fb057249e1f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Tue, 23 Jul 2024 13:05:56 +0200 Subject: [PATCH 15/64] chore: use correct hashbang in get-* scripts --- scripts/get-gwt.sh | 2 +- scripts/get-java.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/get-gwt.sh b/scripts/get-gwt.sh index 5c40c86c..6ca9d129 100755 --- a/scripts/get-gwt.sh +++ b/scripts/get-gwt.sh @@ -1,4 +1,4 @@ -#/bin/bash +#!/bin/bash wget -q https://github.com/gwtproject/gwt/releases/download/2.11.0/gwt-2.11.0.zip -O gwt.zip unzip -q gwt.zip diff --git a/scripts/get-java.sh b/scripts/get-java.sh index 0872bf0d..1cd6926f 100755 --- a/scripts/get-java.sh +++ b/scripts/get-java.sh @@ -1,4 +1,4 @@ -#/bin/bash +#!/bin/bash wget -q https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.4%2B7/OpenJDK21U-jdk_x64_linux_hotspot_21.0.4_7.tar.gz -O jdk.tar.gz tar xf jdk.tar.gz From 579a66eaf01585c63653d442795e69053cad7de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Tue, 23 Jul 2024 15:00:39 +0200 Subject: [PATCH 16/64] refactor: extract UI helper to separate class and use 16px cursor --- full.js | 10 ++-- full.pretty.js | 10 ++-- lib/canvas_editor/create_editor.js | 7 ++- lib/canvas_editor/cursor_manager_16.js | 38 ++++++++++++ ...cursor_manager.js => cursor_manager_32.js} | 3 + lib/canvas_editor/editor_area.js | 29 +--------- lib/canvas_editor/editor_image.js | 9 +++ lib/canvas_editor/index.js | 10 +++- lib/canvas_editor/ui_helper.js | 50 ++++++++++++++++ .../gwt/gui/generic/JSEditorArea.java | 13 +++-- .../gwt/gui/generic/JSEditorToolbar.java | 9 +-- .../research/gwt/gui/generic/JSImage.java | 30 +++++----- .../research/gwt/gui/generic/JSUIHelper.java | 58 ++++++++++++------- 13 files changed, 190 insertions(+), 86 deletions(-) create mode 100644 lib/canvas_editor/cursor_manager_16.js rename lib/canvas_editor/{cursor_manager.js => cursor_manager_32.js} (99%) create mode 100644 lib/canvas_editor/ui_helper.js diff --git a/full.js b/full.js index 18884b9d..6eca3fbe 100644 --- a/full.js +++ b/full.js @@ -3,12 +3,14 @@ const OCL = require('./dist/openchemlib-full.js'); OCL.CanvasEditor = require('./lib/canvas_editor')( - OCL.EditorArea, - OCL.EditorToolbar, + OCL.GenericEditorArea, + OCL.GenericEditorToolbar, + OCL.GenericUIHelper, ); -delete OCL.EditorArea; -delete OCL.EditorToolbar; +delete OCL.GenericEditorArea; +delete OCL.GenericEditorToolbar; +delete OCL.GenericUIHelper; exports.default = OCL; exports.CanonizerUtil = OCL.CanonizerUtil; diff --git a/full.pretty.js b/full.pretty.js index 00a41fc0..785e902d 100644 --- a/full.pretty.js +++ b/full.pretty.js @@ -3,12 +3,14 @@ const OCL = require('./dist/openchemlib-full.pretty.js'); OCL.CanvasEditor = require('./lib/canvas_editor')( - OCL.EditorArea, - OCL.EditorToolbar, + OCL.GenericEditorArea, + OCL.GenericEditorToolbar, + OCL.GenericUIHelper, ); -delete OCL.EditorArea; -delete OCL.EditorToolbar; +delete OCL.GenericEditorArea; +delete OCL.GenericEditorToolbar; +delete OCL.GenericUIHelper; exports.default = OCL; exports.CanonizerUtil = OCL.CanonizerUtil; diff --git a/lib/canvas_editor/create_editor.js b/lib/canvas_editor/create_editor.js index 05a6a831..c5e9bbc8 100644 --- a/lib/canvas_editor/create_editor.js +++ b/lib/canvas_editor/create_editor.js @@ -3,12 +3,14 @@ const EditorArea = require('./editor_area'); const { addMouseListeners, addKeyboardListeners } = require('./events'); const Toolbar = require('./toolbar'); +const UIHelper = require('./ui_helper'); function createEditor( rootElement, onChange, JavaEditorArea, JavaEditorToolbar, + JavaUIHelper, ) { const childElement = document.createElement('div'); childElement.setAttribute( @@ -33,8 +35,11 @@ function createEditor( editorCanvas.width = containerSize.width; editorCanvas.height = containerSize.height; + const uiHelper = new JavaUIHelper(new UIHelper(editorCanvas, JavaEditorArea)); + const editorArea = new JavaEditorArea( - new EditorArea(editorCanvas, onChange, JavaEditorArea), + new EditorArea(editorCanvas, onChange), + uiHelper, ); editorArea.draw(); diff --git a/lib/canvas_editor/cursor_manager_16.js b/lib/canvas_editor/cursor_manager_16.js new file mode 100644 index 00000000..b95459cb --- /dev/null +++ b/lib/canvas_editor/cursor_manager_16.js @@ -0,0 +1,38 @@ +'use strict'; + +const computedCursors = Object.create(null); + +class CursorManager { + constructor(JavaEditorArea, javaUiHelper) { + this.IMAGE_DATA_16 = JavaEditorArea.IMAGE_DATA_16; + this.HOTSPOT_16 = JavaEditorArea.HOTSPOT_16; + this.cPointerCursor = JavaEditorArea.cPointerCursor; + this.cTextCursor = JavaEditorArea.cTextCursor; + this.javaUiHelper = javaUiHelper; + } + + getCursor(cursor) { + if (computedCursors[cursor]) { + return computedCursors[cursor]; + } + if (this.IMAGE_DATA_16[cursor]) { + return this.buildCursor(cursor); + } + switch (cursor) { + case this.cPointerCursor: + return 'default'; + case this.cTextCursor: + return 'text'; + default: + throw new Error(`Unknown cursor: ${cursor}`); + } + } + + buildCursor(cursor) { + const cursorImage = this.javaUiHelper.build16x16CursorImage(cursor); + const dataURL = cursorImage.toDataURL(); + return `url(${dataURL}) ${this.HOTSPOT_16[cursor * 2]} ${this.HOTSPOT_16[cursor * 2 + 1]}, default`; + } +} + +module.exports = CursorManager; diff --git a/lib/canvas_editor/cursor_manager.js b/lib/canvas_editor/cursor_manager_32.js similarity index 99% rename from lib/canvas_editor/cursor_manager.js rename to lib/canvas_editor/cursor_manager_32.js index 7f0a07cc..66f6019f 100644 --- a/lib/canvas_editor/cursor_manager.js +++ b/lib/canvas_editor/cursor_manager_32.js @@ -1,5 +1,8 @@ 'use strict'; +// This code is unused for now and is kept for reference in case we want to +// support high definition cursors in the future. + const customCursors = { cChainCursor: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACOElEQVRIx+3VzUsUcRzH8c/MOLszdlFwY8H6E6JjIARFBlGt7KEsbelgj65ZayJYf0GtERSVFnQOOnTp1j9gRSIlBh7sEhn5uLpbu+3Tu8Os7qqrYpc6+P0df5/vi/k9zIy0U/++DMmQZInNIhsUkmokU0XVbAgYm8CoKHPLB9yiqgKnlXEyDRljs/a0/ashbZ2RJEuGHPlXRkT4f9x+NZwOYziqNoLCmu55OZw5h1kFwJnpvzonHk1kwlQFsGZjIk58hOZ1AO5MX+esEOLxeLYKgT3fLYTF0HsOrgIiwpnrjU6L3QgLmycfcy2rF4K90OXx90YKpzAqgIjwJ25Gv4sAItUvLMTgaJkICmsx6rXHR/NtWAcqNxFnqadzStQhikfxL13wokMf8iFKa091CmFy51O+Heu8fJKU8nJuIhb9JnYh8scwJHypbmEgnr4rhhB26ooQdTwfyZ3Fkhz5JEQgeXFy4MuDrq+elG3BbJQjCV/ymhA2z4YLJ5OXhahlcDR3AkNyS0D9xP2OxXoCCBtRCGFely3HI5zEDY998VYIPw/HsmHMZrkrwOFbU15EiEILkvyVu+4uxMrzA59/t2JeklsBhJrS5UAT5ppTd4WT6PVm747l27FUal8GjsRK18ZC0GivaXcVEf75PhEf/92GWW5fBgJjQ+FMLUGCJDswnXWAq4hwfh7PHsJy5a4BDKQ9c62T+/25fa/NN7Ppvdt4nVHRS+Alcbf5LSgB3nX+u2Lnp/Bf1B//ZTKHt1UOnwAAAABJRU5ErkJggg==)', diff --git a/lib/canvas_editor/editor_area.js b/lib/canvas_editor/editor_area.js index 31bbf672..e309fa98 100644 --- a/lib/canvas_editor/editor_area.js +++ b/lib/canvas_editor/editor_area.js @@ -1,19 +1,13 @@ 'use strict'; const ClipboardHandler = require('./clipboard_handler'); -const CursorManager = require('./cursor_manager'); const DrawContext = require('./draw_context'); -const EditorDialog = require('./editor_dialog'); -const EditorImage = require('./editor_image'); -const { decodeBase64 } = require('./utils'); class EditorArea { - constructor(canvasElement, onChange, JavaEditorArea) { + constructor(canvasElement, onChange) { this.canvasElement = canvasElement; this.changeListener = onChange; - this.cursorManager = new CursorManager(JavaEditorArea); } - // JSEditorArea methods getBackgroundRGB() { return 0xffffff; } @@ -32,27 +26,6 @@ class EditorArea { getClipboardHandler() { return new ClipboardHandler(); } - // JSUIHelper methods - grabFocus() { - this.canvasElement.focus({preventScroll: true}); - } - setCursor(cursor) { - this.canvasElement.style.cursor = this.cursorManager.getCursor(cursor); - } - showHelpDialog(/* url, title */) { - // TODO: implement help dialog? - // console.log({ url, title }); - } - createImageFromBase64(width, height, base64) { - base64 = base64.replaceAll('%', 'A'.repeat(20)); - const decoded = decodeBase64(base64); - const typedArray = new Uint8ClampedArray(decoded); - const imageData = new ImageData(typedArray, width, height); - return new EditorImage(imageData); - } - createDialog(title) { - return new EditorDialog(title); - } } module.exports = EditorArea; diff --git a/lib/canvas_editor/editor_image.js b/lib/canvas_editor/editor_image.js index 2b11ca13..bab92c39 100644 --- a/lib/canvas_editor/editor_image.js +++ b/lib/canvas_editor/editor_image.js @@ -32,6 +32,15 @@ class EditorImage { const rgb = (argb << 8) | alpha; this.dataView.setInt32((y * this.imageData.width + x) * 4, rgb, false); } + + toDataURL() { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.width = this.imageData.width; + canvas.height = this.imageData.height; + ctx.putImageData(this.imageData, 0, 0); + return canvas.toDataURL('image/png'); + } } module.exports = EditorImage; diff --git a/lib/canvas_editor/index.js b/lib/canvas_editor/index.js index ce9b4a2b..2a2278ae 100644 --- a/lib/canvas_editor/index.js +++ b/lib/canvas_editor/index.js @@ -2,24 +2,28 @@ const createEditor = require('./create_editor'); -function createCanvasEditor(JavaEditorArea, JavaEditorToolbar) { +function createCanvasEditor(JavaEditorArea, JavaEditorToolbar, JavaUIHelper) { return class CanvasEditor { #editorArea; // Can be useful for debugging. - // eslint-disable-next-line no-unused-private-class-members + /* eslint-disable no-unused-private-class-members */ #toolbar; + #uiHelper; + /* eslint-enable no-unused-private-class-members */ #onChange; #changeEventMap; constructor(rootElement) { - const { editorArea, toolbar } = createEditor( + const { editorArea, toolbar, uiHelper } = createEditor( rootElement, (event) => this.#handleChange(event), JavaEditorArea, JavaEditorToolbar, + JavaUIHelper, ); this.#editorArea = editorArea; this.#toolbar = toolbar; + this.#uiHelper = uiHelper; this.#onChange = null; this.#changeEventMap = { [JavaEditorArea.EDITOR_EVENT_MOLECULE_CHANGED]: 'molecule', diff --git a/lib/canvas_editor/ui_helper.js b/lib/canvas_editor/ui_helper.js new file mode 100644 index 00000000..7ee24666 --- /dev/null +++ b/lib/canvas_editor/ui_helper.js @@ -0,0 +1,50 @@ +'use strict'; + +const CursorManager = require('./cursor_manager_16'); +const EditorDialog = require('./editor_dialog'); +const EditorImage = require('./editor_image'); +const { decodeBase64 } = require('./utils'); + +class UIHelper { + constructor(canvasElement, JavaEditorArea) { + this.canvasElement = canvasElement; + this.JavaEditorArea = JavaEditorArea; + } + + register(javaUiHelper) { + this.javaUiHelper = javaUiHelper; + this.cursorManager = new CursorManager(this.JavaEditorArea, javaUiHelper); + } + + grabFocus() { + this.canvasElement.focus({ preventScroll: true }); + } + + setCursor(cursor) { + this.canvasElement.style.cursor = this.cursorManager.getCursor(cursor); + } + + showHelpDialog(/* url, title */) { + // TODO: implement help dialog? + // console.log({ url, title }); + } + + createImage(width, height) { + const imageData = new ImageData(width, height); + return new EditorImage(imageData); + } + + createImageFromBase64(width, height, base64) { + base64 = base64.replaceAll('%', 'A'.repeat(20)); + const decoded = decodeBase64(base64); + const typedArray = new Uint8ClampedArray(decoded); + const imageData = new ImageData(typedArray, width, height); + return new EditorImage(imageData); + } + + createDialog(title) { + return new EditorDialog(title); + } +} + +module.exports = UIHelper; diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java index 01011fbe..f7c988ee 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java @@ -7,12 +7,11 @@ import com.actelion.research.gwt.minimal.JSMolecule; import com.actelion.research.gwt.minimal.JSReaction; import com.google.gwt.core.client.JavaScriptObject; - import info.clearthought.layout.TableLayout; +import jsinterop.annotations.JsIgnore; +import jsinterop.annotations.JsType; -import jsinterop.annotations.*; - -@JsType(name = "EditorArea") +@JsType(name = "GenericEditorArea") public class JSEditorArea implements GenericCanvas { private GenericEditorArea mDrawArea; private JavaScriptObject mOptions; @@ -57,13 +56,15 @@ public class JSEditorArea implements GenericCanvas { public static final int cPointerCursor = GenericCursorHelper.cPointerCursor; public static final int cTextCursor = GenericCursorHelper.cTextCursor; public static final int cPointedHandCursor = GenericCursorHelper.cPointedHandCursor; + public static final int[][] IMAGE_DATA_16 = GenericCursorHelper.IMAGE_DATA_16; + public static final int[] HOTSPOT_16 = GenericCursorHelper.HOTSPOT_16; public static final int[] HOTSPOT_32 = GenericCursorHelper.HOTSPOT_32; public static final int TableLayoutPreferred = (int)TableLayout.PREFERRED; public static final int TableLayoutFill = (int)TableLayout.FILL; - public JSEditorArea(JavaScriptObject options) { - mDrawArea = new GenericEditorArea(new StereoMolecule(), 0, new JSUIHelper(options), this); + public JSEditorArea(JavaScriptObject options, JSUIHelper uiHelper) { + mDrawArea = new GenericEditorArea(new StereoMolecule(), 0, uiHelper, this); mDrawArea.addDrawAreaListener(new GenericEventListener() { @Override public void eventHappened(EditorEvent e) { diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java index a9708865..0e62e553 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java @@ -1,12 +1,13 @@ package com.actelion.research.gwt.gui.generic; import com.actelion.research.gui.editor.GenericEditorToolbar; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericCanvas; +import com.actelion.research.gui.generic.GenericMouseEvent; import com.google.gwt.core.client.JavaScriptObject; +import jsinterop.annotations.JsIgnore; +import jsinterop.annotations.JsType; -import jsinterop.annotations.*; - -@JsType(name = "EditorToolbar") +@JsType(name = "GenericEditorToolbar") public class JSEditorToolbar implements GenericCanvas { private GenericEditorToolbar mGenericToolbar; private JavaScriptObject mOptions; diff --git a/src/com/actelion/research/gwt/gui/generic/JSImage.java b/src/com/actelion/research/gwt/gui/generic/JSImage.java index e54511db..6e5a0a25 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSImage.java +++ b/src/com/actelion/research/gwt/gui/generic/JSImage.java @@ -4,55 +4,55 @@ import com.google.gwt.core.client.JavaScriptObject; public class JSImage implements GenericImage { - private JavaScriptObject mJsImage; + private JavaScriptObject mJsImage; public JSImage(JavaScriptObject jsImage) { - mJsImage = jsImage; + mJsImage = jsImage; } - private JavaScriptObject getJsImage() { - return mJsImage; - } + public JavaScriptObject getJsImage() { + return mJsImage; + } @Override public native void scale(int width, int height) /*-{ - var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); - jsImage.scale(width, height); + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + jsImage.scale(width, height); }-*/; @Override public native Object get() /*-{ - var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); return jsImage; }-*/; @Override public native int getWidth() /*-{ - var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); - return jsImage.getWidth(); + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + return jsImage.getWidth(); }-*/; @Override public native int getHeight() /*-{ - var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); return jsImage.getHeight(); }-*/; @Override public native int getRGB(int x, int y) /*-{ - var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); - return jsImage.getRGB(x, y); + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + return jsImage.getRGB(x, y); }-*/; @Override public native void setRGB(int x, int y, int argb) /*-{ - var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); - jsImage.setRGB(x, y, argb); + var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()(); + jsImage.setRGB(x, y, argb); }-*/; } diff --git a/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java b/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java index de17fb2a..286915e8 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java +++ b/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java @@ -1,21 +1,31 @@ package com.actelion.research.gwt.gui.generic; -import java.io.File; - import com.actelion.research.gui.generic.*; import com.google.gwt.core.client.JavaScriptObject; +import jsinterop.annotations.JsIgnore; +import jsinterop.annotations.JsType; +import java.io.File; + +@JsType(name = "GenericUIHelper") public class JSUIHelper implements GenericUIHelper { - private JavaScriptObject mOptions; + private JavaScriptObject mJsObject; - public JSUIHelper(JavaScriptObject options) { - mOptions = options; + public JSUIHelper(JavaScriptObject jsObject) { + mJsObject = jsObject; + registerToJs(); } - private JavaScriptObject getOptions() { - return mOptions; + private JavaScriptObject getJsObject() { + return mJsObject; } + private native void registerToJs() + /*-{ + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getJsObject()(); + jsObject.register(this); + }-*/; + @Override public GenericDialog createDialog(String title, GenericEventListener consumer) { JSDialog dialog = new JSDialog(createNativeDialog(title)); @@ -25,11 +35,12 @@ public GenericDialog createDialog(String title, GenericEventListener @Override public native void grabFocus() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); - return options.grabFocus(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getJsObject()(); + return jsObject.grabFocus(); }-*/; @Override @@ -89,18 +100,23 @@ private void runNow(Runnable r) { @Override public native void setCursor(int cursor) /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); - return options.setCursor(cursor); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getJsObject()(); + return jsObject.setCursor(cursor); }-*/; @Override public native void showHelpDialog(String url, String title) /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()(); - return options.showHelpDialog(url, title); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getJsObject()(); + return jsObject.showHelpDialog(url, title); }-*/; @Override public void showMessage(String message) {} - + + public JavaScriptObject build16x16CursorImage(int cursor) { + JSImage cursorImage = createImage(16, 16); + GenericCursorHelper.build16x16CursorImage(cursorImage, cursor); + return cursorImage.getJsImage(); + } } From 57451bf34871e699a47b7bc677254d96697f8181 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:28:57 +0200 Subject: [PATCH 17/64] chore: add idea config for java modules --- src/openchemlib-js1.iml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/openchemlib-js1.iml diff --git a/src/openchemlib-js1.iml b/src/openchemlib-js1.iml new file mode 100644 index 00000000..f7dd9a33 --- /dev/null +++ b/src/openchemlib-js1.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file From 00a07e7dbebe16b2073b2820b59ab999ac2001a7 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:50:01 +0200 Subject: [PATCH 18/64] fix: repaint editor area after Dialog fireOk --- .run/Build java.run.xml | 17 +++++++++++++++++ .run/Run JS DEV.run.xml | 7 +++++++ .run/build-esm-bundle-watch.run.xml | 12 ++++++++++++ .run/vite.run.xml | 12 ++++++++++++ lib/canvas_editor/create_editor.js | 2 ++ .../research/gwt/gui/generic/JSDialog.java | 5 ++++- .../research/gwt/gui/generic/JSUIHelper.java | 7 ++++++- 7 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 .run/Build java.run.xml create mode 100644 .run/Run JS DEV.run.xml create mode 100644 .run/build-esm-bundle-watch.run.xml create mode 100644 .run/vite.run.xml diff --git a/.run/Build java.run.xml b/.run/Build java.run.xml new file mode 100644 index 00000000..a5d0699c --- /dev/null +++ b/.run/Build java.run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.run/Run JS DEV.run.xml b/.run/Run JS DEV.run.xml new file mode 100644 index 00000000..1890c408 --- /dev/null +++ b/.run/Run JS DEV.run.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.run/build-esm-bundle-watch.run.xml b/.run/build-esm-bundle-watch.run.xml new file mode 100644 index 00000000..a8ba99af --- /dev/null +++ b/.run/build-esm-bundle-watch.run.xml @@ -0,0 +1,12 @@ + + + + + + - - diff --git a/examples/generic_editor.ts b/examples/generic_editor.ts deleted file mode 100644 index b758fa1c..00000000 --- a/examples/generic_editor.ts +++ /dev/null @@ -1,14 +0,0 @@ -import OCL from '../distesm/full.pretty'; - -const editorArea = document.getElementById('editor-area') as HTMLElement; -const eventsArea = document.getElementById('events-area') as HTMLElement; - -const editor = new OCL.CanvasEditor(editorArea); - -const molecule = OCL.Molecule.fromSmiles('COCO'); -editor.setMolecule(molecule); - -editor.setOnChangeListener((event) => { - console.log('change', event); - eventsArea.innerText = `Type: ${event.type}; User event: ${event.isUserEvent}`; -}); diff --git a/examples/generic_editor_poc.html b/examples/generic_editor/demo.html similarity index 85% rename from examples/generic_editor_poc.html rename to examples/generic_editor/demo.html index 40b1676b..6df46f1b 100644 --- a/examples/generic_editor_poc.html +++ b/examples/generic_editor/demo.html @@ -3,7 +3,9 @@ Generic editor - PoC - + + +
OpenChemLib JS examples - +

OpenChemLib JS examples

- +
+

Generic editor

+ +
From 61f17d5b5a774d1220e9eca72a3eec0e011dcd44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 24 Jul 2024 11:28:01 +0200 Subject: [PATCH 27/64] chore: add TailwindCSS for demo styles --- examples/base.css | 30 +- examples/generic_editor/demo.html | 20 +- examples/generic_editor/demo/demo.css | 6 - package-lock.json | 847 +++++++++++++++++++++++++- package.json | 3 + postcss.config.js | 8 + tailwind.config.js | 10 + 7 files changed, 897 insertions(+), 27 deletions(-) delete mode 100644 examples/generic_editor/demo/demo.css create mode 100644 postcss.config.js create mode 100644 tailwind.config.js diff --git a/examples/base.css b/examples/base.css index 9d23ceb0..40971c0b 100644 --- a/examples/base.css +++ b/examples/base.css @@ -1,3 +1,31 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + body { - font-family: sans-serif; + @apply font-sans m-4; +} + +a { + @apply text-blue-600 underline; +} + +button { + @apply bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white py-1 px-2 rounded; +} + +h1 { + @apply text-3xl font-bold mb-3; +} + +h2 { + @apply text-2xl font-bold mb-2; +} + +h3 { + @apply text-xl font-bold mb-1; +} + +ul > li { + @apply list-disc ml-4; } diff --git a/examples/generic_editor/demo.html b/examples/generic_editor/demo.html index 6df46f1b..ba6220f5 100644 --- a/examples/generic_editor/demo.html +++ b/examples/generic_editor/demo.html @@ -4,28 +4,18 @@ Generic editor - PoC - - -
+ +
-
-
+
+
-
+
Change count
ID Code
diff --git a/examples/generic_editor/demo/demo.css b/examples/generic_editor/demo/demo.css deleted file mode 100644 index bd64573a..00000000 --- a/examples/generic_editor/demo/demo.css +++ /dev/null @@ -1,6 +0,0 @@ -#editor-area { - width: 600px; - height: 600px; - background-color: #b5e2ff; - border: 1px solid #000; -} diff --git a/package-lock.json b/package-lock.json index 9a3fa892..1837c271 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "BSD-3-Clause", "devDependencies": { "@types/jest": "^29.5.12", + "autoprefixer": "^10.4.19", "benchmark": "^2.1.4", "esbuild": "^0.23.0", "eslint": "^8.56.0", @@ -20,12 +21,27 @@ "image-js": "0.0.0-next-d280f46ab7", "jest": "^29.7.0", "openchemlib-utils": "^6.3.0", + "postcss": "^8.4.39", "prettier": "^3.3.3", + "tailwindcss": "^3.4.6", "typescript": "^5.5.3", "vite": "^5.3.4", "yargs": "^17.7.2" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -1233,6 +1249,109 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1772,6 +1891,17 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", @@ -2492,6 +2622,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2516,6 +2653,13 @@ "node": ">=14" } }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2540,6 +2684,44 @@ "dev": true, "license": "MIT" }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -2681,6 +2863,19 @@ "platform": "^1.3.3" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/binary-search": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", @@ -2802,6 +2997,16 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001642", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", @@ -2857,6 +3062,44 @@ "dev": true, "license": "MIT" }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ci-info": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", @@ -3045,6 +3288,19 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/d3-array": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-0.7.1.tgz", @@ -3112,6 +3368,13 @@ "node": ">=8" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -3135,6 +3398,13 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3155,6 +3425,13 @@ "dev": true, "license": "MIT" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.4.830", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.830.tgz", @@ -3955,6 +4232,50 @@ "dev": true, "license": "ISC" }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4346,6 +4667,19 @@ "dev": true, "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-builtin-module": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", @@ -4552,6 +4886,22 @@ "node": ">= 12" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -5167,6 +5517,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/jpeg-js": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", @@ -5309,6 +5669,16 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -5453,6 +5823,16 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ml-affine-transform": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ml-affine-transform/-/ml-affine-transform-1.0.3.tgz", @@ -5600,6 +5980,18 @@ "dev": true, "license": "MIT" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -5680,6 +6072,16 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -5693,12 +6095,32 @@ "node": ">=8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -5807,6 +6229,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", @@ -5904,6 +6333,30 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5934,6 +6387,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -6059,6 +6522,140 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6182,6 +6779,16 @@ "dev": true, "license": "MIT" }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -6292,6 +6899,19 @@ "node": ">=8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", @@ -6780,6 +7400,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6793,6 +7429,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -6839,6 +7489,86 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6882,6 +7612,44 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tailwindcss": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz", + "integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6904,6 +7672,29 @@ "dev": true, "license": "MIT" }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tiff": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/tiff/-/tiff-6.1.0.tgz", @@ -6958,6 +7749,13 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", @@ -7111,6 +7909,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -7688,6 +8493,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -7726,6 +8550,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 622b9e6b..dfdb54c7 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "homepage": "https://github.com/cheminfo/openchemlib-js", "devDependencies": { "@types/jest": "^29.5.12", + "autoprefixer": "^10.4.19", "benchmark": "^2.1.4", "esbuild": "^0.23.0", "eslint": "^8.56.0", @@ -86,7 +87,9 @@ "image-js": "0.0.0-next-d280f46ab7", "jest": "^29.7.0", "openchemlib-utils": "^6.3.0", + "postcss": "^8.4.39", "prettier": "^3.3.3", + "tailwindcss": "^3.4.6", "typescript": "^5.5.3", "vite": "^5.3.4", "yargs": "^17.7.2" diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..587af71a --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 00000000..4b4fbb2b --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,10 @@ +'use strict'; + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./index.html', './examples/**/*.{html,js,ts}'], + theme: { + extend: {}, + }, + plugins: [], +}; From c2a515c774f11ae56a3450839c4e02da2a8d7563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 24 Jul 2024 13:15:18 +0200 Subject: [PATCH 28/64] feat: add option to set initial mode --- examples/generic_editor/demo.html | 50 +++- examples/generic_editor/demo/demo.ts | 4 +- lib/canvas_editor/create_editor.js | 16 +- .../research/gwt/gui/generic/JSCheckBox.java | 6 +- .../gwt/gui/generic/JSClipboardHandler.java | 14 +- .../research/gwt/gui/generic/JSComboBox.java | 16 +- .../research/gwt/gui/generic/JSComponent.java | 24 +- .../research/gwt/gui/generic/JSDialog.java | 32 +- .../gwt/gui/generic/JSDrawContext.java | 121 ++++---- .../gwt/gui/generic/JSEditorArea.java | 281 +++++++++--------- .../gwt/gui/generic/JSEditorToolbar.java | 138 ++++----- .../gwt/gui/generic/JSKeyHandler.java | 9 +- .../research/gwt/gui/generic/JSLabel.java | 4 +- .../gwt/gui/generic/JSMouseHandler.java | 9 +- .../research/gwt/gui/generic/JSPolygon.java | 6 +- .../research/gwt/gui/generic/JSRectangle.java | 6 +- .../research/gwt/gui/generic/JSTextField.java | 4 +- .../research/gwt/gui/generic/JSUIHelper.java | 8 +- types.d.ts | 7 + 19 files changed, 404 insertions(+), 351 deletions(-) diff --git a/examples/generic_editor/demo.html b/examples/generic_editor/demo.html index ba6220f5..a7773ce3 100644 --- a/examples/generic_editor/demo.html +++ b/examples/generic_editor/demo.html @@ -7,21 +7,39 @@ -
- - - -
-
-
-
-
-
Change count
-
-
ID Code
-
-
Molfile
-
-
+

Demo - Generic editor

+
+
+ + + +
+
+
+
Change count
+
+
ID Code
+
+
Molfile
+
+
+
diff --git a/examples/generic_editor/demo/demo.ts b/examples/generic_editor/demo/demo.ts index 182df257..6335c78c 100644 --- a/examples/generic_editor/demo/demo.ts +++ b/examples/generic_editor/demo/demo.ts @@ -173,7 +173,9 @@ const molfileDiv = document.getElementById('molfile') as HTMLElement; let changeCount = 0; const editorElement = document.getElementById('editor') as HTMLElement; -const editor = new CanvasEditor(editorElement); +const editor = new CanvasEditor(editorElement, { + initialMode: 'molecule', +}); editor.setOnChangeListener(({ type, isUserEvent }) => { if (isUserEvent && type === 'molecule') { diff --git a/lib/canvas_editor/create_editor.js b/lib/canvas_editor/create_editor.js index fef34acb..664c8d27 100644 --- a/lib/canvas_editor/create_editor.js +++ b/lib/canvas_editor/create_editor.js @@ -13,7 +13,7 @@ function createEditor( JavaEditorToolbar, JavaUIHelper, ) { - const { readOnly = false } = options; + const { readOnly = false, initialMode = 'molecule' } = options; const rootElement = document.createElement('div'); Object.assign(rootElement.style, { @@ -56,6 +56,7 @@ function createEditor( const uiHelper = new JavaUIHelper(new UIHelper(editorCanvas, JavaEditorArea)); const editorArea = new JavaEditorArea( + computeMode(initialMode, JavaEditorArea), new EditorArea(editorCanvas, onChange), uiHelper, ); @@ -110,4 +111,17 @@ function createEditor( return { editorArea, toolbar, uiHelper, destroy }; } +function computeMode(initialMode, JavaEditorArea) { + switch (initialMode) { + case 'molecule': + return 0; + case 'reaction': + return ( + JavaEditorArea.MODE_REACTION | JavaEditorArea.MODE_MULTIPLE_FRAGMENTS + ); + default: + throw new Error(`Invalid initial mode: ${initialMode}`); + } +} + module.exports = createEditor; diff --git a/src/com/actelion/research/gwt/gui/generic/JSCheckBox.java b/src/com/actelion/research/gwt/gui/generic/JSCheckBox.java index 54f2f76d..e856b71a 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSCheckBox.java +++ b/src/com/actelion/research/gwt/gui/generic/JSCheckBox.java @@ -1,6 +1,6 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericCheckBox; import com.google.gwt.core.client.JavaScriptObject; public class JSCheckBox extends JSComponent implements GenericCheckBox { @@ -9,14 +9,14 @@ public JSCheckBox(JavaScriptObject jsCheckBox) { } @Override - public native boolean isSelected() + public native boolean isSelected() /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSCheckBox::getJsComponent()(); return component.isSelected(); }-*/; @Override - public native void setSelected(boolean b) + public native void setSelected(boolean b) /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSCheckBox::getJsComponent()(); return component.setSelected(b); diff --git a/src/com/actelion/research/gwt/gui/generic/JSClipboardHandler.java b/src/com/actelion/research/gwt/gui/generic/JSClipboardHandler.java index 8be00ac0..3121cfee 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSClipboardHandler.java +++ b/src/com/actelion/research/gwt/gui/generic/JSClipboardHandler.java @@ -1,15 +1,15 @@ package com.actelion.research.gwt.gui.generic; -import java.util.ArrayList; - import com.actelion.research.chem.MolfileParser; import com.actelion.research.chem.StereoMolecule; import com.actelion.research.chem.reaction.Reaction; import com.actelion.research.gui.clipboard.IClipboardHandler; import com.google.gwt.core.client.JavaScriptObject; +import java.util.ArrayList; + public class JSClipboardHandler implements IClipboardHandler { - private JavaScriptObject mJsHandler; + private JavaScriptObject mJsHandler; public JSClipboardHandler(JavaScriptObject jsHandler) { mJsHandler = jsHandler; @@ -51,11 +51,11 @@ public Reaction pasteReaction() { public boolean copyMolecule(String molfile) { Object test = null; System.out.println("test: " + test.toString()); - StereoMolecule m = new StereoMolecule(); - MolfileParser p = new MolfileParser(); - p.parse(m, molfile); + StereoMolecule m = new StereoMolecule(); + MolfileParser p = new MolfileParser(); + p.parse(m, molfile); return copyMolecule(m); - } + } public native boolean copyMolecule(StereoMolecule mol) /*-{ diff --git a/src/com/actelion/research/gwt/gui/generic/JSComboBox.java b/src/com/actelion/research/gwt/gui/generic/JSComboBox.java index 43335b65..4fb1f049 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSComboBox.java +++ b/src/com/actelion/research/gwt/gui/generic/JSComboBox.java @@ -1,6 +1,6 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericComboBox; import com.google.gwt.core.client.JavaScriptObject; public class JSComboBox extends JSComponent implements GenericComboBox { @@ -9,49 +9,49 @@ public JSComboBox(JavaScriptObject jsComboBox) { } @Override - public native void addItem(String item) + public native void addItem(String item) /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); return component.addItem(item); }-*/; @Override - public native void removeAllItems() + public native void removeAllItems() /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); return component.removeAllItems(); }-*/; @Override - public native int getSelectedIndex() + public native int getSelectedIndex() /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); return component.getSelectedIndex(); }-*/; @Override - public native String getSelectedItem() + public native String getSelectedItem() /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); return component.getSelectedItem(); }-*/; @Override - public native void setSelectedIndex(int index) + public native void setSelectedIndex(int index) /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); return component.setSelectedIndex(index); }-*/; @Override - public native void setSelectedItem(String item) + public native void setSelectedItem(String item) /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); return component.setSelectedItem(item); }-*/; @Override - public native void setEditable(boolean b) + public native void setEditable(boolean b) /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()(); return component.setEditable(b); diff --git a/src/com/actelion/research/gwt/gui/generic/JSComponent.java b/src/com/actelion/research/gwt/gui/generic/JSComponent.java index edaefb06..3b9f64f6 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSComponent.java +++ b/src/com/actelion/research/gwt/gui/generic/JSComponent.java @@ -1,17 +1,19 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericActionEvent; +import com.actelion.research.gui.generic.GenericComponent; +import com.actelion.research.gui.generic.GenericEventListener; import com.google.gwt.core.client.JavaScriptObject; import java.util.ArrayList; public class JSComponent implements GenericComponent { private JavaScriptObject mJsComponent; - private ArrayList> mConsumerList; + private ArrayList> mConsumerList; public JSComponent(JavaScriptObject jsComponent) { mJsComponent = jsComponent; - mConsumerList = new ArrayList<>(); + mConsumerList = new ArrayList<>(); setEventHandler(jsComponent); } @@ -32,26 +34,26 @@ public JavaScriptObject getJsComponent() { } @Override - public native void setEnabled(boolean b) + public native void setEnabled(boolean b) /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSComponent::getJsComponent()(); return component.setEnabled(b); }-*/; @Override - public void addEventConsumer(GenericEventListener consumer) { - mConsumerList.add(consumer); + public void addEventConsumer(GenericEventListener consumer) { + mConsumerList.add(consumer); } @Override - public void removeEventConsumer(GenericEventListener consumer) { - mConsumerList.remove(consumer); + public void removeEventConsumer(GenericEventListener consumer) { + mConsumerList.remove(consumer); } @Override - public void fireEvent(GenericActionEvent event) { - for (GenericEventListener consumer:mConsumerList) { - consumer.eventHappened(event); + public void fireEvent(GenericActionEvent event) { + for (GenericEventListener consumer:mConsumerList) { + consumer.eventHappened(event); } } } diff --git a/src/com/actelion/research/gwt/gui/generic/JSDialog.java b/src/com/actelion/research/gwt/gui/generic/JSDialog.java index 87adb790..586e8cf9 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSDialog.java +++ b/src/com/actelion/research/gwt/gui/generic/JSDialog.java @@ -20,28 +20,28 @@ private JavaScriptObject getJsDialog() { @Override public native void setLayout(int[] hLayout, int[] vLayout) /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - return dialog.setLayout(hLayout, vLayout); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + return jsDialog.setLayout(hLayout, vLayout); }-*/; @Override public native void add(GenericComponent c, int x, int y) /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - return dialog.add(c, x, y); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + return jsDialog.add(c, x, y); }-*/; @Override public native void add(GenericComponent c, int x1, int y1, int x2, int y2) /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - return dialog.add(c, x1, y1, x2, y2); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + return jsDialog.add(c, x1, y1, x2, y2); }-*/; @Override public native GenericCheckBox createCheckBox(String text) /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); var checkBox = dialog.createCheckBox(text); return @com.actelion.research.gwt.gui.generic.JSCheckBox::new(Lcom/google/gwt/core/client/JavaScriptObject;)(checkBox); }-*/; @@ -49,23 +49,23 @@ public native GenericCheckBox createCheckBox(String text) @Override public native GenericComboBox createComboBox() /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); var comboBox = dialog.createComboBox(); return @com.actelion.research.gwt.gui.generic.JSComboBox::new(Lcom/google/gwt/core/client/JavaScriptObject;)(comboBox); }-*/; @Override - public native GenericLabel createLabel(String text) + public native GenericLabel createLabel(String text) /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); var label = dialog.createLabel(text); return @com.actelion.research.gwt.gui.generic.JSLabel::new(Lcom/google/gwt/core/client/JavaScriptObject;)(label); }-*/; @Override - public native GenericTextField createTextField(int width, int height) + public native GenericTextField createTextField(int width, int height) /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); var textField = dialog.createTextField(width, height); return @com.actelion.research.gwt.gui.generic.JSTextField::new(Lcom/google/gwt/core/client/JavaScriptObject;)(textField); }-*/; @@ -74,7 +74,7 @@ public native GenericTextField createTextField(int width, int height) public native void setEventConsumer(GenericEventListener consumer) /*-{ this.@com.actelion.research.gwt.gui.generic.JSDialog::mConsumer = consumer; - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); var that = this; var jsConsumer = { fireOk: function ok() { @@ -90,21 +90,21 @@ public native void setEventConsumer(GenericEventListener con @Override public native void showDialog() /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); dialog.showDialog(); }-*/; @Override public native void disposeDialog() /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); dialog.disposeDialog(); }-*/; @Override public native void showMessage(String message) /*-{ - var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); + var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); dialog.showMessage(message); }-*/; diff --git a/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java b/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java index 8a5f75b9..b7dbf284 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java +++ b/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java @@ -1,29 +1,32 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericDrawContext; +import com.actelion.research.gui.generic.GenericImage; +import com.actelion.research.gui.generic.GenericPolygon; +import com.actelion.research.gui.generic.GenericRectangle; import com.google.gwt.core.client.JavaScriptObject; public class JSDrawContext implements GenericDrawContext { - private JavaScriptObject mJsContext; + private JavaScriptObject mJsContext; - public JSDrawContext(JavaScriptObject jsContext) { - mJsContext = jsContext; - } + public JSDrawContext(JavaScriptObject jsContext) { + mJsContext = jsContext; + } - private JavaScriptObject getJsContext() { - return mJsContext; - } + private JavaScriptObject getJsContext() { + return mJsContext; + } public native void clearRect(double x, double y, double w, double h) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.clearRect(x, y, w, h); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.clearRect(x, y, w, h); }-*/; @Override public native GenericImage createARGBImage(int width, int height) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); var image = ctx.createARGBImage(width, height); return @com.actelion.research.gwt.gui.generic.JSImage::new(Lcom/google/gwt/core/client/JavaScriptObject;)(image); }-*/; @@ -31,53 +34,53 @@ public native GenericImage createARGBImage(int width, int height) @Override public native void drawCenteredString(double x, double y, String s) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.drawCenteredString(x, y, s); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.drawCenteredString(x, y, s); }-*/; @Override public native void drawCircle(double x, double y, double d) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.drawCircle(x, y, d); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.drawCircle(x, y, d); }-*/; @Override public native void drawDottedLine(double x1, double y1, double x2, double y2) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.drawDottedLine(x1, y1, x2, y2); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.drawDottedLine(x1, y1, x2, y2); }-*/; @Override public native void drawImage(GenericImage image, double x, double y) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()(); - return ctx.drawImage(jsImage, x, y); + return jsContext.drawImage(jsImage, x, y); }-*/; @Override public native void drawImage(GenericImage image, double sx, double sy, double dx, double dy, double w, double h) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()(); - return ctx.drawImage(jsImage, sx, sy, dx, dy, w, h); + return jsContext.drawImage(jsImage, sx, sy, dx, dy, w, h); }-*/; @Override public native void drawImage(GenericImage image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()(); - return ctx.drawImage(jsImage, sx, sy, sw, sh, dx, dy, dw, dh); + return jsContext.drawImage(jsImage, sx, sy, sw, sh, dx, dy, dw, dh); }-*/; @Override public native void drawLine(double x1, double y1, double x2, double y2) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.drawLine(x1, y1, x2, y2); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.drawLine(x1, y1, x2, y2); }-*/; @Override @@ -87,29 +90,29 @@ public void drawPolygon(GenericPolygon p) { public native void drawPolygon(JSPolygon jsPolygon) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.drawPolygon(jsPolygon); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.drawPolygon(jsPolygon); }-*/; @Override public native void drawRectangle(double x, double y, double w, double h) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.drawRectangle(x, y, w, h); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.drawRectangle(x, y, w, h); }-*/; @Override public native void drawString(double x, double y, String s) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.drawString(x, y, s); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.drawString(x, y, s); }-*/; @Override public native void fillCircle(double x, double y, double d) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.fillCircle(x, y, d); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.fillCircle(x, y, d); }-*/; @Override @@ -119,22 +122,22 @@ public void fillPolygon(GenericPolygon p) { public native void fillPolygon(JSPolygon jsPolygon) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.fillPolygon(jsPolygon); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.fillPolygon(jsPolygon); }-*/; @Override public native void fillRectangle(double x, double y, double w, double h) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.fillRectangle(x, y, w, h); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.fillRectangle(x, y, w, h); }-*/; @Override public native int getBackgroundRGB() /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.getBackgroundRGB(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.getBackgroundRGB(); }-*/; @Override @@ -146,71 +149,71 @@ public GenericRectangle getBounds(String s) { public native GenericRectangle getBounds(String s, JSRectangle jsRect) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.getBounds(s, jsRect); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.getBounds(s, jsRect); }-*/; @Override public native int getFontSize() /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.getFontSize(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.getFontSize(); }-*/; @Override public native int getForegroundRGB() /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.getForegroundRGB(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.getForegroundRGB(); }-*/; @Override public native float getLineWidth() /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.getLineWidth(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.getLineWidth(); }-*/; @Override public native int getRGB() /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.getRGB(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.getRGB(); }-*/; @Override public native int getSelectionBackgroundRGB() /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.getSelectionBackgroundRGB(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.getSelectionBackgroundRGB(); }-*/; @Override public native boolean isDarkBackground() /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.isDarkBackground(); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.isDarkBackground(); }-*/; @Override public native void setFont(int size, boolean isBold, boolean isItalic) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.setFont(size, isBold, isItalic); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.setFont(size, isBold, isItalic); }-*/; @Override public native void setLineWidth(float lineWidth) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.setLineWidth(lineWidth); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.setLineWidth(lineWidth); }-*/; @Override public native void setRGB(int rgb) /*-{ - var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); - return ctx.setRGB(rgb); + var jsContext = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()(); + return jsContext.setRGB(rgb); }-*/; } diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java index fcf82d26..b75ab095 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java @@ -13,169 +13,174 @@ @JsType(name = "GenericEditorArea") public class JSEditorArea implements GenericCanvas { - private GenericEditorArea mDrawArea; - private JavaScriptObject mOptions; - private JSMouseHandler mMouseHandler; - private JSKeyHandler mKeyHandler; - - public static final int EDITOR_EVENT_MOLECULE_CHANGED = EditorEvent.WHAT_MOLECULE_CHANGED; - public static final int EDITOR_EVENT_SELECTION_CHANGED = EditorEvent.WHAT_SELECTION_CHANGED; - public static final int EDITOR_EVENT_HIGHLIGHT_ATOM_CHANGED = EditorEvent.WHAT_HILITE_ATOM_CHANGED; - public static final int EDITOR_EVENT_HIGHLIGHT_BOND_CHANGED = EditorEvent.WHAT_HILITE_BOND_CHANGED; - - public static final int MOUSE_EVENT_PRESSED = GenericMouseEvent.MOUSE_PRESSED; - public static final int MOUSE_EVENT_RELEASED = GenericMouseEvent.MOUSE_RELEASED; - public static final int MOUSE_EVENT_CLICKED = GenericMouseEvent.MOUSE_CLICKED; - public static final int MOUSE_EVENT_ENTERED = GenericMouseEvent.MOUSE_ENTERED; - public static final int MOUSE_EVENT_EXITED = GenericMouseEvent.MOUSE_EXITED; - public static final int MOUSE_EVENT_MOVED = GenericMouseEvent.MOUSE_MOVED; - public static final int MOUSE_EVENT_DRAGGED = GenericMouseEvent.MOUSE_DRAGGED; - - public static final int KEY_CTRL = GenericKeyEvent.KEY_CTRL; - public static final int KEY_ALT = GenericKeyEvent.KEY_ALT; - public static final int KEY_SHIFT = GenericKeyEvent.KEY_SHIFT; - public static final int KEY_DELETE = GenericKeyEvent.KEY_DELETE; - public static final int KEY_BACK_SPACE = GenericKeyEvent.KEY_BACK_SPACE; - public static final int KEY_HELP = GenericKeyEvent.KEY_HELP; - public static final int KEY_ESCAPE = GenericKeyEvent.KEY_ESCAPE; - - public static final int KEY_EVENT_PRESSED = GenericKeyEvent.KEY_PRESSED; - public static final int KEY_EVENT_RELEASED = GenericKeyEvent.KEY_RELEASED; - - public static final int cChainCursor = GenericCursorHelper.cChainCursor; - public static final int cDeleteCursor = GenericCursorHelper.cDeleteCursor; - public static final int cHandCursor = GenericCursorHelper.cHandCursor; - public static final int cHandPlusCursor = GenericCursorHelper.cHandPlusCursor; - public static final int cFistCursor = GenericCursorHelper.cFistCursor; - public static final int cLassoCursor = GenericCursorHelper.cLassoCursor; - public static final int cLassoPlusCursor = GenericCursorHelper.cLassoPlusCursor; - public static final int cSelectRectCursor = GenericCursorHelper.cSelectRectCursor; - public static final int cSelectRectPlusCursor = GenericCursorHelper.cSelectRectPlusCursor; - public static final int cZoomCursor = GenericCursorHelper.cZoomCursor; - public static final int cInvisibleCursor = GenericCursorHelper.cInvisibleCursor; - public static final int cPointerCursor = GenericCursorHelper.cPointerCursor; - public static final int cTextCursor = GenericCursorHelper.cTextCursor; - public static final int cPointedHandCursor = GenericCursorHelper.cPointedHandCursor; - public static final int[][] IMAGE_DATA_16 = GenericCursorHelper.IMAGE_DATA_16; - public static final int[] HOTSPOT_16 = GenericCursorHelper.HOTSPOT_16; - public static final int[] HOTSPOT_32 = GenericCursorHelper.HOTSPOT_32; - public static final String[] IMAGE_NAME_32 = GenericCursorHelper.IMAGE_NAME_32; - - public static final int TableLayoutPreferred = (int)TableLayout.PREFERRED; - public static final int TableLayoutFill = (int)TableLayout.FILL; - - public JSEditorArea(JavaScriptObject options, JSUIHelper uiHelper) { - mDrawArea = new GenericEditorArea(new StereoMolecule(), 0, uiHelper, this); - mDrawArea.addDrawAreaListener(new GenericEventListener() { - @Override - public void eventHappened(EditorEvent e) { - callJsEventListener(e.getWhat(), e.isUserChange()); - } - }); - mOptions = options; - - mMouseHandler = new JSMouseHandler(mDrawArea); - mMouseHandler.addListener(mDrawArea); - - mKeyHandler = new JSKeyHandler(mDrawArea); - mKeyHandler.addListener(mDrawArea); - - JavaScriptObject clipboardHandler = getClipboardHandler(); - mDrawArea.setClipboardHandler(new JSClipboardHandler(clipboardHandler)); - } - - public void setMolecule(JSMolecule molecule) { - mDrawArea.setMolecule(molecule.getStereoMolecule()); - } - - public JSMolecule getMolecule() { - return new JSMolecule(mDrawArea.getMolecule()); - } - - public void setReaction(JSReaction reaction) { - mDrawArea.setReaction(reaction.getReaction()); - } - - public JSReaction getReaction() { - return new JSReaction(mDrawArea.getReaction()); - } + private GenericEditorArea mDrawArea; + private JavaScriptObject mJsObject; + private JSMouseHandler mMouseHandler; + private JSKeyHandler mKeyHandler; + + public static final int MODE_MULTIPLE_FRAGMENTS = GenericEditorArea.MODE_MULTIPLE_FRAGMENTS; + public static final int MODE_MARKUSH_STRUCTURE = GenericEditorArea.MODE_MARKUSH_STRUCTURE; + public static final int MODE_REACTION = GenericEditorArea.MODE_REACTION; + public static final int MODE_DRAWING_OBJECTS = GenericEditorArea.MODE_DRAWING_OBJECTS; + + public static final int EDITOR_EVENT_MOLECULE_CHANGED = EditorEvent.WHAT_MOLECULE_CHANGED; + public static final int EDITOR_EVENT_SELECTION_CHANGED = EditorEvent.WHAT_SELECTION_CHANGED; + public static final int EDITOR_EVENT_HIGHLIGHT_ATOM_CHANGED = EditorEvent.WHAT_HILITE_ATOM_CHANGED; + public static final int EDITOR_EVENT_HIGHLIGHT_BOND_CHANGED = EditorEvent.WHAT_HILITE_BOND_CHANGED; + + public static final int MOUSE_EVENT_PRESSED = GenericMouseEvent.MOUSE_PRESSED; + public static final int MOUSE_EVENT_RELEASED = GenericMouseEvent.MOUSE_RELEASED; + public static final int MOUSE_EVENT_CLICKED = GenericMouseEvent.MOUSE_CLICKED; + public static final int MOUSE_EVENT_ENTERED = GenericMouseEvent.MOUSE_ENTERED; + public static final int MOUSE_EVENT_EXITED = GenericMouseEvent.MOUSE_EXITED; + public static final int MOUSE_EVENT_MOVED = GenericMouseEvent.MOUSE_MOVED; + public static final int MOUSE_EVENT_DRAGGED = GenericMouseEvent.MOUSE_DRAGGED; + + public static final int KEY_CTRL = GenericKeyEvent.KEY_CTRL; + public static final int KEY_ALT = GenericKeyEvent.KEY_ALT; + public static final int KEY_SHIFT = GenericKeyEvent.KEY_SHIFT; + public static final int KEY_DELETE = GenericKeyEvent.KEY_DELETE; + public static final int KEY_BACK_SPACE = GenericKeyEvent.KEY_BACK_SPACE; + public static final int KEY_HELP = GenericKeyEvent.KEY_HELP; + public static final int KEY_ESCAPE = GenericKeyEvent.KEY_ESCAPE; + + public static final int KEY_EVENT_PRESSED = GenericKeyEvent.KEY_PRESSED; + public static final int KEY_EVENT_RELEASED = GenericKeyEvent.KEY_RELEASED; + + public static final int cChainCursor = GenericCursorHelper.cChainCursor; + public static final int cDeleteCursor = GenericCursorHelper.cDeleteCursor; + public static final int cHandCursor = GenericCursorHelper.cHandCursor; + public static final int cHandPlusCursor = GenericCursorHelper.cHandPlusCursor; + public static final int cFistCursor = GenericCursorHelper.cFistCursor; + public static final int cLassoCursor = GenericCursorHelper.cLassoCursor; + public static final int cLassoPlusCursor = GenericCursorHelper.cLassoPlusCursor; + public static final int cSelectRectCursor = GenericCursorHelper.cSelectRectCursor; + public static final int cSelectRectPlusCursor = GenericCursorHelper.cSelectRectPlusCursor; + public static final int cZoomCursor = GenericCursorHelper.cZoomCursor; + public static final int cInvisibleCursor = GenericCursorHelper.cInvisibleCursor; + public static final int cPointerCursor = GenericCursorHelper.cPointerCursor; + public static final int cTextCursor = GenericCursorHelper.cTextCursor; + public static final int cPointedHandCursor = GenericCursorHelper.cPointedHandCursor; + public static final int[][] IMAGE_DATA_16 = GenericCursorHelper.IMAGE_DATA_16; + public static final int[] HOTSPOT_16 = GenericCursorHelper.HOTSPOT_16; + public static final int[] HOTSPOT_32 = GenericCursorHelper.HOTSPOT_32; + public static final String[] IMAGE_NAME_32 = GenericCursorHelper.IMAGE_NAME_32; + + public static final int TableLayoutPreferred = (int)TableLayout.PREFERRED; + public static final int TableLayoutFill = (int)TableLayout.FILL; + + public JSEditorArea(int mode, JavaScriptObject jsObject, JSUIHelper uiHelper) { + mDrawArea = new GenericEditorArea(new StereoMolecule(), mode, uiHelper, this); + mDrawArea.addDrawAreaListener(new GenericEventListener() { + @Override + public void eventHappened(EditorEvent e) { + callJsEventListener(e.getWhat(), e.isUserChange()); + } + }); + mJsObject = jsObject; + + mMouseHandler = new JSMouseHandler(mDrawArea); + mMouseHandler.addListener(mDrawArea); + + mKeyHandler = new JSKeyHandler(mDrawArea); + mKeyHandler.addListener(mDrawArea); + + JavaScriptObject clipboardHandler = getClipboardHandler(); + mDrawArea.setClipboardHandler(new JSClipboardHandler(clipboardHandler)); + } + + public void setMolecule(JSMolecule molecule) { + mDrawArea.setMolecule(molecule.getStereoMolecule()); + } + + public JSMolecule getMolecule() { + return new JSMolecule(mDrawArea.getMolecule()); + } + + public void setReaction(JSReaction reaction) { + mDrawArea.setReaction(reaction.getReaction()); + } + + public JSReaction getReaction() { + return new JSReaction(mDrawArea.getReaction()); + } - private native JavaScriptObject getClipboardHandler() + private native JavaScriptObject getClipboardHandler() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); - return options.getClipboardHandler(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getJsObject()(); + return jsObject.getClipboardHandler(); }-*/; private native JavaScriptObject callJsEventListener(int what, boolean isUserChange) /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); - return options.onChange(what, isUserChange); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getJsObject()(); + return jsObject.onChange(what, isUserChange); }-*/; - public void draw() { - mDrawArea.paintContent(getDrawContext()); - } + public void draw() { + mDrawArea.paintContent(getDrawContext()); + } - public GenericEditorArea getGenericEditorArea() { - return mDrawArea; - } + public GenericEditorArea getGenericEditorArea() { + return mDrawArea; + } - public void fireMouseEvent(int what, int button, int clickCount, int x, int y, boolean shiftDown, boolean ctrlDown, boolean altDown, boolean isPopupTrigger) { - GenericMouseEvent gme = new GenericMouseEvent(what, button, clickCount, x, y, shiftDown, ctrlDown, altDown, isPopupTrigger, mDrawArea); - mMouseHandler.fireEvent(gme); - } + public void fireMouseEvent(int what, int button, int clickCount, int x, int y, boolean shiftDown, boolean ctrlDown, boolean altDown, boolean isPopupTrigger) { + GenericMouseEvent gme = new GenericMouseEvent(what, button, clickCount, x, y, shiftDown, ctrlDown, altDown, isPopupTrigger, mDrawArea); + mMouseHandler.fireEvent(gme); + } - public void fireKeyEvent(int what, int key, boolean isAltDown, boolean isCtrlDown, boolean isShiftDown, boolean isMenuShortcut) { - GenericKeyEvent gke = new GenericKeyEvent(what, key, isAltDown, isCtrlDown, isShiftDown, isMenuShortcut, mDrawArea); - mKeyHandler.fireEvent(gke); - } + public void fireKeyEvent(int what, int key, boolean isAltDown, boolean isCtrlDown, boolean isShiftDown, boolean isMenuShortcut) { + GenericKeyEvent gke = new GenericKeyEvent(what, key, isAltDown, isCtrlDown, isShiftDown, isMenuShortcut, mDrawArea); + mKeyHandler.fireEvent(gke); + } - public void toolChanged(int newTool) { - mDrawArea.toolChanged(newTool); - } + public void toolChanged(int newTool) { + mDrawArea.toolChanged(newTool); + } - private JavaScriptObject getOptions() { - return mOptions; - } + private JavaScriptObject getJsObject() { + return mJsObject; + } - @Override - @JsIgnore - public native int getBackgroundRGB() + @Override + @JsIgnore + public native int getBackgroundRGB() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); - return options.getBackgroundRGB(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getJsObject()(); + return jsObject.getBackgroundRGB(); }-*/; - @Override - @JsIgnore - public native double getCanvasHeight() + @Override + @JsIgnore + public native double getCanvasHeight() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); - return options.getCanvasHeight(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getJsObject()(); + return jsObject.getCanvasHeight(); }-*/; - @Override - @JsIgnore - public native double getCanvasWidth() + @Override + @JsIgnore + public native double getCanvasWidth() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); - return options.getCanvasWidth(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getJsObject()(); + return jsObject.getCanvasWidth(); }-*/; - @Override - @JsIgnore - public GenericDrawContext getDrawContext() { - return new JSDrawContext(getDrawContextFromOptions()); - } + @Override + @JsIgnore + public GenericDrawContext getDrawContext() { + return new JSDrawContext(getDrawContextFromJsObject()); + } - private native JavaScriptObject getDrawContextFromOptions() + private native JavaScriptObject getDrawContextFromJsObject() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()(); - return options.getDrawContext(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getJsObject()(); + return jsObject.getDrawContext(); }-*/; - @Override - public void repaint() { - draw(); - } + @Override + public void repaint() { + draw(); + } } diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java index 0e62e553..3c40ede5 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java @@ -9,89 +9,89 @@ @JsType(name = "GenericEditorToolbar") public class JSEditorToolbar implements GenericCanvas { - private GenericEditorToolbar mGenericToolbar; - private JavaScriptObject mOptions; - private JSMouseHandler mMouseHandler; - - public JSEditorToolbar(JSEditorArea jsEditorArea, JavaScriptObject options) { - mGenericToolbar = new GenericEditorToolbar(this, jsEditorArea.getGenericEditorArea()); - mOptions = options; - - mMouseHandler = new JSMouseHandler(mGenericToolbar); - mMouseHandler.addListener(mGenericToolbar); - - setDimensions(mGenericToolbar.getWidth(), mGenericToolbar.getHeight()); - draw(); - } - - public int getWidth() { - return mGenericToolbar.getWidth(); - } - - public int getHeight() { - return mGenericToolbar.getHeight(); - } - - public void draw() { - JSDrawContext ctx = getDrawContext(); - ctx.clearRect(0, 0, getWidth(), getHeight()); - mGenericToolbar.paintContent(getDrawContext()); - } - - public void fireMouseEvent(int what, int button, int clickCount, int x, int y, boolean shiftDown, boolean ctrlDown, boolean altDown, boolean isPopupTrigger) { - GenericMouseEvent gme = new GenericMouseEvent(what, button, clickCount, x, y, shiftDown, ctrlDown, altDown, isPopupTrigger, mGenericToolbar); - mMouseHandler.fireEvent(gme); - } - - private JavaScriptObject getOptions() { - return mOptions; - } - - public native void setDimensions(int width, int height) + private GenericEditorToolbar mGenericToolbar; + private JavaScriptObject mJsObject; + private JSMouseHandler mMouseHandler; + + public JSEditorToolbar(JSEditorArea jsEditorArea, JavaScriptObject jsObject) { + mGenericToolbar = new GenericEditorToolbar(this, jsEditorArea.getGenericEditorArea()); + mJsObject = jsObject; + + mMouseHandler = new JSMouseHandler(mGenericToolbar); + mMouseHandler.addListener(mGenericToolbar); + + setDimensions(mGenericToolbar.getWidth(), mGenericToolbar.getHeight()); + draw(); + } + + public int getWidth() { + return mGenericToolbar.getWidth(); + } + + public int getHeight() { + return mGenericToolbar.getHeight(); + } + + public void draw() { + JSDrawContext ctx = getDrawContext(); + ctx.clearRect(0, 0, getWidth(), getHeight()); + mGenericToolbar.paintContent(getDrawContext()); + } + + public void fireMouseEvent(int what, int button, int clickCount, int x, int y, boolean shiftDown, boolean ctrlDown, boolean altDown, boolean isPopupTrigger) { + GenericMouseEvent gme = new GenericMouseEvent(what, button, clickCount, x, y, shiftDown, ctrlDown, altDown, isPopupTrigger, mGenericToolbar); + mMouseHandler.fireEvent(gme); + } + + private JavaScriptObject getJsObject() { + return mJsObject; + } + + public native void setDimensions(int width, int height) /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); - return options.setDimensions(width, height); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getJsObject()(); + return jsObject.setDimensions(width, height); }-*/; - @Override - @JsIgnore - public native int getBackgroundRGB() + @Override + @JsIgnore + public native int getBackgroundRGB() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); - return options.getBackgroundRGB(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getJsObject()(); + return jsObject.getBackgroundRGB(); }-*/; - @Override - @JsIgnore - public native double getCanvasHeight() + @Override + @JsIgnore + public native double getCanvasHeight() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); - return options.getCanvasHeight(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getJsObject()(); + return jsObject.getCanvasHeight(); }-*/; - @Override - @JsIgnore - public native double getCanvasWidth() + @Override + @JsIgnore + public native double getCanvasWidth() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); - return options.getCanvasWidth(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getJsObject()(); + return jsObject.getCanvasWidth(); }-*/; - @Override - @JsIgnore - public JSDrawContext getDrawContext() { - return new JSDrawContext(getDrawContextFromOptions()); - } + @Override + @JsIgnore + public JSDrawContext getDrawContext() { + return new JSDrawContext(getDrawContextFromJsObject()); + } - private native JavaScriptObject getDrawContextFromOptions() + private native JavaScriptObject getDrawContextFromJsObject() /*-{ - var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()(); - return options.getDrawContext(); + var jsObject = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getJsObject()(); + return jsObject.getDrawContext(); }-*/; - @Override - @JsIgnore - public void repaint() { - draw(); - } + @Override + @JsIgnore + public void repaint() { + draw(); + } } diff --git a/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java b/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java index ffa79bc5..c73d78b8 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java +++ b/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java @@ -1,9 +1,10 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericEventHandler; +import com.actelion.research.gui.generic.GenericKeyEvent; public class JSKeyHandler extends GenericEventHandler { - public JSKeyHandler(Object source) { - super(source); - } + public JSKeyHandler(Object source) { + super(source); + } } diff --git a/src/com/actelion/research/gwt/gui/generic/JSLabel.java b/src/com/actelion/research/gwt/gui/generic/JSLabel.java index 81517813..32656944 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSLabel.java +++ b/src/com/actelion/research/gwt/gui/generic/JSLabel.java @@ -1,6 +1,6 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericLabel; import com.google.gwt.core.client.JavaScriptObject; public class JSLabel extends JSComponent implements GenericLabel { @@ -9,7 +9,7 @@ public JSLabel(JavaScriptObject jsLabel) { } @Override - public native void setText(String text) + public native void setText(String text) /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSLabel::getJsComponent()(); return component.setText(text); diff --git a/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java b/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java index 5b8e4f0d..d51cdc00 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java +++ b/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java @@ -1,9 +1,10 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericEventHandler; +import com.actelion.research.gui.generic.GenericMouseEvent; public class JSMouseHandler extends GenericEventHandler { - public JSMouseHandler(Object source) { - super(source); - } + public JSMouseHandler(Object source) { + super(source); + } } diff --git a/src/com/actelion/research/gwt/gui/generic/JSPolygon.java b/src/com/actelion/research/gwt/gui/generic/JSPolygon.java index 87903005..36120094 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSPolygon.java +++ b/src/com/actelion/research/gwt/gui/generic/JSPolygon.java @@ -1,14 +1,14 @@ package com.actelion.research.gwt.gui.generic; import com.actelion.research.gui.generic.GenericPolygon; - -import jsinterop.annotations.*; +import jsinterop.annotations.JsIgnore; +import jsinterop.annotations.JsType; @JsType(name = "Polygon") public class JSPolygon { private GenericPolygon mPolygon; - @JsIgnore + @JsIgnore JSPolygon(GenericPolygon polygon) { mPolygon = polygon; } diff --git a/src/com/actelion/research/gwt/gui/generic/JSRectangle.java b/src/com/actelion/research/gwt/gui/generic/JSRectangle.java index cb1f6a97..a20237ff 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSRectangle.java +++ b/src/com/actelion/research/gwt/gui/generic/JSRectangle.java @@ -1,8 +1,8 @@ package com.actelion.research.gwt.gui.generic; import com.actelion.research.gui.generic.GenericRectangle; - -import jsinterop.annotations.*; +import jsinterop.annotations.JsIgnore; +import jsinterop.annotations.JsType; @JsType(name = "Rectangle") public class JSRectangle { @@ -12,7 +12,7 @@ public class JSRectangle { mRectangle = new GenericRectangle(); } - public void set(double x, double y, double w, double h) { + public void set(double x, double y, double w, double h) { mRectangle.set(x, y, w, h); } diff --git a/src/com/actelion/research/gwt/gui/generic/JSTextField.java b/src/com/actelion/research/gwt/gui/generic/JSTextField.java index 06340a24..729366de 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSTextField.java +++ b/src/com/actelion/research/gwt/gui/generic/JSTextField.java @@ -1,6 +1,6 @@ package com.actelion.research.gwt.gui.generic; -import com.actelion.research.gui.generic.*; +import com.actelion.research.gui.generic.GenericTextField; import com.google.gwt.core.client.JavaScriptObject; public class JSTextField extends JSComponent implements GenericTextField { @@ -16,7 +16,7 @@ public native String getText() }-*/; @Override - public native void setText(String text) + public native void setText(String text) /*-{ var component = this.@com.actelion.research.gwt.gui.generic.JSTextField::getJsComponent()(); return component.setText(text); diff --git a/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java b/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java index 48897969..1e3ec5be 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java +++ b/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java @@ -18,8 +18,8 @@ public JSUIHelper(JavaScriptObject jsObject) { } private JavaScriptObject getJsObject() { - return mJsObject; - } + return mJsObject; + } private native void registerToJs() /*-{ @@ -27,8 +27,8 @@ private native void registerToJs() jsObject.register(this); }-*/; - public void setEditorArea(JSEditorArea mEditorArea) { - this.mEditorArea = mEditorArea; + public void setEditorArea(JSEditorArea editorArea) { + mEditorArea = editorArea; } @Override diff --git a/types.d.ts b/types.d.ts index 1c386e0f..49567877 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3706,12 +3706,19 @@ export interface OnChangeEvent { export type OnChangeListenerCallback = (event: OnChangeEvent) => void; +export type CanvasEditorMode = 'molecule' | 'reaction'; + export interface CanvasEditorOptions { /** * No toolbar and user interactions are ignored. * @default false */ readOnly?: boolean; + /** + * Mode in which the editor will be initialized. + * @default 'molecule' + */ + initialMode?: CanvasEditorMode; } export declare class CanvasEditor { From acb6d452bdf0c32b01af81a8177b85a55564dc8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 24 Jul 2024 14:23:06 +0200 Subject: [PATCH 29/64] docs: add reset button to demo page --- examples/base.css | 4 +++ examples/generic_editor/demo.html | 14 +++++++-- examples/generic_editor/demo/demo.ts | 33 +++++++------------- examples/generic_editor/demo/editor.ts | 43 ++++++++++++++++++++++++++ examples/generic_editor/demo/result.ts | 25 +++++++++++++++ full.d.ts | 1 + 6 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 examples/generic_editor/demo/editor.ts create mode 100644 examples/generic_editor/demo/result.ts diff --git a/examples/base.css b/examples/base.css index 40971c0b..cd64b6f6 100644 --- a/examples/base.css +++ b/examples/base.css @@ -14,6 +14,10 @@ button { @apply bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white py-1 px-2 rounded; } +select { + @apply bg-white border border-gray-400 hover:border-gray-500 py-1 px-2 rounded; +} + h1 { @apply text-3xl font-bold mb-3; } diff --git a/examples/generic_editor/demo.html b/examples/generic_editor/demo.html index a7773ce3..616f63d2 100644 --- a/examples/generic_editor/demo.html +++ b/examples/generic_editor/demo.html @@ -12,7 +12,7 @@

Demo - Generic editor

class="grid gap-2" style=" grid-template-rows: 30px 600px 30px; - grid-template-columns: 100px 600px; + grid-template-columns: 150px 600px; grid-template-areas: '. top-actions . ' 'left-actions editor result' @@ -24,6 +24,16 @@

Demo - Generic editor

+
+ + +
Demo - Generic editor style="grid-area: result; grid-template-columns: fit-content(100%) 1fr" >
Change count
-
+
0
ID Code
Molfile
diff --git a/examples/generic_editor/demo/demo.ts b/examples/generic_editor/demo/demo.ts index 6335c78c..aa5411d3 100644 --- a/examples/generic_editor/demo/demo.ts +++ b/examples/generic_editor/demo/demo.ts @@ -1,6 +1,7 @@ import OCL from '../../../distesm/full.pretty'; +import { getEditor, resetEditor } from './editor.ts'; -const { CanvasEditor, Molecule, Reaction } = OCL; +const { Molecule, Reaction } = OCL; const rxn = `$RXN @@ -167,24 +168,12 @@ const molfile = `446220 22 43 1 0 0 0 0 M END`; -const changeCountDiv = document.getElementById('changeCount') as HTMLElement; -const idcodeDiv = document.getElementById('idcode') as HTMLElement; -const molfileDiv = document.getElementById('molfile') as HTMLElement; -let changeCount = 0; - -const editorElement = document.getElementById('editor') as HTMLElement; -const editor = new CanvasEditor(editorElement, { - initialMode: 'molecule', -}); - -editor.setOnChangeListener(({ type, isUserEvent }) => { - if (isUserEvent && type === 'molecule') { - changeCountDiv.innerText = String(++changeCount); - const idcodeAndCoords = editor.getMolecule().getIDCodeAndCoordinates(); - idcodeDiv.innerText = `${idcodeAndCoords.idCode} ${idcodeAndCoords.coordinates}`; - molfileDiv.innerText = editor.getMolecule().toMolfileV3(); - } -}); +resetEditor(); + +const resetButton = document.getElementById('resetButton') as HTMLButtonElement; +resetButton.onclick = () => { + resetEditor(); +}; const loadMolecule = document.getElementById( 'loadMolecule', @@ -192,7 +181,7 @@ const loadMolecule = document.getElementById( loadMolecule.onclick = () => { // const molecule = Molecule.fromMolfile(molfile); const molecule = Molecule.fromSmiles('c1ccccc1CO'); - editor.setMolecule(molecule); + getEditor().setMolecule(molecule); }; const loadFragment = document.getElementById( @@ -202,7 +191,7 @@ loadFragment.onclick = () => { // const molecule = Molecule.fromMolfile(molfile); const molecule = Molecule.fromSmiles('CCC'); molecule.setFragment(true); - editor.setMolecule(molecule); + getEditor().setMolecule(molecule); }; const loadReaction = document.getElementById( @@ -213,5 +202,5 @@ loadReaction.onclick = () => { // const reaction = Reaction.fromRxn(rxn); // reaction.addCatalyst(Molecule.fromSmiles('CO')); console.log(reaction.toSmiles()); - editor.setReaction(reaction); + getEditor().setReaction(reaction); }; diff --git a/examples/generic_editor/demo/editor.ts b/examples/generic_editor/demo/editor.ts new file mode 100644 index 00000000..ee27325f --- /dev/null +++ b/examples/generic_editor/demo/editor.ts @@ -0,0 +1,43 @@ +import OCL from '../../../distesm/full.pretty'; +import { + incrementChangeCount, + resetChangeCount, + updateIDCode, + updateMolfile, +} from './result.ts'; + +let editor: OCL.CanvasEditor | undefined; + +export function resetEditor() { + if (editor) { + editor.destroy(); + } + const modeSelect = document.getElementById('modeSelect') as HTMLSelectElement; + const newEditor = new OCL.CanvasEditor( + document.getElementById('editor') as HTMLElement, + { + initialMode: modeSelect.value as OCL.CanvasEditorMode, + }, + ); + + editor = newEditor; + + resetChangeCount(); + + editor.setOnChangeListener(({ type, isUserEvent }) => { + if (type === 'molecule') { + if (isUserEvent) { + incrementChangeCount(); + } + updateIDCode(newEditor.getMolecule()); + updateMolfile(newEditor.getMolecule()); + } + }); +} + +export function getEditor(): OCL.CanvasEditor { + if (!editor) { + throw new Error('Editor not initialized'); + } + return editor; +} diff --git a/examples/generic_editor/demo/result.ts b/examples/generic_editor/demo/result.ts new file mode 100644 index 00000000..76817656 --- /dev/null +++ b/examples/generic_editor/demo/result.ts @@ -0,0 +1,25 @@ +import OCL from '../../../distesm/full.pretty'; + +const changeCountDiv = document.getElementById('changeCount') as HTMLElement; + +export function incrementChangeCount() { + const currentChangeCount = parseInt(changeCountDiv.innerText, 10); + changeCountDiv.innerText = String(currentChangeCount + 1); +} + +export function resetChangeCount() { + changeCountDiv.innerText = '0'; +} + +const idcodeDiv = document.getElementById('idcode') as HTMLElement; + +export function updateIDCode(molecule: OCL.Molecule) { + const idcodeAndCoords = molecule.getIDCodeAndCoordinates(); + idcodeDiv.innerText = `${idcodeAndCoords.idCode} ${idcodeAndCoords.coordinates}`; +} + +const molfileDiv = document.getElementById('molfile') as HTMLElement; + +export function updateMolfile(molecule: OCL.Molecule) { + molfileDiv.innerText = molecule.toMolfileV3(); +} diff --git a/full.d.ts b/full.d.ts index bbc78b23..7633fdf1 100644 --- a/full.d.ts +++ b/full.d.ts @@ -9,6 +9,7 @@ export { OnChangeEventType, OnChangeEvent, OnChangeListenerCallback, + CanvasEditorMode, CanvasEditorOptions, CanvasEditor, } from './types'; From df193bce59f7ae6c11c99529408f41dce2ebd3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 24 Jul 2024 14:31:01 +0200 Subject: [PATCH 30/64] docs: retitle demo page --- examples/generic_editor/demo.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/generic_editor/demo.html b/examples/generic_editor/demo.html index 616f63d2..794b2688 100644 --- a/examples/generic_editor/demo.html +++ b/examples/generic_editor/demo.html @@ -2,12 +2,12 @@ - Generic editor - PoC + Generic editor - Demo -

Demo - Generic editor

+

Generic editor - Demo

Reset +
{ resetEditor(); }; +const clearButton = document.getElementById('clearButton') as HTMLButtonElement; +clearButton.onclick = () => { + getEditor().clearAll(); +}; + const loadMolecule = document.getElementById( 'loadMolecule', ) as HTMLButtonElement; diff --git a/lib/canvas_editor/index.js b/lib/canvas_editor/index.js index c8270f60..093d6240 100644 --- a/lib/canvas_editor/index.js +++ b/lib/canvas_editor/index.js @@ -77,6 +77,10 @@ function createCanvasEditor(JavaEditorArea, JavaEditorToolbar, JavaUIHelper) { this.#onChange = null; } + clearAll() { + this.#editorArea.clearAll(); + } + destroy() { this.#checkNotDestroyed(); this.#destroy(); diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java index b75ab095..d226bbb1 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java @@ -183,4 +183,8 @@ private native JavaScriptObject getDrawContextFromJsObject() public void repaint() { draw(); } + + public void clearAll() { + mDrawArea.clearAll(); + } } diff --git a/types.d.ts b/types.d.ts index 49567877..d72fcf75 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3764,6 +3764,12 @@ export declare class CanvasEditor { */ removeOnChangeListener(): void; + /** + * Clear the editor state. + * Same as clicking on the clear button in the toolbar. + */ + clearAll(): void; + /** * Destroy the editor. * This should be called when the editor is no longer needed to free resources. From e3e3bb98e65dd5eaa31c698f3c5207a008cf03f3 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:14:30 +0200 Subject: [PATCH 32/64] fix: set highlight color to blue NB, I found the default color for AquaLookAndFeel theme in java (0x000080), but it's override by OS accent color, so I picked the macos highlight color from default blue theme. Closes: https://github.com/orgs/cheminfo/projects/11/views/1?pane=issue&itemId=72035204 --- lib/canvas_editor/draw_context.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/canvas_editor/draw_context.js b/lib/canvas_editor/draw_context.js index fbbfe4d2..1ad82f6c 100644 --- a/lib/canvas_editor/draw_context.js +++ b/lib/canvas_editor/draw_context.js @@ -34,7 +34,9 @@ class DrawContext { } getSelectionBackgroundRGB() { - return 0xeb8282; + // return 0xeb8282; // arbitrary color + // return 0x000080; // color from java default AquaLookAndFeel "TextArea.selectionBackground" + return 0xbbd6fc; // color from default macos highlight-accent color (blue) } getLineWidth() { @@ -145,6 +147,10 @@ class DrawContext { fullScaleContext.putImageData(imageData, 0, 0); this.ctx.drawImage(fullScaleCanvas, sx, sy, sw, sh, dx, dy, dw, dh); } + + isDarkBackground() { + return false; + } } module.exports = DrawContext; From f178b1e9b5e57433bb38b11f2637fbced431869b Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:42:30 +0200 Subject: [PATCH 33/64] refactor: move seeds data --- examples/generic_editor/demo/demo.ts | 167 +----------------- examples/generic_editor/demo/seeds/molfile.ts | 95 ++++++++++ examples/generic_editor/demo/seeds/rxn.ts | 72 ++++++++ 3 files changed, 169 insertions(+), 165 deletions(-) create mode 100644 examples/generic_editor/demo/seeds/molfile.ts create mode 100644 examples/generic_editor/demo/seeds/rxn.ts diff --git a/examples/generic_editor/demo/demo.ts b/examples/generic_editor/demo/demo.ts index de4fcb37..d0407878 100644 --- a/examples/generic_editor/demo/demo.ts +++ b/examples/generic_editor/demo/demo.ts @@ -1,173 +1,10 @@ import OCL from '../../../distesm/full.pretty'; import { getEditor, resetEditor } from './editor.ts'; +import rxn from './seeds/rxn.ts'; +import molfile from './seeds/molfile.ts'; const { Molecule, Reaction } = OCL; -const rxn = `$RXN - - -JME Molecular Editor - 2 3 -$MOL -C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC -JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 - - 6 6 0 0 0 0 0 0 0 0999 V2000 - 2.4249 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 2.4249 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.2124 2.8000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1 2 1 0 0 0 0 - 2 3 2 0 0 0 0 - 3 4 1 0 0 0 0 - 4 5 2 0 0 0 0 - 5 6 1 0 0 0 0 - 6 1 2 0 0 0 0 -M END -$MOL -C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC -JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 - - 2 1 0 0 0 0 0 0 0 0999 V2000 - 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1 2 1 0 0 0 0 -M END -$MOL -C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC -JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 - - 6 6 0 0 0 0 0 0 0 0999 V2000 - 2.4249 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 2.4249 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.2124 2.8000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1 2 1 0 0 0 0 - 2 3 2 0 0 0 0 - 3 4 1 0 0 0 0 - 4 5 2 0 0 0 0 - 5 6 1 0 0 0 0 - 6 1 2 0 0 0 0 -M END -$MOL -C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC -JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 - - 3 2 0 0 0 0 0 0 0 0999 V2000 - 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.2124 1.4000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1 2 1 0 0 0 0 - 2 3 1 0 0 0 0 -M END -$MOL -C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC -JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 - - 2 1 0 0 0 0 0 0 0 0999 V2000 - 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 1 2 1 0 0 0 0 -M END`; - -const molfile = `446220 --OEChem-02022300542D - - 43 45 0 1 0 0 0 0 0999 V2000 - 5.5851 -0.3586 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 - 6.5407 -2.1051 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 - 8.2717 -2.0446 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 - 4.9927 1.2690 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 - 8.0971 2.2384 0.0000 N 0 0 3 0 0 0 0 0 0 0 0 0 - 8.5749 0.2899 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 - 8.8337 1.2558 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 - 7.3538 -0.5758 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 - 9.9738 -0.2189 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 10.2326 0.7470 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 6.8004 0.9447 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 6.5248 -0.0166 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 - 7.5971 3.1045 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 7.3888 -1.5752 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 4.8191 0.2842 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 6.5756 -3.1045 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 3.8794 -0.0578 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 3.7057 -1.0426 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 3.1133 0.5850 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 2.7660 -1.3846 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 2.1736 0.2429 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 2.0000 -0.7419 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 - 8.3549 -0.5311 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 9.0537 2.0769 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 6.8116 -0.8763 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 9.7789 -0.8075 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 10.5678 -0.3967 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 10.8266 0.5692 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 10.4276 1.3356 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 6.7954 1.5647 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 6.1861 1.0285 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 6.4493 -0.6319 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 8.1340 3.4145 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 7.2871 3.6414 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 7.0601 2.7945 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 5.9560 -3.1261 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 6.5972 -3.7241 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 7.1952 -3.0828 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 4.1807 -1.4412 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 3.2210 1.1955 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 2.6584 -1.9952 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 1.6987 0.6415 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 1.4174 -0.9539 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 - 12 1 1 1 0 0 0 - 1 15 1 0 0 0 0 - 2 14 1 0 0 0 0 - 2 16 1 0 0 0 0 - 3 14 2 0 0 0 0 - 4 15 2 0 0 0 0 - 5 6 1 0 0 0 0 - 5 7 1 0 0 0 0 - 5 13 1 0 0 0 0 - 6 8 1 0 0 0 0 - 6 9 1 0 0 0 0 - 6 23 1 1 0 0 0 - 7 10 1 0 0 0 0 - 7 11 1 0 0 0 0 - 7 24 1 6 0 0 0 - 8 12 1 0 0 0 0 - 8 14 1 1 0 0 0 - 8 25 1 0 0 0 0 - 9 10 1 0 0 0 0 - 9 26 1 0 0 0 0 - 9 27 1 0 0 0 0 - 10 28 1 0 0 0 0 - 10 29 1 0 0 0 0 - 11 12 1 0 0 0 0 - 11 30 1 0 0 0 0 - 11 31 1 0 0 0 0 - 12 32 1 0 0 0 0 - 13 33 1 0 0 0 0 - 13 34 1 0 0 0 0 - 13 35 1 0 0 0 0 - 15 17 1 0 0 0 0 - 16 36 1 0 0 0 0 - 16 37 1 0 0 0 0 - 16 38 1 0 0 0 0 - 17 18 2 0 0 0 0 - 17 19 1 0 0 0 0 - 18 20 1 0 0 0 0 - 18 39 1 0 0 0 0 - 19 21 2 0 0 0 0 - 19 40 1 0 0 0 0 - 20 22 2 0 0 0 0 - 20 41 1 0 0 0 0 - 21 22 1 0 0 0 0 - 21 42 1 0 0 0 0 - 22 43 1 0 0 0 0 -M END`; - resetEditor(); const resetButton = document.getElementById('resetButton') as HTMLButtonElement; diff --git a/examples/generic_editor/demo/seeds/molfile.ts b/examples/generic_editor/demo/seeds/molfile.ts new file mode 100644 index 00000000..5a9a8400 --- /dev/null +++ b/examples/generic_editor/demo/seeds/molfile.ts @@ -0,0 +1,95 @@ +const molfile = `446220 +-OEChem-02022300542D + + 43 45 0 1 0 0 0 0 0999 V2000 + 5.5851 -0.3586 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5407 -2.1051 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 8.2717 -2.0446 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.9927 1.2690 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 8.0971 2.2384 0.0000 N 0 0 3 0 0 0 0 0 0 0 0 0 + 8.5749 0.2899 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 + 8.8337 1.2558 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 + 7.3538 -0.5758 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 + 9.9738 -0.2189 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 10.2326 0.7470 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.8004 0.9447 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5248 -0.0166 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 + 7.5971 3.1045 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.3888 -1.5752 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.8191 0.2842 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5756 -3.1045 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.8794 -0.0578 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.7057 -1.0426 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.1133 0.5850 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.7660 -1.3846 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.1736 0.2429 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.0000 -0.7419 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 8.3549 -0.5311 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 9.0537 2.0769 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.8116 -0.8763 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 9.7789 -0.8075 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 10.5678 -0.3967 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 10.8266 0.5692 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 10.4276 1.3356 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.7954 1.5647 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.1861 1.0285 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.4493 -0.6319 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 8.1340 3.4145 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 7.2871 3.6414 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 7.0601 2.7945 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 5.9560 -3.1261 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5972 -3.7241 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 7.1952 -3.0828 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.1807 -1.4412 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.2210 1.1955 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.6584 -1.9952 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.6987 0.6415 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.4174 -0.9539 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 + 12 1 1 1 0 0 0 + 1 15 1 0 0 0 0 + 2 14 1 0 0 0 0 + 2 16 1 0 0 0 0 + 3 14 2 0 0 0 0 + 4 15 2 0 0 0 0 + 5 6 1 0 0 0 0 + 5 7 1 0 0 0 0 + 5 13 1 0 0 0 0 + 6 8 1 0 0 0 0 + 6 9 1 0 0 0 0 + 6 23 1 1 0 0 0 + 7 10 1 0 0 0 0 + 7 11 1 0 0 0 0 + 7 24 1 6 0 0 0 + 8 12 1 0 0 0 0 + 8 14 1 1 0 0 0 + 8 25 1 0 0 0 0 + 9 10 1 0 0 0 0 + 9 26 1 0 0 0 0 + 9 27 1 0 0 0 0 + 10 28 1 0 0 0 0 + 10 29 1 0 0 0 0 + 11 12 1 0 0 0 0 + 11 30 1 0 0 0 0 + 11 31 1 0 0 0 0 + 12 32 1 0 0 0 0 + 13 33 1 0 0 0 0 + 13 34 1 0 0 0 0 + 13 35 1 0 0 0 0 + 15 17 1 0 0 0 0 + 16 36 1 0 0 0 0 + 16 37 1 0 0 0 0 + 16 38 1 0 0 0 0 + 17 18 2 0 0 0 0 + 17 19 1 0 0 0 0 + 18 20 1 0 0 0 0 + 18 39 1 0 0 0 0 + 19 21 2 0 0 0 0 + 19 40 1 0 0 0 0 + 20 22 2 0 0 0 0 + 20 41 1 0 0 0 0 + 21 22 1 0 0 0 0 + 21 42 1 0 0 0 0 + 22 43 1 0 0 0 0 +M END`; + +export default molfile; diff --git a/examples/generic_editor/demo/seeds/rxn.ts b/examples/generic_editor/demo/seeds/rxn.ts new file mode 100644 index 00000000..044ba2c0 --- /dev/null +++ b/examples/generic_editor/demo/seeds/rxn.ts @@ -0,0 +1,72 @@ +const rxn = `$RXN + + +JME Molecular Editor + 2 3 +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 6 6 0 0 0 0 0 0 0 0999 V2000 + 2.4249 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.4249 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 2.8000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 2 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 2 0 0 0 0 + 5 6 1 0 0 0 0 + 6 1 2 0 0 0 0 +M END +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M END +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 6 6 0 0 0 0 0 0 0 0999 V2000 + 2.4249 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.4249 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 2.8000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 2 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 2 0 0 0 0 + 5 6 1 0 0 0 0 + 6 1 2 0 0 0 0 +M END +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 3 2 0 0 0 0 0 0 0 0999 V2000 + 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 1.4000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 1 0 0 0 0 +M END +$MOL +C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC +JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015 + + 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M END`; + +export default rxn; From 198f11f9d3583e685ce201ea1bd63b923a3fbe2f Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:01:46 +0200 Subject: [PATCH 34/64] fix: do not make first draw to soon --- lib/canvas_editor/create_editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/canvas_editor/create_editor.js b/lib/canvas_editor/create_editor.js index 664c8d27..79bdc54d 100644 --- a/lib/canvas_editor/create_editor.js +++ b/lib/canvas_editor/create_editor.js @@ -63,7 +63,7 @@ function createEditor( uiHelper.setEditorArea(editorArea); - editorArea.draw(); + requestIdleCallback(() => editorArea.draw()); const resizeObserver = new ResizeObserver(([entry]) => { editorCanvas.width = entry.contentRect.width; From 999f091b8c5f93f0d028742acce20090e3a97b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 24 Jul 2024 16:19:53 +0200 Subject: [PATCH 35/64] fix: draw toolbar before editor area So the size of the canvas can be computed --- examples/generic_editor/demo/demo.ts | 2 -- lib/canvas_editor/create_editor.js | 19 +++++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/examples/generic_editor/demo/demo.ts b/examples/generic_editor/demo/demo.ts index d0407878..e5a6daee 100644 --- a/examples/generic_editor/demo/demo.ts +++ b/examples/generic_editor/demo/demo.ts @@ -1,7 +1,5 @@ import OCL from '../../../distesm/full.pretty'; import { getEditor, resetEditor } from './editor.ts'; -import rxn from './seeds/rxn.ts'; -import molfile from './seeds/molfile.ts'; const { Molecule, Reaction } = OCL; diff --git a/lib/canvas_editor/create_editor.js b/lib/canvas_editor/create_editor.js index 79bdc54d..f2812241 100644 --- a/lib/canvas_editor/create_editor.js +++ b/lib/canvas_editor/create_editor.js @@ -49,10 +49,6 @@ function createEditor( parentElement.append(rootElement); - const containerSize = editorContainer.getBoundingClientRect(); - editorCanvas.width = containerSize.width; - editorCanvas.height = containerSize.height; - const uiHelper = new JavaUIHelper(new UIHelper(editorCanvas, JavaEditorArea)); const editorArea = new JavaEditorArea( @@ -60,10 +56,17 @@ function createEditor( new EditorArea(editorCanvas, onChange), uiHelper, ); - uiHelper.setEditorArea(editorArea); - requestIdleCallback(() => editorArea.draw()); + const toolbar = readOnly + ? null + : new JavaEditorToolbar(editorArea, new Toolbar(toolbarCanvas)); + + const containerSize = editorContainer.getBoundingClientRect(); + editorCanvas.width = containerSize.width; + editorCanvas.height = containerSize.height; + + editorArea.draw(); const resizeObserver = new ResizeObserver(([entry]) => { editorCanvas.width = entry.contentRect.width; @@ -72,10 +75,6 @@ function createEditor( }); resizeObserver.observe(editorContainer); - const toolbar = readOnly - ? null - : new JavaEditorToolbar(editorArea, new Toolbar(toolbarCanvas)); - let removePointerListeners = null; let removeKeyboardListeners = null; let removeToolbarPointerListeners = null; From b93918176fe236a3a4648d99b314f29fbe8d8d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 24 Jul 2024 16:29:47 +0200 Subject: [PATCH 36/64] fix: select current tool before redraw --- scripts/openchemlib/classes.js | 12 ++++++++++++ .../research/gui/editor/GenericEditorArea.java | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/openchemlib/classes.js b/scripts/openchemlib/classes.js index 1360b477..99d12e0a 100644 --- a/scripts/openchemlib/classes.js +++ b/scripts/openchemlib/classes.js @@ -335,6 +335,18 @@ function changeGenericEditorArea(code) { 'private void openReaction() {}', ); code = code.replaceAll('System.getProperty("development") != null', 'false'); + // TODO: remove after the fix has been merged upstream. + code = code.replaceAll(methodRegExp('toolChanged'), `public void toolChanged(int newTool) { +\t\tif (mCurrentTool != newTool) { +\t\t\tif (mCurrentTool == GenericEditorToolbar.cToolMapper +\t\t\t\t\t|| newTool == GenericEditorToolbar.cToolMapper) { +\t\t\t\tmCurrentTool = newTool; +\t\t\t\tupdate(UPDATE_REDRAW); +\t\t\t} else { +\t\t\t\tmCurrentTool = newTool; +\t\t\t} +\t\t} +\t}`) return code; } diff --git a/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/editor/GenericEditorArea.java b/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/editor/GenericEditorArea.java index 54b6e4b7..ad19b810 100644 --- a/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/editor/GenericEditorArea.java +++ b/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/editor/GenericEditorArea.java @@ -593,10 +593,11 @@ public void toolChanged(int newTool) { if (mCurrentTool != newTool) { if (mCurrentTool == GenericEditorToolbar.cToolMapper || newTool == GenericEditorToolbar.cToolMapper) { + mCurrentTool = newTool; update(UPDATE_REDRAW); + } else { + mCurrentTool = newTool; } - - mCurrentTool = newTool; } } From f6025c339fc83fd30c70c8afbae52c32e1146065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 24 Jul 2024 16:47:20 +0200 Subject: [PATCH 37/64] fix: jsDialog --- .../research/gwt/gui/generic/JSDialog.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/com/actelion/research/gwt/gui/generic/JSDialog.java b/src/com/actelion/research/gwt/gui/generic/JSDialog.java index 586e8cf9..5872c03e 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSDialog.java +++ b/src/com/actelion/research/gwt/gui/generic/JSDialog.java @@ -42,7 +42,7 @@ public native void add(GenericComponent c, int x1, int y1, int x2, int y2) public native GenericCheckBox createCheckBox(String text) /*-{ var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - var checkBox = dialog.createCheckBox(text); + var checkBox = jsDialog.createCheckBox(text); return @com.actelion.research.gwt.gui.generic.JSCheckBox::new(Lcom/google/gwt/core/client/JavaScriptObject;)(checkBox); }-*/; @@ -50,7 +50,7 @@ public native GenericCheckBox createCheckBox(String text) public native GenericComboBox createComboBox() /*-{ var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - var comboBox = dialog.createComboBox(); + var comboBox = jsDialog.createComboBox(); return @com.actelion.research.gwt.gui.generic.JSComboBox::new(Lcom/google/gwt/core/client/JavaScriptObject;)(comboBox); }-*/; @@ -58,7 +58,7 @@ public native GenericComboBox createComboBox() public native GenericLabel createLabel(String text) /*-{ var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - var label = dialog.createLabel(text); + var label = jsDialog.createLabel(text); return @com.actelion.research.gwt.gui.generic.JSLabel::new(Lcom/google/gwt/core/client/JavaScriptObject;)(label); }-*/; @@ -66,7 +66,7 @@ public native GenericLabel createLabel(String text) public native GenericTextField createTextField(int width, int height) /*-{ var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - var textField = dialog.createTextField(width, height); + var textField = jsDialog.createTextField(width, height); return @com.actelion.research.gwt.gui.generic.JSTextField::new(Lcom/google/gwt/core/client/JavaScriptObject;)(textField); }-*/; @@ -84,28 +84,28 @@ public native void setEventConsumer(GenericEventListener con that.@com.actelion.research.gwt.gui.generic.JSDialog::fireCancel()(); } }; - dialog.setEventConsumer(jsConsumer); + jsDialog.setEventConsumer(jsConsumer); }-*/; @Override public native void showDialog() /*-{ var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - dialog.showDialog(); + jsDialog.showDialog(); }-*/; @Override public native void disposeDialog() /*-{ var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - dialog.disposeDialog(); + jsDialog.disposeDialog(); }-*/; @Override public native void showMessage(String message) /*-{ var jsDialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()(); - dialog.showMessage(message); + jsDialog.showMessage(message); }-*/; private void fireOk() { From 9d5ebee15ac6a50a240dd7bb7b21884271ba4c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 24 Jul 2024 17:01:42 +0200 Subject: [PATCH 38/64] docs: update demo examples --- examples/generic_editor/demo.html | 1 + examples/generic_editor/demo/demo.ts | 22 +-- examples/generic_editor/demo/seeds/molfile.ts | 2 +- examples/generic_editor/demo/seeds/rxn.ts | 136 +++++++++--------- 4 files changed, 84 insertions(+), 77 deletions(-) diff --git a/examples/generic_editor/demo.html b/examples/generic_editor/demo.html index 94f9fe7e..b9232f1d 100644 --- a/examples/generic_editor/demo.html +++ b/examples/generic_editor/demo.html @@ -23,6 +23,7 @@

Generic editor - Demo

+
diff --git a/examples/generic_editor/demo/editor.ts b/examples/generic_editor/demo/editor.ts index ee27325f..589ecf95 100644 --- a/examples/generic_editor/demo/editor.ts +++ b/examples/generic_editor/demo/editor.ts @@ -3,7 +3,7 @@ import { incrementChangeCount, resetChangeCount, updateIDCode, - updateMolfile, + updateMolfileOrRxn, } from './result.ts'; let editor: OCL.CanvasEditor | undefined; @@ -29,8 +29,17 @@ export function resetEditor() { if (isUserEvent) { incrementChangeCount(); } - updateIDCode(newEditor.getMolecule()); - updateMolfile(newEditor.getMolecule()); + const mode = newEditor.getMode(); + if (mode === 'molecule') { + const molecule = newEditor.getMolecule(); + updateIDCode(molecule.getIDCode()); + updateMolfileOrRxn(molecule.toMolfileV3()); + } else { + const reaction = newEditor.getReaction(); + const encoder = new OCL.ReactionEncoder(); + updateIDCode(OCL.ReactionEncoder.encode(reaction)); + updateMolfileOrRxn(reaction.toRxnV3()); + } } }); } diff --git a/examples/generic_editor/demo/result.ts b/examples/generic_editor/demo/result.ts index 76817656..89110674 100644 --- a/examples/generic_editor/demo/result.ts +++ b/examples/generic_editor/demo/result.ts @@ -1,5 +1,3 @@ -import OCL from '../../../distesm/full.pretty'; - const changeCountDiv = document.getElementById('changeCount') as HTMLElement; export function incrementChangeCount() { @@ -13,13 +11,12 @@ export function resetChangeCount() { const idcodeDiv = document.getElementById('idcode') as HTMLElement; -export function updateIDCode(molecule: OCL.Molecule) { - const idcodeAndCoords = molecule.getIDCodeAndCoordinates(); - idcodeDiv.innerText = `${idcodeAndCoords.idCode} ${idcodeAndCoords.coordinates}`; +export function updateIDCode(idCode: string) { + idcodeDiv.innerText = idCode; } -const molfileDiv = document.getElementById('molfile') as HTMLElement; +const molfileArea = document.getElementById('molfile') as HTMLTextAreaElement; -export function updateMolfile(molecule: OCL.Molecule) { - molfileDiv.innerText = molecule.toMolfileV3(); +export function updateMolfileOrRxn(value: string) { + molfileArea.value = value; } diff --git a/lib/canvas_editor/init/canvas_editor.js b/lib/canvas_editor/init/canvas_editor.js index ef2c787a..5b6f0c98 100644 --- a/lib/canvas_editor/init/canvas_editor.js +++ b/lib/canvas_editor/init/canvas_editor.js @@ -39,6 +39,12 @@ function initCanvasEditor(JavaEditorArea, JavaEditorToolbar, JavaUIHelper) { this.#destroy = destroy; } + getMode() { + const mode = this.#editorArea.getMode(); + const isReaction = mode & (JavaEditorArea.MODE_REACTION !== 0); + return isReaction ? 'reaction' : 'molecule'; + } + setMolecule(molecule) { this.#checkNotDestroyed(); @@ -60,7 +66,7 @@ function initCanvasEditor(JavaEditorArea, JavaEditorToolbar, JavaUIHelper) { getReaction() { this.#checkNotDestroyed(); - this.#editorArea.getReaction(); + return this.#editorArea.getReaction(); } setOnChangeListener(onChange) { diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java index 8aac2fed..dacf9af8 100644 --- a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java +++ b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java @@ -191,4 +191,8 @@ public native void repaint() public void clearAll() { mDrawArea.clearAll(); } + + public int getMode() { + return mDrawArea.getMode(); + } } diff --git a/types.d.ts b/types.d.ts index d72fcf75..ff1d422b 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3729,6 +3729,11 @@ export declare class CanvasEditor { */ constructor(element: HTMLElement, options?: CanvasEditorOptions); + /** + * Get the current editor mode. + */ + getMode(): CanvasEditorMode; + /** * Set the molecule to be edited. * Actions in the editor will mutate the molecule object directly. From 2a79004f4031a5f60b3c09e9b05b19fb6a3596ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 08:59:04 +0200 Subject: [PATCH 43/64] docs: empty all results on reset --- examples/generic_editor/demo/editor.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/generic_editor/demo/editor.ts b/examples/generic_editor/demo/editor.ts index 589ecf95..4370fa3b 100644 --- a/examples/generic_editor/demo/editor.ts +++ b/examples/generic_editor/demo/editor.ts @@ -23,6 +23,8 @@ export function resetEditor() { editor = newEditor; resetChangeCount(); + updateIDCode(''); + updateMolfileOrRxn(''); editor.setOnChangeListener(({ type, isUserEvent }) => { if (type === 'molecule') { From 467259a796bcc5ea1fef178dbdb5a9278f2afd2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 08:59:48 +0200 Subject: [PATCH 44/64] docs: remove unused variable --- examples/generic_editor/demo/editor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/generic_editor/demo/editor.ts b/examples/generic_editor/demo/editor.ts index 4370fa3b..4bb36492 100644 --- a/examples/generic_editor/demo/editor.ts +++ b/examples/generic_editor/demo/editor.ts @@ -38,7 +38,6 @@ export function resetEditor() { updateMolfileOrRxn(molecule.toMolfileV3()); } else { const reaction = newEditor.getReaction(); - const encoder = new OCL.ReactionEncoder(); updateIDCode(OCL.ReactionEncoder.encode(reaction)); updateMolfileOrRxn(reaction.toRxnV3()); } From f7eda57f95f5d06128b7b692d99fe69c8a1f6105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 09:11:50 +0200 Subject: [PATCH 45/64] chore: update exposed name --- __tests__/__snapshots__/library.js.snap | 4 ++++ __tests__/library.js | 1 + full.js | 2 +- full.pretty.js | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/__tests__/__snapshots__/library.js.snap b/__tests__/__snapshots__/library.js.snap index 539c95b1..62a01d5b 100644 --- a/__tests__/__snapshots__/library.js.snap +++ b/__tests__/__snapshots__/library.js.snap @@ -407,6 +407,8 @@ exports[`prototype properties of Transformer 1`] = ` exports[`prototype properties of Util 1`] = `[]`; +exports[`prototype properties of registerCustomElement 1`] = `[]`; + exports[`static properties of CanonizerUtil 1`] = ` [ "BACKBONE", @@ -780,3 +782,5 @@ exports[`static properties of Util 1`] = ` "getHoseCodesFromDiastereotopicID", ] `; + +exports[`static properties of registerCustomElement 1`] = `[]`; diff --git a/__tests__/library.js b/__tests__/library.js index 06bc5af3..2857029b 100644 --- a/__tests__/library.js +++ b/__tests__/library.js @@ -33,6 +33,7 @@ const coreAPI = [ const fullAPI = [ 'CanvasEditor', + 'registerCustomElement', 'StructureView', 'StructureEditor', 'SVGRenderer', diff --git a/full.js b/full.js index 425bd42d..cbd0fb85 100644 --- a/full.js +++ b/full.js @@ -5,7 +5,7 @@ const OCL = require('./dist/openchemlib-full.js'); require('./lib/canvas_editor/init')(OCL); exports.CanvasEditor = OCL.CanvasEditor; -exports.registerCanvasEditor = OCL.registerCanvasEditor; +exports.registerCustomElement = OCL.registerCustomElement; exports.default = OCL; exports.CanonizerUtil = OCL.CanonizerUtil; diff --git a/full.pretty.js b/full.pretty.js index 4ec7b22d..1f5509da 100644 --- a/full.pretty.js +++ b/full.pretty.js @@ -5,7 +5,7 @@ const OCL = require('./dist/openchemlib-full.pretty.js'); require('./lib/canvas_editor/init')(OCL); exports.CanvasEditor = OCL.CanvasEditor; -exports.registerCanvasEditor = OCL.registerCanvasEditor; +exports.registerCustomElement = OCL.registerCustomElement; exports.default = OCL; exports.CanonizerUtil = OCL.CanonizerUtil; From 1e542ac0e5765a9f12097c2295092c07d52a0402 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:06:43 +0200 Subject: [PATCH 46/64] chore: continue implem of custom element --- .../init/canvas_editor_element.js | 156 ++++++++++++++++-- lib/canvas_editor/init/index.js | 7 +- 2 files changed, 144 insertions(+), 19 deletions(-) diff --git a/lib/canvas_editor/init/canvas_editor_element.js b/lib/canvas_editor/init/canvas_editor_element.js index ada86299..9ebfcaca 100644 --- a/lib/canvas_editor/init/canvas_editor_element.js +++ b/lib/canvas_editor/init/canvas_editor_element.js @@ -1,6 +1,25 @@ 'use strict'; -function initCanvasEditorElement(CanvasEditor, Molecule) { +function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { + /** + * `` support 4 observable attributes : + * + * - idcode: init molecule with `Molecule.fromIDCode(idcode)` or reaction with `ReactionEncoder.decode(this.idcode)`. + * fallback to an empty string if not defined. + * - fragment: enable or disable fragment on Molecule / Reaction. + * fallback to false if not defined. + * - mode: can be either `'molecule'` or `'reaction'`. `CanvasEditorElement.MODE` map these values for enum style. + * fallback to `CanvasEditorElement.MODE.MOLECULE` if not defined. + * - readonly: if enable, it do not add toolbar and don't handle user interactions. + * fallback to false. + * + * @example + * + * Empty editor: + * ```html + * + * ``` + */ class CanvasEditorElement extends HTMLElement { /** @type {{MOLECULE: 'molecule', REACTION: 'reaction'}} */ static MODE = Object.freeze( @@ -23,42 +42,121 @@ function initCanvasEditorElement(CanvasEditor, Molecule) { CanvasEditorElement.MODE.MOLECULE; /** @type {boolean} */ readonly = false; - /** @type {CanvasEditor} */ #editor; + /* --- custom element api --- */ + /** + * @param {Molecule} molecule + */ + setMolecule(molecule) { + this.fragment = molecule.isFragment(); + this.idcode = molecule.getIDCode(); + + this.#editor.setMolecule(molecule); + } /** - * Custom element added to page. + * @return {Molecule} */ - connectedCallback() { - const root = this.attachShadow({ mode: 'open' }); - root.adoptedStyleSheets = [new CSSStyleSheet()]; + getMolecule() { + return this.#editor.getMolecule(); + } + + /** + * @param {Reaction} reaction + */ + setReaction(reaction) { + this.fragment = reaction.isFragment(); + this.idcode = ReactionEncoder.encode(reaction); - this.#editor = new CanvasEditor(root, { + this.#editor.setReaction(reaction); + } + + /** + * @return {Reaction} + */ + getReaction() { + return this.#editor.getReaction(); + } + + clearAll() { + this.#editor.clearAll(); + this.idcode = ''; + } + + /* --- internals --- */ + /** @type {CanvasEditor} */ #editor; + + #initEditor() { + this.#editor = new CanvasEditor(this.shadowRoot, { readOnly: this.readonly, initialMode: this.mode, }); + this.#editor.setOnChangeListener(this.#handleChange); + this.#initIdCode(); + } + + #initIdCode() { switch (this.mode) { case this.constructor.MODE.MOLECULE: { - const molecule = Molecule.fromIDCode(this.idcode || ''); - molecule.setFragment(this.fragment); - - return this.#editor.setMolecule(molecule); + return this.#initMolecule(); } case this.constructor.MODE.REACTION: { - throw new Error( - 'Unsupported because `Reaction.fromIDCode` do not exists', - ); + return this.#initReaction(); } default: throw new Error(`Mode ${this.mode} is not supported`); } } + #initMolecule() { + const molecule = Molecule.fromIDCode(this.idcode); + molecule.setFragment(this.fragment); + + this.#editor.setMolecule(molecule); + } + + #initReaction() { + const reaction = ReactionEncoder.decode(this.idcode); + reaction.setFragment(this.fragment); + + this.#editor.setReaction(reaction); + } + + #handleChange = (editorEventOnChange) => { + const domEvent = new CustomEvent('change', { + detail: editorEventOnChange, + }); + this.dispatchEvent(domEvent); + }; + + #destroyEditor() { + this.#editor.destroy(); + this.#editor = undefined; + } + + #resetEditor() { + this.#destroyEditor(); + this.#initEditor(); + } + + /* --- lifecycle hooks --- */ + /** + * Custom element added to page. + */ + connectedCallback() { + this.attachShadow({ mode: 'open' }); + this.shadowRoot.adoptedStyleSheets = [new CSSStyleSheet()]; + this.#initEditor(); + } + /** * Custom element removed from page. */ - // eslint-disable-next-line no-empty-function - disconnectedCallback() {} + disconnectedCallback() { + if (!this.#editor) return; + this.#editor.destroy(); + this.#editor = undefined; + } /** * Custom element moved to new page. @@ -69,8 +167,30 @@ function initCanvasEditorElement(CanvasEditor, Molecule) { /** * Attribute ${name} has changed from ${oldValue} to ${newValue} */ - // eslint-disable-next-line no-unused-vars,no-empty-function - attributeChangedCallback(name, oldValue, newValue) {} + attributeChangedCallback(name, oldValue, newValue) { + switch (name) { + case 'idcode': { + this.idcode = newValue; + return void this.#initIdCode(); + } + case 'fragment': { + this.fragment = newValue; + const molecule = this.#editor.getMolecule(); + molecule.setFragment(this.fragment); + return void this.#editor.setMolecule(molecule); + } + case 'mode': { + this.mode = newValue; + return void this.#resetEditor(); + } + case 'readonly': { + this.readonly = newValue; + return void this.#resetEditor(); + } + default: + throw new Error('unsupported attribute change'); + } + } } return CanvasEditorElement; diff --git a/lib/canvas_editor/init/index.js b/lib/canvas_editor/init/index.js index c655d79b..df802297 100644 --- a/lib/canvas_editor/init/index.js +++ b/lib/canvas_editor/init/index.js @@ -9,6 +9,7 @@ function init(OCL) { GenericEditorToolbar: JavaEditorToolbar, GenericUIHelper: JavaUIHelper, Molecule, + ReactionEncoder, } = OCL; const CanvasEditor = initCanvasEditor( @@ -18,7 +19,11 @@ function init(OCL) { ); function registerCustomElement() { - const CanvasEditorElement = initCanvasEditorElement(CanvasEditor, Molecule); + const CanvasEditorElement = initCanvasEditorElement( + CanvasEditor, + Molecule, + ReactionEncoder, + ); customElements.define('openchemlib-editor', CanvasEditorElement); } From 5a787be6ff79dffa6aa40ba186f05634e5bc9de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 09:55:03 +0200 Subject: [PATCH 47/64] fix: allow backspace for delete actions --- lib/canvas_editor/events.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/canvas_editor/events.js b/lib/canvas_editor/events.js index e38f67f8..e1bf8b27 100644 --- a/lib/canvas_editor/events.js +++ b/lib/canvas_editor/events.js @@ -112,9 +112,10 @@ function getKeyFromEvent(ev, JavaEditorArea) { case 'Shift': return JavaEditorArea.KEY_SHIFT; case 'Delete': - return JavaEditorArea.KEY_DELETE; case 'Backspace': - return JavaEditorArea.KEY_BACKSPACE; + return JavaEditorArea.KEY_DELETE; + // Backspace is currently unused by the Java code, so we remap it. + // return JavaEditorArea.KEY_BACKSPACE; case 'F1': return JavaEditorArea.KEY_HELP; case 'Escape': From 55d97602311021b51d95b9a6465b2231281e7b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 10:01:18 +0200 Subject: [PATCH 48/64] fix: add more checks for not destroyed --- lib/canvas_editor/init/canvas_editor.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/canvas_editor/init/canvas_editor.js b/lib/canvas_editor/init/canvas_editor.js index 5b6f0c98..5a17f028 100644 --- a/lib/canvas_editor/init/canvas_editor.js +++ b/lib/canvas_editor/init/canvas_editor.js @@ -40,6 +40,8 @@ function initCanvasEditor(JavaEditorArea, JavaEditorToolbar, JavaUIHelper) { } getMode() { + this.#checkNotDestroyed(); + const mode = this.#editorArea.getMode(); const isReaction = mode & (JavaEditorArea.MODE_REACTION !== 0); return isReaction ? 'reaction' : 'molecule'; @@ -84,11 +86,14 @@ function initCanvasEditor(JavaEditorArea, JavaEditorToolbar, JavaUIHelper) { } clearAll() { + this.#checkNotDestroyed(); + this.#editorArea.clearAll(); } destroy() { this.#checkNotDestroyed(); + this.#destroy(); this.#editorArea = null; this.#toolbar = null; From e67a25412195551c517789b9df234bd7bc8a668a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 10:08:12 +0200 Subject: [PATCH 49/64] fix: only create toolbar canvas if needed --- lib/canvas_editor/create_editor.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/canvas_editor/create_editor.js b/lib/canvas_editor/create_editor.js index 5f46e646..ef938da5 100644 --- a/lib/canvas_editor/create_editor.js +++ b/lib/canvas_editor/create_editor.js @@ -30,8 +30,11 @@ function createEditor( webkitUserSelect: 'none', }); - const toolbarCanvas = document.createElement('canvas'); - rootElement.append(toolbarCanvas); + let toolbarCanvas = null; + if (!readOnly) { + toolbarCanvas = document.createElement('canvas'); + rootElement.append(toolbarCanvas); + } const editorContainer = document.createElement('div'); Object.assign(editorContainer.style, { From ba0e4fb9cc8c9e7cd78b1acf20e16bb6eb50411a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 10:08:39 +0200 Subject: [PATCH 50/64] docs: add readonly option to init and display molfile v2 --- examples/generic_editor/demo.html | 5 ++++- examples/generic_editor/demo/editor.ts | 12 ++++++++++-- examples/generic_editor/demo/result.ts | 10 +++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/examples/generic_editor/demo.html b/examples/generic_editor/demo.html index 5a64acd7..b91f1b19 100644 --- a/examples/generic_editor/demo.html +++ b/examples/generic_editor/demo.html @@ -33,7 +33,8 @@

Generic editor - Demo

- + +
Generic editor - Demo
+ +
diff --git a/examples/generic_editor/demo/editor.ts b/examples/generic_editor/demo/editor.ts index 4bb36492..d9a1aa13 100644 --- a/examples/generic_editor/demo/editor.ts +++ b/examples/generic_editor/demo/editor.ts @@ -4,6 +4,7 @@ import { resetChangeCount, updateIDCode, updateMolfileOrRxn, + updateMolfileOrRxnV3, } from './result.ts'; let editor: OCL.CanvasEditor | undefined; @@ -13,9 +14,13 @@ export function resetEditor() { editor.destroy(); } const modeSelect = document.getElementById('modeSelect') as HTMLSelectElement; + const readOnlyCheckbox = document.getElementById( + 'readOnlyCheckbox', + ) as HTMLInputElement; const newEditor = new OCL.CanvasEditor( document.getElementById('editor') as HTMLElement, { + readOnly: readOnlyCheckbox.checked, initialMode: modeSelect.value as OCL.CanvasEditorMode, }, ); @@ -25,6 +30,7 @@ export function resetEditor() { resetChangeCount(); updateIDCode(''); updateMolfileOrRxn(''); + updateMolfileOrRxnV3(''); editor.setOnChangeListener(({ type, isUserEvent }) => { if (type === 'molecule') { @@ -35,11 +41,13 @@ export function resetEditor() { if (mode === 'molecule') { const molecule = newEditor.getMolecule(); updateIDCode(molecule.getIDCode()); - updateMolfileOrRxn(molecule.toMolfileV3()); + updateMolfileOrRxn(molecule.toMolfile()); + updateMolfileOrRxnV3(molecule.toMolfileV3()); } else { const reaction = newEditor.getReaction(); updateIDCode(OCL.ReactionEncoder.encode(reaction)); - updateMolfileOrRxn(reaction.toRxnV3()); + updateMolfileOrRxn(reaction.toRxn()); + updateMolfileOrRxnV3(reaction.toRxnV3()); } } }); diff --git a/examples/generic_editor/demo/result.ts b/examples/generic_editor/demo/result.ts index 89110674..e915506f 100644 --- a/examples/generic_editor/demo/result.ts +++ b/examples/generic_editor/demo/result.ts @@ -12,7 +12,7 @@ export function resetChangeCount() { const idcodeDiv = document.getElementById('idcode') as HTMLElement; export function updateIDCode(idCode: string) { - idcodeDiv.innerText = idCode; + idcodeDiv.innerText = idCode.replaceAll('\x7f', '?'); } const molfileArea = document.getElementById('molfile') as HTMLTextAreaElement; @@ -20,3 +20,11 @@ const molfileArea = document.getElementById('molfile') as HTMLTextAreaElement; export function updateMolfileOrRxn(value: string) { molfileArea.value = value; } + +const molfileV3Area = document.getElementById( + 'molfilev3', +) as HTMLTextAreaElement; + +export function updateMolfileOrRxnV3(value: string) { + molfileV3Area.value = value; +} From dc6cb1845297007f91419382ad60c8a65e07fb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 11:29:10 +0200 Subject: [PATCH 51/64] feat: add initialFragment option and trigger update on dialog submit --- .prettierignore | 18 +++++------ examples/generic_editor/demo.html | 3 +- examples/generic_editor/demo/editor.ts | 7 ++++- lib/canvas_editor/create_editor.js | 24 +++++++++++--- lib/canvas_editor/init/canvas_editor.js | 10 +++++- lib/canvas_editor/init/index.js | 3 ++ scripts/openchemlib/classes.js | 16 +++------- .../gui/editor/GenericEditorArea.java | 31 +++++++++---------- .../research/gwt/gui/generic/JSDialog.java | 2 +- .../gwt/gui/generic/JSEditorArea.java | 2 +- .../gwt/gui/generic/JSEditorToolbar.java | 2 +- types.d.ts | 6 +++- 12 files changed, 76 insertions(+), 48 deletions(-) diff --git a/.prettierignore b/.prettierignore index 15b08b45..d5b09124 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,9 +1,9 @@ -CHANGELOG.md -dist -distbuild -distesm -distold -gwt -lib/canvas_editor/cursors_24px.js -openchemlib -war +/CHANGELOG.md +/dist +/distbuild +/distesm +/distold +/gwt +/lib/canvas_editor/cursors_24px.js +/openchemlib +/war diff --git a/examples/generic_editor/demo.html b/examples/generic_editor/demo.html index b91f1b19..05eb3acc 100644 --- a/examples/generic_editor/demo.html +++ b/examples/generic_editor/demo.html @@ -34,7 +34,8 @@

Generic editor - Demo

- + +
Date: Thu, 25 Jul 2024 11:29:28 +0200 Subject: [PATCH 52/64] chore: update submodule --- openchemlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openchemlib b/openchemlib index 66a2646c..b52c78dd 160000 --- a/openchemlib +++ b/openchemlib @@ -1 +1 @@ -Subproject commit 66a2646c61eb7e3713ae07130c4d102c9eb202ee +Subproject commit b52c78ddc7df65c97bf96e18846966d24da85a52 From 898d8296a7131afc877e13ea745ab2712e3d8966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 12:01:55 +0200 Subject: [PATCH 53/64] fix: rxn v3 bug Refs: https://github.com/Actelion/openchemlib/issues/111 --- scripts/openchemlib/classes.js | 9 +++++++-- .../com/actelion/research/chem/TextDrawingObject.java | 4 +++- .../com/actelion/research/chem/io/RXNFileV3Creator.java | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/openchemlib/classes.js b/scripts/openchemlib/classes.js index c9bbb56a..b5626c6e 100644 --- a/scripts/openchemlib/classes.js +++ b/scripts/openchemlib/classes.js @@ -317,10 +317,15 @@ function changeTautomerHelper(code) { } function changeTextDrawingObject(code) { + code = replaceChecked( + code, + 'import java.util.ArrayList;', + 'import java.math.BigDecimal;\nimport java.math.MathContext;\nimport java.util.ArrayList;', + ); return replaceChecked( code, String.raw`detail.append(String.format(" size=\"%.4f\"", new Double(mSize)));`, - String.raw`detail.append(" size=\""+mSize+"\"");`, + String.raw`detail.append(" size=\""+new BigDecimal(mSize, new MathContext(4)).toString()+"\"");`, ); } @@ -365,7 +370,7 @@ function removeRXNStringFormat(code) { return replaceChecked( code, 'theWriter.write(String.format("M V30 COUNTS %d %d"+NL,rcnt,pcnt));', - 'theWriter.write("M V30 COUNTS "+rcnt+" "+pcnt+NL,rcnt,pcnt);', + 'theWriter.write("M V30 COUNTS "+rcnt+" "+pcnt+NL);', ); } diff --git a/src/com/actelion/research/gwt/chemlib/com/actelion/research/chem/TextDrawingObject.java b/src/com/actelion/research/gwt/chemlib/com/actelion/research/chem/TextDrawingObject.java index 84bd2691..97f85877 100644 --- a/src/com/actelion/research/gwt/chemlib/com/actelion/research/chem/TextDrawingObject.java +++ b/src/com/actelion/research/gwt/chemlib/com/actelion/research/chem/TextDrawingObject.java @@ -38,6 +38,8 @@ import com.actelion.research.gui.generic.GenericRectangle; import java.awt.*; +import java.math.BigDecimal; +import java.math.MathContext; import java.util.ArrayList; public class TextDrawingObject extends AbstractDrawingObject { @@ -145,7 +147,7 @@ public String getDescriptorDetail() { detail.append(" x=\""+mPoint[0].x + "\""); detail.append(" y=\""+mPoint[0].y + "\""); if (mSize != DEFAULT_SIZE) - detail.append(" size=\""+mSize+"\""); + detail.append(" size=\""+new BigDecimal(mSize, new MathContext(4)).toString()+"\""); if (mStyle != DEFAULT_STYLE) detail.append(" style=\""+mStyle+ "\""); diff --git a/src/com/actelion/research/gwt/chemlib/com/actelion/research/chem/io/RXNFileV3Creator.java b/src/com/actelion/research/gwt/chemlib/com/actelion/research/chem/io/RXNFileV3Creator.java index 8da38551..89d799bc 100644 --- a/src/com/actelion/research/gwt/chemlib/com/actelion/research/chem/io/RXNFileV3Creator.java +++ b/src/com/actelion/research/gwt/chemlib/com/actelion/research/chem/io/RXNFileV3Creator.java @@ -71,7 +71,7 @@ public RXNFileV3Creator(Reaction r, String programName) { theWriter.write(NL); int rcnt = rxn.getReactants(); int pcnt = rxn.getProducts(); - theWriter.write("M V30 COUNTS "+rcnt+" "+pcnt+NL,rcnt,pcnt); + theWriter.write("M V30 COUNTS "+rcnt+" "+pcnt+NL); double scalingFactor = getScalingFactor(rxn); From c2c9393f6d1bbff36df6f238fa1423c1e8222594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 12:31:05 +0200 Subject: [PATCH 54/64] test: update snapshot --- .github/workflows/nodejs.yml | 2 +- __tests__/__snapshots__/reaction.js.snap | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 2545c0f7..152f3896 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -51,7 +51,7 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: debug-build + name: debug-build-${{ matrix.node-version }} path: | dist/openchemlib-full.pretty.js compile-full.log diff --git a/__tests__/__snapshots__/reaction.js.snap b/__tests__/__snapshots__/reaction.js.snap index 337054b8..0cdafaca 100644 --- a/__tests__/__snapshots__/reaction.js.snap +++ b/__tests__/__snapshots__/reaction.js.snap @@ -68,7 +68,8 @@ exports[`Reaction class should load and create RXN files 2`] = ` OCL_RXN_V1.0:gJX@@eKU@@ gGQHDHaInfh@!gJX@@eKU@@ fHdP@@##!ROvp@[C}c@FNZz@@ !R_\`CW?Hc|_\`BH_Ykh@@ !ROvp@[C}c@FNZz@@ !RGP@@## - VM V30 BEGIN REACTANT +M V30 COUNTS 2 2 +M V30 BEGIN REACTANT M V30 BEGIN CTAB M V30 COUNTS 6 5 0 0 0 M V30 BEGIN ATOM From 8e1d4b9ed2dc0bb693c574cb0a72e200d6851014 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:32:17 +0200 Subject: [PATCH 55/64] chore: continue implem of custom element --- examples/generic_editor/demo.html | 4 +- examples/index.html | 5 +- examples/web_component/demo.html | 25 +++++++++ examples/web_component/demo.ts | 3 ++ full.d.ts | 1 + .../init/canvas_editor_element.js | 15 ++++-- lib/canvas_editor/init/index.js | 19 ++++++- types.d.ts | 51 +++++++++++++++++++ 8 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 examples/web_component/demo.html create mode 100644 examples/web_component/demo.ts diff --git a/examples/generic_editor/demo.html b/examples/generic_editor/demo.html index 05eb3acc..a7fa7d8e 100644 --- a/examples/generic_editor/demo.html +++ b/examples/generic_editor/demo.html @@ -2,12 +2,12 @@ - Generic editor - Demo + Generic editor - Demo lib CanvasEditor -

Generic editor - Demo

+

Generic editor - Demo lib CanvasEditor

Demo + Demo lib CanvasEditor + +
  • + Demo CanvasEditorElement (WebComponent)
  • diff --git a/examples/web_component/demo.html b/examples/web_component/demo.html new file mode 100644 index 00000000..e74af70b --- /dev/null +++ b/examples/web_component/demo.html @@ -0,0 +1,25 @@ + + + + + Generic editor - Demo CanvasEditorElement (WebComponent) + + + + +

    Generic editor - Demo CanvasEditorElement (WebComponent)

    + + +

    Empty editor

    +
    + + + + + + + + + + + diff --git a/examples/web_component/demo.ts b/examples/web_component/demo.ts new file mode 100644 index 00000000..1d1e0dff --- /dev/null +++ b/examples/web_component/demo.ts @@ -0,0 +1,3 @@ +import OCL from '../../distesm/full.pretty'; + +OCL.registerCustomElement(); diff --git a/full.d.ts b/full.d.ts index 7633fdf1..cc51c702 100644 --- a/full.d.ts +++ b/full.d.ts @@ -12,4 +12,5 @@ export { CanvasEditorMode, CanvasEditorOptions, CanvasEditor, + registerCustomElement, } from './types'; diff --git a/lib/canvas_editor/init/canvas_editor_element.js b/lib/canvas_editor/init/canvas_editor_element.js index 9ebfcaca..9e63dada 100644 --- a/lib/canvas_editor/init/canvas_editor_element.js +++ b/lib/canvas_editor/init/canvas_editor_element.js @@ -20,7 +20,7 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { * * ``` */ - class CanvasEditorElement extends HTMLElement { + class CanvasEditorElement extends HTMLDivElement { /** @type {{MOLECULE: 'molecule', REACTION: 'reaction'}} */ static MODE = Object.freeze( Object.create({ @@ -86,6 +86,8 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { /** @type {CanvasEditor} */ #editor; #initEditor() { + if (this.#editor) return; + this.#editor = new CanvasEditor(this.shadowRoot, { readOnly: this.readonly, initialMode: this.mode, @@ -130,6 +132,8 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { }; #destroyEditor() { + if (!this.#editor) return; + this.#editor.destroy(); this.#editor = undefined; } @@ -146,6 +150,9 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { connectedCallback() { this.attachShadow({ mode: 'open' }); this.shadowRoot.adoptedStyleSheets = [new CSSStyleSheet()]; + + this.shadowRoot.append(...this.childNodes); + this.#initEditor(); } @@ -153,9 +160,7 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { * Custom element removed from page. */ disconnectedCallback() { - if (!this.#editor) return; - this.#editor.destroy(); - this.#editor = undefined; + this.#destroyEditor(); } /** @@ -168,6 +173,8 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { * Attribute ${name} has changed from ${oldValue} to ${newValue} */ attributeChangedCallback(name, oldValue, newValue) { + if (!this.shadowRoot) return; + switch (name) { case 'idcode': { this.idcode = newValue; diff --git a/lib/canvas_editor/init/index.js b/lib/canvas_editor/init/index.js index 7c66506f..4bfe5527 100644 --- a/lib/canvas_editor/init/index.js +++ b/lib/canvas_editor/init/index.js @@ -27,7 +27,24 @@ function init(OCL) { Molecule, ReactionEncoder, ); - customElements.define('openchemlib-editor', CanvasEditorElement); + + const constructor = customElements.get('openchemlib-editor'); + if (constructor) return constructor; + + customElements.define('openchemlib-editor', CanvasEditorElement, { + extends: 'div', + }); + + const css = new CSSStyleSheet(); + css.replaceSync(` + openchemlib-editor:defined { + display: block; + height: 400px; + } + `); + document.adoptedStyleSheets.push(css); + + return CanvasEditorElement; } OCL.CanvasEditor = CanvasEditor; diff --git a/types.d.ts b/types.d.ts index 75705725..9cd86a91 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3786,3 +3786,54 @@ export declare class CanvasEditor { */ destroy(): void; } + +interface CanvasEditorElementModeEnum { + MOLECULE: 'molecule'; + REACTION: 'reaction'; +} + +type CanvasEditorElementMode = 'molecule' | 'reaction'; + +interface CanvasEditorElementConstructor extends CustomElementConstructor { + readonly MODE: CanvasEditorElementModeEnum; + readonly observedAttributes: ['idcode', 'fragment', 'mode', 'readonly']; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new (...params: any[]): CanvasEditorElement; +} + +/** + * The class CanvasEditorElement is not exposed in OCL scope. + * You can obtain it from `registerCustomElement` + */ +declare interface CanvasEditorElement extends HTMLElement { + /** + * @defaultValue '' + */ + idcode: string; + /** + * @defaultValue false + */ + fragment: boolean; + /** + * @defaultValue 'molecule' + */ + mode: CanvasEditorElementMode; + /** + * @defaultValue false + */ + readonly: boolean; + + getMolecule(): Molecule; + setMolecule(molecule: Molecule): void; + + getReaction(): Reaction; + setReaction(reaction: Reaction): void; + + clearAll(): void; +} + +/** + * register `` element with `CanvasEditorElementConstructor` if not already defined. + */ +declare function registerCustomElement(): CanvasEditorElementConstructor; From 0b67d9cfbd180a5b59e597813b558181df845d48 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:14:21 +0200 Subject: [PATCH 56/64] chore: cast dom attributes values --- lib/canvas_editor/init/canvas_editor_element.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/canvas_editor/init/canvas_editor_element.js b/lib/canvas_editor/init/canvas_editor_element.js index 9e63dada..27c0f5fc 100644 --- a/lib/canvas_editor/init/canvas_editor_element.js +++ b/lib/canvas_editor/init/canvas_editor_element.js @@ -127,6 +127,7 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { #handleChange = (editorEventOnChange) => { const domEvent = new CustomEvent('change', { detail: editorEventOnChange, + composed: true, }); this.dispatchEvent(domEvent); }; @@ -177,21 +178,21 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { switch (name) { case 'idcode': { - this.idcode = newValue; + this.idcode = String(newValue); return void this.#initIdCode(); } case 'fragment': { - this.fragment = newValue; + this.fragment = Boolean(newValue); const molecule = this.#editor.getMolecule(); molecule.setFragment(this.fragment); return void this.#editor.setMolecule(molecule); } case 'mode': { - this.mode = newValue; + this.mode = String(newValue); return void this.#resetEditor(); } case 'readonly': { - this.readonly = newValue; + this.readonly = Boolean(newValue); return void this.#resetEditor(); } default: From d486cdcfa8ba79161bbcabd32bc3de22ca42db32 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:39:34 +0200 Subject: [PATCH 57/64] fix: chromium derived do not support custom element extending builtin element --- examples/web_component/demo.html | 16 +++---- .../init/canvas_editor_element.js | 2 +- lib/canvas_editor/init/index.js | 4 +- types.d.ts | 43 +++++++++++++++++++ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/examples/web_component/demo.html b/examples/web_component/demo.html index e74af70b..bf0de701 100644 --- a/examples/web_component/demo.html +++ b/examples/web_component/demo.html @@ -13,13 +13,13 @@

    Generic editor - Demo CanvasEditorElement (WebComponent)

    Empty editor

    - - - - - - - - + +

    + Readonly editor with idcode ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUh@ +

    +
    diff --git a/lib/canvas_editor/init/canvas_editor_element.js b/lib/canvas_editor/init/canvas_editor_element.js index 27c0f5fc..e6facb64 100644 --- a/lib/canvas_editor/init/canvas_editor_element.js +++ b/lib/canvas_editor/init/canvas_editor_element.js @@ -20,7 +20,7 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { * * ``` */ - class CanvasEditorElement extends HTMLDivElement { + class CanvasEditorElement extends HTMLElement { /** @type {{MOLECULE: 'molecule', REACTION: 'reaction'}} */ static MODE = Object.freeze( Object.create({ diff --git a/lib/canvas_editor/init/index.js b/lib/canvas_editor/init/index.js index 4bfe5527..ad9fa944 100644 --- a/lib/canvas_editor/init/index.js +++ b/lib/canvas_editor/init/index.js @@ -31,9 +31,7 @@ function init(OCL) { const constructor = customElements.get('openchemlib-editor'); if (constructor) return constructor; - customElements.define('openchemlib-editor', CanvasEditorElement, { - extends: 'div', - }); + customElements.define('openchemlib-editor', CanvasEditorElement); const css = new CSSStyleSheet(); css.replaceSync(` diff --git a/types.d.ts b/types.d.ts index 9cd86a91..63ba54b6 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3803,8 +3803,51 @@ interface CanvasEditorElementConstructor extends CustomElementConstructor { } /** + * a Webcomponent to wrap CanvasEditor + * * The class CanvasEditorElement is not exposed in OCL scope. * You can obtain it from `registerCustomElement` + * + * Usage: + * + * ```js + * import {registerCustomElement} from 'openchemlib/minimal'; + * + * // register CanvasEditorElement with `openchemlib-editor` tag name + * const CanvasEditorElement = registerCustomElement(); + * + * // CanvasEditorElementConstructor.MODE return enums of possible modes + * + * ``` + * + * @example + * + * ```html + * Molecule + * + * + * Molecule Fragment + * + * + * Reaction + * + * + * Reaction Fragment + * + * + * Molecule readonly + * + * ``` */ declare interface CanvasEditorElement extends HTMLElement { /** From 0bab565361a6999b63da4cfcc408b98860166878 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:35:53 +0200 Subject: [PATCH 58/64] fix: init and document styling --- examples/web_component/demo.html | 54 +++++++++++-- .../init/canvas_editor_element.js | 70 ++++++----------- lib/canvas_editor/init/index.js | 29 +++++-- types.d.ts | 76 ++++++++++++++----- 4 files changed, 151 insertions(+), 78 deletions(-) diff --git a/examples/web_component/demo.html b/examples/web_component/demo.html index bf0de701..8dadb6ad 100644 --- a/examples/web_component/demo.html +++ b/examples/web_component/demo.html @@ -5,21 +5,59 @@ Generic editor - Demo CanvasEditorElement (WebComponent) + +

    Generic editor - Demo CanvasEditorElement (WebComponent)

    - -

    Empty editor

    -
    +

    Empty editor

    + +

    Molecule ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUh@

    + +

    + Molecule Fragment + + ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUhCyqHiCHy@leBhMEh]B\sa^kp + +

    + + +

    Reaction gJX@@eKU@@ gGQHDHaImfh@!defH@DAIfUVjj`@

    + + +

    + Reaction Fragment + gJX@@eKU@P gGQHDHaImfhB!defH@DAIfUVjj`B +

    + + +

    + Molecule readonly + ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUh@ +

    + -

    - Readonly editor with idcode ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUh@ -

    -
    + idcode="ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUh@" + > diff --git a/lib/canvas_editor/init/canvas_editor_element.js b/lib/canvas_editor/init/canvas_editor_element.js index e6facb64..1609441b 100644 --- a/lib/canvas_editor/init/canvas_editor_element.js +++ b/lib/canvas_editor/init/canvas_editor_element.js @@ -1,25 +1,6 @@ 'use strict'; function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { - /** - * `` support 4 observable attributes : - * - * - idcode: init molecule with `Molecule.fromIDCode(idcode)` or reaction with `ReactionEncoder.decode(this.idcode)`. - * fallback to an empty string if not defined. - * - fragment: enable or disable fragment on Molecule / Reaction. - * fallback to false if not defined. - * - mode: can be either `'molecule'` or `'reaction'`. `CanvasEditorElement.MODE` map these values for enum style. - * fallback to `CanvasEditorElement.MODE.MOLECULE` if not defined. - * - readonly: if enable, it do not add toolbar and don't handle user interactions. - * fallback to false. - * - * @example - * - * Empty editor: - * ```html - * - * ``` - */ class CanvasEditorElement extends HTMLElement { /** @type {{MOLECULE: 'molecule', REACTION: 'reaction'}} */ static MODE = Object.freeze( @@ -94,7 +75,7 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { }); this.#editor.setOnChangeListener(this.#handleChange); - this.#initIdCode(); + requestIdleCallback(() => this.#initIdCode()); } #initIdCode() { @@ -152,8 +133,6 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { this.attachShadow({ mode: 'open' }); this.shadowRoot.adoptedStyleSheets = [new CSSStyleSheet()]; - this.shadowRoot.append(...this.childNodes); - this.#initEditor(); } @@ -174,30 +153,31 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { * Attribute ${name} has changed from ${oldValue} to ${newValue} */ attributeChangedCallback(name, oldValue, newValue) { - if (!this.shadowRoot) return; - - switch (name) { - case 'idcode': { - this.idcode = String(newValue); - return void this.#initIdCode(); - } - case 'fragment': { - this.fragment = Boolean(newValue); - const molecule = this.#editor.getMolecule(); - molecule.setFragment(this.fragment); - return void this.#editor.setMolecule(molecule); - } - case 'mode': { - this.mode = String(newValue); - return void this.#resetEditor(); + const mutatorHandler = (() => { + switch (name) { + case 'idcode': { + this.idcode = String(newValue); + return () => this.#initIdCode(); + } + case 'fragment': { + this.fragment = newValue !== null; + return () => this.#initIdCode(); + } + case 'mode': { + this.mode = String(newValue); + return () => this.#resetEditor(); + } + case 'readonly': { + this.readonly = newValue !== null; + return () => this.#resetEditor(); + } + default: + throw new Error('unsupported attribute change'); } - case 'readonly': { - this.readonly = Boolean(newValue); - return void this.#resetEditor(); - } - default: - throw new Error('unsupported attribute change'); - } + })(); + + if (!this.shadowRoot) return; + mutatorHandler(); } } diff --git a/lib/canvas_editor/init/index.js b/lib/canvas_editor/init/index.js index ad9fa944..c18e4cc8 100644 --- a/lib/canvas_editor/init/index.js +++ b/lib/canvas_editor/init/index.js @@ -33,14 +33,27 @@ function init(OCL) { customElements.define('openchemlib-editor', CanvasEditorElement); - const css = new CSSStyleSheet(); - css.replaceSync(` - openchemlib-editor:defined { - display: block; - height: 400px; - } - `); - document.adoptedStyleSheets.push(css); + // mutate the first stylesheet available or construct a new one, added to adoptedStyleSheets + // It's default styling for openchemlib-editor element, + // should be considered as a user-agent stylesheet (low-priority) + const css = + document.styleSheets[0] ?? + (() => { + const css = new CSSStyleSheet(); + document.adoptedStyleSheets.unshift(css); + return css; + })(); + css.insertRule( + ` + /* dynamicaly added from openchemlib registerCustomElement with low priority */ + openchemlib-editor:defined { + display: block; + height: 400px; + width: 600px; + } + `, + 0, + ); return CanvasEditorElement; } diff --git a/types.d.ts b/types.d.ts index 63ba54b6..a2b49eca 100644 --- a/types.d.ts +++ b/types.d.ts @@ -3810,43 +3810,85 @@ interface CanvasEditorElementConstructor extends CustomElementConstructor { * * Usage: * + * In Javascript: * ```js - * import {registerCustomElement} from 'openchemlib/minimal'; + * import {registerCustomElement, Molecule, ReactionEncoder} from 'openchemlib/minimal'; * * // register CanvasEditorElement with `openchemlib-editor` tag name * const CanvasEditorElement = registerCustomElement(); * * // CanvasEditorElementConstructor.MODE return enums of possible modes + * const firstEditor = document.querySelector('openchemlib-editor'); + * console.assert(firstEditor instanceof CanvasEditorElement); * - * ``` + * firstEditor.setMolecule(Molecule.fromIDCode('ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUh@')); + * const molecule = firstEditor.getMolecule(); * - * @example + * firstEditor.setReaction(ReactionEncoder.decode('gJX@@eKU@@ gGQHDHaImfh@!defH@DAIfUVjj`@')); + * const reaction = firstEditor.getReaction(); + * + * firstEditor.clearAll(); + * ``` * + * In HTML * ```html - * Molecule - * + *

    Empty editor

    + * + * + *

    Molecule ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUh@

    + * + * + *

    + * Molecule Fragment + * + * ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUhCyqHiCHy@leBhMEh]B\sa^kp + * + *

    + * * - * Molecule Fragment + *

    Reaction gJX@@eKU@@ gGQHDHaImfh@!defH@DAIfUVjj`@

    * * - * Reaction + *

    + * Reaction Fragment + * gJX@@eKU@P gGQHDHaImfhB!defH@DAIfUVjj`B + *

    * * - * Reaction Fragment + *

    + * Molecule readonly + * ffc`P@H`QxNQQJJIJIZJHiSkQSejB`jFjhhaEqFUh@ + *

    * + * ``` + * + * Styling: + * `registerCustomElement()` insertRule to define height to 400px and width to 600px on `openchemlib-editor` element. + * This element need to be contained by fixed width and height, or it will grow indefinitely. + * So for responsive layout, ensure your container has max-width and max-height + * before override the inserted rules to 100% instead fixed units. * - * Molecule readonly - * + * ```css + * // need to be into a fixed size container + * openchemlib-editor:defined { + * width: 100%; + * height: 100% + * } * ``` */ declare interface CanvasEditorElement extends HTMLElement { From 220bfdeb4f1b7c425a1fd4a0f97bbd6d325c28e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 15:58:16 +0200 Subject: [PATCH 59/64] fix: center dialog and isolate it with shadow dom --- lib/canvas_editor/create_editor.js | 13 ++++++++++--- lib/canvas_editor/editor_dialog.js | 15 +++++++++++++-- lib/canvas_editor/editor_stylesheet.js | 21 +++++++++++++++++++++ lib/canvas_editor/ui_helper.js | 11 +++++++++-- 4 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 lib/canvas_editor/editor_stylesheet.js diff --git a/lib/canvas_editor/create_editor.js b/lib/canvas_editor/create_editor.js index 9dc02964..bd1cca95 100644 --- a/lib/canvas_editor/create_editor.js +++ b/lib/canvas_editor/create_editor.js @@ -1,6 +1,7 @@ 'use strict'; const EditorArea = require('./editor_area'); +const getEditorStylesheet = require('./editor_stylesheet'); const { addPointerListeners, addKeyboardListeners } = require('./events'); const Toolbar = require('./toolbar'); const UIHelper = require('./ui_helper'); @@ -36,10 +37,14 @@ function createEditor( webkitUserSelect: 'none', }); + const shadowRoot = rootElement.attachShadow({ mode: 'closed' }); + + shadowRoot.adoptedStyleSheets = [getEditorStylesheet()]; + let toolbarCanvas = null; if (!readOnly) { toolbarCanvas = document.createElement('canvas'); - rootElement.append(toolbarCanvas); + shadowRoot.append(toolbarCanvas); } const editorContainer = document.createElement('div'); @@ -47,7 +52,7 @@ function createEditor( width: '100%', height: '100%', }); - rootElement.append(editorContainer); + shadowRoot.append(editorContainer); const editorCanvas = document.createElement('canvas'); editorCanvas.tabIndex = 0; @@ -58,7 +63,9 @@ function createEditor( parentElement.append(rootElement); - const uiHelper = new JavaUIHelper(new UIHelper(editorCanvas, JavaEditorArea)); + const uiHelper = new JavaUIHelper( + new UIHelper(editorCanvas, editorContainer, JavaEditorArea), + ); const editorArea = new JavaEditorArea( computeMode(initialMode, JavaEditorArea), diff --git a/lib/canvas_editor/editor_dialog.js b/lib/canvas_editor/editor_dialog.js index 5058a24d..51f2b5dd 100644 --- a/lib/canvas_editor/editor_dialog.js +++ b/lib/canvas_editor/editor_dialog.js @@ -4,9 +4,11 @@ class EditorDialog { /** * * @param {string} title + * @param {HTMLElement} rootElement */ - constructor(title) { + constructor(title, rootElement) { this.title = title; + this.rootElement = rootElement; this.elements = []; this.dialogElement = null; } @@ -47,8 +49,17 @@ class EditorDialog { showDialog() { const dialog = document.createElement('dialog'); + const rootElementBounds = this.rootElement.getBoundingClientRect(); + // Center the dialog horizontally relative to the editor's canvas. + Object.assign(dialog.style, { + position: 'absolute', + marginBlock: 0, + left: `${rootElementBounds.left}px`, + right: `${document.body.parentElement.clientWidth - rootElementBounds.right}px`, + top: `${this.rootElement.offsetTop + 30}px`, + }); this.dialogElement = dialog; - document.body.append(dialog); + this.rootElement.getRootNode().append(dialog); const grid = document.createElement('div'); grid.style.display = 'grid'; grid.style.gridTemplateColumns = this.hLayout; diff --git a/lib/canvas_editor/editor_stylesheet.js b/lib/canvas_editor/editor_stylesheet.js new file mode 100644 index 00000000..2b666236 --- /dev/null +++ b/lib/canvas_editor/editor_stylesheet.js @@ -0,0 +1,21 @@ +'use strict'; + +const styles = ` +/* We can customize editor styles here. */ +`; + +let stylesheet; + +function getEditorStylesheet() { + if (stylesheet) { + return stylesheet; + } + + const sheet = new CSSStyleSheet(); + sheet.replaceSync(styles); + + stylesheet = sheet; + return sheet; +} + +module.exports = getEditorStylesheet; diff --git a/lib/canvas_editor/ui_helper.js b/lib/canvas_editor/ui_helper.js index 170392eb..ec0711dd 100644 --- a/lib/canvas_editor/ui_helper.js +++ b/lib/canvas_editor/ui_helper.js @@ -6,8 +6,15 @@ const EditorImage = require('./editor_image'); const { decodeBase64 } = require('./utils'); class UIHelper { - constructor(canvasElement, JavaEditorArea) { + /** + * + * @param {HTMLCanvasElement} canvasElement + * @param {HTMLElement} dialogRoot + * @param JavaEditorArea + */ + constructor(canvasElement, dialogRoot, JavaEditorArea) { this.canvasElement = canvasElement; + this.dialogRoot = dialogRoot; this.JavaEditorArea = JavaEditorArea; } @@ -43,7 +50,7 @@ class UIHelper { } createDialog(title) { - return new EditorDialog(title); + return new EditorDialog(title, this.dialogRoot); } } From 282d6a23680e873be7b6927d000ef4c356b75c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 16:01:46 +0200 Subject: [PATCH 60/64] fix: remove shadow root from custom element --- lib/canvas_editor/init/canvas_editor_element.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/canvas_editor/init/canvas_editor_element.js b/lib/canvas_editor/init/canvas_editor_element.js index 1609441b..4eb38e15 100644 --- a/lib/canvas_editor/init/canvas_editor_element.js +++ b/lib/canvas_editor/init/canvas_editor_element.js @@ -69,7 +69,7 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { #initEditor() { if (this.#editor) return; - this.#editor = new CanvasEditor(this.shadowRoot, { + this.#editor = new CanvasEditor(this, { readOnly: this.readonly, initialMode: this.mode, }); @@ -130,9 +130,6 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { * Custom element added to page. */ connectedCallback() { - this.attachShadow({ mode: 'open' }); - this.shadowRoot.adoptedStyleSheets = [new CSSStyleSheet()]; - this.#initEditor(); } @@ -176,7 +173,6 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { } })(); - if (!this.shadowRoot) return; mutatorHandler(); } } From 959e16d6eb93761693f0d4da175afa774203aae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 16:13:23 +0200 Subject: [PATCH 61/64] refactor: do not init for nothing --- lib/canvas_editor/init/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/canvas_editor/init/index.js b/lib/canvas_editor/init/index.js index c18e4cc8..c0dcf715 100644 --- a/lib/canvas_editor/init/index.js +++ b/lib/canvas_editor/init/index.js @@ -22,15 +22,15 @@ function init(OCL) { ); function registerCustomElement() { + const constructor = customElements.get('openchemlib-editor'); + if (constructor) return constructor; + const CanvasEditorElement = initCanvasEditorElement( CanvasEditor, Molecule, ReactionEncoder, ); - const constructor = customElements.get('openchemlib-editor'); - if (constructor) return constructor; - customElements.define('openchemlib-editor', CanvasEditorElement); // mutate the first stylesheet available or construct a new one, added to adoptedStyleSheets From d0de3da32cb3be2196020abc1ab60ffbe3795b72 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:21:14 +0200 Subject: [PATCH 62/64] fix: run mutatorHandler if this.#editor is defined --- lib/canvas_editor/init/canvas_editor_element.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/canvas_editor/init/canvas_editor_element.js b/lib/canvas_editor/init/canvas_editor_element.js index 4eb38e15..e8c8c86b 100644 --- a/lib/canvas_editor/init/canvas_editor_element.js +++ b/lib/canvas_editor/init/canvas_editor_element.js @@ -173,6 +173,7 @@ function initCanvasEditorElement(CanvasEditor, Molecule, ReactionEncoder) { } })(); + if (!this.#editor) return; mutatorHandler(); } } From 70bacec15e806b4351df6e96875b4695435534d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 16:15:43 +0200 Subject: [PATCH 63/64] chore: run Prettier --- examples/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/index.html b/examples/index.html index d22c51f3..6e89a614 100644 --- a/examples/index.html +++ b/examples/index.html @@ -14,7 +14,9 @@

    Generic editor

    Demo lib CanvasEditor
  • - Demo CanvasEditorElement (WebComponent) + Demo CanvasEditorElement (WebComponent)
  • From a4bcddb146f964320e8f0fa6337e0ef55d0a9f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 25 Jul 2024 16:21:45 +0200 Subject: [PATCH 64/64] docs: keep default height --- examples/web_component/demo.html | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/web_component/demo.html b/examples/web_component/demo.html index 8dadb6ad..fb46c1fe 100644 --- a/examples/web_component/demo.html +++ b/examples/web_component/demo.html @@ -8,7 +8,6 @@