diff --git a/docs/en_US/release_notes_5_0.rst b/docs/en_US/release_notes_5_0.rst index 8a9783e7a..ddfc5b329 100644 --- a/docs/en_US/release_notes_5_0.rst +++ b/docs/en_US/release_notes_5_0.rst @@ -40,4 +40,5 @@ Bug fixes | `Issue #6180 `_ - Updated missing documentation for the 'Download Image' option in ERD. | `Issue #6187 `_ - Limit the upgrade check to run once per day. | `Issue #6193 `_ - Ensure that ERD throws a warning before closing unsaved changes if open in a new tab. +| `Issue #6197 `_ - Fixed an issue where the ERD image is not properly downloaded. | `Issue #6208 `_ - Fixed an issue where utility(Backup, Maintenance, ...) jobs are failing when the log level is set to DEBUG. diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js index 3935af360..23b592149 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js @@ -53,6 +53,8 @@ export default class ERDCore { let model = new ERDModel(); if(data) { model.deserializeModel(data, this.engine); + this.fireEvent(model.getOptions(), 'offsetUpdated', true); + this.fireEvent(model.getOptions(), 'zoomUpdated', true); } const registerNodeEvents = (node) => { diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx index 5451e868f..624bf1cba 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx @@ -87,22 +87,12 @@ export default class BodyWidget extends React.Component { this.noteNode = null; this.keyboardActionObj = null; - this.onLoadDiagram = this.onLoadDiagram.bind(this); - this.onSaveDiagram = this.onSaveDiagram.bind(this); - this.onSaveAsDiagram = this.onSaveAsDiagram.bind(this); - this.onSQLClick = this.onSQLClick.bind(this); - this.onImageClick = this.onImageClick.bind(this); - this.onAddNewNode = this.onAddNewNode.bind(this); - this.onEditNode = this.onEditNode.bind(this); - this.onCloneNode = this.onCloneNode.bind(this); - this.onDeleteNode = this.onDeleteNode.bind(this); - this.onNoteClick = this.onNoteClick.bind(this); - this.onNoteClose = this.onNoteClose.bind(this); - this.onOneToManyClick = this.onOneToManyClick.bind(this); - this.onManyToManyClick = this.onManyToManyClick.bind(this); - this.onAutoDistribute = this.onAutoDistribute.bind(this); - this.onDetailsToggle = this.onDetailsToggle.bind(this); - this.onHelpClick = this.onHelpClick.bind(this); + _.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick', + 'onImageClick', 'onAddNewNode', 'onEditNode', 'onCloneNode', 'onDeleteNode', 'onNoteClick', + 'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle', + 'onDetailsToggle', 'onHelpClick' + ]); + this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram); this.diagram.zoomIn = this.diagram.zoomIn.bind(this.diagram); this.diagram.zoomOut = this.diagram.zoomOut.bind(this.diagram); @@ -217,7 +207,7 @@ export default class BodyWidget extends React.Component { }, ()=>this.registerKeyboardShortcuts()); }); - this.props.panel?.on(window.wcDocker?.EVENT.CLOSING, () => { + this.props.panel.on(window.wcDocker.EVENT.CLOSING, () => { if(this.state.dirty) { this.closeOnSave = false; this.confirmBeforeClose(); @@ -552,6 +542,30 @@ export default class BodyWidget extends React.Component { onImageClick() { this.setLoading(gettext('Preparing the image...')); + /* Move the diagram temporarily to align it to top-left of the canvas so that when + * taking the snapshot all the nodes are covered. Once the image is taken, repaint + * the canvas back to original state. + * Code referred from - zoomToFitNodes function. + */ + let nodesRect = this.diagram.getEngine().getBoundingNodesRect(this.diagram.getModel().getNodes(), 10); + let canvasRect = this.canvasEle.getBoundingClientRect(); + let canvasTopLeftPoint = { + x: canvasRect.left, + y: canvasRect.top + }; + let nodeLayerTopLeftPoint = { + x: canvasTopLeftPoint.x + this.diagram.getModel().getOffsetX(), + y: canvasTopLeftPoint.y + this.diagram.getModel().getOffsetY() + }; + let nodesRectTopLeftPoint = { + x: nodeLayerTopLeftPoint.x + nodesRect.getTopLeft().x, + y: nodeLayerTopLeftPoint.y + nodesRect.getTopLeft().y + }; + let prevTransform = this.canvasEle.querySelector('div').style.transform; + this.canvasEle.childNodes.forEach((ele)=>{ + ele.style.transform = `translate(${nodeLayerTopLeftPoint.x - nodesRectTopLeftPoint.x}px, ${nodeLayerTopLeftPoint.y - nodesRectTopLeftPoint.y}px) scale(1.0)`; + }); + /* Change the styles for suiting html2canvas */ this.canvasEle.classList.add('html2canvas-reset'); this.canvasEle.style.width = this.canvasEle.scrollWidth + 'px'; @@ -567,10 +581,16 @@ export default class BodyWidget extends React.Component { 'font' ]; let svgElems = Array.from(targetElem.getElementsByTagName("svg")); - for (let svgElement of svgElems) { - svgElement.setAttribute('width', svgElement.clientWidth); - svgElement.setAttribute('height', svgElement.clientHeight); - recurseElementChildren(svgElement); + for (let svgEle of svgElems) { + svgEle.setAttribute('width', svgEle.clientWidth); + svgEle.setAttribute('height', svgEle.clientHeight); + /* Wrap the SVG in a div tag so that transforms are consistent with html */ + let wrap = document.createElement('div'); + wrap.setAttribute('style', svgEle.getAttribute('style')); + svgEle.setAttribute('style', null); + svgEle.parentNode.appendChild(wrap); + wrap.appendChild(svgEle); + recurseElementChildren(svgEle); } function recurseElementChildren(node) { if (!node.style) @@ -586,40 +606,45 @@ export default class BodyWidget extends React.Component { } } - html2canvas(this.canvasEle, { - width: this.canvasEle.scrollWidth + 10, - height: this.canvasEle.scrollHeight + 10, - scrollX: 0, - scrollY: 0, - useCORS: true, - allowTaint: true, - backgroundColor: window.getComputedStyle(this.canvasEle).backgroundColor, - onclone: (clonedEle)=>{ - setSvgInlineStyles(clonedEle); - return clonedEle; - }, - }).then((canvas)=>{ - let link = document.createElement('a'); - link.setAttribute('href', canvas.toDataURL('image/png')); - link.setAttribute('download', this.getCurrentProjectName() + '.png'); - link.click(); - link.remove(); - }).catch((err)=>{ - console.error(err); - let msg = gettext('Unknown error. Check console logs'); - if(err.name) { - msg = `${err.name}: ${err.message}`; - } - this.props.alertify.alert() - .set('title', gettext('Error')) - .set('message', msg).show(); - }).then(()=>{ - /* Revert back to the original CSS styles */ - this.canvasEle.classList.remove('html2canvas-reset'); - this.canvasEle.style.width = ''; - this.canvasEle.style.height = ''; - this.setLoading(null); - }); + setTimeout(()=>{ + html2canvas(this.canvasEle, { + width: this.canvasEle.scrollWidth + 10, + height: this.canvasEle.scrollHeight + 10, + scrollX: 0, + scrollY: 0, + useCORS: true, + allowTaint: true, + backgroundColor: window.getComputedStyle(this.canvasEle).backgroundColor, + onclone: (clonedEle)=>{ + setSvgInlineStyles(clonedEle); + return clonedEle; + }, + }).then((canvas)=>{ + let link = document.createElement('a'); + link.setAttribute('href', canvas.toDataURL('image/png')); + link.setAttribute('download', this.getCurrentProjectName() + '.png'); + link.click(); + link.remove(); + }).catch((err)=>{ + console.error(err); + let msg = gettext('Unknown error. Check console logs'); + if(err.name) { + msg = `${err.name}: ${err.message}`; + } + this.props.alertify.alert() + .set('title', gettext('Error')) + .set('message', msg).show(); + }).then(()=>{ + /* Revert back to the original CSS styles */ + this.canvasEle.classList.remove('html2canvas-reset'); + this.canvasEle.style.width = ''; + this.canvasEle.style.height = ''; + this.canvasEle.childNodes.forEach((ele)=>{ + ele.style.transform = prevTransform; + }); + this.setLoading(null); + }); + }, 1000); } onOneToManyClick() { diff --git a/web/pgadmin/tools/erd/static/scss/_erd.scss b/web/pgadmin/tools/erd/static/scss/_erd.scss index a54df1284..f48d65b4c 100644 --- a/web/pgadmin/tools/erd/static/scss/_erd.scss +++ b/web/pgadmin/tools/erd/static/scss/_erd.scss @@ -74,10 +74,6 @@ .html2canvas-reset { background-image: none !important; overflow: auto !important; - - & > svg, & > div { - transform: none !important; - } } .diagram-canvas{