diff --git a/README.md b/README.md index 39b9e4cf9..8bbf458b2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ and ReactJS, HTML5 with CSS for the client side processing and UI. Although developed using web technologies, pgAdmin 4 can be deployed either on a web server using a browser, or standalone on a workstation. The runtime/ -subdirectory contains an NWjs based runtime application intended to allow this, +subdirectory contains an Electron based runtime application intended to allow this, which will execute the Python server and display the UI. ## Building the Runtime @@ -35,13 +35,7 @@ paths it would expect to find in the standard package for your platform. You can then execute the runtime by running something like: ```bash -node_modules/nw/nwjs/nw . -``` - -or on macOS: - -```bash -node_modules/nw/nwjs/nwjs.app/Contents/MacOS/nwjs . +yarn run start ``` # Configuring the Python Environment diff --git a/docs/en_US/code_overview.rst b/docs/en_US/code_overview.rst index 7dc7b7c97..cd0a124e5 100644 --- a/docs/en_US/code_overview.rst +++ b/docs/en_US/code_overview.rst @@ -7,12 +7,12 @@ The bulk of pgAdmin is a Python web application written using the Flask framework on the backend, and HTML5 with CSS3,ReactJS on the front end. A desktop runtime is also included for users that prefer a desktop application to -a web application, which is written using NWjs (Node Webkit). +a web application, which is written using Electron. Runtime ******* -The runtime is based on NWjs which integrates a browser and the Python server +The runtime is based on Electron which integrates a browser and the Python server creating a standalone application. The source code can be found in the **/runtime** directory in the source tree. diff --git a/docs/en_US/desktop_deployment.rst b/docs/en_US/desktop_deployment.rst index d9ebe1c16..1e1661de3 100644 --- a/docs/en_US/desktop_deployment.rst +++ b/docs/en_US/desktop_deployment.rst @@ -36,7 +36,7 @@ See :ref:`config_py` for more information on configuration settings. Desktop Runtime Standalone Application ====================================== -The Desktop Runtime is based on `NWjs `_ which integrates a +The Desktop Runtime is based on `Electron `_ which integrates a browser and the Python server creating a standalone application. .. image:: images/runtime_standalone.png diff --git a/pkg/linux/build-functions.sh b/pkg/linux/build-functions.sh index 151fa529a..74fba08ba 100644 --- a/pkg/linux/build-functions.sh +++ b/pkg/linux/build-functions.sh @@ -153,8 +153,6 @@ _build_runtime() { # Copy electron into the staging directory mkdir -p "${DESKTOPROOT}/usr/${APP_NAME}/bin" - # The chmod command below is needed to fix the permission issue of - # the NWjs binaries and files. # Change the permission for others and group the same as the owner chmod -R og=u "${BUILDROOT}/electron-v${ELECTRON_VERSION}-linux-${ELECTRON_ARCH}"/* # Explicitly remove write permissions for others and group diff --git a/pkg/mac/entitlements.plist.in b/pkg/mac/entitlements.plist.in index 2a5583b88..97c975b9e 100644 --- a/pkg/mac/entitlements.plist.in +++ b/pkg/mac/entitlements.plist.in @@ -4,7 +4,7 @@ com.apple.security.cs.allow-jit @@ -50,7 +50,7 @@ com.apple.security.cs.disable-executable-page-protection diff --git a/tools/dependency_inventory.py b/tools/dependency_inventory.py index 5fa89ec83..354b3b14a 100644 --- a/tools/dependency_inventory.py +++ b/tools/dependency_inventory.py @@ -199,9 +199,9 @@ def dump_header(): def dump_runtime(): print_title("Runtime Dependencies") print_table_header() - print_row("Python", "3.6+", "PSF", "https://www.python.org/") - print_row("nw", "0.50.2", "MIT", - "git://github.com/nwjs/npm-installer.git") + print_row("Python", "3.8+", "PSF", "https://www.python.org/") + print_row("Electron", "30.0.5", "MIT", + "https://www.npmjs.com/package/electron") # Make sure to change the count of hardcoded_deps if we will # manually add some more dependencies in future. diff --git a/web/pgadmin/browser/static/js/MainMenuFactory.js b/web/pgadmin/browser/static/js/MainMenuFactory.js index b28c28c98..545abeaab 100644 --- a/web/pgadmin/browser/static/js/MainMenuFactory.js +++ b/web/pgadmin/browser/static/js/MainMenuFactory.js @@ -107,7 +107,7 @@ export default class MainMenuFactory { static refreshMainMenuItems(menu, menuItems) { menu.setMenuItems(menuItems); window.electronUI?.setMenus(MainMenuFactory.toElectron()); - pgAdmin.Browser.Events.trigger('pgadmin:nw-refresh-menu-item', pgAdmin.Browser.MainMenus); + pgAdmin.Browser.Events.trigger('pgadmin:refresh-menu-item', pgAdmin.Browser.MainMenus); } static createMenuItem(options) { @@ -130,10 +130,8 @@ export default class MainMenuFactory { }); } }}, (menu, item)=> { - pgAdmin.Browser.Events.trigger('pgadmin:nw-enable-disable-menu-items', menu, item); + pgAdmin.Browser.Events.trigger('pgadmin:enable-disable-menu-items', menu, item); window.electronUI?.enableDisableMenuItems(menu?.serialize(), item?.serialize()); - }, (item) => { - pgAdmin.Browser.Events.trigger('pgadmin:nw-update-checked-menu-item', item); }); } diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index 8886c5b21..b3c428972 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -920,7 +920,7 @@ define('pgadmin.browser', [ this.success = function() { addItemNode(); - }.bind(this); + }; // We can refresh the collection node, but - let's not bother about // it right now. this.notFound = errorOut; @@ -961,7 +961,7 @@ define('pgadmin.browser', [ this.success = function() { addItemNode(); - }.bind(this); + }; // We can refresh the collection node, but - let's not bother about // it right now. this.notFound = errorOut; @@ -1021,7 +1021,7 @@ define('pgadmin.browser', [ this.load = true; this.success = function() { addItemNode(); - }.bind(this); + }; if (_d._type == this.old._type) { // We were already searching the old object under the parent. @@ -1423,7 +1423,7 @@ define('pgadmin.browser', [ }); } }); - }.bind(this); + }; if (n?.collection_node) { let p = ctx.i = this.tree.parent(_i), diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx index e1e7de6c8..696029ab3 100644 --- a/web/pgadmin/dashboard/static/js/Dashboard.jsx +++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx @@ -25,7 +25,7 @@ import _ from 'lodash'; import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage'; import TabPanel from '../../../static/js/components/TabPanel'; import Summary from './SystemStats/Summary'; -import CPU from './SystemStats/CPU'; +import CpuDetails from './SystemStats/CpuDetails'; import Memory from './SystemStats/Memory'; import Storage from './SystemStats/Storage'; import withStandardTabInfo from '../../../static/js/helpers/withStandardTabInfo'; @@ -86,6 +86,160 @@ const Root = styled('div')(({theme}) => ({ let activeQSchemaObj = new ActiveQuery(); +const cellPropTypes = { + row: PropTypes.any, +}; + +function getTerminateCell(pgAdmin, sid, did, canTakeAction, onSuccess) { + function TerminateCell({row}) { + let terminate_session_url = + url_for('dashboard.index') + 'terminate_session' + '/' + sid, + title = gettext('Terminate Session?'), + txtConfirm = gettext( + 'Are you sure you wish to terminate the session?' + ), + txtSuccess = gettext('Session terminated successfully.'), + txtError = gettext( + 'An error occurred whilst terminating the active query.' + ); + const action_url = did + ? terminate_session_url + '/' + did + : terminate_session_url; + + const api = getApiInstance(); + + return ( + } + className='Dashboard-terminateButton' + onClick={() => { + if ( + !canTakeAction(row, 'terminate') + ) + return; + let url = action_url + '/' + row.original.pid; + pgAdmin.Browser.notifier.confirm( + title, + txtConfirm, + function () { + api + .delete(url) + .then(function (res) { + if (res.data == gettext('Success')) { + pgAdmin.Browser.notifier.success(txtSuccess); + onSuccess?.(); + } else { + pgAdmin.Browser.notifier.error(txtError); + } + }) + .catch(function (error) { + pgAdmin.Browser.notifier.alert( + gettext('Failed to perform the operation.'), + parseApiError(error) + ); + }); + }, + function () { + return true; + } + ); + }} + aria-label="Terminate Session?" + title={gettext('Terminate Session?')} + > + ); + } + + TerminateCell.propTypes = cellPropTypes; + return TerminateCell; +} + +function getCancelCell(pgAdmin, sid, did, canTakeAction, onSuccess) { + function CancelCell({ row }) { + let cancel_query_url = + url_for('dashboard.index') + 'cancel_query' + '/' + sid, + title = gettext('Cancel Active Query?'), + txtConfirm = gettext( + 'Are you sure you wish to cancel the active query?' + ), + txtSuccess = gettext('Active query cancelled successfully.'), + txtError = gettext( + 'An error occurred whilst cancelling the active query.' + ); + + const action_url = did ? cancel_query_url + '/' + did : cancel_query_url; + + const api = getApiInstance(); + + return ( + } + onClick={() => { + if (!canTakeAction(row, 'cancel')) + return; + let url = action_url + '/' + row.original.pid; + pgAdmin.Browser.notifier.confirm( + title, + txtConfirm, + function () { + api + .delete(url) + .then(function (res) { + if (res.data == gettext('Success')) { + pgAdmin.Browser.notifier.success(txtSuccess); + onSuccess?.(); + } else { + pgAdmin.Browser.notifier.error(txtError); + onSuccess?.(); + } + }) + .catch(function (error) { + pgAdmin.Browser.notifier.alert( + gettext('Failed to perform the operation.'), + parseApiError(error) + ); + }); + }, + function () { + return true; + } + ); + }} + aria-label="Cancel the query" + title={gettext('Cancel the active query')} + > + ); + } + CancelCell.propTypes = cellPropTypes; + return CancelCell; +} + +function ActiveOnlyHeader({activeOnly, setActiveOnly}) { + return ( + { + e.preventDefault(); + setActiveOnly(e.target.checked); + }} + value={activeOnly} + controlProps={{ + label: gettext('Active sessions only'), + }} + /> + ); +} +ActiveOnlyHeader.propTypes = { + activeOnly: PropTypes.bool, + setActiveOnly: PropTypes.func, +}; + function Dashboard({ nodeItem, nodeData, node, treeNodeInfo, ...props @@ -134,6 +288,62 @@ function Dashboard({ setMainTabVal(tabVal); }; + const canTakeAction = (row, cellAction) => { + // We will validate if user is allowed to cancel the active query + // If there is only one active session means it probably our main + // connection session + cellAction = cellAction || null; + let pg_version = treeNodeInfo.server.version || null, + is_cancel_session = cellAction === 'cancel', + txtMessage, + maintenance_database = treeNodeInfo.server.db; + + let maintenanceActiveSessions = dashData.filter((data) => data.state === 'active'&& + maintenance_database === data.datname); + + // With PG10, We have background process showing on dashboard + // We will not allow user to cancel them as they will fail with error + // anyway, so better usability we will throw our on notification + + // Background processes do not have database field populated + if (pg_version && pg_version >= 100000 && !row.original.datname) { + if (is_cancel_session) { + txtMessage = gettext('You cannot cancel background worker processes.'); + } else { + txtMessage = gettext( + 'You cannot terminate background worker processes.' + ); + } + pgAdmin.Browser.notifier.info(txtMessage); + return false; + // If it is the last active connection on maintenance db then error out + } else if ( + maintenance_database == row.original.datname && + row.original.state == 'active' && + maintenanceActiveSessions.length === 1 + ) { + if (is_cancel_session) { + txtMessage = gettext( + 'You are not allowed to cancel the main active session.' + ); + } else { + txtMessage = gettext( + 'You are not allowed to terminate the main active session.' + ); + } + pgAdmin.Browser.notifier.error(txtMessage); + return false; + } else if (is_cancel_session && row.original.state == 'idle') { + // If this session is already idle then do nothing + pgAdmin.Browser.notifier.info(gettext('The session is already in idle state.')); + return false; + } else { + // Will return true and let the backend handle all the cases. + // Added as fix of #7217 + return true; + } + }; + const serverConfigColumns = [ { accessorKey: 'name', @@ -190,67 +400,7 @@ function Dashboard({ maxSize: 35, minSize: 35, id: 'btn-terminate', - // eslint-disable-next-line react/display-name - cell: ({ row }) => { - let terminate_session_url = - url_for('dashboard.index') + 'terminate_session' + '/' + sid, - title = gettext('Terminate Session?'), - txtConfirm = gettext( - 'Are you sure you wish to terminate the session?' - ), - txtSuccess = gettext('Session terminated successfully.'), - txtError = gettext( - 'An error occurred whilst terminating the active query.' - ); - const action_url = did - ? terminate_session_url + '/' + did - : terminate_session_url; - - const api = getApiInstance(); - - return ( - } - className='Dashboard-terminateButton' - onClick={() => { - if ( - !canTakeAction(row, 'terminate') - ) - return; - let url = action_url + '/' + row.original.pid; - pgAdmin.Browser.notifier.confirm( - title, - txtConfirm, - function () { - api - .delete(url) - .then(function (res) { - if (res.data == gettext('Success')) { - pgAdmin.Browser.notifier.success(txtSuccess); - setRefresh(!refresh); - } else { - pgAdmin.Browser.notifier.error(txtError); - } - }) - .catch(function (error) { - pgAdmin.Browser.notifier.alert( - gettext('Failed to perform the operation.'), - parseApiError(error) - ); - }); - }, - function () { - return true; - } - ); - }} - aria-label="Terminate Session?" - title={gettext('Terminate Session?')} - > - ); - }, + cell: getTerminateCell(pgAdmin, sid, did, canTakeAction, setRefresh, ()=>setRefresh(!refresh)), }, { header: () => null, @@ -261,64 +411,7 @@ function Dashboard({ maxSize: 35, minSize: 35, id: 'btn-cancel', - cell: ({ row }) => { - let cancel_query_url = - url_for('dashboard.index') + 'cancel_query' + '/' + sid, - title = gettext('Cancel Active Query?'), - txtConfirm = gettext( - 'Are you sure you wish to cancel the active query?' - ), - txtSuccess = gettext('Active query cancelled successfully.'), - txtError = gettext( - 'An error occurred whilst cancelling the active query.' - ); - - const action_url = did ? cancel_query_url + '/' + did : cancel_query_url; - - const api = getApiInstance(); - - return ( - } - onClick={() => { - if (!canTakeAction(row, 'cancel')) - return; - let url = action_url + '/' + row.original.pid; - pgAdmin.Browser.notifier.confirm( - title, - txtConfirm, - function () { - api - .delete(url) - .then(function (res) { - if (res.data == gettext('Success')) { - pgAdmin.Browser.notifier.success(txtSuccess); - setRefresh(!refresh); - } else { - pgAdmin.Browser.notifier.error(txtError); - setRefresh(!refresh); - - } - }) - .catch(function (error) { - pgAdmin.Browser.notifier.alert( - gettext('Failed to perform the operation.'), - parseApiError(error) - ); - }); - }, - function () { - return true; - } - ); - }} - aria-label="Cancel the query" - title={gettext('Cancel the active query')} - > - ); - }, + cell: getCancelCell(pgAdmin, sid, did, canTakeAction, setRefresh, ()=>setRefresh(!refresh)), }, { header: () => null, @@ -590,61 +683,6 @@ function Dashboard({ }, ]; - const canTakeAction = (row, cellAction) => { - // We will validate if user is allowed to cancel the active query - // If there is only one active session means it probably our main - // connection session - cellAction = cellAction || null; - let pg_version = treeNodeInfo.server.version || null, - is_cancel_session = cellAction === 'cancel', - txtMessage, - maintenance_database = treeNodeInfo.server.db; - - let maintenanceActiveSessions = dashData.filter((data) => data.state === 'active'&& - maintenance_database === data.datname); - - // With PG10, We have background process showing on dashboard - // We will not allow user to cancel them as they will fail with error - // anyway, so better usability we will throw our on notification - - // Background processes do not have database field populated - if (pg_version && pg_version >= 100000 && !row.original.datname) { - if (is_cancel_session) { - txtMessage = gettext('You cannot cancel background worker processes.'); - } else { - txtMessage = gettext( - 'You cannot terminate background worker processes.' - ); - } - pgAdmin.Browser.notifier.info(txtMessage); - return false; - // If it is the last active connection on maintenance db then error out - } else if ( - maintenance_database == row.original.datname && - row.original.state == 'active' && - maintenanceActiveSessions.length === 1 - ) { - if (is_cancel_session) { - txtMessage = gettext( - 'You are not allowed to cancel the main active session.' - ); - } else { - txtMessage = gettext( - 'You are not allowed to terminate the main active session.' - ); - } - pgAdmin.Browser.notifier.error(txtMessage); - return false; - } else if (is_cancel_session && row.original.state == 'idle') { - // If this session is already idle then do nothing - pgAdmin.Browser.notifier.info(gettext('The session is already in idle state.')); - return false; - } else { - // Will return true and let the backend handle all the cases. - // Added as fix of #7217 - return true; - } - }; useEffect(() => { // Reset Tab values to 0, so that it will select "Sessions" on node changed. nodeData?._type === 'database' && setTabVal(0); @@ -766,25 +804,6 @@ function Dashboard({ ); }; - const CustomActiveOnlyHeaderLabel = - { - label: gettext('Active sessions only'), - }; - const CustomActiveOnlyHeader = () => { - return ( - { - e.preventDefault(); - setActiveOnly(e.target.checked); - }} - value={activeOnly} - controlProps={CustomActiveOnlyHeaderLabel} - >); - }; - return ( ( {sid && serverConnected ? ( @@ -832,7 +851,7 @@ function Dashboard({ } columns={activityColumns} data={filteredDashData} schema={activeQSchemaObj} @@ -891,7 +910,7 @@ function Dashboard({ /> - } + className='Processes-stopButton' + disabled={row.original.process_state != BgProcessManagerProcessState.PROCESS_STARTED + || row.original.server_id != null} + onClick={(e) => { + e.preventDefault(); + pgAdmin.Browser.BgProcessManager.stopProcess(row.original.id); + }} + aria-label="Stop Process" + title={gettext('Stop Process')} + > + ); +} +CancelCell.propTypes = cellPropTypes; + +function getLogsCell(pgAdmin, onViewDetailsClick) { + function LogsCell({ row }) { + return ( + } + noBorder + onClick={(e) => { + e.preventDefault(); + onViewDetailsClick(row.original); + }} + aria-label="View details" + title={gettext('View details')} + /> + ); + } + LogsCell.propTypes = cellPropTypes; + + return LogsCell; +} + +function StatusCell({row}) { + const [text, bgcolor] = ProcessStateTextAndColor[row.original.process_state]; + return {text}; +} +StatusCell.propTypes = cellPropTypes; + +function CustomHeader({selectedRowIDs, setSelectedRows}) { + const pgAdmin = usePgAdmin(); + + return ( + + + } + aria-label="Acknowledge and Remove" + title={gettext('Acknowledge and Remove')} + onClick={() => { + pgAdmin.Browser.notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{ + pgAdmin.Browser.BgProcessManager.acknowledge(selectedRowIDs); + setSelectedRows({}); + }); + }} + disabled={selectedRowIDs.length <= 0} + > + } + aria-label="Help" + title={gettext('Help')} + onClick={() => { + window.open(url_for('help.static', {'filename': 'processes.html'})); + }} + > + + + ); +} +CustomHeader.propTypes = { + selectedRowIDs: PropTypes.array, + setSelectedRows: PropTypes.func, +}; + export default function Processes() { const pgAdmin = usePgAdmin(); @@ -89,56 +178,6 @@ export default function Processes() { const columns = useMemo(()=>{ - const cellPropTypes = { - row: PropTypes.any, - }; - - const CancelCell = ({row}) => { - return ( - } - className='Processes-stopButton' - disabled={row.original.process_state != BgProcessManagerProcessState.PROCESS_STARTED - || row.original.server_id != null} - onClick={(e) => { - e.preventDefault(); - pgAdmin.Browser.BgProcessManager.stopProcess(row.original.id); - }} - aria-label="Stop Process" - title={gettext('Stop Process')} - > - ); - }; - CancelCell.displayName = 'CancelCell'; - CancelCell.propTypes = cellPropTypes; - - const LogsCell = ({ row }) => { - return ( - } - noBorder - onClick={(e) => { - e.preventDefault(); - onViewDetailsClick(row.original); - }} - aria-label="View details" - title={gettext('View details')} - /> - ); - }; - LogsCell.displayName = 'LogsCell'; - LogsCell.propTypes = cellPropTypes; - - const StatusCell = ({row})=>{ - const [text, bgcolor] = ProcessStateTextAndColor[row.original.process_state]; - return {text}; - }; - StatusCell.displayName = 'StatusCell'; - StatusCell.propTypes = cellPropTypes; - return [{ header: () => null, enableSorting: false, @@ -159,7 +198,7 @@ export default function Processes() { maxSize: 35, minSize: 35, id: 'btn-logs', - cell: LogsCell, + cell: getLogsCell(pgAdmin, onViewDetailsClick), }, { header: gettext('PID'), @@ -257,34 +296,7 @@ export default function Processes() { return row.id; } }} - CustomHeader={()=>{ - return ( - - - } - aria-label="Acknowledge and Remove" - title={gettext('Acknowledge and Remove')} - onClick={() => { - pgAdmin.Browser.notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{ - pgAdmin.Browser.BgProcessManager.acknowledge(selectedRowIDs); - setSelectedRows({}); - }); - }} - disabled={selectedRowIDs.length <= 0} - > - } - aria-label="Help" - title={gettext('Help')} - onClick={() => { - window.open(url_for('help.static', {'filename': 'processes.html'})); - }} - > - - - ); - }} + customHeader={} > ); } diff --git a/web/pgadmin/misc/properties/CollectionNodeProperties.jsx b/web/pgadmin/misc/properties/CollectionNodeProperties.jsx index 08603fa3b..4781a7055 100644 --- a/web/pgadmin/misc/properties/CollectionNodeProperties.jsx +++ b/web/pgadmin/misc/properties/CollectionNodeProperties.jsx @@ -36,6 +36,66 @@ const StyledBox = styled(Box)(({theme}) => ({ } })); +function CustomHeader({node, nodeData, nodeItem, treeNodeInfo, selectedObject, onDrop}) { + const canDrop = evalFunc(node, node.canDrop, nodeData, nodeItem, treeNodeInfo); + const canDropCascade = evalFunc(node, node.canDropCascade, nodeData, nodeItem, treeNodeInfo); + const canDropForce = evalFunc(node, node.canDropForce, nodeData, nodeItem, treeNodeInfo); + return ( + + + } + aria-label="Delete" + title={gettext('Delete')} + onClick={() => { + onDrop('drop'); + }} + disabled={ + (Object.keys(selectedObject).length > 0) + ? !canDrop + : true + } + > + {node.type !== 'coll-database' ? } + aria-label="Delete Cascade" + title={gettext('Delete (Cascade)')} + onClick={() => { + onDrop('dropCascade'); + }} + disabled={ + (Object.keys(selectedObject).length > 0) + ? !canDropCascade + : true + } + > : + } + aria-label="Delete Force" + title={gettext('Delete (Force)')} + onClick={() => { + onDrop('dropForce'); + }} + disabled={ + (Object.keys(selectedObject).length > 0) + ? !canDropForce + : true + } + >} + + + ); +} + +CustomHeader.propTypes = { + node: PropTypes.func, + nodeData: PropTypes.object, + treeNodeInfo: PropTypes.object, + nodeItem: PropTypes.object, + selectedObject: PropTypes.object, + onDrop: PropTypes.func, +}; + export default function CollectionNodeProperties({ node, treeNodeInfo, @@ -221,56 +281,6 @@ export default function CollectionNodeProperties({ } }, [nodeData, node, nodeItem, isStale, isActive]); - const CustomHeader = () => { - const canDrop = evalFunc(node, node.canDrop, nodeData, nodeItem, treeNodeInfo); - const canDropCascade = evalFunc(node, node.canDropCascade, nodeData, nodeItem, treeNodeInfo); - const canDropForce = evalFunc(node, node.canDropForce, nodeData, nodeItem, treeNodeInfo); - return ( - - - } - aria-label="Delete" - title={gettext('Delete')} - onClick={() => { - onDrop('drop'); - }} - disabled={ - (Object.keys(selectedObject).length > 0) - ? !canDrop - : true - } - > - {node.type !== 'coll-database' ? } - aria-label="Delete Cascade" - title={gettext('Delete (Cascade)')} - onClick={() => { - onDrop('dropCascade'); - }} - disabled={ - (Object.keys(selectedObject).length > 0) - ? !canDropCascade - : true - } - > : - } - aria-label="Delete Force" - title={gettext('Delete (Force)')} - onClick={() => { - onDrop('dropForce'); - }} - disabled={ - (Object.keys(selectedObject).length > 0) - ? !canDropForce - : true - } - >} - - ); - }; - return ( <> @@ -279,7 +289,7 @@ export default function CollectionNodeProperties({ ( } columns={pgTableColumns} data={data} type={'panel'} diff --git a/web/pgadmin/static/js/AppMenuBar.jsx b/web/pgadmin/static/js/AppMenuBar.jsx index 687ad8285..c83b04ac9 100644 --- a/web/pgadmin/static/js/AppMenuBar.jsx +++ b/web/pgadmin/static/js/AppMenuBar.jsx @@ -64,10 +64,10 @@ export default function AppMenuBar() { const pgAdmin = usePgAdmin(); useEffect(()=>{ - pgAdmin.Browser.Events.on('pgadmin:nw-enable-disable-menu-items', _.debounce(()=>{ + pgAdmin.Browser.Events.on('pgadmin:enable-disable-menu-items', _.debounce(()=>{ forceUpdate(); }, 100)); - pgAdmin.Browser.Events.on('pgadmin:nw-refresh-menu-item', _.debounce(()=>{ + pgAdmin.Browser.Events.on('pgadmin:refresh-menu-item', _.debounce(()=>{ forceUpdate(); }, 100)); }, []); diff --git a/web/pgadmin/static/js/SchemaView/DataGridView.jsx b/web/pgadmin/static/js/SchemaView/DataGridView.jsx index 576936820..d7592cdf1 100644 --- a/web/pgadmin/static/js/SchemaView/DataGridView.jsx +++ b/web/pgadmin/static/js/SchemaView/DataGridView.jsx @@ -15,9 +15,6 @@ import { Box } from '@mui/material'; import { PgIconButton } from '../components/Buttons'; import AddIcon from '@mui/icons-material/AddOutlined'; import { MappedCellControl } from './MappedControl'; -import DragIndicatorRoundedIcon from '@mui/icons-material/DragIndicatorRounded'; -import EditRoundedIcon from '@mui/icons-material/EditRounded'; -import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; import { useReactTable, @@ -42,7 +39,9 @@ import { useIsMounted } from '../custom_hooks'; import { InputText } from '../components/FormComponents'; import { usePgAdmin } from '../BrowserComponent'; import { requestAnimationAndFocus } from '../utils'; -import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from '../components/PgReactTableStyled'; +import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, + PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent, + getDeleteCell, getEditCell, getReorderCell } from '../components/PgReactTableStyled'; import { useVirtualizer } from '@tanstack/react-virtual'; const StyledBox = styled(Box)(({theme}) => ({ @@ -82,16 +81,6 @@ const StyledBox = styled(Box)(({theme}) => ({ '&.btn-cell, &.expanded-icon-cell': { padding: '2px 0px' }, - '& .DataGridView-gridRowButton': { - border: 0, - borderRadius: 0, - padding: 0, - minWidth: 0, - backgroundColor: 'inherit', - '&.Mui-disabled': { - border: 0, - }, - }, } }, } @@ -108,10 +97,6 @@ const StyledBox = styled(Box)(({theme}) => ({ opacity: 0.75, } }, - '& .DataGridView-btnReorder': { - cursor: 'move', - padding: '4px 2px', - }, '& .DataGridView-resizer': { display: 'inline-block', width: '5px', @@ -298,6 +283,58 @@ DataGridHeader.propTypes = { onSearchTextChange: PropTypes.func, }; +function getMappedCell({ + field, + schemaRef, + viewHelperProps, + accessPath, + dataDispatch +}) { + const Cell = ({row, ...other}) => { + const value = other.getValue(); + /* Make sure to take the latest field info from schema */ + field = _.find(schemaRef.current.fields, (f)=>f.id==field.id) || field; + + let {editable, disabled, modeSupported} = getFieldMetaData(field, schemaRef.current, row.original || {}, viewHelperProps); + + if(_.isUndefined(field.cell)) { + console.error('cell is required ', field); + } + + return modeSupported && { + if(field.radioType) { + dataDispatch({ + type: SCHEMA_STATE_ACTIONS.BULK_UPDATE, + path: accessPath, + value: changeValue, + id: field.id + }); + } + dataDispatch({ + type: SCHEMA_STATE_ACTIONS.SET_VALUE, + path: accessPath.concat([row.index, field.id]), + value: changeValue, + }); + }} + reRenderRow={other.reRenderRow} + />; + }; + + Cell.displayName = 'Cell'; + Cell.propTypes = { + row: PropTypes.object.isRequired, + value: PropTypes.any, + onCellChange: PropTypes.func, + }; + + return Cell; +} + export default function DataGridView({ value, viewHelperProps, schema, accessPath, dataDispatch, containerClassName, fixedRows, ...props}) { @@ -325,13 +362,8 @@ export default function DataGridView({ size: 36, maxSize: 26, minSize: 26, - cell: ()=>{ - return
- -
; - } + cell: getReorderCell(), }; - colInfo.cell.displayName = 'Cell'; cols.push(colInfo); } if(props.canEdit) { @@ -345,21 +377,16 @@ export default function DataGridView({ size: 26, maxSize: 26, minSize: 26, - cell: ({row})=>{ - let canEditRow = true; - if(props.canEditRow) { - canEditRow = evalFunc(schemaRef.current, props.canEditRow, row.original || {}); - } - return } className='DataGridView-gridRowButton' - onClick={()=>{ - row.toggleExpanded(); - }} disabled={!canEditRow} - />; - } - }; - colInfo.cell.displayName = 'Cell'; - colInfo.cell.propTypes = { - row: PropTypes.object.isRequired, + cell: getEditCell({ + isDisabled: (row)=>{ + let canEditRow = true; + if(props.canEditRow) { + canEditRow = evalFunc(schemaRef.current, props.canEditRow, row.original || {}); + } + return !canEditRow; + }, + title: gettext('Edit row'), + }) }; cols.push(colInfo); } @@ -374,43 +401,39 @@ export default function DataGridView({ size: 26, maxSize: 26, minSize: 26, - cell: ({row}) => { - let canDeleteRow = true; - if(props.canDeleteRow) { - canDeleteRow = evalFunc(schemaRef.current, props.canDeleteRow, row.original || {}); - } + cell: getDeleteCell({ + title: gettext('Delete row'), + isDisabled: (row)=>{ + let canDeleteRow = true; + if(props.canDeleteRow) { + canDeleteRow = evalFunc(schemaRef.current, props.canDeleteRow, row.original || {}); + } + return !canDeleteRow; + }, + onClick: (row)=>{ + const deleteRow = ()=> { + dataDispatch({ + type: SCHEMA_STATE_ACTIONS.DELETE_ROW, + path: accessPath, + value: row.index, + }); + return true; + }; - return ( - } - onClick={()=>{ - const deleteRow = ()=> { - dataDispatch({ - type: SCHEMA_STATE_ACTIONS.DELETE_ROW, - path: accessPath, - value: row.index, - }); + if (props.onDelete){ + props.onDelete(row.original || {}, deleteRow); + } else { + pgAdmin.Browser.notifier.confirm( + props.customDeleteTitle || gettext('Delete Row'), + props.customDeleteMsg || gettext('Are you sure you wish to delete this row?'), + deleteRow, + function() { return true; - }; - - if (props.onDelete){ - props.onDelete(row.original || {}, deleteRow); - } else { - pgAdmin.Browser.notifier.confirm( - props.customDeleteTitle || gettext('Delete Row'), - props.customDeleteMsg || gettext('Are you sure you wish to delete this row?'), - deleteRow, - function() { - return true; - } - ); } - }} className='DataGridView-gridRowButton' disabled={!canDeleteRow} /> - ); - } - }; - colInfo.cell.displayName = 'Cell'; - colInfo.cell.propTypes = { - row: PropTypes.object.isRequired, + ); + } + } + }), }; cols.push(colInfo); } @@ -447,47 +470,15 @@ export default function DataGridView({ enableResizing: true, enableSorting: false, ...widthParms, - cell: ({row, ...other}) => { - const value = other.getValue(); - /* Make sure to take the latest field info from schema */ - field = _.find(schemaRef.current.fields, (f)=>f.id==field.id) || field; - - let {editable, disabled, modeSupported} = getFieldMetaData(field, schemaRef.current, row.original || {}, viewHelperProps); - - if(_.isUndefined(field.cell)) { - console.error('cell is required ', field); - } - - return modeSupported && { - if(field.radioType) { - dataDispatch({ - type: SCHEMA_STATE_ACTIONS.BULK_UPDATE, - path: accessPath, - value: changeValue, - id: field.id - }); - } - dataDispatch({ - type: SCHEMA_STATE_ACTIONS.SET_VALUE, - path: accessPath.concat([row.index, field.id]), - value: changeValue, - }); - }} - reRenderRow={other.reRenderRow} - />; - }, - }; - colInfo.cell.displayName = 'Cell'; - colInfo.cell.propTypes = { - row: PropTypes.object.isRequired, - value: PropTypes.any, - onCellChange: PropTypes.func, + cell: getMappedCell({ + field: field, + schemaRef: schemaRef, + viewHelperProps: viewHelperProps, + accessPath: accessPath, + dataDispatch: dataDispatch, + }), }; + return colInfo; }) ); @@ -568,7 +559,7 @@ export default function DataGridView({ // Try autofocus on newly added row. setTimeout(()=>{ - const rowInput = tableRef.current.querySelector(`.pgrt-row[data-index="${newRowIndex.current}"] input`); + const rowInput = tableRef.current?.querySelector(`.pgrt-row[data-index="${newRowIndex.current}"] input`); if(!rowInput) return; requestAnimationAndFocus(tableRef.current.querySelector(`.pgrt-row[data-index="${newRowIndex.current}"] input`)); diff --git a/web/pgadmin/static/js/SecurityPages/BasePage.jsx b/web/pgadmin/static/js/SecurityPages/BasePage.jsx index 288cf1ef2..0c0aa219e 100644 --- a/web/pgadmin/static/js/SecurityPages/BasePage.jsx +++ b/web/pgadmin/static/js/SecurityPages/BasePage.jsx @@ -61,17 +61,19 @@ export function SecurityButton({...props}) { export default function BasePage({pageImage, title, children, messages}) { const snackbar = useSnackbar(); useEffect(()=>{ - messages?.forEach((m)=>{ - snackbar.enqueueSnackbar({ + messages?.forEach((message)=>{ + let options = { autoHideDuration: null, - content: (key)=>{ - if(Array.isArray(m[0])) m[0] = m[0][0]; - const type = Object.values(MESSAGE_TYPE).includes(m[0]) ? m[0] : MESSAGE_TYPE.INFO; + content:(key)=>{ + if(Array.isArray(message[0])) message[0] = message[0][0]; + const type = Object.values(MESSAGE_TYPE).includes(message[0]) ? message[0] : MESSAGE_TYPE.INFO; return - {snackbar.closeSnackbar(key);}} style={{maxWidth: '400px'}} /> + {snackbar.closeSnackbar(key);}} style={{maxWidth: '400px'}} /> ; } - }); + }; + options.content.displayName = 'content'; + snackbar.enqueueSnackbar(options); }); }, [messages]); return ( diff --git a/web/pgadmin/static/js/ToolView.jsx b/web/pgadmin/static/js/ToolView.jsx index 9e3f10999..070269b17 100644 --- a/web/pgadmin/static/js/ToolView.jsx +++ b/web/pgadmin/static/js/ToolView.jsx @@ -49,10 +49,6 @@ export default function ToolView() { ReactDOM.render( , div ); - // Send the signal to runtime, so that proper zoom level will be set. - setTimeout(function () { - pgAdmin.Browser.Events.trigger('pgadmin:nw-set-new-window-open-size'); - }, 500); } else { window.open(toolUrl); } diff --git a/web/pgadmin/static/js/components/PgReactTableStyled.jsx b/web/pgadmin/static/js/components/PgReactTableStyled.jsx index 0cd89710c..c9a4a24a0 100644 --- a/web/pgadmin/static/js/components/PgReactTableStyled.jsx +++ b/web/pgadmin/static/js/components/PgReactTableStyled.jsx @@ -14,9 +14,13 @@ import PropTypes from 'prop-types'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import DragIndicatorRoundedIcon from '@mui/icons-material/DragIndicatorRounded'; +import EditRoundedIcon from '@mui/icons-material/EditRounded'; +import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; import { PgIconButton } from './Buttons'; import CustomPropTypes from '../custom_prop_types'; import { InputSwitch } from './FormComponents'; +import { Checkbox } from '@mui/material'; const StyledDiv = styled('div')(({theme})=>({ @@ -129,6 +133,21 @@ const StyledDiv = styled('div')(({theme})=>({ whiteSpace: 'nowrap', userSelect: 'text', width: '100%', + }, + + '& .reorder-cell': { + cursor: 'move', + padding: '4px 2px', + }, + '& .pgrt-cell-button': { + border: 0, + borderRadius: 0, + padding: 0, + minWidth: 0, + backgroundColor: 'inherit', + '&.Mui-disabled': { + border: 0, + }, } } }, @@ -139,7 +158,7 @@ const StyledDiv = styled('div')(({theme})=>({ flexGrow: 1, } } - } + }, } })); @@ -329,7 +348,7 @@ PgReactTable.propTypes = { children: CustomPropTypes.children, }; -export function getExpandCell({ onClick, ...props }) { +export function getExpandCell({ onClick, title }) { const Cell = ({ row }) => { const onClickFinal = (e) => { e.preventDefault(); @@ -347,16 +366,14 @@ export function getExpandCell({ onClick, ...props }) { ) } noBorder - {...props} onClick={onClickFinal} - aria-label={props.title} + aria-label={title} /> ); }; Cell.displayName = 'ExpandCell'; Cell.propTypes = { - title: PropTypes.string, row: PropTypes.any, }; @@ -375,3 +392,89 @@ export function getSwitchCell() { return Cell; } + +export function getCheckboxCell({title}) { + const Cell = ({ table }) => { + return ( +
+ +
+ ); + }; + + Cell.displayName = 'CheckboxCell'; + Cell.propTypes = { + table: PropTypes.object, + }; +} + +export function getCheckboxHeaderCell({title}) { + const Cell = ({ row }) => { + return ( +
+ +
+ ); + }; + + Cell.displayName = 'CheckboxHeaderCell'; + Cell.propTypes = { + row: PropTypes.object, + }; +} + +export function getReorderCell() { + const Cell = () => { + return
+ +
; + }; + + Cell.displayName = 'ReorderCell'; +} + +export function getEditCell({isDisabled, title}) { + const Cell = ({ row }) => { + return } className='pgrt-cell-button' + onClick={()=>{ + row.toggleExpanded(); + }} disabled={isDisabled?.(row)} + />; + }; + + Cell.displayName = 'EditCell'; + Cell.propTypes = { + row: PropTypes.any, + }; + + return Cell; +} + +export function getDeleteCell({isDisabled, title, onClick}) { + const Cell = ({ row }) => ( + } + onClick={()=>onClick?.(row)} + className='pgrt-cell-button' disabled={isDisabled?.(row)} + /> + ); + + Cell.displayName = 'DeleteCell'; + Cell.propTypes = { + row: PropTypes.any, + }; + + return Cell; +} diff --git a/web/pgadmin/static/js/components/PgTable.jsx b/web/pgadmin/static/js/components/PgTable.jsx index 1d4ac6c79..371cf1e34 100644 --- a/web/pgadmin/static/js/components/PgTable.jsx +++ b/web/pgadmin/static/js/components/PgTable.jsx @@ -19,13 +19,13 @@ import { import { useVirtualizer } from '@tanstack/react-virtual'; import { styled } from '@mui/material/styles'; import PropTypes from 'prop-types'; -import { Checkbox, Box } from '@mui/material'; import { InputText } from './FormComponents'; import _ from 'lodash'; import gettext from 'sources/gettext'; import SchemaView from '../SchemaView'; import EmptyPanelMessage from './EmptyPanelMessage'; -import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent } from './PgReactTableStyled'; +import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent, getCheckboxCell, getCheckboxHeaderCell } from './PgReactTableStyled'; +import { Box } from '@mui/material'; const ROW_HEIGHT = 30; function TableRow({ index, style, schema, row, measureElement}) { @@ -86,31 +86,12 @@ export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableP const finalColumns = useMemo(() => (hasSelectRow ? [{ id: 'selection', - header: ({ table }) => { - return ( -
- -
- ); - }, - cell: ({ row }) => ( -
- -
- ), + header: getCheckboxCell({ + title: gettext('Select All Rows'), + }), + cell: getCheckboxHeaderCell({ + title: gettext('Select Row'), + }), enableSorting: false, enableResizing: false, maxSize: 35, @@ -239,7 +220,7 @@ export default function PgTable({ caveTable = true, tableNoBorder = true, ...pro return ( - {props.CustomHeader && ( )} + {props.customHeader && ({props.customHeader})} { - const deregister = pgAdmin.Browser.Events.on('pgadmin:nw-enable-disable-menu-items', _.debounce(checkMenuState, 100)); + const deregister = pgAdmin.Browser.Events.on('pgadmin:enable-disable-menu-items', _.debounce(checkMenuState, 100)); checkMenuState(); return ()=>{ deregister(); 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 df43cee5a..eaf963ec5 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 @@ -758,7 +758,7 @@ export default class ERDTool extends React.Component { height = 32766; isCut = true; } - toPng(this.canvasEle) + toPng(this.canvasEle, {width, height}) .then((dataUrl)=>{ let link = document.createElement('a'); link.setAttribute('href', dataUrl); diff --git a/web/pgadmin/tools/schema_diff/static/js/components/ResultGridComponent.jsx b/web/pgadmin/tools/schema_diff/static/js/components/ResultGridComponent.jsx index 38d73a929..43e1d8f47 100644 --- a/web/pgadmin/tools/schema_diff/static/js/components/ResultGridComponent.jsx +++ b/web/pgadmin/tools/schema_diff/static/js/components/ResultGridComponent.jsx @@ -432,6 +432,124 @@ function reducer(rows, { type, id, filterParams, gridData }) { } } +function selectHeaderRenderer({selectedRows, setSelectedRows, rootSelection, setRootSelection, allRowIds, selectedRowIds}) { + const Cell = ()=>( + { + if (e.target.checked) { + setRootSelection(true); + setSelectedRows([...allRowIds]); + selectedRowIds([...allRowIds]); + } else { + setRootSelection(false); + setSelectedRows([]); + selectedRowIds([]); + } + } + } + > + ); + Cell.displayName = 'Cell'; + return Cell; +} +function selectFormatter({selectedRows, setSelectedRows, setRootSelection, activeRow, setActiveRow, allRowIds, selectedRowIds, selectedResultRows, deselectResultRows, getStyleClassName}) { + const Cell = ({ row, isCellSelected }) => { + isCellSelected && setActiveRow(row.id); + return ( + + { + setSelectedRows((prev) => { + let tempSelectedRows = [...prev]; + if (!prev.includes(e.target.id)) { + selectedResultRows(row, tempSelectedRows); + tempSelectedRows.length === allRowIds.length && setRootSelection(true); + } else { + deselectResultRows(row, tempSelectedRows); + } + tempSelectedRows = new Set(tempSelectedRows); + selectedRowIds([...tempSelectedRows]); + return [...tempSelectedRows]; + }); + } + } + > + + ); + }; + + Cell.displayName = 'Cell'; + Cell.propTypes = { + row: PropTypes.object, + isCellSelected: PropTypes.bool, + }; + return Cell; +} + +function expandFormatter({activeRow, setActiveRow, filterParams, gridData, selectedRows, dispatch, getStyleClassName}) { + const Cell = ({ row, isCellSelected })=>{ + const hasChildren = row.children !== undefined; + isCellSelected && setActiveRow(row.id); + return ( + <> + {hasChildren && ( + + dispatch({ id: row.id, type: 'toggleSubRow', filterParams: filterParams, gridData: gridData, selectedRows: selectedRows })} + /> + )} +
+ + {!hasChildren && ( + + + {row.label} + + )} +
+ + ); + }; + + Cell.displayName = 'Cell'; + Cell.propTypes = { + row: PropTypes.object, + isCellSelected: PropTypes.bool, + }; + return Cell; +} + +function resultFormatter({selectedRows, activeRow, setActiveRow, getStyleClassName}) { + const Cell = ({ row, isCellSelected })=>{ + isCellSelected && setActiveRow(row.id); + + return ( + + {row.status} + + ); + }; + + Cell.displayName = 'Cell'; + Cell.propTypes = { + row: PropTypes.object, + isCellSelected: PropTypes.bool, + }; + return Cell; +} + export function ResultGridComponent({ gridData, allRowIds, filterParams, selectedRowIds, transId, sourceData, targetData }) { const [rows, dispatch] = useReducer(reducer, [...gridData]); @@ -553,56 +671,15 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte ...SelectColumn, minWidth: 30, width: 30, - headerRenderer() { - return ( - { - if (e.target.checked) { - setRootSelection(true); - setSelectedRows([...allRowIds]); - selectedRowIds([...allRowIds]); - } else { - setRootSelection(false); - setSelectedRows([]); - selectedRowIds([]); - } - } - } - > - ); - }, - formatter({ row, isCellSelected }) { - isCellSelected && setActiveRow(row.id); - return ( - - { - setSelectedRows((prev) => { - let tempSelectedRows = [...prev]; - if (!prev.includes(e.target.id)) { - selectedResultRows(row, tempSelectedRows); - tempSelectedRows.length === allRowIds.length && setRootSelection(true); - } else { - deselectResultRows(row, tempSelectedRows); - } - tempSelectedRows = new Set(tempSelectedRows); - selectedRowIds([...tempSelectedRows]); - return [...tempSelectedRows]; - }); - } - } - > - - ); - } + headerRenderer: selectHeaderRenderer({ + selectedRows, setSelectedRows, rootSelection, + setRootSelection, allRowIds, selectedRowIds + }), + formatter: selectFormatter({ + selectedRows, setSelectedRows, setRootSelection, + activeRow, setActiveRow, allRowIds, selectedRowIds, + selectedResultRows, deselectResultRows, getStyleClassName + }), }, { key: 'label', @@ -615,46 +692,15 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte return 1; }, - formatter({ row, isCellSelected }) { - const hasChildren = row.children !== undefined; - isCellSelected && setActiveRow(row.id); - return ( - <> - {hasChildren && ( - - dispatch({ id: row.id, type: 'toggleSubRow', filterParams: filterParams, gridData: gridData, selectedRows: selectedRows })} - /> - )} -
- - {!hasChildren && ( - - - {row.label} - - )} -
- - ); - } + formatter: expandFormatter({ + activeRow, setActiveRow, filterParams, gridData, + selectedRows, dispatch, getStyleClassName + }), }, { key: 'status', name: 'Comparison Result', - formatter({ row, isCellSelected }) { - isCellSelected && setActiveRow(row.id); - - return ( - - {row.status} - - ); - } + formatter: resultFormatter({selectedRows, activeRow, setActiveRow, getStyleClassName}), }, ]; diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSetToolbar.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSetToolbar.jsx index 606a58cd5..c6371054c 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSetToolbar.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSetToolbar.jsx @@ -51,6 +51,31 @@ const StyledEditor = styled('div')(({theme})=>({ } })); +function ShowDataOutputQueryPopup({query}) { + function suppressEnterKey(e) { + if(e.keyCode == 13) { + e.stopPropagation(); + } + } + + return ( + + { + setEditorPosition(document.getElementById('sql-query'), ele, '.MuiBox-root', 29); + }} onKeyDown={suppressEnterKey}> + + + + ); +} +ShowDataOutputQueryPopup.propTypes = { + query: PropTypes.string, +}; + export function ResultSetToolbar({query,canEdit, totalRowCount}) { const eventBus = useContext(QueryToolEventsContext); const queryToolCtx = useContext(QueryToolContext); @@ -182,28 +207,6 @@ export function ResultSetToolbar({query,canEdit, totalRowCount}) { }, ], queryToolCtx.mainContainerRef); - function suppressEnterKey(e) { - if(e.keyCode == 13) { - e.stopPropagation(); - } - } - - const ShowDataOutputQueryPopup =()=> { - return ( - - { - setEditorPosition(document.getElementById('sql-query'), ele, '.MuiBox-root', 29); - }} onKeyDown={suppressEnterKey}> - - - - ); - }; - return ( <> @@ -234,13 +237,13 @@ export function ResultSetToolbar({query,canEdit, totalRowCount}) { } onClick={showGraphVisualiser} disabled={buttonsDisabled['save-result']} /> - {query && + {query && <> } onClick={()=>{setDataOutputQueryBtn(prev=>!prev);}} onBlur={()=>{setDataOutputQueryBtn(false);}} disabled={!query} id='sql-query'/> - { dataOutputQueryBtn && } + { dataOutputQueryBtn && } }