Fixed an issue in ERD tool where the downloaded images have a few links cut. #4995

This commit is contained in:
Aditya Toshniwal 2023-10-04 18:23:41 +05:30
parent 9666bd6c8d
commit 5d2ce142dd
5 changed files with 73 additions and 132 deletions

View File

@ -33,6 +33,7 @@ Housekeeping
Bug fixes
*********
| `Issue #4995 <https://github.com/pgadmin-org/pgadmin4/issues/4995>`_ - Fixed an issue in ERD tool where the downloaded images have a few links cut.
| `Issue #6482 <https://github.com/pgadmin-org/pgadmin4/issues/6482>`_ - Fixed an issue where the wrong message "Current database has been moved or renamed" is displayed when debugging any function.
| `Issue #6674 <https://github.com/pgadmin-org/pgadmin4/issues/6674>`_ - Fix an issue where foreign table column name becomes "none" if the user changes any column data type.
| `Issue #6718 <https://github.com/pgadmin-org/pgadmin4/issues/6718>`_ - Pin the cryptography version to fix PyO3 modules initialisation error.

View File

@ -116,7 +116,7 @@
"dagre": "^0.8.4",
"date-fns": "^2.24.0",
"diff-arrays-of-objects": "^1.1.8",
"html2canvas": "^1.0.0-rc.7",
"html-to-image": "^1.11.11",
"immutability-helper": "^3.0.0",
"insert-if": "^1.1.0",
"ip-address": "^7.1.0",

View File

