diff --git a/packages/ketcher-core/src/application/formatters/formatProperties.ts b/packages/ketcher-core/src/application/formatters/formatProperties.ts index 65d40159b8..514455c7c7 100644 --- a/packages/ketcher-core/src/application/formatters/formatProperties.ts +++ b/packages/ketcher-core/src/application/formatters/formatProperties.ts @@ -146,4 +146,17 @@ function getPropertiesByFormat(format: SupportedFormat) { return formatProperties[format]; } -export { formatProperties, getPropertiesByFormat, getPropertiesByImgFormat }; +function getFormatMimeTypeByFileName(fileName: string) { + const fileExtension = '.' + fileName.split('.').pop(); + const format = Object.values(formatProperties).find((properties) => { + return properties.extensions.includes(fileExtension); + }); + return format?.mime; +} + +export { + formatProperties, + getPropertiesByFormat, + getPropertiesByImgFormat, + getFormatMimeTypeByFileName, +}; diff --git a/packages/ketcher-react/src/script/ui/component/cliparea/cliparea.jsx b/packages/ketcher-react/src/script/ui/component/cliparea/cliparea.jsx index 42ca411fee..6a24d745ca 100644 --- a/packages/ketcher-react/src/script/ui/component/cliparea/cliparea.jsx +++ b/packages/ketcher-react/src/script/ui/component/cliparea/cliparea.jsx @@ -18,6 +18,7 @@ import { Component, createRef } from 'react'; import clsx from 'clsx'; import classes from './cliparea.module.less'; import { KetcherLogger } from 'ketcher-core'; +import { isControlKey } from '../../data/convert/keynorm'; const ieCb = window.clipboardData; @@ -73,6 +74,17 @@ class ClipArea extends Component { event.preventDefault(); } }, + keydown: async (event) => { + if (this.props.focused() && this.props.onPaste) { + if (isControlKey(event) && event.altKey && event.code === 'KeyV') { + const clipboardData = await navigator.clipboard.read(); + const data = await pasteByKeydown(clipboardData); + if (data) { + this.props.onPaste(data, true); + } + } + } + }, }; Object.keys(this.listeners).forEach((en) => { @@ -148,6 +160,19 @@ function paste(cb, formats) { return data; } +async function pasteByKeydown(cb) { + const data = {}; + if (!cb && ieCb) { + data['text/plain'] = ieCb.getData('text'); + } else { + for (const item of cb) { + const textPlain = await item.getType('text/plain'); + data['text/plain'] = await textPlain.text(); + } + } + return data; +} + export const actions = ['cut', 'copy', 'paste']; export function exec(action) { diff --git a/packages/ketcher-react/src/script/ui/data/convert/keynorm.js b/packages/ketcher-react/src/script/ui/data/convert/keynorm.js index 5ce1ece767..b047d98a64 100644 --- a/packages/ketcher-react/src/script/ui/data/convert/keynorm.js +++ b/packages/ketcher-react/src/script/ui/data/convert/keynorm.js @@ -82,6 +82,10 @@ function normalizeKeyEvent(event, base = false) { : modifiers(KN.base[event.keyCode], event, true); } +export function isControlKey(event) { + return mac ? event.metaKey : event.ctrlKey; +} + function keyNorm(obj) { if (obj instanceof KeyboardEvent) // eslint-disable-line no-undef diff --git a/packages/ketcher-react/src/script/ui/state/hotkeys.ts b/packages/ketcher-react/src/script/ui/state/hotkeys.ts index c8b3ef39cf..c842c74e59 100644 --- a/packages/ketcher-react/src/script/ui/state/hotkeys.ts +++ b/packages/ketcher-react/src/script/ui/state/hotkeys.ts @@ -292,7 +292,7 @@ export function initClipboard(dispatch) { editor.selection(null); return data; }, - onPaste(data) { + onPaste(data, isSmarts: boolean) { const structStr = data[ChemicalMimeType.KET] || data[ChemicalMimeType.Mol] || @@ -300,7 +300,13 @@ export function initClipboard(dispatch) { data['text/plain']; if (structStr || !rxnTextPlain.test(data['text/plain'])) - loadStruct(structStr, { fragment: true, isPaste: true }); + isSmarts + ? loadStruct(structStr, { + fragment: true, + isPaste: true, + 'input-format': ChemicalMimeType.DaylightSmarts, + }) + : loadStruct(structStr, { fragment: true, isPaste: true }); }, }; } diff --git a/packages/ketcher-react/src/script/ui/views/modal/components/document/Open/Open.container.ts b/packages/ketcher-react/src/script/ui/views/modal/components/document/Open/Open.container.ts index 17aa597d00..9d21967cba 100644 --- a/packages/ketcher-react/src/script/ui/views/modal/components/document/Open/Open.container.ts +++ b/packages/ketcher-react/src/script/ui/views/modal/components/document/Open/Open.container.ts @@ -44,6 +44,7 @@ const mapDispatchToProps = (dispatch): DispatchProps => ({ load(result.structStr, { badHeaderRecover: true, fragment: result.fragment, + 'input-format': result['input-format'], }), // TODO: Removed ownProps.onOk call. consider refactoring of load function in release 2.4 // See PR #731 (https://github.com/epam/ketcher/pull/731) diff --git a/packages/ketcher-react/src/script/ui/views/modal/components/document/Open/Open.tsx b/packages/ketcher-react/src/script/ui/views/modal/components/document/Open/Open.tsx index 15969cbacf..755282848f 100644 --- a/packages/ketcher-react/src/script/ui/views/modal/components/document/Open/Open.tsx +++ b/packages/ketcher-react/src/script/ui/views/modal/components/document/Open/Open.tsx @@ -22,6 +22,7 @@ import Recognize from '../../process/Recognize/Recognize'; import { fileOpener } from '../../../../../utils/'; import { DialogActionButton } from './components/DialogActionButton'; import { ViewSwitcher } from './components/ViewSwitcher'; +import { getFormatMimeTypeByFileName } from 'ketcher-core'; interface OpenProps { server: any; @@ -115,7 +116,8 @@ const Open: FC = (props) => { const { onOk } = rest; const copyHandler = () => { - onOk({ structStr, fragment: true }); + const format = getFormatMimeTypeByFileName(fileName); + onOk({ structStr, fragment: true, 'input-format': format }); }; const openHandler = () => { diff --git a/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.ts b/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.ts index cb660f47e1..8bb2fe08a9 100644 --- a/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.ts +++ b/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.ts @@ -109,8 +109,15 @@ self.onmessage = (e: MessageEvent>) => { case Command.Layout: { const data: LayoutCommandData = message.data as LayoutCommandData; handle( - (indigo, indigoOptions) => - indigo.layout(data.struct, data.format, indigoOptions), + (indigo, indigoOptions) => { + const response = indigo.layout( + data.struct, + data.format, + indigoOptions, + ); + const { struct } = JSON.parse(response); + return struct; + }, data.options, Command.Layout, ); diff --git a/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts b/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts index 8c3c2903ca..635c237c73 100644 --- a/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts +++ b/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts @@ -266,7 +266,9 @@ class IndigoService implements StructService { reject(msg.error); } }; - + if (options?.['input-format']) { + delete options['input-format']; + } const commandOptions: CommandOptions = { ...this.defaultOptions, ...options, @@ -315,6 +317,7 @@ class IndigoService implements StructService { const commandOptions: CommandOptions = { ...this.defaultOptions, ...options, + 'output-content-type': 'application/json', }; const commandData: LayoutCommandData = {