diff --git a/.gitignore b/.gitignore
index de51f68..83fb69e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,6 @@ testem.log
# System Files
.DS_Store
Thumbs.db
+
+
+/.npmrc
diff --git a/angular.json b/angular.json
index 56daba4..485ab3f 100644
--- a/angular.json
+++ b/angular.json
@@ -28,12 +28,22 @@
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
- "src/assets"
+ "src/assets",
+ {
+ "glob": "**/*",
+ "input": "node_modules/@chemaxon/marvinjs",
+ "output": "./assets/marvin-js"
+ }
],
"styles": [
+ "node_modules/@chemaxon/marvinjs/gui/css/editor.css",
"src/styles.scss"
],
- "scripts": []
+ "scripts": [
+ "node_modules/@chemaxon/marvinjs/gui/lib/promise-1.0.0.min.js",
+ "node_modules/@chemaxon/marvinjs/js/marvinjslauncher.js",
+ "node_modules/@chemaxon/marvinjs/js/util.js"
+ ]
},
"configurations": {
"production": {
@@ -96,12 +106,22 @@
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
- "src/assets"
+ "src/assets",
+ {
+ "glob": "**/*",
+ "input": "node_modules/@chemaxon/marvinjs",
+ "output": "./assets/marvin-js"
+ }
],
"styles": [
+ "node_modules/@chemaxon/marvinjs/gui/css/editor.css",
"src/styles.scss"
],
- "scripts": []
+ "scripts": [
+ "node_modules/@chemaxon/marvinjs/gui/lib/promise-1.0.0.min.js",
+ "node_modules/@chemaxon/marvinjs/js/marvinjslauncher.js",
+ "node_modules/@chemaxon/marvinjs/js/util.js"
+ ]
}
}
}
diff --git a/package-lock.json b/package-lock.json
index 58709ed..ae8a60e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0",
+ "@chemaxon/marvinjs": "^21.3.0",
"rxjs": "~6.6.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
@@ -2250,6 +2251,12 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@chemaxon/marvinjs": {
+ "version": "21.20.0",
+ "resolved": "https://hub.chemaxon.com:443/artifactory/api/npm/npm/@chemaxon/marvinjs/-/@chemaxon/marvinjs-21.20.0.tgz",
+ "integrity": "sha1-e8jxXwuam+PYV+Q8crd9V3wJMWA=",
+ "license": "ChemAxon End-User License Agreement / End User Subscription Agreement - EUSA"
+ },
"node_modules/@csstools/convert-colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz",
@@ -17178,6 +17185,11 @@
"to-fast-properties": "^2.0.0"
}
},
+ "@chemaxon/marvinjs": {
+ "version": "21.20.0",
+ "resolved": "https://hub.chemaxon.com:443/artifactory/api/npm/npm/@chemaxon/marvinjs/-/@chemaxon/marvinjs-21.20.0.tgz",
+ "integrity": "sha1-e8jxXwuam+PYV+Q8crd9V3wJMWA="
+ },
"@csstools/convert-colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz",
diff --git a/package.json b/package.json
index 3eaac17..ddc664f 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0",
+ "@chemaxon/marvinjs": "^21.3.0",
"rxjs": "~6.6.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 33752f3..85c15e8 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,500 +1,19 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ title }} app is running!
-
-
-
-
-
-
-
Resources
-
Here are some links to help you get started:
-
-
-
-
-
Next Steps
-
What do you want to do next with your app?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ng generate component xyz
-
ng add @angular/material
-
ng add @angular/pwa
-
ng add _____
-
ng test
-
ng build
-
-
-
-
-
-
-
-
-
+
+
Value in form group: {{ formGroup.value.structure }}
+
Dirty: {{ formGroup.controls.structure.dirty }}
+
Pristine: {{ formGroup.controls.structure.pristine }}
+
Touched: {{ formGroup.controls.structure.touched }}
-
-
-
-
-
-
-
-
-
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index e69de29..55a4879 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -0,0 +1,11 @@
+:host {
+ display: grid;
+ height: 100vh;
+ max-height: 100vh;
+ grid-gap: 5px;
+ grid-template-columns: 1fr 1fr;
+}
+
+form {
+ flex-grow: 1;
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 572be7d..054e1e4 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,5 @@
import { Component } from '@angular/core';
+import { FormControl, FormGroup } from "@angular/forms";
@Component({
selector: 'app-root',
@@ -6,5 +7,34 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
- title = 'MarvinJsOnAngular';
+
+ benzene: string = '\n' +
+ ' MJ212000 \n' +
+ '\n' +
+ ' 8 8 0 0 0 0 0 0 0 0999 V2000\n' +
+ ' -4.9218 -0.0455 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n' +
+ ' -5.6363 -0.4580 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n' +
+ ' -5.6363 -1.2831 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n' +
+ ' -4.9218 -1.6955 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n' +
+ ' -4.2073 -1.2831 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n' +
+ ' -4.2073 -0.4580 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n' +
+ ' -4.9218 0.7794 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n' +
+ ' -5.6363 1.1919 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n' +
+ ' 1 2 1 0 0 0 0\n' +
+ ' 1 6 1 0 0 0 0\n' +
+ ' 2 3 1 0 0 0 0\n' +
+ ' 3 4 1 0 0 0 0\n' +
+ ' 4 5 1 0 0 0 0\n' +
+ ' 5 6 1 0 0 0 0\n' +
+ ' 1 7 1 0 0 0 0\n' +
+ ' 7 8 1 0 0 0 0\n' +
+ 'M END\n'
+
+ formGroup: FormGroup;
+
+ constructor() {
+ this.formGroup = new FormGroup({
+ structure: new FormControl(this.benzene)
+ });
+ }
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 8dfc1d6..a874d83 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -2,15 +2,20 @@ import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
+import { ReactiveFormsModule } from "@angular/forms";
+import { MarvinJsModule } from "./marvin-js/marvin-js.module";
@NgModule({
declarations: [
AppComponent
],
imports: [
- BrowserModule
+ BrowserModule,
+ MarvinJsModule,
+ ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
-export class AppModule { }
+export class AppModule {
+}
diff --git a/src/app/marvin-js/index.ts b/src/app/marvin-js/index.ts
new file mode 100644
index 0000000..077f45c
--- /dev/null
+++ b/src/app/marvin-js/index.ts
@@ -0,0 +1,20 @@
+import {
+ ImageExporter,
+ MarvinJSUtilInstance,
+ MarvinPackage,
+ RenderParams,
+ RenderParamsSettings,
+ Sketch
+} from "./marvin-js";
+
+import { MarvinJsEditorComponent } from "./marvin-js-editor.component";
+
+export {
+ ImageExporter,
+ MarvinJsEditorComponent,
+ MarvinJSUtilInstance,
+ MarvinPackage,
+ RenderParams,
+ RenderParamsSettings,
+ Sketch
+};
diff --git a/src/app/marvin-js/marvin-js-editor.component.ts b/src/app/marvin-js/marvin-js-editor.component.ts
new file mode 100644
index 0000000..23d81c5
--- /dev/null
+++ b/src/app/marvin-js/marvin-js-editor.component.ts
@@ -0,0 +1,83 @@
+import { Component } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
+import { ReplaySubject } from "rxjs";
+import { MarvinJSUtilInstance, Sketch } from ".";
+
+export type onChangeFunc
= (newValue: T) => void;
+export type onTouchedFunc = () => void;
+
+declare let MarvinJSUtil: MarvinJSUtilInstance;
+
+@Component({
+ selector: 'app-marvin-js-editor',
+ template: '',
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ multi:true,
+ useExisting: MarvinJsEditorComponent
+ }
+ ]
+})
+export class MarvinJsEditorComponent implements ControlValueAccessor {
+ private sketcherInstance$ = new ReplaySubject(1);
+
+ private onTouched: onTouchedFunc = () => {};
+ private onChange: onChangeFunc = s => {};
+
+ private isTouched = false;
+
+ constructor() {
+ this.grabSketcherInstance();
+ }
+
+ writeValue(val: string): void {
+ this.sketcherInstance$.subscribe(sketcherInstance => {
+ if (val) {
+ sketcherInstance.importStructure('mol', val).then();
+ } else {
+ sketcherInstance.clear();
+ }
+ });
+ }
+
+ markAsTouched(): void {
+ if (!this.isTouched) {
+ this.onTouched();
+ this.isTouched = true;
+ }
+ }
+
+ registerOnChange(fn: any): void {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ this.onTouched = fn;
+ }
+
+ setDisabledState?(): void {
+ // not supported
+ }
+
+ private grabSketcherInstance(): void {
+ MarvinJSUtil.getEditor('#sketch').then(
+ sketcherInstance => {
+ this.sketcherInstance$.next(sketcherInstance);
+ sketcherInstance.on('molchange', async () => {
+ this.markAsTouched();
+ const structure = sketcherInstance.isEmpty()
+ ? undefined
+ : await sketcherInstance.exportStructure('mol');
+ this.onChange(structure);
+ });
+ },
+ () => {
+ // try again in 100ms when the first attempt was too early
+ setTimeout(() => {
+ this.grabSketcherInstance();
+ }, 100);
+ }
+ );
+ }
+}
diff --git a/src/app/marvin-js/marvin-js.d.ts b/src/app/marvin-js/marvin-js.d.ts
new file mode 100644
index 0000000..f2a6175
--- /dev/null
+++ b/src/app/marvin-js/marvin-js.d.ts
@@ -0,0 +1,105 @@
+export interface MarvinJSUtilInstance {
+ getEditor(elementId: string): Promise;
+
+ getPackage(elementId: string): Promise;
+}
+
+export interface MarvinPackage {
+ onReady(callback: () => void): void;
+
+ ImageExporter : ImageExporter;
+
+ Sketch(elementName: string): Sketch;
+}
+
+export interface ImageExporter {
+ molToDataUrl(structure: string, format: string, settings: RenderParamsSettings): string;
+}
+
+// The MarvinJs Structure Editor
+// https://marvinjs-demo.chemaxon.com/latest/jsdoc.html#marvin.Sketch
+export interface Sketch {
+
+ // Returns true if HTML5 canvas is supported, false otherwise.
+ isSupported(): true;
+
+ // Remove the canvas content.
+ clear(): void;
+
+ // Determines whether the canvas is empty or not.
+ isEmpty(): boolean
+
+ // Returns a Promise object to import a molecule source. If the import operation is successful, the old structure will be replaced by the new one.
+ importStructure(format: string, structure: string): Promise;
+
+ // Returns a Promise object to access the molecule source.
+ exportStructure(format: string): Promise;
+
+ // Attaches an event handler function for the specified event to the editor. Supported events:
+ // "molchange": molecule change events
+ // "selectionchange": selection change events
+ // "undo": events when undo is performed
+ // "redo": events when redo is performed]
+ on(eventType: string, callback: () => void): void;
+
+ // Removes one or more event handlers for the specified event to the editor.
+ off(eventType: string, callback: () => void): void;
+
+ // Setup auto chirality mode. When you activate it, setup chiral flag on current structure and force it on every imported structure later.
+ setAutoChirality(enable: boolean): void;
+
+ setServices(param: { clean2dws: string; automapperws: string; reactionconvertws: string; stereoinfows: string; molconvertws: string; aromatizews: string; hydrogenizews: string; clean3dws: string }): void;
+}
+
+export interface RenderParams {
+ imageType: string;
+ settings: RenderParamsSettings;
+ inputFormat: string;
+}
+
+export interface RenderParamsSettings {
+ // Default: false (does not apply in "BALLSTICK" display mode)
+ carbonLabelVisible: boolean;
+
+ // Default: true
+ chiralFlagVisible: boolean;
+
+ // Default: true
+ valenceErrorVisible: boolean;
+
+ // Show lone pairs if MRV source contains proper info or auto lone pair calculation is enabled. Default: false.
+ lonePairsVisible: boolean;
+
+ // Set it to true to calculate lone pair on the current structure. Default: false.
+ lonepaircalculationenabled: boolean;
+
+ // Draw circle around positive and negative sign at charge or not. Default: false.
+ circledsign: boolean;
+
+ // default: false
+ atomIndicesVisible: boolean;
+
+ // default: true
+ atomMapsVisible: boolean;
+
+ // default: true
+ cpkColoring: boolean;
+
+ // Possible values: ["WIREFRAME" | "BALLSTICK" | "STICK" | "SPACEFILL"]. Default: "WIREFRAME".
+ displayMode: string;
+
+ // Possible values: ["ALL" | "OFF" | "HETERO" | "TERMINAL_AND_HETERO"]. Default: "TERMINAL_AND_HETERO".
+ implicitHydrogen: string;
+
+ // CSS color used as background color. Default: white or transparent (png).
+ backgroundColor: string;
+
+ // "fit" will expand every molecule to the given size, "autoshrink" scales down large molecules only. Default: "fit"
+ zoomMode: string;
+ //
+ // Default: 200
+ width: number;
+
+ // Default: 200
+ height: number;
+}
diff --git a/src/app/marvin-js/marvin-js.module.ts b/src/app/marvin-js/marvin-js.module.ts
new file mode 100644
index 0000000..8cc3ce0
--- /dev/null
+++ b/src/app/marvin-js/marvin-js.module.ts
@@ -0,0 +1,18 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MarvinJsEditorComponent } from '.';
+
+
+
+@NgModule({
+ declarations: [
+ MarvinJsEditorComponent
+ ],
+ exports: [
+ MarvinJsEditorComponent
+ ],
+ imports: [
+ CommonModule
+ ]
+})
+export class MarvinJsModule { }