From f2af0c3e026432bc53e2eb5672722f40e5964c6a Mon Sep 17 00:00:00 2001 From: Kirill Kapytov <75835582+r1z3rISGOD@users.noreply.github.com> Date: Mon, 29 Nov 2021 18:07:23 +0300 Subject: [PATCH 1/6] #1030 Copy/Past actions dont work for simple objects (#1037) * Change format name for copy and past actions * Change hardcode parts to enum elements * Used prettier Co-authored-by: Kirill Kapytov --- .../src/script/ui/state/hotkeys.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/ketcher-react/src/script/ui/state/hotkeys.js b/packages/ketcher-react/src/script/ui/state/hotkeys.js index 68ce245a01..cb7f83839a 100644 --- a/packages/ketcher-react/src/script/ui/state/hotkeys.js +++ b/packages/ketcher-react/src/script/ui/state/hotkeys.js @@ -16,7 +16,12 @@ import * as clipArea from '../component/cliparea/cliparea' -import { KetSerializer, MolSerializer, formatProperties } from 'ketcher-core' +import { + KetSerializer, + MolSerializer, + formatProperties, + ChemicalMimeType +} from 'ketcher-core' import { debounce, isEqual } from 'lodash/fp' import { load, onAction } from './shared' @@ -144,9 +149,9 @@ export function initClipboard(dispatch, getState) { }, onPaste(data) { const structStr = - data['application/json'] || - data['chemical/x-mdl-molfile'] || - data['chemical/x-mdl-rxnfile'] || + data[ChemicalMimeType.KET] || + data[ChemicalMimeType.Mol] || + data[ChemicalMimeType.Rxn] || data['text/plain'] if (structStr || !rxnTextPlain.test(data['text/plain'])) @@ -175,11 +180,9 @@ function clipData(editor) { try { const serializer = new KetSerializer() const ket = serializer.serialize(struct) - res['application/json'] = ket + res[ChemicalMimeType.KET] = ket - const type = struct.isReaction - ? 'chemical/x-mdl-molfile' - : 'chemical/x-mdl-rxnfile' + const type = struct.isReaction ? ChemicalMimeType.Mol : ChemicalMimeType.Rxn const data = molSerializer.serialize(struct) res['text/plain'] = data res[type] = data From 360492ff18394f923223ac44331ce3a8dfa78d4b Mon Sep 17 00:00:00 2001 From: Kirill Kapytov <75835582+r1z3rISGOD@users.noreply.github.com> Date: Tue, 30 Nov 2021 11:25:07 +0300 Subject: [PATCH 2/6] Revert changes in #637 (#1041) Co-authored-by: Kirill Kapytov --- .../src/application/render/restruct/reatom.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ketcher-core/src/application/render/restruct/reatom.ts b/packages/ketcher-core/src/application/render/restruct/reatom.ts index 95cf2a245d..70830104b1 100644 --- a/packages/ketcher-core/src/application/render/restruct/reatom.ts +++ b/packages/ketcher-core/src/application/render/restruct/reatom.ts @@ -305,7 +305,7 @@ class ReAtom extends ReObject { } if (this.a.attpnt) { - const lsb = bisectSmallestSector(this, restruct.molecule) + const lsb = bisectLargestSector(this, restruct.molecule) showAttpnt(this, render, lsb, restruct.addReObjectPath.bind(restruct)) } @@ -349,7 +349,7 @@ class ReAtom extends ReObject { draw.recenterText(aamPath, aamBox) const visel = this.visel let t = 3 - let dir = bisectSmallestSector(this, restruct.molecule) + let dir = bisectLargestSector(this, restruct.molecule) // estimate the shift to clear the atom label for (let i = 0; i < visel.exts.length; ++i) t = Math.max(t, util.shiftRayBox(ps, dir, visel.exts[i].translate(ps))) @@ -991,7 +991,7 @@ function pathAndRBoxTranslate(path, rbb, x, y) { rbb.y += y } -function bisectSmallestSector(atom: ReAtom, struct: Struct) { +function bisectLargestSector(atom: ReAtom, struct: Struct) { let angles: Array = [] atom.a.neighbors.forEach(hbid => { const hb = struct.halfBonds.get(hbid) @@ -1003,11 +1003,11 @@ function bisectSmallestSector(atom: ReAtom, struct: Struct) { da.push(angles[(i + 1) % angles.length] - angles[i]) } da.push(angles[0] - angles[angles.length - 1] + 2 * Math.PI) - let daMin = Number.MAX_VALUE + let daMax = 0 let ang = -Math.PI / 2 for (let i = 0; i < angles.length; ++i) { - if (da[i] < daMin) { - daMin = da[i] + if (da[i] > daMax) { + daMax = da[i] ang = angles[i] + da[i] / 2 } } From 33a1e52f79716d5e167ee5f2f1eaa47bfbae3996 Mon Sep 17 00:00:00 2001 From: Aleksandr Gabrielov <94836496+Alex-Work-Account@users.noreply.github.com> Date: Wed, 1 Dec 2021 18:56:39 +0300 Subject: [PATCH 3/6] #1029 structure shouldnt flip if part selected (#1043) * Fix structure flip if part selected * Fix bug with if condition and refactor --- .../src/application/editor/actions/rotate.ts | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/ketcher-core/src/application/editor/actions/rotate.ts b/packages/ketcher-core/src/application/editor/actions/rotate.ts index e48d9c7d46..deda7379eb 100644 --- a/packages/ketcher-core/src/application/editor/actions/rotate.ts +++ b/packages/ketcher-core/src/application/editor/actions/rotate.ts @@ -34,26 +34,37 @@ export function fromFlip(restruct, selection, dir, center) { const action = new Action() - if (!selection) selection = structSelection(struct) + if (!selection) { + selection = structSelection(struct) + } - if (!selection.atoms) return action.perform(restruct) + if (!selection.atoms) { + return action.perform(restruct) + } const fids = selection.atoms.reduce((acc, aid) => { const atom = struct.atoms.get(aid) - if (!acc[atom.fragment]) acc[atom.fragment] = [] + if (!acc[atom.fragment]) { + acc[atom.fragment] = [] + } acc[atom.fragment].push(aid) return acc }, {}) - const isFragFound = Object.keys(fids) - .map(frag => parseInt(frag, 10)) - .find(frag => { - return !struct.getFragmentIds(frag).equals(new Pile(fids[frag])) - }) + const fidsNumberKeys = Object.keys(fids).map(frag => parseInt(frag, 10)) + + const isFragFound = fidsNumberKeys.find(frag => { + const allFragmentsOfStructure = struct.getFragmentIds(frag) + const selectedFragmentsOfStructure = new Pile(fids[frag]) + const res = allFragmentsOfStructure.equals(selectedFragmentsOfStructure) + return !res + }) - if (isFragFound) return action // empty action + if (typeof isFragFound === 'number') { + return action // empty action + } Object.keys(fids).forEach(frag => { const fragment = new Pile(fids[frag]) @@ -80,7 +91,9 @@ export function fromFlip(restruct, selection, dir, center) { selection.bonds.forEach(bid => { const bond = struct.bonds.get(bid) - if (bond.type !== Bond.PATTERN.TYPE.SINGLE) return + if (bond.type !== Bond.PATTERN.TYPE.SINGLE) { + return + } if (bond.stereo === Bond.PATTERN.STEREO.UP) { action.addOp(new BondAttr(bid, 'stereo', Bond.PATTERN.STEREO.DOWN)) @@ -120,7 +133,9 @@ export function fromRotate(restruct, selection, center, angle) { const action = new Action() - if (!selection) selection = structSelection(struct) + if (!selection) { + selection = structSelection(struct) + } if (selection.atoms) { selection.atoms.forEach(aid => { From 24ddecff94fea2dddb99eec288ab29c06b7aea2f Mon Sep 17 00:00:00 2001 From: Aleksandr Gabrielov <94836496+Alex-Work-Account@users.noreply.github.com> Date: Wed, 1 Dec 2021 19:16:04 +0300 Subject: [PATCH 4/6] #1003 error when apply 3 d to empty canvas (#1034) * Add ErrorModalProps to ErrorModal * Fix error when apply 3D to empty canvas * Fix pr * Fix PR --- example/src/ErrorModal/ErrorModal.tsx | 3 ++- .../src/script/ui/views/modal/components/process/Miew/Miew.jsx | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/example/src/ErrorModal/ErrorModal.tsx b/example/src/ErrorModal/ErrorModal.tsx index 496fd54bf6..0f983928ce 100644 --- a/example/src/ErrorModal/ErrorModal.tsx +++ b/example/src/ErrorModal/ErrorModal.tsx @@ -31,7 +31,8 @@ const ErrorModal = ({ message, close }: ErrorModalProps) => { className={'ok'} onClick={() => { close() - }}> + }} + > OK diff --git a/packages/ketcher-react/src/script/ui/views/modal/components/process/Miew/Miew.jsx b/packages/ketcher-react/src/script/ui/views/modal/components/process/Miew/Miew.jsx index 56b764f007..730b145a5c 100644 --- a/packages/ketcher-react/src/script/ui/views/modal/components/process/Miew/Miew.jsx +++ b/packages/ketcher-react/src/script/ui/views/modal/components/process/Miew/Miew.jsx @@ -110,6 +110,9 @@ class MiewDialog extends Component { exportCML() { const cmlStruct = this.viewer.exportCML() + if (!cmlStruct) { + return + } this.props.onExportCML(cmlStruct) } From d221fc1c3fd5024a71267c894ca2fcaae04d7a08 Mon Sep 17 00:00:00 2001 From: ElenaOdnoshivkina <89905163+ElenaOdnoshivkina@users.noreply.github.com> Date: Thu, 2 Dec 2021 17:44:13 +0300 Subject: [PATCH 5/6] #1025 Redesign the floating windows - Save structure (#1050) * redesign Save modal window * fix styles and error handler --- .../formatters/formatProperties.ts | 11 +- packages/ketcher-react/src/Editor.module.less | 2 - .../components/Dialog/Dialog.module.less | 14 +- .../modal/components/document/Save/Save.jsx | 236 ++++++++++-------- .../components/document/Save/Save.module.less | 95 +++++-- packages/ketcher-react/src/style/mixins.less | 6 - 6 files changed, 226 insertions(+), 138 deletions(-) diff --git a/packages/ketcher-core/src/application/formatters/formatProperties.ts b/packages/ketcher-core/src/application/formatters/formatProperties.ts index 5be72899c6..9e2fd133e1 100644 --- a/packages/ketcher-core/src/application/formatters/formatProperties.ts +++ b/packages/ketcher-core/src/application/formatters/formatProperties.ts @@ -89,8 +89,17 @@ const formatProperties: FormatPropertiesMap = { ) } +const imgFormatProperties = { + svg: { extension: '.svg', name: 'SVG Document' }, + png: { extension: '.png', name: 'PNG Image' } +} + +function getPropertiesByImgFormat(format) { + return imgFormatProperties[format] +} + function getPropertiesByFormat(format: SupportedFormat) { return formatProperties[format] } -export { formatProperties, getPropertiesByFormat } +export { formatProperties, getPropertiesByFormat, getPropertiesByImgFormat } diff --git a/packages/ketcher-react/src/Editor.module.less b/packages/ketcher-react/src/Editor.module.less index 8831c744b3..5997a2e960 100644 --- a/packages/ketcher-react/src/Editor.module.less +++ b/packages/ketcher-react/src/Editor.module.less @@ -110,8 +110,6 @@ &[readonly], fieldset[disabled] & { cursor: not-allowed; - background: #efefef; - opacity: 0.6; } } diff --git a/packages/ketcher-react/src/script/ui/views/components/Dialog/Dialog.module.less b/packages/ketcher-react/src/script/ui/views/components/Dialog/Dialog.module.less index 2dcf81eab0..70571e3e4a 100644 --- a/packages/ketcher-react/src/script/ui/views/components/Dialog/Dialog.module.less +++ b/packages/ketcher-react/src/script/ui/views/components/Dialog/Dialog.module.less @@ -138,11 +138,17 @@ } button { - background: #d1d5e3; + background: transparent; + border: 1px solid #343434; + color: #343434; float: left; - } - button:hover { - background: #aaadb9; + &:hover { + border: 1px solid #000; + color: #000; + } + &:disabled { + opacity: 0.4; + } } } .body { diff --git a/packages/ketcher-react/src/script/ui/views/modal/components/document/Save/Save.jsx b/packages/ketcher-react/src/script/ui/views/modal/components/document/Save/Save.jsx index 3881348b08..f53a8068e2 100644 --- a/packages/ketcher-react/src/script/ui/views/modal/components/document/Save/Save.jsx +++ b/packages/ketcher-react/src/script/ui/views/modal/components/document/Save/Save.jsx @@ -20,15 +20,15 @@ import Form, { Field } from '../../../../../component/form/form/form' import { FormatterFactory, formatProperties, - getPropertiesByFormat + getPropertiesByFormat, + getPropertiesByImgFormat, + KetSerializer } from 'ketcher-core' import { Component, createRef } from 'react' import { Dialog } from '../../../../components' import { ErrorsContext } from '../../../../../../../contexts' import SaveButton from '../../../../../component/view/savebutton' -import SaveImageTab from './SaveImageTab' -import Tabs from '../../../../../component/view/Tabs' import { check } from '../../../../../state/server' import classes from './Save.module.less' import { connect } from 'react-redux' @@ -40,7 +40,7 @@ const saveSchema = { type: 'object', properties: { filename: { - title: 'Filename', + title: 'File name:', type: 'string', maxLength: 128, pattern: /^[^.<>:?"*|/\\][^<>:?"*|/\\]*$/, @@ -51,7 +51,7 @@ const saveSchema = { } }, format: { - title: 'Format', + title: 'File format:', enum: Object.keys(formatProperties), enumNames: Object.keys(formatProperties).map( format => formatProperties[format].name @@ -79,7 +79,9 @@ class SaveDialog extends Component { 'smarts', 'inChI', 'inChIAuxInfo', - 'cml' + 'cml', + 'svg', + 'png' ) this.saveSchema = saveSchema @@ -87,7 +89,11 @@ class SaveDialog extends Component { this.saveSchema.properties.format, { enum: formats, - enumNames: formats.map(format => getPropertiesByFormat(format).name) + enumNames: formats.map(format => { + const formatProps = + getPropertiesByFormat(format) || getPropertiesByImgFormat(format) + return formatProps.name + }) } ) } @@ -100,41 +106,61 @@ class SaveDialog extends Component { ) } + isImageFormat = format => { + return !!getPropertiesByImgFormat(format) + } + showStructWarningMessage = format => { const { errors } = this.props.formState return format !== 'mol' && Object.keys(errors).length > 0 } changeType = type => { - const errorHandler = this.context.errorHandler - this.setState({ disableControls: true }) - const { struct, server, options, formState } = this.props + const errorHandler = this.context.errorHandler + if (this.isImageFormat(type)) { + const ketSerialize = new KetSerializer() + const structStr = ketSerialize.serialize(struct) + this.setState({ imageFormat: type, structStr }) + let options = {} + options.outputFormat = type - const factory = new FormatterFactory(server) - - const service = factory.create(type, options) - - return service - .getStructureFromStructAsync(struct) - .then( - structStr => { - this.setState({ structStr }) - setTimeout(() => { - if (this.textAreaRef.current) { - this.textAreaRef.current.select() - } - }, 10) // TODO: remove hack - }, - e => { - errorHandler(e.message) + return server + .generateImageAsBase64(structStr, options) + .then(base64 => { + this.setState({ imageSrc: base64 }) + }) + .catch(e => { + errorHandler(e) this.props.onResetForm(formState) return e - } - ) - .finally(() => { - this.setState({ disableControls: false }) - }) + }) + } else { + this.setState({ disableControls: true }) + const factory = new FormatterFactory(server) + const service = factory.create(type, options) + + return service + .getStructureFromStructAsync(struct) + .then( + structStr => { + this.setState({ structStr }) + setTimeout(() => { + if (this.textAreaRef.current) { + this.textAreaRef.current.select() + } + }, 10) // TODO: remove hack + }, + e => { + errorHandler(e.message) + this.props.onResetForm(formState) + return e + } + ) + .finally(() => { + this.setState({ disableControls: false }) + }) + } } getWarnings = format => { @@ -143,14 +169,15 @@ class SaveDialog extends Component { const structWarning = 'Structure contains errors, please check the data, otherwise you ' + 'can lose some properties or the whole structure after saving in this format.' - const saveWarning = structFormat.couldBeSaved(struct, format) - const isStructInvalid = this.showStructWarningMessage(format) - - if (isStructInvalid) { - warnings.push(structWarning) - } - if (saveWarning) { - warnings.push(saveWarning) + if (!this.isImageFormat(format)) { + const saveWarning = structFormat.couldBeSaved(struct, format) + const isStructInvalid = this.showStructWarningMessage(format) + if (isStructInvalid) { + warnings.push(structWarning) + } + if (saveWarning) { + warnings.push(saveWarning) + } } if (moleculeErrors) { @@ -159,20 +186,14 @@ class SaveDialog extends Component { return warnings } - changeTab = tabIndex => { - this.setState({ tabIndex }) - } - - changeImageFormat = imageFormat => { - this.setState({ imageFormat }) - } - renderSaveFile = () => { const formState = Object.assign({}, this.props.formState) delete formState.moleculeErrors const { filename, format } = formState.result const warnings = this.getWarnings(format) - const { structStr } = this.state + const { structStr, imageSrc } = this.state + const isCleanStruct = this.props.struct.isBlank() + return (
-