Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/en_US/erd_tool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ Editing Options
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| Icon | Behavior | Shortcut |
+======================+===================================================================================================+================+
| *Search table* | Click to search for a table in the diagram. Selecting a table from the search results will bring | Option/Alt + |
| | it into view and highlight it. | Ctrl + F |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Add table* | Click this button to add a new table to the diagram. On clicking, this will open a table dialog | Option/Alt + |
| | where you can put the table details. | Ctrl + A |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
Expand All @@ -109,11 +112,14 @@ Table Relationship Options
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| Icon | Behavior | Shortcut |
+======================+===================================================================================================+================+
| *1M* | Click this button to open a one-to-many relationship dialog to add a relationship between the | Option/Alt + |
| *1-1* | Click this button to open a one-to-one relationship dialog to add a relationship between the | Option/Alt + |
| | two tables. The selected table becomes the referencing table. | Ctrl + B |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *1-M* | Click this button to open a one-to-many relationship dialog to add a relationship between the | Option/Alt + |
| | two tables. The selected table becomes the referencing table and will have the *many* endpoint of | Ctrl + O |
| | the link. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *MM* | Click this button to open a many-to-many relationship dialog to add a relationship between the | Option/Alt + |
| *M-M* | Click this button to open a many-to-many relationship dialog to add a relationship between the | Option/Alt + |
| | two tables. This option will create a new table based on the selected columns for the two relating| Ctrl + M |
| | tables and link them. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
Expand Down
Binary file modified docs/en_US/images/erd_tool.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/en_US/images/erd_tool_toolbar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/en_US/release_notes_9_10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Bundled PostgreSQL Utilities
New features
************

| `Issue #4306 <https://github.com/pgadmin-org/pgadmin4/issues/4306>`_ - Added the ability to search for tables and automatically bring them into view in the ERD tool.
| `Issue #6698 <https://github.com/pgadmin-org/pgadmin4/issues/6698>`_ - Add support for setting image download resolution in the ERD tool.
| `Issue #7885 <https://github.com/pgadmin-org/pgadmin4/issues/7885>`_ - Add support for displaying detailed Citus query plans instead of 'Custom Scan' placeholder.
| `Issue #8912 <https://github.com/pgadmin-org/pgadmin4/issues/8912>`_ - Add support for formatting .pgerd ERD project file.
Expand All @@ -34,4 +35,6 @@ Bug fixes

| `Issue #8504 <https://github.com/pgadmin-org/pgadmin4/issues/8504>`_ - Fixed an issue where data output column resize is not sticking in Safari.
| `Issue #9117 <https://github.com/pgadmin-org/pgadmin4/issues/9117>`_ - Fixed an issue where Schema Diff does not ignore Tablespace for indexes.
| `Issue #9132 <https://github.com/pgadmin-org/pgadmin4/issues/9132>`_ - Fixed an issue where the 2FA window redirected to the login page after session expiration.
| `Issue #9240 <https://github.com/pgadmin-org/pgadmin4/issues/9240>`_ - Fixed an issue where the Debian build process failed with a "Sphinx module not found" error when using a Python virtual environment.
| `Issue #9304 <https://github.com/pgadmin-org/pgadmin4/issues/9304>`_ - Fixed an issue that prevented assigning multiple users to an RLS policy.
3 changes: 2 additions & 1 deletion web/pgadmin/static/js/helpers/ModalProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ const StyleDialog = styled(Dialog)(({theme}) => ({
},
}));

