Allow changing cardinality notation in ERD to use Chen notation. #5832

This commit is contained in:
Aditya Toshniwal 2023-02-10 10:27:16 +05:30 committed by GitHub
parent 696cb0fa05
commit 1806866bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 116 additions and 30 deletions

View File

@ -135,18 +135,21 @@ Utility Options
:class: longtable
:widths: 1 4 1
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| Icon | Behavior | Shortcut |
+======================+===================================================================================================+================+
| *Add/Edit note* | Click this button to make notes on tables nodes while designing the database. | Option/Alt + |
| | | Ctrl + N |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Auto align* | Click this button to auto align all tables and links to make it look more cleaner. | Option/Alt + |
| | | Ctrl + L |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Show details* | Click this button to toggle the column details visibility. It allows you to show few or more | Option/Alt + |
| | column details. | Shift + D |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| Icon | Behavior | Shortcut |
+=========================+================================================================================================+================+
| *Add/Edit note* | Click this button to make notes on tables nodes while designing the database. | Option/Alt + |
| | | Ctrl + N |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| *Auto align* | Click this button to auto align all tables and links to make it look more cleaner. | Option/Alt + |
| | | Ctrl + L |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| *Show details* | Click this button to toggle the column details visibility. It allows you to show few or more | Option/Alt + |
| | column details. | Shift + D |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| *Cardinality Notation* | Change the cardinality notation format used to present relationship links. Options available | |
| | are - Crow's Foot Notation and Chen Notation. | |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
Zoom Options
************

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -400,6 +400,31 @@ class ERDModule(PgAdminModule):
)
)
self.preference.register(
'options', 'cardinality_notation',
gettext('Cardinality Notation'), 'radioModern', 'crows',
category_label=PREF_LABEL_OPTIONS, options=[
{'label': gettext('Crow\'s foot'), 'value': 'crows'},
{'label': gettext('Chen'), 'value': 'chen'},
],
help_str=gettext(
'Notation to be used to present cardinality.'
)
)
self.preference.register(
'options',
'sql_with_drop',
gettext('SQL With DROP Table'),
'boolean',
False,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'If enabled, the SQL generated by the ERD Tool will add '
'DROP table DDL before each CREATE table DDL.'
)
)
blueprint = ERDModule(MODULE_NAME, __name__, static_url_path='/static')

View File

