Added support to specify the background fill color to the table nodes in the ERD tool. #4392
parent
5b09122676
commit
794cbed57c
|
@ -111,6 +111,23 @@ Table Relationship Options
|
|||
| | tables and link them. | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
|
||||
Node Color Options
|
||||
**************************
|
||||
|
||||
.. table::
|
||||
:class: longtable
|
||||
:widths: 1 5
|
||||
|
||||
+----------------------+----------------------------------------------------------------------------------------------------------+
|
||||
| Icon | Behavior |
|
||||
+======================+==========================================================================================================+
|
||||
| *Fill Color* | Use Fill Color to change the background color of a table node. This is helpful if you want to |
|
||||
| | identify a of group tables. Once set, all the newly added tables will take the same color. |
|
||||
+----------------------+----------------------------------------------------------------------------------------------------------+
|
||||
| *Text Color* | Use Text Color to change the text color of a table node based on the fill color to make text |
|
||||
| | easily readable. |
|
||||
+----------------------+----------------------------------------------------------------------------------------------------------+
|
||||
|
||||
Utility Options
|
||||
***************
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
|
@ -15,3 +15,5 @@
|
|||
|
||||
@import 'node_modules/jsoneditor/dist/jsoneditor.min.css';
|
||||
@import 'node_modules/react-checkbox-tree/lib/react-checkbox-tree.css';
|
||||
|
||||
@import 'node_modules/@simonwep/pickr/dist/themes/monolith.min.css';
|
||||
|
|
|
@ -20,6 +20,7 @@ import getStandardTheme from './standard';
|
|||
import getDarkTheme from './dark';
|
||||
import getHightContrastTheme from './high_contrast';
|
||||
import { CssBaseline } from '@material-ui/core';
|
||||
import pickrOverride from './overrides/pickr.override';
|
||||
|
||||
/* Common settings across all themes */
|
||||
let basicSettings = createMuiTheme();
|
||||
|
@ -309,7 +310,8 @@ function getFinalTheme(baseTheme) {
|
|||
listStyle: 'none',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
}
|
||||
},
|
||||
...pickrOverride(baseTheme),
|
||||
},
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
export default function pickrOverride(theme) {
|
||||
return {
|
||||
'.pickr, .pcr-app': {
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
'& *:focus': {
|
||||
outline: 'none !important',
|
||||
},
|
||||
'& .pcr-save': {
|
||||
backgroundColor: theme.palette.primary.main + '!important',
|
||||
borderRadius: theme.shape.borderRadius + 'px !important',
|
||||
color: theme.palette.primary.contrastText + '!important',
|
||||
border: '1px solid '+theme.palette.primary.main,
|
||||
},
|
||||
'& .pcr-clear': {
|
||||
backgroundColor: theme.palette.default.main + '!important',
|
||||
borderRadius: theme.shape.borderRadius + 'px !important',
|
||||
color: theme.palette.default.contrastText + '!important',
|
||||
border: '1px solid '+theme.palette.default.borderColor,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
|
@ -25,7 +25,6 @@ import DescriptionIcon from '@material-ui/icons/Description';
|
|||
import AssignmentTurnedIn from '@material-ui/icons/AssignmentTurnedIn';
|
||||
import Select, { components as RSComponents } from 'react-select';
|
||||
import CreatableSelect from 'react-select/creatable';
|
||||
import Pickr from '@simonwep/pickr';
|
||||
import clsx from 'clsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import HTMLReactParse from 'html-react-parser';
|
||||
|
@ -42,6 +41,7 @@ import KeyboardShortcuts from './KeyboardShortcuts';
|
|||
import QueryThresholds from './QueryThresholds';
|
||||
import SelectThemes from './SelectThemes';
|
||||
import { showFileManager } from '../helpers/showFileManager';
|
||||
import { withColorPicker } from '../helpers/withColorPicker';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
@ -980,120 +980,17 @@ FormInputSelect.propTypes = {
|
|||
inputRef: CustomPropTypes.ref
|
||||
};
|
||||
|
||||
/* React wrapper on color pickr */
|
||||
const ColorButton = withColorPicker(PgIconButton);
|
||||
export function InputColor({ value, controlProps, disabled, onChange, currObj }) {
|
||||
const pickrOptions = {
|
||||
showPalette: true,
|
||||
allowEmpty: true,
|
||||
colorFormat: 'HEX',
|
||||
defaultColor: null,
|
||||
position: 'right-middle',
|
||||
clearText: gettext('No color'),
|
||||
...controlProps,
|
||||
disabled: disabled,
|
||||
};
|
||||
const eleRef = useRef();
|
||||
const pickrObj = useRef();
|
||||
const classes = useStyles();
|
||||
|
||||
const setColor = (newVal) => {
|
||||
pickrObj.current &&
|
||||
pickrObj.current.setColor((_.isUndefined(newVal) || newVal == '') ? pickrOptions.defaultColor : newVal);
|
||||
};
|
||||
|
||||
const destroyPickr = () => {
|
||||
if (pickrObj.current) {
|
||||
pickrObj.current.destroy();
|
||||
pickrObj.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const initPickr = () => {
|
||||
/* pickr does not have way to update options, need to
|
||||
destroy and recreate pickr to reflect options */
|
||||
destroyPickr();
|
||||
|
||||
pickrObj.current = new Pickr({
|
||||
el: eleRef.current,
|
||||
useAsButton: true,
|
||||
theme: 'monolith',
|
||||
swatches: [
|
||||
'#000', '#666', '#ccc', '#fff', '#f90', '#ff0', '#0f0',
|
||||
'#f0f', '#f4cccc', '#fce5cd', '#d0e0e3', '#cfe2f3', '#ead1dc', '#ea9999',
|
||||
'#b6d7a8', '#a2c4c9', '#d5a6bd', '#e06666', '#93c47d', '#76a5af', '#c27ba0',
|
||||
'#f1c232', '#6aa84f', '#45818e', '#a64d79', '#bf9000', '#0c343d', '#4c1130',
|
||||
],
|
||||
position: pickrOptions.position,
|
||||
strings: {
|
||||
clear: pickrOptions.clearText,
|
||||
},
|
||||
components: {
|
||||
palette: pickrOptions.showPalette,
|
||||
preview: true,
|
||||
hue: pickrOptions.showPalette,
|
||||
interaction: {
|
||||
clear: pickrOptions.allowEmpty,
|
||||
defaultRepresentation: pickrOptions.colorFormat,
|
||||
disabled: pickrOptions.disabled,
|
||||
},
|
||||
},
|
||||
}).on('init', instance => {
|
||||
setColor(value);
|
||||
disabled && instance.disable();
|
||||
|
||||
const { lastColor } = instance.getRoot().preview;
|
||||
const { clear } = instance.getRoot().interaction;
|
||||
|
||||
/* Cycle the keyboard navigation within the color picker */
|
||||
clear.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
lastColor.focus();
|
||||
}
|
||||
});
|
||||
|
||||
lastColor.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode === 9 && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
clear.focus();
|
||||
}
|
||||
});
|
||||
}).on('clear', () => {
|
||||
onChange && onChange('');
|
||||
}).on('change', (color) => {
|
||||
onChange && onChange(color.toHEXA().toString());
|
||||
}).on('show', (color, instance) => {
|
||||
const { palette } = instance.getRoot().palette;
|
||||
palette.focus();
|
||||
}).on('hide', (instance) => {
|
||||
const button = instance.getRoot().button;
|
||||
button.focus();
|
||||
});
|
||||
|
||||
if (currObj) {
|
||||
currObj(pickrObj.current);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initPickr();
|
||||
return () => {
|
||||
destroyPickr();
|
||||
};
|
||||
}, [...Object.values(pickrOptions)]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pickrObj.current) {
|
||||
setColor(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
let btnStyles = { backgroundColor: value };
|
||||
return (
|
||||
<PgIconButton ref={eleRef} title={gettext('Select the color')} className={classes.colorBtn} style={btnStyles} disabled={pickrOptions.disabled}
|
||||
icon={(_.isUndefined(value) || _.isNull(value) || value === '') && <CloseIcon />}
|
||||
<ColorButton title={gettext('Select the color')} className={classes.colorBtn} style={btnStyles} disabled={disabled}
|
||||
icon={(_.isUndefined(value) || _.isNull(value) || value === '') && <CloseIcon />} options={{
|
||||
...controlProps,
|
||||
disabled: disabled
|
||||
}} onChange={onChange} value={value} currObj={currObj}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
/* React HOC on color pickr */
|
||||
import Pickr from '@simonwep/pickr';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import gettext from 'sources/gettext';
|
||||
import PropTypes from 'prop-types';
|
||||
import { fullHexColor } from '../utils';
|
||||
|
||||
export function withColorPicker(Component) {
|
||||
// eslint-disable-next-line react/display-name
|
||||
const HOCComponent = ({value, currObj, onChange, onSave, options, ...props})=>{
|
||||
const pickrOptions = {
|
||||
showPalette: true,
|
||||
allowEmpty: true,
|
||||
allowSave: false,
|
||||
colorFormat: 'HEX',
|
||||
defaultColor: null,
|
||||
position: 'right-middle',
|
||||
clearText: gettext('Clear'),
|
||||
...options,
|
||||
};
|
||||
const eleRef = useRef();
|
||||
const pickrObj = useRef();
|
||||
|
||||
const setColor = (newVal) => {
|
||||
pickrObj.current?.setColor((_.isUndefined(newVal) || newVal == '') ? pickrOptions.defaultColor : newVal);
|
||||
};
|
||||
|
||||
const destroyPickr = () => {
|
||||
if (pickrObj.current) {
|
||||
pickrObj.current.destroy();
|
||||
pickrObj.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const initPickr = () => {
|
||||
/* pickr does not have way to update options, need to
|
||||
destroy and recreate pickr to reflect options */
|
||||
destroyPickr();
|
||||
|
||||
pickrObj.current = new Pickr({
|
||||
el: eleRef.current,
|
||||
useAsButton: true,
|
||||
theme: 'monolith',
|
||||
swatches: [
|
||||
'#000', '#666', '#ccc', '#fff', '#f90', '#ff0', '#0f0',
|
||||
'#f0f', '#f4cccc', '#fce5cd', '#d0e0e3', '#cfe2f3', '#ead1dc', '#ea9999',
|
||||
'#b6d7a8', '#a2c4c9', '#d5a6bd', '#e06666', '#93c47d', '#76a5af', '#c27ba0',
|
||||
'#f1c232', '#6aa84f', '#45818e', '#a64d79', '#bf9000', '#0c343d', '#4c1130',
|
||||
],
|
||||
position: pickrOptions.position,
|
||||
strings: {
|
||||
clear: pickrOptions.clearText,
|
||||
},
|
||||
components: {
|
||||
palette: pickrOptions.showPalette,
|
||||
preview: true,
|
||||
hue: pickrOptions.showPalette,
|
||||
interaction: {
|
||||
clear: pickrOptions.allowEmpty,
|
||||
defaultRepresentation: pickrOptions.colorFormat,
|
||||
disabled: pickrOptions.disabled,
|
||||
save: pickrOptions.allowSave,
|
||||
},
|
||||
},
|
||||
}).on('init', instance => {
|
||||
setColor(value);
|
||||
pickrOptions.disabled && instance.disable();
|
||||
|
||||
const { lastColor } = instance.getRoot().preview;
|
||||
const { clear } = instance.getRoot().interaction;
|
||||
|
||||
/* Cycle the keyboard navigation within the color picker */
|
||||
clear.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
lastColor.focus();
|
||||
}
|
||||
});
|
||||
|
||||
lastColor.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode === 9 && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
clear.focus();
|
||||
}
|
||||
});
|
||||
}).on('clear', () => {
|
||||
onChange?.('');
|
||||
}).on('change', (color) => {
|
||||
onChange?.(color.toHEXA().toString());
|
||||
}).on('show', (color, instance) => {
|
||||
const { palette } = instance.getRoot().palette;
|
||||
palette.focus();
|
||||
}).on('hide', (instance) => {
|
||||
const button = instance.getRoot().button;
|
||||
button.focus();
|
||||
}).on('save', (color, instance) => {
|
||||
if(color) {
|
||||
color.toHEXA().toString() != fullHexColor(value) && onSave?.(color.toHEXA().toString());
|
||||
instance?.hide();
|
||||
} else {
|
||||
onSave?.('');
|
||||
}
|
||||
});
|
||||
|
||||
if (currObj) {
|
||||
currObj(pickrObj.current);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initPickr();
|
||||
return () => {
|
||||
destroyPickr();
|
||||
};
|
||||
}, [...Object.values(pickrOptions)]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pickrObj.current && !pickrOptions.allowSave) {
|
||||
setColor(value);
|
||||
}
|
||||
}, [value, pickrOptions.allowSave]);
|
||||
|
||||
return <Component ref={eleRef} {...props}/>;
|
||||
};
|
||||
|
||||
HOCComponent.propTypes = {
|
||||
value: PropTypes.string,
|
||||
currObj: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onSave: PropTypes.func,
|
||||
options: PropTypes.object,
|
||||
};
|
||||
|
||||
return HOCComponent;
|
||||
}
|
|
@ -658,3 +658,10 @@ export function pgHandleItemError(xhr, args) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function fullHexColor(shortHex) {
|
||||
if(shortHex?.length == 4) {
|
||||
return shortHex.replace(RegExp('#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])'), '#$1$1$2$2$3$3').toUpperCase();
|
||||
}
|
||||
return shortHex;
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
$palette-soft-red: $color-ternary;
|
||||
$border-radius-mid: $btn-border-radius;
|
||||
$font-family: $font-family-base;
|
||||
$box-shadow-app: $popover-box-shadow;
|
||||
|
||||
@import "node_modules/@simonwep/pickr/src/scss/themes/monolith.scss";
|
||||
|
||||
.pickr, .pcr-app {
|
||||
*:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.pickr .pcr-button{
|
||||
border: $input-border-width solid $input-border-color;
|
||||
&:focus {
|
||||
box-shadow: $input-btn-focus-box-shadow !important;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,6 @@ $theme-colors: (
|
|||
@import 'pgadmin.grid';
|
||||
@import 'pgadmin.style';
|
||||
@import 'bootstrap4-toggle.overrides';
|
||||
@import 'pickr.overrides';
|
||||
@import 'jsoneditor.overrides';
|
||||
@import 'pgadmin4-tree.overrides';
|
||||
@import 'pgadmin4-tree/src/css/styles';
|
||||
|
|
|
@ -13,6 +13,7 @@ export const ERD_EVENTS = {
|
|||
MANY_TO_MANY: 'MANY_TO_MANY',
|
||||
AUTO_DISTRIBUTE: 'AUTO_DISTRIBUTE',
|
||||
TOGGLE_DETAILS: 'TOGGLE_DETAILS',
|
||||
CHANGE_COLORS: 'CHANGE_COLORS',
|
||||
ZOOM_FIT: 'ZOOM_FIT',
|
||||
ZOOM_IN: 'ZOOM_IN',
|
||||
ZOOM_OUT: 'ZOOM_OUT',
|
||||
|
|
|
@ -202,10 +202,11 @@ export default class ERDCore {
|
|||
});
|
||||
}
|
||||
|
||||
addNode(data, position=[50, 50]) {
|
||||
addNode(data, position=[50, 50], metadata={}) {
|
||||
let newNode = this.getNewNode(data);
|
||||
this.clearSelection();
|
||||
newNode.setPosition(position[0], position[1]);
|
||||
newNode.setMetadata(metadata);
|
||||
this.getModel().addNode(newNode);
|
||||
return newNode;
|
||||
}
|
||||
|
|
|
@ -118,6 +118,8 @@ class ERDTool extends React.Component {
|
|||
oto_dialog_open: true,
|
||||
otm_dialog_open: true,
|
||||
database: null,
|
||||
fill_color: null,
|
||||
text_color: null,
|
||||
};
|
||||
this.diagram = new ERDCore();
|
||||
/* Flag for checking if user has opted for save before close */
|
||||
|
@ -137,7 +139,7 @@ class ERDTool extends React.Component {
|
|||
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick',
|
||||
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
||||
'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
|
||||
'onDetailsToggle', 'onHelpClick', 'onDropNode', 'onBeforeUnload',
|
||||
'onDetailsToggle', 'onChangeColors', 'onHelpClick', 'onDropNode', 'onBeforeUnload',
|
||||
]);
|
||||
|
||||
this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram);
|
||||
|
@ -214,6 +216,7 @@ class ERDTool extends React.Component {
|
|||
this.eventBus.registerListener(ERD_EVENTS.MANY_TO_MANY, this.onManyToManyClick);
|
||||
this.eventBus.registerListener(ERD_EVENTS.AUTO_DISTRIBUTE, this.onAutoDistribute);
|
||||
this.eventBus.registerListener(ERD_EVENTS.TOGGLE_DETAILS, this.onDetailsToggle);
|
||||
this.eventBus.registerListener(ERD_EVENTS.CHANGE_COLORS, this.onChangeColors);
|
||||
this.eventBus.registerListener(ERD_EVENTS.ZOOM_FIT, this.diagram.zoomToFit);
|
||||
this.eventBus.registerListener(ERD_EVENTS.ZOOM_IN, this.diagram.zoomIn);
|
||||
this.eventBus.registerListener(ERD_EVENTS.ZOOM_OUT, this.diagram.zoomOut);
|
||||
|
@ -427,7 +430,10 @@ class ERDTool extends React.Component {
|
|||
if(this.diagram.anyDuplicateNodeName(newData)) {
|
||||
return gettext('Table name already exists');
|
||||
}
|
||||
let newNode = this.diagram.addNode(newData);
|
||||
let newNode = this.diagram.addNode(newData, [50, 50], {
|
||||
fillColor: this.state.fill_color,
|
||||
textColor: this.state.text_color,
|
||||
});
|
||||
this.diagram.syncTableLinks(newNode);
|
||||
newNode.setSelected(true);
|
||||
});
|
||||
|
@ -461,7 +467,10 @@ class ERDTool extends React.Component {
|
|||
});
|
||||
});
|
||||
const {x, y} = this.diagram.getEngine().getRelativeMousePoint(e);
|
||||
this.diagram.addNode(dataPromise, [x, y]).setSelected(true);
|
||||
this.diagram.addNode(dataPromise, [x, y], {
|
||||
fillColor: this.state.fill_color,
|
||||
textColor: this.state.text_color,
|
||||
}).setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -484,6 +493,7 @@ class ERDTool extends React.Component {
|
|||
if(newData) {
|
||||
let {x, y} = selected[0].getPosition();
|
||||
let newNode = this.diagram.addNode(newData, [x+20, y+20]);
|
||||
newNode.setMetadata(_.pick(selected[0].getMetadata(), ['fillColor', 'textColor']));
|
||||
newNode.setSelected(true);
|
||||
}
|
||||
}
|
||||
|
@ -511,6 +521,16 @@ class ERDTool extends React.Component {
|
|||
this.diagram.dagreDistributeNodes();
|
||||
}
|
||||
|
||||
onChangeColors(fillColor, textColor) {
|
||||
this.setState({
|
||||
fill_color: fillColor,
|
||||
text_color: textColor,
|
||||
});
|
||||
this.diagram.getSelectedNodes().forEach((node)=>{
|
||||
node.fireEvent({fillColor: fillColor, textColor: textColor}, 'changeColors');
|
||||
});
|
||||
}
|
||||
|
||||
onDetailsToggle() {
|
||||
this.setState((prevState)=>({
|
||||
show_details: !prevState.show_details,
|
||||
|
@ -903,7 +923,9 @@ class ERDTool extends React.Component {
|
|||
<Box ref={this.containerRef} height="100%">
|
||||
<ConnectionBar status={this.state.conn_status} bgcolor={this.props.params.bgcolor}
|
||||
fgcolor={this.props.params.fgcolor} title={this.props.params.title}/>
|
||||
<MainToolBar containerRef={this.containerRef} preferences={this.state.preferences} connStatus={this.state.conn_status} params={this.props.params} eventBus={this.eventBus} />
|
||||
<MainToolBar preferences={this.state.preferences} eventBus={this.eventBus}
|
||||
fillColor={this.state.fill_color} textColor={this.state.text_color}
|
||||
/>
|
||||
<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();}}>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//////////////////////////////////////////////////////////////
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { Box } from '@material-ui/core';
|
||||
import { Box, useTheme } from '@material-ui/core';
|
||||
import { PgButtonGroup, PgIconButton } from '../../../../../../static/js/components/Buttons';
|
||||
import FolderRoundedIcon from '@material-ui/icons/FolderRounded';
|
||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||
|
@ -25,6 +25,8 @@ import NoteRoundedIcon from '@material-ui/icons/NoteRounded';
|
|||
import VisibilityRoundedIcon from '@material-ui/icons/VisibilityRounded';
|
||||
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 { PgMenu, PgMenuItem, usePgMenuGroup } from '../../../../../../static/js/components/Menu';
|
||||
import gettext from 'sources/gettext';
|
||||
|
@ -33,6 +35,7 @@ import PropTypes from 'prop-types';
|
|||
import { ERD_EVENTS } from '../ERDConstants';
|
||||
import { MagicIcon, SQLFileIcon } from '../../../../../../static/js/components/ExternalIcon';
|
||||
import { useModal } from '../../../../../../static/js/helpers/ModalProvider';
|
||||
import { withColorPicker } from '../../../../../../static/js/helpers/withColorPicker';
|
||||
|
||||
const useStyles = makeStyles((theme)=>({
|
||||
root: {
|
||||
|
@ -54,8 +57,9 @@ const useStyles = makeStyles((theme)=>({
|
|||
},
|
||||
}));
|
||||
|
||||
export function MainToolBar({preferences, eventBus}) {
|
||||
export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const [buttonsDisabled, setButtonsDisabled] = useState({
|
||||
'save': true,
|
||||
'edit-table': true,
|
||||
|
@ -196,7 +200,7 @@ export function MainToolBar({preferences, eventBus}) {
|
|||
<PgIconButton title={gettext('Add Table')} icon={<AddBoxIcon />}
|
||||
shortcut={preferences.add_table}
|
||||
onClick={()=>{
|
||||
eventBus.fireEvent(ERD_EVENTS.ADD_NODE);
|
||||
eventBus.fireEvent(ERD_EVENTS.ADD_NODE, {fillColor: fillColor, textColor: textColor});
|
||||
}} />
|
||||
<PgIconButton title={gettext('Edit Table')} icon={<EditRoundedIcon />}
|
||||
shortcut={preferences.edit_table} disabled={buttonsDisabled['edit-table']}
|
||||
|
@ -226,6 +230,30 @@ export function MainToolBar({preferences, eventBus}) {
|
|||
eventBus.fireEvent(ERD_EVENTS.MANY_TO_MANY);
|
||||
}} />
|
||||
</PgButtonGroup>
|
||||
<PgButtonGroup size="small">
|
||||
<ColorButton title={gettext('Fill Color')} icon={<FormatColorFillRoundedIcon />}
|
||||
value={fillColor ?? theme.palette.background.default} options={{
|
||||
allowSave: true,
|
||||
}}
|
||||
onSave={(val)=>{
|
||||
if(val) {
|
||||
eventBus.fireEvent(ERD_EVENTS.CHANGE_COLORS, val, textColor);
|
||||
} else {
|
||||
eventBus.fireEvent(ERD_EVENTS.CHANGE_COLORS, null, textColor);
|
||||
}
|
||||
}}/>
|
||||
<ColorButton title={gettext('Text Color')} icon={<FormatColorTextRoundedIcon />}
|
||||
value={textColor ?? theme.palette.text.primary} options={{
|
||||
allowSave: true,
|
||||
}}
|
||||
onSave={(val)=>{
|
||||
if(val) {
|
||||
eventBus.fireEvent(ERD_EVENTS.CHANGE_COLORS, fillColor, val);
|
||||
} else {
|
||||
eventBus.fireEvent(ERD_EVENTS.CHANGE_COLORS, fillColor, null);
|
||||
}
|
||||
}}/>
|
||||
</PgButtonGroup>
|
||||
<PgButtonGroup size="small">
|
||||
<PgIconButton title={gettext('Add/Edit Note')} icon={<NoteRoundedIcon />}
|
||||
shortcut={preferences.add_edit_note} disabled={buttonsDisabled['show-note']}
|
||||
|
@ -290,4 +318,8 @@ export function MainToolBar({preferences, eventBus}) {
|
|||
MainToolBar.propTypes = {
|
||||
preferences: PropTypes.object,
|
||||
eventBus: PropTypes.object,
|
||||
fillColor: PropTypes.string,
|
||||
textColor: PropTypes.string,
|
||||
};
|
||||
|
||||
const ColorButton = withColorPicker(PgIconButton);
|
||||
|
|
|
@ -49,6 +49,7 @@ export class TableNodeModel extends DefaultNodeModel {
|
|||
/* Once the data is available, it is no more a promise */
|
||||
this._data = data;
|
||||
this._metadata = {
|
||||
...this._metadata,
|
||||
data_failed: false,
|
||||
is_promise: false,
|
||||
};
|
||||
|
@ -56,6 +57,7 @@ export class TableNodeModel extends DefaultNodeModel {
|
|||
this.fireEvent({}, 'nodeUpdated');
|
||||
}).catch(()=>{
|
||||
this._metadata = {
|
||||
...this._metadata,
|
||||
data_failed: true,
|
||||
is_promise: true,
|
||||
};
|
||||
|
@ -85,6 +87,13 @@ export class TableNodeModel extends DefaultNodeModel {
|
|||
return this._metadata;
|
||||
}
|
||||
|
||||
setMetadata(metadata) {
|
||||
this._metadata = {
|
||||
...this._metadata,
|
||||
...metadata,
|
||||
};
|
||||
}
|
||||
|
||||
addColumn(col) {
|
||||
this._data.columns.push(col);
|
||||
}
|
||||
|
@ -152,6 +161,7 @@ RowIcon.propTypes = {
|
|||
const styles = (theme)=>({
|
||||
tableNode: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
...theme.mixins.panelBorder.all,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
position: 'relative',
|
||||
|
@ -202,6 +212,12 @@ class TableNodeWidgetRaw extends React.Component {
|
|||
toggleDetails: (event) => {
|
||||
this.setState({show_details: event.show_details});
|
||||
},
|
||||
changeColors: (event)=>{
|
||||
this.props.node.setMetadata({
|
||||
fillColor: event.fillColor, textColor: event.textColor,
|
||||
});
|
||||
this.setState({});
|
||||
},
|
||||
dataAvaiable: ()=>{
|
||||
/* Just re-render */
|
||||
this.setState({});
|
||||
|
@ -271,8 +287,13 @@ class TableNodeWidgetRaw extends React.Component {
|
|||
localUkCols.push(...uk.columns.map((c)=>c.column));
|
||||
});
|
||||
const {classes} = this.props;
|
||||
const styles = {
|
||||
backgroundColor: tableMetaData.fillColor,
|
||||
color: tableMetaData.textColor,
|
||||
};
|
||||
return (
|
||||
<div className={clsx(classes.tableNode, (this.props.node.isSelected() ? classes.tableNodeSelected: ''))} onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}}>
|
||||
<div className={clsx(classes.tableNode, (this.props.node.isSelected() ? classes.tableNodeSelected: ''))}
|
||||
onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}} style={styles}>
|
||||
<div className={clsx(classes.tableSection, classes.tableToolbar)}>
|
||||
<PgIconButton size="xs" title={gettext('Show Details')} icon={this.state.show_details ? <VisibilityRoundedIcon /> : <VisibilityOffRoundedIcon />}
|
||||
onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} />
|
||||
|
|
|
@ -25,6 +25,7 @@ export class FakeNode {
|
|||
retVal.name = tabName;
|
||||
return retVal;
|
||||
}
|
||||
setMetadata() {/* no-op */}
|
||||
getMetadata() {
|
||||
return {
|
||||
is_promise: false,
|
||||
|
|
|
@ -132,6 +132,7 @@ describe('ERDTool', ()=>{
|
|||
body = erd.find('ERDTool');
|
||||
bodyInstance = body.instance();
|
||||
spyOn(bodyInstance, 'getDialog').and.callFake(getDialog);
|
||||
spyOn(bodyInstance, 'onChangeColors').and.callFake(()=>{/*no op*/});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
@ -248,7 +249,7 @@ describe('ERDTool', ()=>{
|
|||
let saveCallback = tableDialog.calls.mostRecent().args[3];
|
||||
let newData = {key: 'value'};
|
||||
saveCallback(newData);
|
||||
expect(bodyInstance.diagram.addNode).toHaveBeenCalledWith(newData);
|
||||
expect(bodyInstance.diagram.addNode.calls.mostRecent().args[0]).toEqual(newData);
|
||||
|
||||
/* Existing */
|
||||
tableDialog.calls.reset();
|
||||
|
|
Loading…
Reference in New Issue