mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-12-22 23:23:57 -06:00
Allow changing cardinality notation in ERD to use Chen notation. #5832
This commit is contained in:
parent
696cb0fa05
commit
1806866bf5
@ -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 |
@ -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')
|
||||
|
||||
|
@ -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({});
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
|
Loading…
Reference in New Issue
Block a user