function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH, showTitle=true }) {
function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH, showTitle=true, ...props }) {
let useModalRef = useModal();
let closeModal = (_e, reason) => {
if(reason == 'backdropClick' && showTitle) {
Expand All @@ -321,6 +321,7 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
fullScreen={isFullScreen}
fullWidth={isFullWidth}
disablePortal
{...props}
>
{ showTitle &&
<DialogTitle className='modal-drag-area'>
Expand Down
18 changes: 18 additions & 0 deletions web/pgadmin/tools/erd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@ def register_preferences(self):
fields=shortcut_fields
)

self.preference.register(
'keyboard_shortcuts',
'search_table',
gettext('Search table'),
'keyboardshortcut',
{
'alt': True,
'shift': False,
'control': True,
'key': {
'key_code': 70,
'char': 'f'
}
},
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
fields=shortcut_fields
)

self.preference.register(
'keyboard_shortcuts',
'add_table',
Expand Down
3 changes: 3 additions & 0 deletions web/pgadmin/tools/erd/static/js/erd_tool/ERDConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ export const ERD_EVENTS = {
TRIGGER_SHOW_SQL: 'TRIGGER_SHOW_SQL',
SHOW_SQL: 'SHOW_SQL',
DOWNLOAD_IMAGE: 'DOWNLOAD_IMAGE',

SEARCH_NODE: 'SEARCH_NODE',
ADD_NODE: 'ADD_NODE',
EDIT_NODE: 'EDIT_NODE',
CLONE_NODE: 'CLONE_NODE',
DELETE_NODE: 'DELETE_NODE',

SHOW_NOTE: 'SHOW_NOTE',
ONE_TO_ONE: 'ONE_TO_ONE',
ONE_TO_MANY: 'ONE_TO_MANY',
Expand Down
76 changes: 72 additions & 4 deletions web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { useApplicationState } from '../../../../../../settings/static/Applicati
import { connectServerModal, connectServer } from '../../../../../sqleditor/static/js/components/connectServer';
import { useEffect } from 'react';
import { FileManagerUtils } from '../../../../../../misc/file_manager/static/js/components/FileManager';
import SearchNode from './SearchNode';

/* Custom react-diagram action for keyboard events */
export class KeyboardShortcutAction extends Action {
Expand Down Expand Up @@ -178,9 +179,9 @@ export default class ERDTool extends React.Component {
this.eventBus = new EventBus();

_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSQLClick',
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
'onImageClick', 'onSearchNode', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
'onNoteClose', 'onOneToOneClick', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
'onChangeColors', 'onDropNode', 'onNotationChange', 'closePanel'
'onChangeColors', 'onDropNode', 'onNotationChange', 'closePanel', 'scrollToNode'
]);

this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram);
Expand Down Expand Up @@ -249,6 +250,7 @@ export default class ERDTool extends React.Component {
this.eventBus.registerListener(ERD_EVENTS.SAVE_DIAGRAM, this.onSaveDiagram);
this.eventBus.registerListener(ERD_EVENTS.SHOW_SQL, this.onSQLClick);
this.eventBus.registerListener(ERD_EVENTS.DOWNLOAD_IMAGE, this.onImageClick);
this.eventBus.registerListener(ERD_EVENTS.SEARCH_NODE, this.onSearchNode);
this.eventBus.registerListener(ERD_EVENTS.ADD_NODE, this.onAddNewNode);
this.eventBus.registerListener(ERD_EVENTS.EDIT_NODE, this.onEditTable);
this.eventBus.registerListener(ERD_EVENTS.CLONE_NODE, this.onCloneNode);
Expand Down Expand Up @@ -285,6 +287,9 @@ export default class ERDTool extends React.Component {
[this.state.preferences.download_image, ()=>{
this.eventBus.fireEvent(ERD_EVENTS.DOWNLOAD_IMAGE);
}],
[this.state.preferences.search_table, ()=>{
this.eventBus.fireEvent(ERD_EVENTS.SEARCH_NODE);
}],
[this.state.preferences.add_table, ()=>{
this.eventBus.fireEvent(ERD_EVENTS.ADD_NODE);
}],
Expand Down Expand Up @@ -488,12 +493,69 @@ export default class ERDTool extends React.Component {
}
}

scrollToNode(node) {
const engine = this.diagram.getEngine();
const model = engine.getModel();
const container = this.canvasEle;
if (!node || !container) return;

const { x, y } = node.getPosition();
const zoom = model.getZoomLevel() / 100;
const offsetX = model.getOffsetX();
const offsetY = model.getOffsetY();

const viewportWidth = container.clientWidth;
const viewportHeight = container.clientHeight;

const nodeWidth = node.width; // Approximate width of a table node
const nodeHeight = node.height; // Approximate height of a table node

// Node screen bounds
const nodeLeft = x * zoom + offsetX;
const nodeRight = nodeLeft + nodeWidth * zoom;
const nodeTop = y * zoom + offsetY;
const nodeBottom = nodeTop + nodeHeight * zoom;

let newOffsetX = offsetX;
let newOffsetY = offsetY;

// Check horizontal visibility
if (nodeLeft < 0) {
newOffsetX += -nodeLeft + 20; // 20px padding
} else if (nodeRight > viewportWidth) {
newOffsetX -= nodeRight - viewportWidth + 20;
}

// Check vertical visibility
if (nodeHeight * zoom >= viewportHeight) {
// Node taller than viewport: snap top of node to top of viewport
newOffsetY = offsetY + viewportHeight / 2 - (nodeHeight * zoom) / 2;
newOffsetY = offsetY - (nodeTop - 20); // aligns top
} else {
// Node fits in viewport: ensure fully visible
if (nodeTop < 0) {
newOffsetY += -nodeTop + 20;
} else if (nodeBottom > viewportHeight) {
newOffsetY -= nodeBottom - viewportHeight + 20;
}
}

// Update offset only if needed
if (newOffsetX !== offsetX || newOffsetY !== offsetY) {
model.setOffset(newOffsetX, newOffsetY);
}

this.diagram.repaint();
node.setSelected(true);
node.fireEvent({}, 'highlightFlash');
};


addEditTable(node) {
let dialog = this.getDialog('table_dialog');
if(node) {
let [schema, table] = node.getSchemaTableName();
let oldData = node.getData();
dialog(gettext('Table: %s (%s)', _.escape(table),_.escape(schema)), oldData, false, (newData)=>{
dialog(gettext('Table: %s', node.getDisplayName()), oldData, false, (newData)=>{
if(this.diagram.anyDuplicateNodeName(newData, oldData)) {
return gettext('Table name already exists');
}
Expand Down Expand Up @@ -560,6 +622,12 @@ export default class ERDTool extends React.Component {
}
}

onSearchNode() {
this.context.showModal(gettext('Search'), (closeModal)=>(
<SearchNode tableNodes={this.diagram.getModel().getNodesDict()} onClose={closeModal} scrollToNode={this.scrollToNode} />
), {id: 'id-erd-search-node', showTitle: false, disableRestoreFocus: true});
}

onAddNewNode() {
this.addEditTable();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ export default function FloatingNote({open, onClose, anchorEl, rows, noteNode})

const header = useMemo(()=>{
if(noteNode) {
let [schema, name] = noteNode.getSchemaTableName();
return `${name} (${schema})`;
return noteNode.getDisplayName();
}
return '';
}, [open]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import ImageRoundedIcon from '@mui/icons-material/ImageRounded';
import FormatColorFillRoundedIcon from '@mui/icons-material/FormatColorFillRounded';
import FormatColorTextRoundedIcon from '@mui/icons-material/FormatColorTextRounded';
import AccountTreeOutlinedIcon from '@mui/icons-material/AccountTreeOutlined';
import SearchOutlinedIcon from '@mui/icons-material/SearchOutlined';

import { PgMenu, PgMenuItem, usePgMenuGroup } from '../../../../../../static/js/components/Menu';
import gettext from 'sources/gettext';
Expand Down Expand Up @@ -201,6 +202,11 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
}} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton title={gettext('Search Table')} icon={<SearchOutlinedIcon />}
shortcut={preferences.search_table}
onClick={()=>{
eventBus.fireEvent(ERD_EVENTS.SEARCH_NODE);
}} />
<PgIconButton title={gettext('Add Table')} icon={<AddBoxIcon />}
shortcut={preferences.add_table}
onClick={()=>{
Expand Down
40 changes: 40 additions & 0 deletions web/pgadmin/tools/erd/static/js/erd_tool/components/SearchNode.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////

import PropTypes from 'prop-types';
import { InputSelect } from '../../../../../../static/js/components/FormComponents';


export default function SearchNode({tableNodes, onClose, scrollToNode}) {
const onSelectChange = (val) => {
let node = tableNodes[val];
if(node) {
scrollToNode(node);
}
onClose();
};

return (
<InputSelect
options={Object.values(tableNodes).map(node => ({
value: node.getID(),
label: node.getDisplayName(),
}))}
onChange={onSelectChange}
autoFocus
placeholder="Select a table"
openMenuOnFocus
/>
);
}

SearchNode.propTypes = {
tableNodes: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
};
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ class ManyToManySchema extends BaseUISchema {
export function getManyToManyDialogSchema(attributes, tableNodesDict) {
let tablesData = [];
_.forEach(tableNodesDict, (node, uid)=>{
let [schema, name] = node.getSchemaTableName();
tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
tablesData.push({value: uid, label: node.getDisplayName(), image: 'icon-table'});
});

return new ManyToManySchema({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ class OneToManySchema extends BaseUISchema {
export function getOneToManyDialogSchema(attributes, tableNodesDict) {
let tablesData = [];
_.forEach(tableNodesDict, (node, uid)=>{
let [schema, name] = node.getSchemaTableName();
tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
tablesData.push({value: uid, label: node.getDisplayName(), image: 'icon-table'});
});

return new OneToManySchema({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ class OneToOneSchema extends BaseUISchema {
export function getOneToOneDialogSchema(attributes, tableNodesDict) {
let tablesData = [];
_.forEach(tableNodesDict, (node, uid)=>{
let [schema, name] = node.getSchemaTableName();
tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
tablesData.push({value: uid, label: node.getDisplayName(), image: 'icon-table'});
});

return new OneToOneSchema({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ export function getTableDialogSchema(attributes, isNew, tableNodesDict, colTypes
references: ()=>{
let retOpts = [];
_.forEach(tableNodesDict, (node, uid)=>{
let [schema, name] = node.getSchemaTableName();
retOpts.push({value: uid, label: `(${schema}) ${name}`});
retOpts.push({value: uid, label: node.getDisplayName()});
});
return retOpts;
}
Expand Down
Loading
Loading