- - -
- - - Rocket Ship - - - - - - - - - - {{ title }} app is running! - - - Rocket Ship Smoke - - - -
- - -

Resources

-

Here are some links to help you get started:

- -
- - - Learn Angular - - - - - CLI Documentation - - - - - - Angular Blog - - - - - - Angular DevTools - - - -
- - -

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
-
- - -
- - - - - - - - - - - - - Angular CLI Logo - - - - - - - - - - - - - Meetup Logo - - - - - - - Discord Logo - - - - -
- - - - - - Gray Clouds Background - - +
+ 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 { }