From a2137b80dbcdb23aac6fff4a4491686cbd0476dd Mon Sep 17 00:00:00 2001 From: Roman Rodionov Date: Sat, 3 Aug 2024 16:54:58 +0200 Subject: [PATCH] #4878 - Replace selected monomer on canvas with monomer from library in sequence mode - added ketcher-standalone builds for indigo without render module - added structService reinitialization --- example/vite.config.js | 4 + package-lock.json | 67 ++++++++++++- .../ketcher-core/src/application/ketcher.ts | 33 ++++--- .../services/struct/structService.types.ts | 1 + packages/ketcher-react/src/Editor.tsx | 34 +++++-- packages/ketcher-react/src/constants.ts | 3 + packages/ketcher-react/src/script/api.ts | 1 + .../script/builders/ketcher/KetcherBuilder.ts | 41 +++++++- packages/ketcher-react/src/script/index.ts | 19 ++-- .../src/script/ui/App/initApp.tsx | 16 +++- .../src/script/ui/state/index.js | 16 ++++ packages/ketcher-standalone/package.json | 13 ++- packages/ketcher-standalone/rollup.config.js | 95 ++++++++++++++++++- packages/ketcher-standalone/src/index.ts | 7 +- .../src/infrastructure/services/index.ts | 1 + .../services/struct/constants.ts | 3 + .../infrastructure/services/struct/index.ts | 1 + .../services/struct/indigoWorker.ts | 4 +- .../struct/standaloneStructService.ts | 39 ++++++-- 19 files changed, 339 insertions(+), 59 deletions(-) create mode 100644 packages/ketcher-standalone/src/infrastructure/services/struct/constants.ts diff --git a/example/vite.config.js b/example/vite.config.js index 185123e1af..9f8fdbe00c 100644 --- a/example/vite.config.js +++ b/example/vite.config.js @@ -205,6 +205,10 @@ export default defineConfig({ find: 'web-worker:./indigoWorker', replacement: './indigoWorker?worker', }, + { + find: '_indigo-ketcher-import-alias_', + replacement: 'indigo-ketcher', + }, ], }, customLogger: logger, diff --git a/package-lock.json b/package-lock.json index 324a43d374..5fc0214d91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15625,9 +15625,9 @@ } }, "node_modules/indigo-ketcher": { - "version": "1.22.0-rc.3", - "resolved": "https://registry.npmjs.org/indigo-ketcher/-/indigo-ketcher-1.22.0-rc.3.tgz", - "integrity": "sha512-JIvC5UG/m7wOk0y/K8soj7A1k/uaOC4IjK14qgM2RnsA1seO3Pt+qR+4WQ1uhqOQjeZEwZcgW4NfgJYp8bdZCw==" + "version": "1.23.0-dev.3", + "resolved": "https://registry.npmjs.org/indigo-ketcher/-/indigo-ketcher-1.23.0-dev.3.tgz", + "integrity": "sha512-AKdHnsT9wSRGHUM1vWmlhS4EWQtJlz5HO5ZrQ+7DerVu+SgLXPEjWgB5Oe0gbQA85Pgtm5jUL1vVyvMZrZzB3A==" }, "node_modules/inflight": { "version": "1.0.6", @@ -35268,7 +35268,7 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.17.9", - "indigo-ketcher": "1.22.0-rc.3", + "indigo-ketcher": "1.23.0-dev.3", "ketcher-core": "*" }, "devDependencies": { @@ -35280,6 +35280,7 @@ "@rollup/plugin-babel": "^5.2.1", "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-strip": "^2.0.0", "@types/jest": "^27.0.3", "@types/node": "^16.11.12", @@ -35303,6 +35304,12 @@ "node": ">=14" } }, + "packages/ketcher-standalone/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, "packages/ketcher-standalone/node_modules/@rollup/plugin-node-resolve": { "version": "15.2.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", @@ -35350,6 +35357,49 @@ } } }, + "packages/ketcher-standalone/node_modules/@rollup/plugin-replace": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", + "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "packages/ketcher-standalone/node_modules/@rollup/plugin-replace/node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "packages/ketcher-standalone/node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -35361,6 +35411,15 @@ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true + }, + "packages/ketcher-standalone/node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } } } } diff --git a/packages/ketcher-core/src/application/ketcher.ts b/packages/ketcher-core/src/application/ketcher.ts index 783dec0e26..e82603fc2a 100644 --- a/packages/ketcher-core/src/application/ketcher.ts +++ b/packages/ketcher-core/src/application/ketcher.ts @@ -55,10 +55,10 @@ const allowedApiSettings = { export class Ketcher { logging: LogSettings; - #structService: StructService; + structService: StructService; #formatterFactory: FormatterFactory; #editor: Editor; - #indigo: Indigo; + _indigo: Indigo; #eventBus: EventEmitter; get editor(): Editor { @@ -79,9 +79,9 @@ export class Ketcher { assert(formatterFactory != null); this.#editor = editor; - this.#structService = structService; + this.structService = structService; this.#formatterFactory = formatterFactory; - this.#indigo = new Indigo(this.#structService); + this._indigo = new Indigo(this.structService); this.#eventBus = new EventEmitter(); this.logging = { enabled: false, @@ -95,7 +95,7 @@ export class Ketcher { } get indigo() { - return this.#indigo; + return this._indigo; } // TEMP.: getting only dearomatize-on-load setting @@ -300,7 +300,7 @@ export class Ketcher { this.#editor.struct(), ); - return this.#structService.getInChIKey(struct); + return this.structService.getInChIKey(struct); } containsReaction(): boolean { @@ -354,11 +354,11 @@ export class Ketcher { if (window.isPolymerEditorTurnedOn) { deleteAllEntitiesOnCanvas(); - await parseAndAddMacromoleculesOnCanvas(structStr, this.#structService); + await parseAndAddMacromoleculesOnCanvas(structStr, this.structService); } else { const struct: Struct = await prepareStructToRender( structStr, - this.#structService, + this.structService, this, ); @@ -375,7 +375,7 @@ export class Ketcher { assert(typeof helmStr === 'string'); const struct: Struct = await prepareStructToRender( helmStr, - this.#structService, + this.structService, this, ); struct.rescale(); @@ -393,11 +393,11 @@ export class Ketcher { assert(typeof structStr === 'string'); if (window.isPolymerEditorTurnedOn) { - await parseAndAddMacromoleculesOnCanvas(structStr, this.#structService); + await parseAndAddMacromoleculesOnCanvas(structStr, this.structService); } else { const struct: Struct = await prepareStructToRender( structStr, - this.#structService, + this.structService, this, ); @@ -413,7 +413,7 @@ export class Ketcher { } runAsyncAction(async () => { - const struct = await this.#indigo.layout(this.#editor.struct()); + const struct = await this._indigo.layout(this.#editor.struct()); const ketSerializer = new KetSerializer(); this.setMolecule(ketSerializer.serialize(struct)); }, this.eventBus); @@ -458,7 +458,7 @@ export class Ketcher { if (window.isPolymerEditorTurnedOn) { throw new Error('Recognize is not available in macro mode'); } - return this.#indigo.recognize(image, { version }); + return this._indigo.recognize(image, { version }); } async generateImage( @@ -481,7 +481,7 @@ export class Ketcher { options.outputFormat = 'png'; } - const base64 = await this.#structService.generateImageAsBase64( + const base64 = await this.structService.generateImageAsBase64( data, options, ); @@ -494,4 +494,9 @@ export class Ketcher { const blob = new Blob([byteArray], { type: meta }); return blob; } + + public reinitializeIndigo(structService: StructService) { + this.structService = structService; + this._indigo = new Indigo(structService); + } } diff --git a/packages/ketcher-core/src/domain/services/struct/structService.types.ts b/packages/ketcher-core/src/domain/services/struct/structService.types.ts index 177f8d6c27..31e6acce45 100644 --- a/packages/ketcher-core/src/domain/services/struct/structService.types.ts +++ b/packages/ketcher-core/src/domain/services/struct/structService.types.ts @@ -204,4 +204,5 @@ export interface StructService { data: ExplicitHydrogensData, options?: StructServiceOptions, ) => Promise; + destroy?: () => void; } diff --git a/packages/ketcher-react/src/Editor.tsx b/packages/ketcher-react/src/Editor.tsx index f07800e803..82f9e09cd2 100644 --- a/packages/ketcher-react/src/Editor.tsx +++ b/packages/ketcher-react/src/Editor.tsx @@ -25,7 +25,7 @@ import init, { Config } from './script'; import { useEffect, useRef } from 'react'; import { createRoot, Root } from 'react-dom/client'; -import { Ketcher } from 'ketcher-core'; +import { Ketcher, StructService } from 'ketcher-core'; import classes from './Editor.module.less'; import clsx from 'clsx'; import { useResizeObserver } from './hooks'; @@ -33,6 +33,7 @@ import { ketcherInitEventName, KETCHER_ROOT_NODE_CLASS_NAME, } from './constants'; +import { KetcherBuilder } from './script/builders'; const mediaSizes = { smallWidth: 1040, @@ -47,6 +48,10 @@ function Editor(props: EditorProps) { const initPromiseRef = useRef | null>(null); const appRootRef = useRef(null); const cleanupRef = useRef<(() => unknown) | null>(null); + const ketcherBuilderRef = useRef(null); + // eslint-disable-next-line @typescript-eslint/no-empty-function + const setServerRef = useRef<(structService: StructService) => void>(() => {}); + const structServiceProvider = props.structServiceProvider; const rootElRef = useRef(null); @@ -54,6 +59,13 @@ function Editor(props: EditorProps) { ref: rootElRef, }); + useEffect(() => { + ketcherBuilderRef.current?.reinitializeApi( + props.structServiceProvider, + setServerRef.current, + ); + }, [structServiceProvider]); + const initKetcher = () => { appRootRef.current = createRoot(rootElRef.current as HTMLDivElement); @@ -63,15 +75,19 @@ function Editor(props: EditorProps) { appRoot: appRootRef.current, }); - initPromiseRef.current?.then(({ ketcher, ketcherId, cleanup }) => { - cleanupRef.current = cleanup; + initPromiseRef.current?.then( + ({ ketcher, ketcherId, cleanup, builder, setServer }) => { + cleanupRef.current = cleanup; + ketcherBuilderRef.current = builder; + setServerRef.current = setServer; - if (typeof props.onInit === 'function' && ketcher) { - props.onInit(ketcher); - const ketcherInitEvent = new Event(ketcherInitEventName(ketcherId)); - window.dispatchEvent(ketcherInitEvent); - } - }); + if (typeof props.onInit === 'function' && ketcher) { + props.onInit(ketcher); + const ketcherInitEvent = new Event(ketcherInitEventName(ketcherId)); + window.dispatchEvent(ketcherInitEvent); + } + }, + ); }; useEffect(() => { if (initPromiseRef.current === null) { diff --git a/packages/ketcher-react/src/constants.ts b/packages/ketcher-react/src/constants.ts index 4a43d7df87..34fdad4631 100644 --- a/packages/ketcher-react/src/constants.ts +++ b/packages/ketcher-react/src/constants.ts @@ -31,3 +31,6 @@ export const KETCHER_ROOT_NODE_CSS_SELECTOR = `.${KETCHER_ROOT_NODE_CLASS_NAME}` export const EditorClassName = 'Ketcher-polymer-editor-root'; export const KETCHER_MACROMOLECULES_ROOT_NODE_SELECTOR = `.${EditorClassName}`; +export const STRUCT_SERVICE_NO_RENDER_INITIALIZED_EVENT = + 'struct-service-no-render-initialized'; +export const STRUCT_SERVICE_INITIALIZED_EVENT = 'struct-service-initialized'; diff --git a/packages/ketcher-react/src/script/api.ts b/packages/ketcher-react/src/script/api.ts index b18f31346c..c881d90da0 100644 --- a/packages/ketcher-react/src/script/api.ts +++ b/packages/ketcher-react/src/script/api.ts @@ -49,6 +49,7 @@ function createApi( getInChIKey: structService.getInChIKey.bind(structService), toggleExplicitHydrogens: structService.toggleExplicitHydrogens.bind(structService), + destroy: structService.destroy?.bind(structService), }); } diff --git a/packages/ketcher-react/src/script/builders/ketcher/KetcherBuilder.ts b/packages/ketcher-react/src/script/builders/ketcher/KetcherBuilder.ts index 7d58ee72d4..29f665282f 100644 --- a/packages/ketcher-react/src/script/builders/ketcher/KetcherBuilder.ts +++ b/packages/ketcher-react/src/script/builders/ketcher/KetcherBuilder.ts @@ -22,6 +22,7 @@ import { StructService, StructServiceProvider, ketcherProvider, + KetcherLogger, } from 'ketcher-core'; import { ButtonsConfig } from './ButtonsConfig'; @@ -30,6 +31,7 @@ import createApi from '../../api'; import { initApp } from '../../ui'; import { Root } from 'react-dom/client'; import { IndigoProvider } from 'src/script/providers'; +import { STRUCT_SERVICE_INITIALIZED_EVENT } from '../../../constants'; class KetcherBuilder { private structService: StructService | null; @@ -51,6 +53,39 @@ class KetcherBuilder { ); } + reinitializeApi( + structServiceProvider: StructServiceProvider, + setStructServiceToStore: (structService: StructService) => void, + ) { + const oldStructService = this.structService; + + this.structService = createApi( + structServiceProvider, + DefaultStructServiceOptions, + ); + + window.addEventListener( + STRUCT_SERVICE_INITIALIZED_EVENT, + () => { + oldStructService?.destroy?.(); + + if (!this.structService) { + KetcherLogger.warn('Structure service is not reinitialized'); + + return; + } + + const ketcher = ketcherProvider.getKetcher(); + ketcher.reinitializeIndigo(this.structService); + IndigoProvider.setIndigo(this.structService); + setStructServiceToStore(this.structService); + }, + { once: true }, + ); + + return this.structService; + } + appendServiceMode(mode: ServiceMode) { this.serviceMode = mode; } @@ -66,14 +101,16 @@ class KetcherBuilder { setKetcher: (ketcher: Ketcher) => void; ketcherId: string; cleanup: ReturnType | null; + setServer: (structService: StructService) => void; }> { const { structService } = this; let cleanup: ReturnType | null = null; - const { editor, setKetcher, ketcherId } = await new Promise<{ + const { editor, setKetcher, ketcherId, setServer } = await new Promise<{ editor: Editor; setKetcher: (ketcher: Ketcher) => void; ketcherId: string; + setServer: (structService: StructService) => void; }>((resolve) => { cleanup = initApp( element, @@ -100,7 +137,7 @@ class KetcherBuilder { () => {}; this.formatterFactory = new FormatterFactory(structService!); - return { setKetcher, ketcherId, cleanup }; + return { setKetcher, ketcherId, cleanup, setServer }; } build() { diff --git a/packages/ketcher-react/src/script/index.ts b/packages/ketcher-react/src/script/index.ts index d51b76b4dc..e98e0bd53a 100644 --- a/packages/ketcher-react/src/script/index.ts +++ b/packages/ketcher-react/src/script/index.ts @@ -42,20 +42,21 @@ async function buildKetcherAsync({ await builder.appendApiAsync(structServiceProvider); builder.appendServiceMode(structServiceProvider.mode); - const { setKetcher, ketcherId, cleanup } = await builder.appendUiAsync( - element, - appRoot, - staticResourcesUrl, - errorHandler, - buttons, - togglerComponent, - ); + const { setKetcher, ketcherId, cleanup, setServer } = + await builder.appendUiAsync( + element, + appRoot, + staticResourcesUrl, + errorHandler, + buttons, + togglerComponent, + ); const ketcher = builder.build(); if (ketcher) { setKetcher(ketcher); } - return { ketcher, ketcherId, cleanup }; + return { ketcher, ketcherId, cleanup, builder, setServer }; } export type { Config, ButtonsConfig }; diff --git a/packages/ketcher-react/src/script/ui/App/initApp.tsx b/packages/ketcher-react/src/script/ui/App/initApp.tsx index 6acf036292..701bd693db 100644 --- a/packages/ketcher-react/src/script/ui/App/initApp.tsx +++ b/packages/ketcher-react/src/script/ui/App/initApp.tsx @@ -25,7 +25,7 @@ import App from './App.container'; import { Provider } from 'react-redux'; import { uniqueId } from 'lodash'; import { Root } from 'react-dom/client'; -import createStore from '../state'; +import createStore, { setServer } from '../state'; import { initKeydownListener, removeKeydownListener } from '../state/hotkeys'; import { initResize } from '../state/toolbar'; import { initMouseListener, removeMouseListeners } from '../state/mouse'; @@ -40,6 +40,7 @@ function initApp( editor: any; setKetcher: (ketcher: Ketcher) => void; ketcherId: string; + setServer: (server: StructService) => void; }) => void, togglerComponent?: JSX.Element, ) { @@ -48,11 +49,22 @@ function initApp( ketcherRef = ketcher; }; const ketcherId = uniqueId(); + // hack to return server setter to Editor.tsx + // because it does not have access to store + let getServerSetter: () => (structService: StructService) => void; + const setEditor = (editor) => { - resolve({ editor, setKetcher, ketcherId }); + const setServer = getServerSetter(); + resolve({ editor, setKetcher, ketcherId, setServer }); }; const store = createStore(options, server, setEditor); + getServerSetter = () => { + return (structService: StructService) => { + store.dispatch(setServer(structService)); + }; + }; + store.dispatch(initKeydownListener(element)); store.dispatch(initMouseListener(element)); store.dispatch(initResize()); diff --git a/packages/ketcher-react/src/script/ui/state/index.js b/packages/ketcher-react/src/script/ui/state/index.js index 49c7e339a8..aec9b8ded1 100644 --- a/packages/ketcher-react/src/script/ui/state/index.js +++ b/packages/ketcher-react/src/script/ui/state/index.js @@ -34,6 +34,8 @@ import floatingToolsReducer from './floatingTools'; export { onAction, load }; +export const SET_SERVER = 'SET_SERVER'; + const shared = combineReducers({ common: commonReducer, actionState: actionStateReducer, @@ -69,6 +71,13 @@ function getRootReducer(setEditor) { } = action; if (data) state = { ...state, ...data }; } + + case SET_SERVER: { + state = { + ...state, + server: action.server || state.server, + }; + } } /* eslint-enable no-fallthrough */ @@ -115,3 +124,10 @@ export default function (options, server, setEditor) { const rootReducer = getRootReducer(setEditor); return createStore(rootReducer, initState, applyMiddleware(...middleware)); } + +export function setServer(server) { + return { + type: SET_SERVER, + server, + }; +} diff --git a/packages/ketcher-standalone/package.json b/packages/ketcher-standalone/package.json index 2bea36ec93..1e0be405c8 100644 --- a/packages/ketcher-standalone/package.json +++ b/packages/ketcher-standalone/package.json @@ -29,7 +29,7 @@ "node": ">=14" }, "scripts": { - "build": "cross-env NODE_ENV=production rollup -c -m true && cross-env NODE_ENV=production BINARY_WASM=true rollup -c -m true", + "build": "cross-env NODE_ENV=production rollup -c -m true && cross-env NODE_ENV=production INDIGO_MODULE_NAME=wasm rollup -c -m true && cross-env NODE_ENV=production INDIGO_MODULE_NAME=base64WithoutRender SEPARATE_INDIGO_RENDER=true rollup -c -m true && cross-env NODE_ENV=production INDIGO_MODULE_NAME=wasmWithoutRender SEPARATE_INDIGO_RENDER=true rollup -c -m true", "start": "cross-env NODE_ENV=development rollup -c -m true -w", "test": "run-s test:prettier test:eslint:quiet test:types test:unit", "test:eslint": "eslint . --ext .ts,.js", @@ -42,7 +42,7 @@ }, "dependencies": { "@babel/runtime": "^7.17.9", - "indigo-ketcher": "1.22.0-rc.3", + "indigo-ketcher": "1.23.0-dev.3", "ketcher-core": "*" }, "devDependencies": { @@ -54,6 +54,7 @@ "@rollup/plugin-babel": "^5.2.1", "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-strip": "^2.0.0", "@types/jest": "^27.0.3", "@types/node": "^16.11.12", @@ -84,6 +85,14 @@ "./dist/binaryWasm": { "import": "./dist/binaryWasm/index.modern.js", "require": "./dist/binaryWasm/index.js" + }, + "./dist/jsNoRender": { + "import": "./dist/jsNoRender/index.modern.js", + "require": "./dist/jsNoRender/index.js" + }, + "./dist/binaryWasmNoRender": { + "import": "./dist/binaryWasmNoRender/index.modern.js", + "require": "./dist/binaryWasmNoRender/index.js" } } } diff --git a/packages/ketcher-standalone/rollup.config.js b/packages/ketcher-standalone/rollup.config.js index 1efbdaf061..f7bfba7050 100644 --- a/packages/ketcher-standalone/rollup.config.js +++ b/packages/ketcher-standalone/rollup.config.js @@ -11,6 +11,7 @@ import webWorkerLoader from 'rollup-plugin-web-worker-loader'; import copy from 'rollup-plugin-copy'; import alias from '@rollup/plugin-alias'; import { license } from '../../license.ts'; +import replace from '@rollup/plugin-replace'; const mode = { PRODUCTION: 'production', @@ -45,6 +46,9 @@ const baseConfig = { include: includePattern, comments: 'none', }), + replace({ + 'process.env.SEPARATE_INDIGO_RENDER': process.env.SEPARATE_INDIGO_RENDER, + }), ...(isProduction ? [strip({ include: includePattern })] : []), ], }; @@ -71,8 +75,17 @@ const configWithWasmBase64 = { runOnce: true, }), ...baseConfig.plugins, + alias({ + entries: [ + { + find: '_indigo-ketcher-import-alias_', + replacement: 'indigo-ketcher', + }, + ], + }), ], }; + const configWithWasmFetch = { ...baseConfig, output: [ @@ -93,7 +106,10 @@ const configWithWasmFetch = { ...baseConfig.plugins, alias({ entries: [ - { find: 'indigo-ketcher', replacement: 'indigo-ketcher/binaryWasm' }, + { + find: '_indigo-ketcher-import-alias_', + replacement: 'indigo-ketcher/binaryWasm', + }, ], }), copy({ @@ -107,6 +123,77 @@ const configWithWasmFetch = { ], }; -export default process.env.BINARY_WASM - ? configWithWasmFetch - : configWithWasmBase64; +const configBase64WithoutRender = { + ...baseConfig, + output: [ + { + file: pkg.exports['./dist/jsNoRender'].require, + exports: 'named', + format: 'cjs', + banner: license, + }, + { + file: pkg.exports['./dist/jsNoRender'].import, + exports: 'named', + format: 'es', + banner: license, + }, + ], + plugins: [ + ...baseConfig.plugins, + alias({ + entries: [ + { + find: '_indigo-ketcher-import-alias_', + replacement: 'indigo-ketcher/jsNoRender', + }, + ], + }), + ], +}; + +const configWithWasmWithoutRender = { + ...baseConfig, + output: [ + { + file: pkg.exports['./dist/binaryWasmNoRender'].require, + exports: 'named', + format: 'cjs', + banner: license, + }, + { + file: pkg.exports['./dist/binaryWasmNoRender'].import, + exports: 'named', + format: 'es', + banner: license, + }, + ], + plugins: [ + ...baseConfig.plugins, + alias({ + entries: [ + { + find: '_indigo-ketcher-import-alias_', + replacement: 'indigo-ketcher/binaryWasmNoRender', + }, + ], + }), + copy({ + targets: [ + { + src: '../../node_modules/indigo-ketcher/*.wasm', + dest: 'dist/binaryWasmNoRender', + }, + ], + }), + ], +}; + +const modulesMap = { + base64: configWithWasmBase64, + wasm: configWithWasmFetch, + base64WithoutRender: configBase64WithoutRender, + wasmWithoutRender: configWithWasmWithoutRender, +}; + +export default modulesMap[process.env.INDIGO_MODULE_NAME] || modulesMap.base64; diff --git a/packages/ketcher-standalone/src/index.ts b/packages/ketcher-standalone/src/index.ts index fed122cfbb..f7e605f63b 100644 --- a/packages/ketcher-standalone/src/index.ts +++ b/packages/ketcher-standalone/src/index.ts @@ -14,9 +14,4 @@ * limitations under the License. ***************************************************************************/ -import { - StandaloneStructService, - StandaloneStructServiceProvider, -} from './infrastructure/services'; - -export { StandaloneStructServiceProvider, StandaloneStructService }; +export * from './infrastructure/services'; diff --git a/packages/ketcher-standalone/src/infrastructure/services/index.ts b/packages/ketcher-standalone/src/infrastructure/services/index.ts index afc93ef122..d5a66d6ebc 100644 --- a/packages/ketcher-standalone/src/infrastructure/services/index.ts +++ b/packages/ketcher-standalone/src/infrastructure/services/index.ts @@ -20,3 +20,4 @@ import { } from './struct'; export { StandaloneStructServiceProvider, StandaloneStructService }; +export * from './struct/constants'; diff --git a/packages/ketcher-standalone/src/infrastructure/services/struct/constants.ts b/packages/ketcher-standalone/src/infrastructure/services/struct/constants.ts new file mode 100644 index 0000000000..a22d5655d8 --- /dev/null +++ b/packages/ketcher-standalone/src/infrastructure/services/struct/constants.ts @@ -0,0 +1,3 @@ +export const STRUCT_SERVICE_NO_RENDER_INITIALIZED_EVENT = + 'struct-service-no-render-initialized'; +export const STRUCT_SERVICE_INITIALIZED_EVENT = 'struct-service-initialized'; diff --git a/packages/ketcher-standalone/src/infrastructure/services/struct/index.ts b/packages/ketcher-standalone/src/infrastructure/services/struct/index.ts index f9e34d1a7a..a050c13740 100644 --- a/packages/ketcher-standalone/src/infrastructure/services/struct/index.ts +++ b/packages/ketcher-standalone/src/infrastructure/services/struct/index.ts @@ -18,3 +18,4 @@ import StandaloneStructService from './standaloneStructService'; import StandaloneStructServiceProvider from './standaloneStructServiceProvider'; export { StandaloneStructService, StandaloneStructServiceProvider }; +export * from './constants'; diff --git a/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.ts b/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.ts index f8db05ab3f..c17ee4ff4e 100644 --- a/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.ts +++ b/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.ts @@ -38,7 +38,7 @@ import { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -import indigoModuleFn from 'indigo-ketcher'; +import indigoModuleFn from '_indigo-ketcher-import-alias_'; interface IndigoOptions { set: (key: string, value: string) => void; @@ -113,6 +113,8 @@ self.onmessage = (e: MessageEvent>) => { case Command.Layout: { const data: LayoutCommandData = message.data as LayoutCommandData; + // eslint-disable-next-line no-debugger + console.info('hew'); handle( (indigo, indigoOptions) => { const response = indigo.layout( diff --git a/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts b/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts index 6cc1f71861..39fec01798 100644 --- a/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts +++ b/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts @@ -24,17 +24,17 @@ import { CleanCommandData, Command, CommandOptions, - WorkerEvent, ConvertCommandData, DearomatizeCommandData, + ExplicitHydrogensCommandData, GenerateImageCommandData, GenerateInchIKeyCommandData, InputMessage, LayoutCommandData, OutputMessage, - SupportedFormat, OutputMessageWrapper, - ExplicitHydrogensCommandData, + SupportedFormat, + WorkerEvent, } from './indigoWorker.types'; import { AromatizeData, @@ -52,25 +52,29 @@ import { CleanResult, ConvertData, ConvertResult, + CoreEditor, DearomatizeData, DearomatizeResult, ExplicitHydrogensData, ExplicitHydrogensResult, GenerateImageOptions, + getLabelRenderModeForIndigo, InfoResult, LayoutData, LayoutResult, RecognizeResult, StructService, StructServiceOptions, - getLabelRenderModeForIndigo, - CoreEditor, } from 'ketcher-core'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import IndigoWorker from 'web-worker:./indigoWorker'; import EventEmitter from 'events'; +import { + STRUCT_SERVICE_INITIALIZED_EVENT, + STRUCT_SERVICE_NO_RENDER_INITIALIZED_EVENT, +} from './constants'; interface KeyValuePair { [key: string]: number | string | boolean | object; @@ -207,7 +211,7 @@ let worker: IndigoWorker; class IndigoService implements StructService { private readonly defaultOptions: StructServiceOptions; - private readonly worker: IndigoWorker; + private worker: IndigoWorker; private readonly EE: EventEmitter = new EventEmitter(); constructor(defaultOptions: StructServiceOptions) { @@ -215,6 +219,14 @@ class IndigoService implements StructService { this.worker = worker || new IndigoWorker(); worker = this.worker; this.worker.onmessage = (e: MessageEvent>) => { + if (e.data.type === Command.Info) { + const callbackMethod = process.env.SEPARATE_INDIGO_RENDER + ? this.callIndigoNoRenderLoadedCallback + : this.callIndigoLoadedCallback; + + callbackMethod(); + } + const message: OutputMessage = e.data; if (message.type !== undefined) { const event = messageTypeToEventMapping[message.type]; @@ -223,6 +235,14 @@ class IndigoService implements StructService { }; } + private callIndigoNoRenderLoadedCallback() { + window.dispatchEvent(new Event(STRUCT_SERVICE_NO_RENDER_INITIALIZED_EVENT)); + } + + private callIndigoLoadedCallback() { + window.dispatchEvent(new Event(STRUCT_SERVICE_INITIALIZED_EVENT)); + } + async getInChIKey(struct: string): Promise { return new Promise((resolve, reject) => { const action = ({ data }: OutputMessageWrapper) => { @@ -799,6 +819,13 @@ class IndigoService implements StructService { this.worker.postMessage(inputMessage); }); } + + public destroy() { + this.worker.terminate(); + this.worker.onmessage = null; + this.worker = null; + worker = null; + } } export default IndigoService;