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 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 #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 #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. | `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", "dagre": "^0.8.4",
"date-fns": "^2.24.0", "date-fns": "^2.24.0",
"diff-arrays-of-objects": "^1.1.8", "diff-arrays-of-objects": "^1.1.8",
"html2canvas": "^1.0.0-rc.7", "html-to-image": "^1.11.11",
"immutability-helper": "^3.0.0", "immutability-helper": "^3.0.0",
"insert-if": "^1.1.0", "insert-if": "^1.1.0",
"ip-address": "^7.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 diffArray from 'diff-arrays-of-objects';
import TableSchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/static/js/table.ui'; 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 ColumnSchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui';
import { Polygon } from '@projectstorm/geometry';
export default class ERDCore { export default class ERDCore {
constructor() { constructor() {
@ -175,6 +176,11 @@ export default class ERDCore {
getModel() {return this.getEngine().getModel();} getModel() {return this.getEngine().getModel();}
getBoundingLinksRect() {
return Polygon.boundingBoxFromPolygons(
this.getEngine().getModel().getLinks().map((l)=>l.getBoundingBox()));
}
getNewNode(initData, dataUrl=null) { getNewNode(initData, dataUrl=null) {
return this.getEngine().getNodeFactories().getFactory('table').generateModel({ return this.getEngine().getNodeFactories().getFactory('table').generateModel({
initialConfig: { initialConfig: {

View File

@ -11,7 +11,7 @@ import * as React from 'react';
import { CanvasWidget, Action, InputType } from '@projectstorm/react-canvas-core'; import { CanvasWidget, Action, InputType } from '@projectstorm/react-canvas-core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import _ from 'lodash'; import _ from 'lodash';
import html2canvas from 'html2canvas'; import {toPng} from 'html-to-image';
import ERDCore from '../ERDCore'; import ERDCore from '../ERDCore';
import ConnectionBar, { STATUS as CONNECT_STATUS } from './ConnectionBar'; import ConnectionBar, { STATUS as CONNECT_STATUS } from './ConnectionBar';
@ -76,7 +76,7 @@ const styles = ((theme)=>({
diagramContainer: { diagramContainer: {
position: 'relative', position: 'relative',
width: '100%', width: '100%',
height: '100%', flexGrow: 1,
minHeight: 0, minHeight: 0,
}, },
diagramCanvas: { diagramCanvas: {
@ -722,66 +722,49 @@ class ERDTool extends React.Component {
* the canvas back to original state. * the canvas back to original state.
* Code referred from - zoomToFitNodes function. * 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 canvasRect = this.canvasEle.getBoundingClientRect();
let canvasTopLeftPoint = { let canvasTopLeftOnScreen = {
x: canvasRect.left, x: canvasRect.left,
y: canvasRect.top y: canvasRect.top
}; };
let nodeLayerTopLeftPoint = { let nodeLayerTopLeftPoint = {
x: canvasTopLeftPoint.x + this.diagram.getModel().getOffsetX(), x: canvasTopLeftOnScreen.x + this.diagram.getModel().getOffsetX(),
y: canvasTopLeftPoint.y + this.diagram.getModel().getOffsetY() y: canvasTopLeftOnScreen.y + this.diagram.getModel().getOffsetY()
}; };
let nodesRectTopLeftPoint = { let nodesRectTopLeftPoint = {
x: nodeLayerTopLeftPoint.x + nodesRect.getTopLeft().x, x: nodeLayerTopLeftPoint.x + topLeftXY.x,
y: nodeLayerTopLeftPoint.y + nodesRect.getTopLeft().y y: nodeLayerTopLeftPoint.y + topLeftXY.y
}; };
let prevTransform = this.canvasEle.querySelector('div').style.transform; let prevTransform = this.canvasEle.querySelector('div').style.transform;
this.canvasEle.childNodes.forEach((ele)=>{ this.canvasEle.childNodes.forEach((ele)=>{
ele.style.transform = `translate(${nodeLayerTopLeftPoint.x - nodesRectTopLeftPoint.x}px, ${nodeLayerTopLeftPoint.y - nodesRectTopLeftPoint.y}px) scale(1.0)`; ele.style.transform = `translate(${nodeLayerTopLeftPoint.x - nodesRectTopLeftPoint.x}px, ${nodeLayerTopLeftPoint.y - nodesRectTopLeftPoint.y}px) scale(1.0)`;
}); });
/* Change the styles for suiting html2canvas */ // Capture the links beyond the nodes as well.
this.canvasEle.classList.add(this.props.classes.html2canvasReset); const linkOutsideWidth = linksRect.getBottomRight().x - nodesRect.getBottomRight().x;
this.canvasEle.style.width = this.canvasEle.scrollWidth + 'px'; const linkOutsideHeight = linksRect.getBottomRight().y - nodesRect.getBottomRight().y;
this.canvasEle.style.height = this.canvasEle.scrollHeight + 'px'; 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';
/* 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);
}
}
};
setTimeout(()=>{ setTimeout(()=>{
let width = this.canvasEle.scrollWidth + 10; let width = this.canvasEle.scrollWidth + 10;
@ -796,46 +779,34 @@ class ERDTool extends React.Component {
height = 32766; height = 32766;
isCut = true; isCut = true;
} }
html2canvas(this.canvasEle, { toPng(this.canvasEle)
width: width, .then((dataUrl)=>{
height: height, let link = document.createElement('a');
scrollX: 0, link.setAttribute('href', dataUrl);
scrollY: 0, link.setAttribute('download', this.getCurrentProjectName() + '.png');
scale: 1, link.click();
useCORS: true, link.remove();
allowTaint: true, }).catch((err)=>{
backgroundColor: window.getComputedStyle(this.canvasEle).backgroundColor, console.error(err);
onclone: (clonedEle)=>{ let msg = gettext('Unknown error. Check console logs');
setSvgInlineStyles(clonedEle.body.querySelector('div[data-test="diagram-container"]')); if(err.name) {
return clonedEle; msg = `${err.name}: ${err.message}`;
}, }
}).then((canvas)=>{ Notify.alert(gettext('Error'), msg);
let link = document.createElement('a'); }).then(()=>{
link.setAttribute('href', canvas.toDataURL('image/png')); /* Revert back to the original CSS styles */
link.setAttribute('download', this.getCurrentProjectName() + '.png'); this.diagramContainerRef.current.classList.remove(this.props.classes.html2canvasReset);
link.click(); this.canvasEle.style.width = '';
link.remove(); this.canvasEle.style.height = '';
}).catch((err)=>{ this.canvasEle.childNodes.forEach((ele)=>{
console.error(err); ele.style.transform = prevTransform;
let msg = gettext('Unknown error. Check console logs'); });
if(err.name) { this.setLoading(null);
msg = `${err.name}: ${err.message}`; if(isCut) {
} Notify.alert(gettext('Maximum image size limit'),
Notify.alert(gettext('Error'), msg); gettext('The downloaded image has exceeded the maximum size of 32767 x 32767 pixels, and has been cropped to that size.'));
}).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;
}); });
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); }, 1000);
} }
@ -959,7 +930,7 @@ class ERDTool extends React.Component {
this.erdDialogs.modal = this.context; this.erdDialogs.modal = this.context;
return ( 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} <ConnectionBar status={this.state.conn_status} bgcolor={this.props.params.bgcolor}
fgcolor={this.props.params.fgcolor} title={_.unescape(this.props.params.title)}/> fgcolor={this.props.params.fgcolor} title={_.unescape(this.props.params.title)}/>
<MainToolBar preferences={this.state.preferences} eventBus={this.eventBus} <MainToolBar preferences={this.state.preferences} eventBus={this.eventBus}

View File

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