diff --git a/packages/ketcher-core/src/application/render/raphaelRender.ts b/packages/ketcher-core/src/application/render/raphaelRender.ts index c41a05cddd..ae38ea2d8d 100644 --- a/packages/ketcher-core/src/application/render/raphaelRender.ts +++ b/packages/ketcher-core/src/application/render/raphaelRender.ts @@ -199,58 +199,82 @@ export class Render { } update(force = false, viewSz: Vec2 | null = null) { - // eslint-disable-line max-statements - viewSz = - viewSz || - new Vec2( - this.clientArea.clientWidth || 100, - this.clientArea.clientHeight || 100, - ); - const changes = this.ctab.update(force); this.ctab.setSelection(); // [MK] redraw the selection bits where necessary if (changes) { - const bb = this.ctab - .getVBoxObj() - .transform(Scale.modelToCanvas, this.options) - .translate(this.options.offset || new Vec2()); - if (this.options.downScale) { this.ctab.molecule.rescale(); } - const isAutoScale = this.options.autoScale || this.options.downScale; if (!isAutoScale) { if (!this.oldCb) this.oldCb = new Box2Abs(); this.scrollbar.update(); this.options.offset = this.options.offset || new Vec2(); } else { - const sz1 = bb.sz(); + viewSz = this.getViewSize(viewSz); + const boundingBox = this.getBoundingBox(); + const boundingBoxSize = boundingBox.sz(); const marg = this.options.autoScaleMargin; - const mv = new Vec2(marg, marg); - const csz = viewSz; - if (marg && (csz.x < 2 * marg + 1 || csz.y < 2 * marg + 1)) { + const margVector = new Vec2(marg, marg); + if (marg && (viewSz.x < 2 * marg + 1 || viewSz.y < 2 * marg + 1)) { throw new Error('View box too small for the given margin'); } - let rescale = - this.options.rescaleAmount || - Math.max(sz1.x / (csz.x - 2 * marg), sz1.y / (csz.y - 2 * marg)); - - const isForceDownscale = this.options.downScale && rescale < 1; - const isBondsLengthFit = this.options.maxBondLength / rescale > 1; - if (isBondsLengthFit || isForceDownscale) { - rescale = 1; - } - const sz2 = sz1.add(mv.scaled(2 * rescale)); + const rescale = this.calculateRescale(); + const scaledBoundingBoxSize = boundingBoxSize.add( + margVector.scaled(2 * rescale), + ); this.paper.setViewBox( - bb.pos().x - marg * rescale - (csz.x * rescale - sz2.x) / 2, - bb.pos().y - marg * rescale - (csz.y * rescale - sz2.y) / 2, - csz.x * rescale, - csz.y * rescale, + boundingBox.pos().x - + marg * rescale - + (viewSz.x * rescale - scaledBoundingBoxSize.x) / 2, + boundingBox.pos().y - + marg * rescale - + (viewSz.y * rescale - scaledBoundingBoxSize.y) / 2, + viewSz.x * rescale, + viewSz.y * rescale, ); } notifyRenderComplete(); } } + + private getViewSize(viewSz: Vec2 | null) { + viewSz = + viewSz || + new Vec2( + this.clientArea.clientWidth || 100, + this.clientArea.clientHeight || 100, + ); + return viewSz; + } + + calculateRescale(viewSz: Vec2 | null = null) { + const marg = this.options.autoScaleMargin; + viewSz = this.getViewSize(viewSz); + const boundingBox = this.getBoundingBox(); + const boundingBoxSize = boundingBox.sz(); + let rescale = + this.options.rescaleAmount || + Math.max( + boundingBoxSize.x / (viewSz.x - 2 * marg), + boundingBoxSize.y / (viewSz.y - 2 * marg), + ); + if (this.ctab.molecule.minPreviewRescale) { + rescale = Math.min(this.ctab.molecule.minPreviewRescale, rescale); + } + const isForceDownscale = this.options.downScale && rescale < 1; + const isBondsLengthFit = this.options.maxBondLength / rescale > 1; + if (isBondsLengthFit || isForceDownscale) { + rescale = 1; + } + return rescale; + } + + private getBoundingBox() { + return this.ctab + .getVBoxObj() + .transform(Scale.modelToCanvas, this.options) + .translate(this.options.offset || new Vec2()); + } } diff --git a/packages/ketcher-core/src/application/render/renderStruct.ts b/packages/ketcher-core/src/application/render/renderStruct.ts index 401b5f03b5..ff0ea2107e 100644 --- a/packages/ketcher-core/src/application/render/renderStruct.ts +++ b/packages/ketcher-core/src/application/render/renderStruct.ts @@ -59,6 +59,9 @@ export class RenderStruct { el: HTMLElement | null, struct: Struct | null, options: any = {}, + originalStruct: Struct, + update = false, + monomerConnectionMode = false, ) { if (el && struct) { const { cachePrefix = '', needCache = true } = options; @@ -87,6 +90,14 @@ export class RenderStruct { preparedStruct.rescale(); rnd.setMolecule(preparedStruct); + if (monomerConnectionMode) { + const rescale = rnd.calculateRescale(); + if (update) { + originalStruct.previewRescale = rescale; + } else { + originalStruct.minPreviewRescale = rescale; + } + } this.removeSmallAttachmentPointLabelsInModal(rnd, options); if (needCache) { diff --git a/packages/ketcher-core/src/domain/entities/struct.ts b/packages/ketcher-core/src/domain/entities/struct.ts index 78d66a02b2..a00ad79195 100644 --- a/packages/ketcher-core/src/domain/entities/struct.ts +++ b/packages/ketcher-core/src/domain/entities/struct.ts @@ -75,6 +75,8 @@ export class Struct { texts: Pool; functionalGroups: Pool; highlights: Pool; + previewRescale: number; + minPreviewRescale: number; constructor() { this.atoms = new Pool(); @@ -95,6 +97,8 @@ export class Struct { this.texts = new Pool(); this.functionalGroups = new Pool(); this.highlights = new Pool(); + this.previewRescale = 0; + this.minPreviewRescale = 0; } hasRxnProps(): boolean { diff --git a/packages/ketcher-macromolecules/src/components/modal/monomerConnection/MonomerConnections.tsx b/packages/ketcher-macromolecules/src/components/modal/monomerConnection/MonomerConnections.tsx index 93e33467c0..8c85bc87bb 100644 --- a/packages/ketcher-macromolecules/src/components/modal/monomerConnection/MonomerConnections.tsx +++ b/packages/ketcher-macromolecules/src/components/modal/monomerConnection/MonomerConnections.tsx @@ -94,6 +94,24 @@ const MonomerConnection = ({ useState(getDefaultAttachmentPoint(secondMonomer)); const [modalExpanded, setModalExpanded] = useState(false); + const [rescaleForMonomers, setRescaleForMonomers] = useState( + null, + ); + + useEffect(() => { + if (modalExpanded) { + const rescale = Math.max( + firstMonomer.monomerItem.struct.previewRescale, + secondMonomer.monomerItem.struct.previewRescale, + ); + setRescaleForMonomers(rescale); + } + }, [ + firstMonomer.monomerItem.struct.previewRescale, + modalExpanded, + secondMonomer.monomerItem.struct.previewRescale, + ]); + const cancelBondCreationAndClose = () => { editor.events.cancelBondCreationViaModal.dispatch(secondMonomer); onClose(); @@ -114,6 +132,11 @@ const MonomerConnection = ({ onClose(); }; + const handleExpanded = (expand: boolean) => { + setModalExpanded(expand); + setRescaleForMonomers(null); + }; + return ( @@ -135,6 +158,7 @@ const MonomerConnection = ({ selectedAttachmentPoint={firstSelectedAttachmentPoint} onSelectAttachmentPoint={setFirstSelectedAttachmentPoint} expanded={modalExpanded} + rescaleForMonomers={rescaleForMonomers} /> @@ -148,6 +172,7 @@ const MonomerConnection = ({ selectedAttachmentPoint={secondSelectedAttachmentPoint} onSelectAttachmentPoint={setSecondSelectedAttachmentPoint} expanded={modalExpanded} + rescaleForMonomers={rescaleForMonomers} /> @@ -176,6 +201,7 @@ interface AttachmentPointSelectionPanelProps { selectedAttachmentPoint: string | null; onSelectAttachmentPoint: (attachmentPoint: string) => void; expanded?: boolean; + rescaleForMonomers: number | null; } function AttachmentPointSelectionPanel({ @@ -183,6 +209,7 @@ function AttachmentPointSelectionPanel({ selectedAttachmentPoint, onSelectAttachmentPoint, expanded = false, + rescaleForMonomers, }: AttachmentPointSelectionPanelProps): React.ReactElement { const [bonds, setBonds] = useState(monomer.attachmentPointsToBonds); const [connectedAttachmentPoints, setConnectedAttachmentPoints] = useState( @@ -236,8 +263,11 @@ function AttachmentPointSelectionPanel({ selectedAttachmentPoint ?? undefined, labelInMonomerConnectionsModal: true, needCache: false, + autoScaleMargin: 1, + rescaleAmount: rescaleForMonomers, }} update={expanded} + monomerConnectionMode={true} isExpanded={expanded} /> diff --git a/packages/ketcher-react/src/components/StructRender/StructRender.tsx b/packages/ketcher-react/src/components/StructRender/StructRender.tsx index 40775b4c07..c7583228a4 100644 --- a/packages/ketcher-react/src/components/StructRender/StructRender.tsx +++ b/packages/ketcher-react/src/components/StructRender/StructRender.tsx @@ -42,6 +42,7 @@ const StructRender = ({ options, className, update, + monomerConnectionMode, }: IStructRenderProps) => { const renderRef = useRef(null); useEffect(() => { @@ -50,7 +51,14 @@ const StructRender = ({ if (container) { container.innerHTML = ''; const normalizedStruct = normalizeStruct(struct); - RenderStruct.render(container, normalizedStruct, options); + RenderStruct.render( + container, + normalizedStruct, + options, + struct, + update, + monomerConnectionMode, + ); } }, [struct, options, update]); diff --git a/packages/ketcher-react/src/components/StructRender/types.ts b/packages/ketcher-react/src/components/StructRender/types.ts index 7a45480636..3a228fbc4e 100644 --- a/packages/ketcher-react/src/components/StructRender/types.ts +++ b/packages/ketcher-react/src/components/StructRender/types.ts @@ -26,4 +26,5 @@ export interface IStructRenderProps { options?: (RenderOptions & CasheOptions) | CasheOptions; className?: string; update?: boolean; + monomerConnectionMode?: boolean; }