@ -22,6 +22,7 @@ import ForeignKeySchema from '../../../../../browser/server_groups/servers/datab
import diffArray from 'diff-arrays-of-objects';
import TableSchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/static/js/table.ui';
import ColumnSchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui';
import { Polygon } from '@projectstorm/geometry';
export default class ERDCore {
constructor() {
@ -175,6 +176,11 @@ export default class ERDCore {
getModel() {return this.getEngine().getModel();}
getBoundingLinksRect() {
return Polygon.boundingBoxFromPolygons(
this.getEngine().getModel().getLinks().map((l)=>l.getBoundingBox()));
}
getNewNode(initData, dataUrl=null) {
return this.getEngine().getNodeFactories().getFactory('table').generateModel({
initialConfig: {

View File

@ -11,7 +11,7 @@ import * as React from 'react';
import { CanvasWidget, Action, InputType } from '@projectstorm/react-canvas-core';
import PropTypes from 'prop-types';
import _ from 'lodash';
import html2canvas from 'html2canvas';
import {toPng} from 'html-to-image';
import ERDCore from '../ERDCore';
import ConnectionBar, { STATUS as CONNECT_STATUS } from './ConnectionBar';
@ -76,7 +76,7 @@ const styles = ((theme)=>({
diagramContainer: {
position: 'relative',
width: '100%',
height: '100%',
flexGrow: 1,
minHeight: 0,
},
diagramCanvas: {
@ -722,66 +722,49 @@ class ERDTool extends React.Component {
* the canvas back to original state.
* Code referred from - zoomToFitNodes function.
*/
let nodesRect = this.diagram.getEngine().getBoundingNodesRect(this.diagram.getModel().getNodes(), 10);
this.diagramContainerRef.current?.classList.add(this.props.classes.html2canvasReset);
const margin = 10;
let nodesRect = this.diagram.getEngine().getBoundingNodesRect(this.diagram.getModel().getNodes());
let linksRect = this.diagram.getBoundingLinksRect();
// Check what is to the most top left - links or nodes?
let topLeftXY = {
x: nodesRect.getTopLeft().x,
y: nodesRect.getTopLeft().y
};
if(topLeftXY.x > linksRect.getTopLeft().x) {
topLeftXY.x = linksRect.getTopLeft().x;
}
if(topLeftXY.y > linksRect.getTopLeft().y) {
topLeftXY.y = linksRect.getTopLeft().y;
}
topLeftXY.x -= margin;
topLeftXY.y -= margin;
let canvasRect = this.canvasEle.getBoundingClientRect();
let canvasTopLeftPoint = {
let canvasTopLeftOnScreen = {
x: canvasRect.left,
y: canvasRect.top
};
let nodeLayerTopLeftPoint = {
x: canvasTopLeftPoint.x + this.diagram.getModel().getOffsetX(),
y: canvasTopLeftPoint.y + this.diagram.getModel().getOffsetY()
x: canvasTopLeftOnScreen.x + this.diagram.getModel().getOffsetX(),
y: canvasTopLeftOnScreen.y + this.diagram.getModel().getOffsetY()
};
let nodesRectTopLeftPoint = {
x: nodeLayerTopLeftPoint.x + nodesRect.getTopLeft().x,
y: nodeLayerTopLeftPoint.y + nodesRect.getTopLeft().y
x: nodeLayerTopLeftPoint.x + topLeftXY.x,
y: nodeLayerTopLeftPoint.y + topLeftXY.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(this.props.classes.html2canvasReset);
this.canvasEle.style.width = this.canvasEle.scrollWidth + 'px';
this.canvasEle.style.height = this.canvasEle.scrollHeight + 'px';
/* html2canvas ignores CSS styles, set the CSS styles to inline */
const setSvgInlineStyles = (targetElem) => {
const transformProperties = [
'fill',
'color',
'font-size',
'stroke',
'font',
'display',
];
let svgElems = Array.from(targetElem.getElementsByTagName('svg'));
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.style.display = 'block';
svgEle.parentNode.insertBefore(wrap, svgEle);
wrap.appendChild(svgEle);
recurseElementChildren(svgEle);
}
function recurseElementChildren(node) {
if (!node.style)
return;
let styles = getComputedStyle(node);
for (let transformProperty of transformProperties) {
node.style[transformProperty] = styles[transformProperty];
}
for (let child of Array.from(node.childNodes)) {
recurseElementChildren(child);
}
}
};
// Capture the links beyond the nodes as well.
const linkOutsideWidth = linksRect.getBottomRight().x - nodesRect.getBottomRight().x;
const linkOutsideHeight = linksRect.getBottomRight().y - nodesRect.getBottomRight().y;
this.canvasEle.style.width = this.canvasEle.scrollWidth + (linkOutsideWidth > 0 ? linkOutsideWidth : 0) + margin + 'px';
this.canvasEle.style.height = this.canvasEle.scrollHeight + (linkOutsideHeight > 0 ? linkOutsideHeight : 0) + margin + 'px';
setTimeout(()=>{
let width = this.canvasEle.scrollWidth + 10;
@ -796,46 +779,34 @@ class ERDTool extends React.Component {
height = 32766;
isCut = true;
}
html2canvas(this.canvasEle, {
width: width,
height: height,
scrollX: 0,
scrollY: 0,
scale: 1,
useCORS: true,
allowTaint: true,
backgroundColor: window.getComputedStyle(this.canvasEle).backgroundColor,
onclone: (clonedEle)=>{
setSvgInlineStyles(clonedEle.body.querySelector('div[data-test="diagram-container"]'));
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}`;
}
Notify.alert(gettext('Error'), msg);
}).then(()=>{
/* Revert back to the original CSS styles */
this.canvasEle.classList.remove(this.props.classes.html2canvasReset);
this.canvasEle.style.width = '';
this.canvasEle.style.height = '';
this.canvasEle.childNodes.forEach((ele)=>{
ele.style.transform = prevTransform;
toPng(this.canvasEle)
.then((dataUrl)=>{
let link = document.createElement('a');
link.setAttribute('href', dataUrl);
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}`;
}
Notify.alert(gettext('Error'), msg);
}).then(()=>{
/* Revert back to the original CSS styles */
this.diagramContainerRef.current.classList.remove(this.props.classes.html2canvasReset);
this.canvasEle.style.width = '';
this.canvasEle.style.height = '';
this.canvasEle.childNodes.forEach((ele)=>{
ele.style.transform = prevTransform;
});
this.setLoading(null);
if(isCut) {
Notify.alert(gettext('Maximum image size limit'),
gettext('The downloaded image has exceeded the maximum size of 32767 x 32767 pixels, and has been cropped to that size.'));
}
});
this.setLoading(null);
if(isCut) {
Notify.alert(gettext('Maximum image size limit'),
gettext('The downloaded image has exceeded the maximum size of 32767 x 32767 pixels, and has been cropped to that size.'));
}
});
}, 1000);
}
@ -959,7 +930,7 @@ class ERDTool extends React.Component {
this.erdDialogs.modal = this.context;
return (
<Box ref={this.containerRef} height="100%">
<Box ref={this.containerRef} height="100%" display="flex" flexDirection="column">
<ConnectionBar status={this.state.conn_status} bgcolor={this.props.params.bgcolor}
fgcolor={this.props.params.fgcolor} title={_.unescape(this.props.params.title)}/>
<MainToolBar preferences={this.state.preferences} eventBus={this.eventBus}

View File

@ -3984,13 +3984,6 @@ __metadata:
languageName: node
linkType: hard
"base64-arraybuffer@npm:^1.0.2":
version: 1.0.2
resolution: "base64-arraybuffer@npm:1.0.2"
checksum: 15e6400d2d028bf18be4ed97702b11418f8f8779fb8c743251c863b726638d52f69571d4cc1843224da7838abef0949c670bde46936663c45ad078e89fee5c62
languageName: node
linkType: hard
"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
@ -5238,15 +5231,6 @@ __metadata:
languageName: node
linkType: hard
"css-line-break@npm:^2.1.0":
version: 2.1.0
resolution: "css-line-break@npm:2.1.0"
dependencies:
utrie: ^1.0.2
checksum: 37b1fe632b03be7a287cd394cef8b5285666343443125c510df9cfb6a4734a2c71e154ec8f7bbff72d7c339e1e5872989b1c52d86162aed27d6cc114725bb4d0
languageName: node
linkType: hard
"css-loader@npm:^6.7.2":
version: 6.8.1
resolution: "css-loader@npm:6.8.1"
@ -7745,13 +7729,10 @@ __metadata:
languageName: node
linkType: hard
"html2canvas@npm:^1.0.0-rc.7":
version: 1.4.1
resolution: "html2canvas@npm:1.4.1"
dependencies:
css-line-break: ^2.1.0
text-segmentation: ^1.0.3
checksum: c134324af57f3262eecf982e436a4843fded3c6cf61954440ffd682527e4dd350e0c2fafd217c0b6f9a455fe345d0c67b4505689796ab160d4ca7c91c3766739
"html-to-image@npm:^1.11.11":
version: 1.11.11
resolution: "html-to-image@npm:1.11.11"
checksum: b453beca72a697bf06fae4945e5460d1d9b1751e8569a0d721dda9485df1dde093938cc9bd9172b8df5fc23133a53a4d619777b3d22f7211cd8a67e3197ab4e8
languageName: node
linkType: hard
@ -13145,7 +13126,7 @@ __metadata:
eslint-plugin-react-hooks: ^4.3.0
exports-loader: ^4.0.0
html-react-parser: ^4.2.0
html2canvas: ^1.0.0-rc.7
html-to-image: ^1.11.11
image-minimizer-webpack-plugin: ^3.8.2
imagemin: ^8.0.1
imagemin-mozjpeg: ^10.0.0
@ -14463,15 +14444,6 @@ __metadata:
languageName: node
linkType: hard
"text-segmentation@npm:^1.0.3":
version: 1.0.3
resolution: "text-segmentation@npm:1.0.3"
dependencies:
utrie: ^1.0.2
checksum: 2e24632d59567c55ab49ac324815e2f7a8043e63e26b109636322ac3e30692cee8679a448fd5d0f0598a345f407afd0e34ba612e22524cf576d382d84058c013
languageName: node
linkType: hard
"text-table@npm:^0.2.0":
version: 0.2.0
resolution: "text-table@npm:0.2.0"
@ -15056,15 +15028,6 @@ __metadata:
languageName: node
linkType: hard
"utrie@npm:^1.0.2":
version: 1.0.2
resolution: "utrie@npm:1.0.2"
dependencies:
base64-arraybuffer: ^1.0.2
checksum: c96fbb7d4d8855a154327da0b18e39b7511cc70a7e4bcc3658e24f424bb884312d72b5ba777500b8858e34d365dc6b1a921dc5ca2f0d341182519c6b78e280a5
languageName: node
linkType: hard
"uuid@npm:^3.0.1":
version: 3.4.0
resolution: "uuid@npm:3.4.0"