From 794cbed57c7dfbdc680e7f1c073edc0be6e247dd Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Wed, 12 Oct 2022 10:07:30 +0530 Subject: [PATCH] Added support to specify the background fill color to the table nodes in the ERD tool. #4392 --- docs/en_US/erd_tool.rst | 17 ++ docs/en_US/images/erd_tool_toolbar.png | Bin 24678 -> 22534 bytes web/pgadmin/static/css/style.css | 2 + web/pgadmin/static/js/Theme/index.jsx | 4 +- .../js/Theme/overrides/pickr.override.js | 31 ++++ .../static/js/components/FormComponents.jsx | 117 +------------- .../static/js/helpers/withColorPicker.js | 146 ++++++++++++++++++ web/pgadmin/static/js/utils.js | 7 + web/pgadmin/static/scss/_pickr.overrides.scss | 19 --- web/pgadmin/static/scss/pgadmin.scss | 1 - .../erd/static/js/erd_tool/ERDConstants.js | 1 + .../tools/erd/static/js/erd_tool/ERDCore.js | 3 +- .../static/js/erd_tool/components/ERDTool.jsx | 30 +++- .../js/erd_tool/components/MainToolBar.jsx | 38 ++++- .../static/js/erd_tool/nodes/TableNode.jsx | 23 ++- web/regression/javascript/erd/fake_item.js | 1 + .../erd/ui_components/ERDTool.spec.js | 3 +- 17 files changed, 302 insertions(+), 141 deletions(-) create mode 100644 web/pgadmin/static/js/Theme/overrides/pickr.override.js create mode 100644 web/pgadmin/static/js/helpers/withColorPicker.js delete mode 100644 web/pgadmin/static/scss/_pickr.overrides.scss diff --git a/docs/en_US/erd_tool.rst b/docs/en_US/erd_tool.rst index 844cf29f5..39c7f1f26 100644 --- a/docs/en_US/erd_tool.rst +++ b/docs/en_US/erd_tool.rst @@ -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 *************** diff --git a/docs/en_US/images/erd_tool_toolbar.png b/docs/en_US/images/erd_tool_toolbar.png index e6e88a9c05ef517ceb6524b9119e0cf8dc8c009c..9e70249a0a9f5aa78b641747db0358acd00df0c5 100644 GIT binary patch delta 18622 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 X()a8%PP&WM00000NkvXXu0mjfuT7S0 diff --git a/web/pgadmin/static/css/style.css b/web/pgadmin/static/css/style.css index db1bf87f1..9928a52ac 100644 --- a/web/pgadmin/static/css/style.css +++ b/web/pgadmin/static/css/style.css @@ -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'; diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx index 5bc215c1b..7702818be 100644 --- a/web/pgadmin/static/js/Theme/index.jsx +++ b/web/pgadmin/static/js/Theme/index.jsx @@ -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: { diff --git a/web/pgadmin/static/js/Theme/overrides/pickr.override.js b/web/pgadmin/static/js/Theme/overrides/pickr.override.js new file mode 100644 index 000000000..b2759e63c --- /dev/null +++ b/web/pgadmin/static/js/Theme/overrides/pickr.override.js @@ -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, + }, + } + }; +} diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx index 71de75158..c6f2bcf23 100644 --- a/web/pgadmin/static/js/components/FormComponents.jsx +++ b/web/pgadmin/static/js/components/FormComponents.jsx @@ -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 ( - } + } options={{ + ...controlProps, + disabled: disabled + }} onChange={onChange} value={value} currObj={currObj} /> ); } diff --git a/web/pgadmin/static/js/helpers/withColorPicker.js b/web/pgadmin/static/js/helpers/withColorPicker.js new file mode 100644 index 000000000..58278baee --- /dev/null +++ b/web/pgadmin/static/js/helpers/withColorPicker.js @@ -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 ; + }; + + HOCComponent.propTypes = { + value: PropTypes.string, + currObj: PropTypes.func, + onChange: PropTypes.func, + onSave: PropTypes.func, + options: PropTypes.object, + }; + + return HOCComponent; +} diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js index 7392abc73..34f697f27 100644 --- a/web/pgadmin/static/js/utils.js +++ b/web/pgadmin/static/js/utils.js @@ -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; +} diff --git a/web/pgadmin/static/scss/_pickr.overrides.scss b/web/pgadmin/static/scss/_pickr.overrides.scss deleted file mode 100644 index 97cdce529..000000000 --- a/web/pgadmin/static/scss/_pickr.overrides.scss +++ /dev/null @@ -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; - } -} diff --git a/web/pgadmin/static/scss/pgadmin.scss b/web/pgadmin/static/scss/pgadmin.scss index 4e6311a6c..2416b6782 100644 --- a/web/pgadmin/static/scss/pgadmin.scss +++ b/web/pgadmin/static/scss/pgadmin.scss @@ -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'; diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ERDConstants.js b/web/pgadmin/tools/erd/static/js/erd_tool/ERDConstants.js index 9b763ffe0..558a92711 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ERDConstants.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ERDConstants.js @@ -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', diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js index 626f649cc..755e6daf0 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js @@ -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; } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx index 5d6ae3672..9d34895b1 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx @@ -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 { - +
{e.preventDefault();}}> diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/components/MainToolBar.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/components/MainToolBar.jsx index 22122046c..ff28cfae7 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/components/MainToolBar.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/components/MainToolBar.jsx @@ -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}) { } shortcut={preferences.add_table} onClick={()=>{ - eventBus.fireEvent(ERD_EVENTS.ADD_NODE); + eventBus.fireEvent(ERD_EVENTS.ADD_NODE, {fillColor: fillColor, textColor: textColor}); }} /> } shortcut={preferences.edit_table} disabled={buttonsDisabled['edit-table']} @@ -226,6 +230,30 @@ export function MainToolBar({preferences, eventBus}) { eventBus.fireEvent(ERD_EVENTS.MANY_TO_MANY); }} /> + + } + 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); + } + }}/> + } + 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); + } + }}/> + } 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); diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx index 58f97f487..bbb6d2bfb 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx @@ -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 ( -
{this.props.node.fireEvent({}, 'editTable');}}> +
{this.props.node.fireEvent({}, 'editTable');}} style={styles}>
: } onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} /> diff --git a/web/regression/javascript/erd/fake_item.js b/web/regression/javascript/erd/fake_item.js index 94b510c8d..9332ca73b 100644 --- a/web/regression/javascript/erd/fake_item.js +++ b/web/regression/javascript/erd/fake_item.js @@ -25,6 +25,7 @@ export class FakeNode { retVal.name = tabName; return retVal; } + setMetadata() {/* no-op */} getMetadata() { return { is_promise: false, diff --git a/web/regression/javascript/erd/ui_components/ERDTool.spec.js b/web/regression/javascript/erd/ui_components/ERDTool.spec.js index c65131113..a9dfdcd15 100644 --- a/web/regression/javascript/erd/ui_components/ERDTool.spec.js +++ b/web/regression/javascript/erd/ui_components/ERDTool.spec.js @@ -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();