@ -141,7 +141,7 @@ class ERDTool extends React.Component {
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick',
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
'onDetailsToggle', 'onChangeColors', 'onHelpClick', 'onDropNode', 'onBeforeUnload',
'onChangeColors', 'onHelpClick', 'onDropNode', 'onBeforeUnload', 'onNotationChange',
]);
this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram);
@ -293,11 +293,13 @@ class ERDTool extends React.Component {
this.setLoading(gettext('Preparing...'));
this.registerEvents();
const erdPref = this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd');
this.setState({
preferences: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd'),
preferences: erdPref,
is_new_tab: (this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('browser').new_browser_tab_open || '')
.includes('erd_tool'),
is_close_tab_warning: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_refresh_close,
cardinality_notation: erdPref.cardinality_notation,
}, ()=>{
this.registerKeyboardShortcuts();
this.setTitle(this.state.current_file);
@ -559,6 +561,10 @@ class ERDTool extends React.Component {
});
}
onNotationChange(e) {
this.setState({cardinality_notation: e.value});
}
onHelpClick() {
let url = url_for('help.static', {'filename': 'erd_tool.html'});
if (this.props.pgWindow) {
@ -948,12 +954,17 @@ class ERDTool extends React.Component {
fgcolor={this.props.params.fgcolor} title={this.props.params.title}/>
<MainToolBar preferences={this.state.preferences} eventBus={this.eventBus}
fillColor={this.state.fill_color} textColor={this.state.text_color}
notation={this.state.cardinality_notation} onNotationChange={this.onNotationChange}
/>
<FloatingNote open={this.state.note_open} onClose={this.onNoteClose}
anchorEl={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
<div className={this.props.classes.diagramContainer} data-test="diagram-container" ref={this.diagramContainerRef} onDrop={this.onDropNode} onDragOver={e => {e.preventDefault();}}>
<Loader message={this.state.loading_msg} autoEllipsis={true}/>
<CanvasWidget className={this.props.classes.diagramCanvas} ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
<ERDCanvasSettings.Provider value={{
cardinality_notation: this.state.cardinality_notation
}}>
<CanvasWidget className={this.props.classes.diagramCanvas} ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
</ERDCanvasSettings.Provider>
</div>
</Box>
);
@ -981,3 +992,5 @@ ERDTool.propTypes = {
panel: PropTypes.object,
classes: PropTypes.object,
};
export const ERDCanvasSettings = React.createContext({});

View File

@ -27,6 +27,7 @@ import VisibilityOffRoundedIcon from '@material-ui/icons/VisibilityOffRounded';
import ImageRoundedIcon from '@material-ui/icons/ImageRounded';
import FormatColorFillRoundedIcon from '@material-ui/icons/FormatColorFillRounded';
import FormatColorTextRoundedIcon from '@material-ui/icons/FormatColorTextRounded';
import AccountTreeOutlinedIcon from '@material-ui/icons/AccountTreeOutlined';
import { PgMenu, PgMenuItem, usePgMenuGroup } from '../../../../../../static/js/components/Menu';
import gettext from 'sources/gettext';
@ -69,7 +70,7 @@ const useStyles = makeStyles((theme)=>({
}),
}));
export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
export function MainToolBar({preferences, eventBus, fillColor, textColor, notation, onNotationChange}) {
const classes = useStyles({fillColor,textColor});
const theme = useTheme();
const [buttonsDisabled, setButtonsDisabled] = useState({
@ -86,6 +87,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
const {openMenuName, toggleMenu, onMenuClose} = usePgMenuGroup();
const saveAsMenuRef = React.useRef(null);
const sqlMenuRef = React.useRef(null);
const notationMenuRef = React.useRef(null);
const isDirtyRef = React.useRef(null);
const [checkedMenuItems, setCheckedMenuItems] = React.useState({});
const modal = useModal();
@ -283,6 +285,9 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
eventBus.fireEvent(ERD_EVENTS.TOGGLE_DETAILS);
setShowDetails((prev)=>!prev);
}} />
<PgIconButton title={gettext('Cardinality Notation')} icon={
<><AccountTreeOutlinedIcon /><KeyboardArrowDownIcon style={{marginLeft: '-10px'}} /></>}
name="menu-notation" ref={notationMenuRef} onClick={toggleMenu} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton title={gettext('Zoom In')} icon={<ZoomInIcon />}
@ -323,6 +328,16 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
>
<PgMenuItem hasCheck value="sql_with_drop" checked={checkedMenuItems['sql_with_drop']} onClick={checkMenuClick}>{gettext('With DROP Table')}</PgMenuItem>
</PgMenu>
<PgMenu
anchorRef={notationMenuRef}
open={openMenuName=='menu-notation'}
onClose={onMenuClose}
label={gettext('Cardinality Notation')}
>
<PgMenuItem hasCheck closeOnCheck value="crows" checked={notation == 'crows'} onClick={onNotationChange}>{gettext('Crow\'s Foot Notation')}</PgMenuItem>
<PgMenuItem hasCheck closeOnCheck value="chen" checked={notation == 'chen'} onClick={onNotationChange}>{gettext('Chen Notation')}</PgMenuItem>
</PgMenu>
</>
);
}
@ -332,6 +347,8 @@ MainToolBar.propTypes = {
eventBus: PropTypes.object,
fillColor: PropTypes.string,
textColor: PropTypes.string,
notation: PropTypes.string,
onNotationChange: PropTypes.func,
};
const ColorButton = withColorPicker(PgIconButton);

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import React, { forwardRef } from 'react';
import React, { forwardRef, useContext } from 'react';
import {
RightAngleLinkModel,
RightAngleLinkWidget,
@ -21,6 +21,7 @@ import _ from 'lodash';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import { ERDCanvasSettings } from '../components/ERDTool';
export const POINTER_SIZE = 30;
@ -81,6 +82,7 @@ export class OneToManyLinkModel extends RightAngleLinkModel {
const useStyles = makeStyles((theme)=>({
svgLink: {
stroke: theme.palette.text.primary,
fontSize: '0.8em',
},
'@keyframes svgLinkSelected': {
'from': { strokeDashoffset: 24},
@ -99,15 +101,37 @@ const useStyles = makeStyles((theme)=>({
}
}));
const CustomLinkEndWidget = props => {
function ChenNotation({rotation, type}) {
const classes = useStyles();
const textX = Math.sign(rotation) > 0 ? -14 : 8;
const textY = -5;
return (
<>
<text className={classes.svgLink} x={textX} y={textY} transform={'rotate(' + -rotation + ')' }>
{type == 'one' ? '1' : 'N'}
</text>
<line className={classes.svgLink} x1="0" y1="0" x2="0" y2="30"></line>
</>
);
}
ChenNotation.propTypes = {
rotation: PropTypes.number,
type: PropTypes.string,
};
function CustomLinkEndWidget(props) {
const { point, rotation, tx, ty, type } = props;
const classes = useStyles();
const settings = useContext(ERDCanvasSettings);
const svgForType = (itype) => {
if(settings.cardinality_notation == 'chen') {
return <ChenNotation rotation={rotation} type={itype} />;
}
if(itype == 'many') {
return (
<>
<circle className={clsx(classes.svgLink, classes.svgLinkCircle)} cx="0" cy="16" r={props.width*1.75} strokeWidth={props.width} />
<circle className={clsx(classes.svgLink, classes.svgLinkCircle)} cx="0" cy="16" r={props.width*2.5} strokeWidth={props.width} />
<polyline className={classes.svgLink} points="-8,0 0,15 0,0 0,30 0,15 8,0" fill="none" strokeWidth={props.width} />
</>
);
@ -127,7 +151,7 @@ const CustomLinkEndWidget = props => {
</g>
</g>
);
};
}
CustomLinkEndWidget.propTypes = {
point: PropTypes.instanceOf(PointModel).isRequired,

View File

@ -202,6 +202,17 @@ const styles = (theme)=>({
padding: '0.125rem 0.25rem',
display: 'flex',
},
columnSection: {
display:'flex',
width: '100%' ,
...theme.mixins.panelBorder.bottom,
},
columnName: {
display:'flex',
width: '100%' ,
padding: '0.125rem 0.25rem',
wordBreak: 'break-all',
},
tableToolbar: {
background: theme.otherVars.editorToolbarBg,
borderTopLeftRadius: 'inherit',
@ -269,11 +280,11 @@ class TableNodeWidgetRaw extends React.Component {
const {classes} = this.props;
return (
<div className={classes.tableSection} key={col.attnum} data-test="column-row">
<Box className={classes.columnSection} key={col.attnum} data-test="column-row">
<Box marginRight="auto" padding="0" minHeight="0" display="flex" alignItems="center">
{this.generatePort(leftPort)}
</Box>
<Box display="flex" width="100%" style={{wordBreak: 'break-all'}}>
<Box className={classes.columnName}>
<RowIcon icon={icon} />
<Box margin="auto 0">
<span data-test="column-name">{col.name}</span>&nbsp;
@ -284,7 +295,7 @@ class TableNodeWidgetRaw extends React.Component {
<Box marginLeft="auto" padding="0" minHeight="0" display="flex" alignItems="center">
{this.generatePort(rightPort)}
</Box>
</div>
</Box>
);
}

View File

@ -291,13 +291,6 @@ describe('ERDCore', ()=>{
]));
});
it('dagreDistributeNodes', ()=>{
spyOn(erdCoreObj.dagre_engine, 'redistribute');
erdCoreObj.dagreDistributeNodes();
expect(erdEngine.getLinkFactories().getFactory().calculateRoutingMatrix).toHaveBeenCalled();
expect(erdCoreObj.dagre_engine.redistribute).toHaveBeenCalledWith(erdEngine.getModel());
});
it('zoomIn', ()=>{
spyOn(erdEngine.getModel(), 'getZoomLevel').and.returnValue(100);
spyOn(erdCoreObj, 'repaint');