From 760e38293c64ea07c17deaec5e6c2ffe73863e2e Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Mon, 8 Jul 2024 19:24:29 +0530 Subject: [PATCH] Fix issues found during Electron testing. #7494 Fix application crash when using users dialog. #7607 --- runtime/src/js/menu.js | 6 +- runtime/src/js/misc.js | 13 ++- runtime/src/js/pgadmin.js | 15 ++-- .../browser/static/js/MainMenuFactory.js | 2 +- .../static/js/FileManagerModule.jsx | 2 +- web/pgadmin/static/js/custom_hooks.js | 45 +++++++++++ .../js/erd_tool/components/BeforeUnload.jsx | 21 +++++ .../static/js/erd_tool/components/ERDTool.jsx | 80 ++++++++----------- .../js/components/QueryToolComponent.jsx | 52 +++--------- .../static/js/UserManagementDialog.jsx | 6 +- 10 files changed, 135 insertions(+), 107 deletions(-) create mode 100644 web/pgadmin/tools/erd/static/js/erd_tool/components/BeforeUnload.jsx diff --git a/runtime/src/js/menu.js b/runtime/src/js/menu.js index 6a3807b24..0be66fa20 100644 --- a/runtime/src/js/menu.js +++ b/runtime/src/js/menu.js @@ -7,7 +7,7 @@ // ////////////////////////////////////////////////////////////// -import { app, Menu, ipcMain } from 'electron'; +import { app, Menu, ipcMain, BrowserWindow } from 'electron'; const isMac = process.platform == 'darwin'; let mainMenu; @@ -72,8 +72,8 @@ function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) { { label: 'View', submenu: [ - { label: 'Reload', click: ()=>pgAdminMainScreen.webContents.reload()}, - { label: 'Toggle Developer Tools', click: ()=>pgAdminMainScreen.webContents.openDevTools({ mode: 'bottom' })}, + { label: 'Reload', click: ()=>BrowserWindow.getFocusedWindow().webContents.reload()}, + { label: 'Toggle Developer Tools', click: ()=>BrowserWindow.getFocusedWindow().webContents.openDevTools({ mode: 'bottom' })}, { type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, diff --git a/runtime/src/js/misc.js b/runtime/src/js/misc.js index 5ae5a42ae..c277ed9c4 100644 --- a/runtime/src/js/misc.js +++ b/runtime/src/js/misc.js @@ -11,7 +11,7 @@ import fs from 'fs'; import path from 'path'; import net from 'net'; import {platform} from 'os'; -import { app } from 'electron'; +import { app, session } from 'electron'; let pgadminServerProcess = null; @@ -141,7 +141,7 @@ export const getServerLogFile = () => { // This function is used to kill the server process, remove the log files // and quit the application. -export const cleanupAndQuitApp = (pgAdminWindowObject) => { +export const cleanupAndQuitApp = () => { // Remove the server log file on exit removeLogFile(); @@ -155,10 +155,7 @@ export const cleanupAndQuitApp = (pgAdminWindowObject) => { } } - if (pgAdminWindowObject != null) { - const ses = pgAdminWindowObject.webContents.session; - ses.clearStorageData({ - storages: ['cookies'], - }); - } + session.defaultSession.clearStorageData({ + storages: ['cookies', 'localstorage'], + }); }; diff --git a/runtime/src/js/pgadmin.js b/runtime/src/js/pgadmin.js index 567357d45..68f26b43e 100644 --- a/runtime/src/js/pgadmin.js +++ b/runtime/src/js/pgadmin.js @@ -58,7 +58,7 @@ function openConfigure() { const win = new BrowserWindow({ show: false, width: 600, - height: 600, + height: 620, position: 'center', resizable: false, icon: '../../assets/pgAdmin4.png', @@ -81,8 +81,8 @@ function showErrorDialog(intervalID) { new BrowserWindow({ 'frame': true, - 'width': 790, - 'height': 430, + 'width': 800, + 'height': 450, 'position': 'center', 'resizable': false, 'focus': true, @@ -251,8 +251,8 @@ function launchPgAdminWindow() { 'view_logs': ()=>{ const win = new BrowserWindow({ show: false, - width: 790, - height: 425, + width: 800, + height: 460, position: 'center', resizable: false, icon: '../../assets/pgAdmin4.png', @@ -302,9 +302,12 @@ function launchPgAdminWindow() { } }); + pgAdminMainScreen.on('closed', ()=>{ + misc.cleanupAndQuitApp(); + }); + pgAdminMainScreen.on('close', () => { configStore.set('bounds', pgAdminMainScreen.getBounds()); - misc.cleanupAndQuitApp(pgAdminMainScreen); pgAdminMainScreen.removeAllListeners('close'); pgAdminMainScreen.close(); }); diff --git a/web/pgadmin/browser/static/js/MainMenuFactory.js b/web/pgadmin/browser/static/js/MainMenuFactory.js index 545abeaab..80f697f7f 100644 --- a/web/pgadmin/browser/static/js/MainMenuFactory.js +++ b/web/pgadmin/browser/static/js/MainMenuFactory.js @@ -61,7 +61,7 @@ export default class MainMenuFactory { return { ...sm.serialize(), submenu: sm.getMenuItems()?.map((smsm)=>{ - MainMenuFactory.electronCallbacks[`${smName}_${smsm.name}`] = sm.callback; + MainMenuFactory.electronCallbacks[`${smName}_${smsm.name}`] = smsm.callback; return { ...smsm.serialize(), }; diff --git a/web/pgadmin/misc/file_manager/static/js/FileManagerModule.jsx b/web/pgadmin/misc/file_manager/static/js/FileManagerModule.jsx index 84c733aff..c061ef8ec 100644 --- a/web/pgadmin/misc/file_manager/static/js/FileManagerModule.jsx +++ b/web/pgadmin/misc/file_manager/static/js/FileManagerModule.jsx @@ -107,7 +107,7 @@ export default class FileManagerModule { if(res.canceled) { onCancel?.(); } else { - onOK?.(res.filePaths[0]); + onOK?.(res.filePath ? res.filePath : res.filePaths[0]); } } diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js index 93bfd03d8..b0f579e70 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -9,6 +9,7 @@ import React, {useRef, useEffect, useState, useCallback, useLayoutEffect} from 'react'; import moment from 'moment'; import { isMac } from './keyboard_shortcuts'; +import { getBrowser } from './utils'; /* React hook for setInterval */ export function useInterval(callback, delay) { @@ -209,3 +210,47 @@ export function useWindowSize() { export function useForceUpdate() { return React.useReducer(() => ({}), {})[1]; } + +export function useBeforeUnload({enabled, isNewTab, beforeClose, closePanel }) { + const onBeforeUnload = useCallback((e)=>{ + e.preventDefault(); + e.returnValue = 'prevent'; + }, []); + + const onBeforeUnloadElectron = useCallback((e)=>{ + e.preventDefault(); + e.returnValue = 'prevent'; + beforeClose?.(); + }, []); + + useEffect(()=>{ + if(getBrowser().name == 'Electron') { + window.addEventListener('beforeunload', onBeforeUnloadElectron); + } else { + if(enabled){ + window.addEventListener('beforeunload', onBeforeUnload); + } else { + window.removeEventListener('beforeunload', onBeforeUnload); + } + } + + return () => { + window.removeEventListener('beforeunload', onBeforeUnloadElectron); + window.removeEventListener('beforeunload', onBeforeUnload); + }; + }, [enabled]); + + + function forceClose() { + if(getBrowser().name == 'Electron' && isNewTab) { + window.removeEventListener('beforeunload', onBeforeUnloadElectron); + // somehow window.close was not working may becuase the removeEventListener + // was not completely executed. Add timeout. + setTimeout(()=>window.close(), 50); + } else { + closePanel?.(); + } + } + + return {forceClose}; +} diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/components/BeforeUnload.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/components/BeforeUnload.jsx new file mode 100644 index 000000000..e16f6d641 --- /dev/null +++ b/web/pgadmin/tools/erd/static/js/erd_tool/components/BeforeUnload.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useBeforeUnload } from '../../../../../../static/js/custom_hooks'; + +export default function BeforeUnload({enabled, isNewTab, beforeClose, closePanel, getForceClose}) { + const {forceClose} = useBeforeUnload( + {enabled, isNewTab, beforeClose, closePanel} + ); + + getForceClose(forceClose); + + return <>; +} + +BeforeUnload.propTypes = { + enabled: PropTypes.bool, + isNewTab: PropTypes.bool, + beforeClose: PropTypes.func, + closePanel: PropTypes.func, + getForceClose: PropTypes.func, +}; 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 777f40719..64b9d92bb 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 @@ -35,6 +35,7 @@ import { LAYOUT_EVENTS } from '../../../../../../static/js/helpers/Layout'; import usePreferences from '../../../../../../preferences/static/js/store'; import pgAdmin from 'sources/pgadmin'; import { styled } from '@mui/material/styles'; +import BeforeUnload from './BeforeUnload'; /* Custom react-diagram action for keyboard events */ export class KeyboardShortcutAction extends Action { @@ -144,12 +145,13 @@ export default class ERDTool extends React.Component { _.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSQLClick', 'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick', 'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle', - 'onChangeColors', 'onDropNode', 'onBeforeUnload', 'onNotationChange', + 'onChangeColors', 'onDropNode', 'onNotationChange', 'closePanel' ]); this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram); this.diagram.zoomIn = this.diagram.zoomIn.bind(this.diagram); this.diagram.zoomOut = this.diagram.zoomOut.bind(this.diagram); + this.forceClose = this.closePanel; } registerModelEvents() { @@ -315,14 +317,7 @@ export default class ERDTool extends React.Component { this.props.panelDocker.eventBus.registerListener(LAYOUT_EVENTS.CLOSING, (id)=>{ if(this.props.panelId == id) { - window.removeEventListener('beforeunload', this.onBeforeUnload); - if(this.state.dirty) { - this.closeOnSave = false; - this.confirmBeforeClose(); - return false; - } - this.closePanel(); - return true; + this.confirmBeforeClose(); } }); @@ -354,45 +349,34 @@ export default class ERDTool extends React.Component { if(this.props.params.gen) { await this.loadTablesData(); } - - if(this.state.is_close_tab_warning) { - window.addEventListener('beforeunload', this.onBeforeUnload); - } else { - window.removeEventListener('beforeunload', this.onBeforeUnload); - } - } - - componentWillUnmount() { - window.removeEventListener('beforeunload', this.onBeforeUnload); } componentDidUpdate() { if(this.state.dirty) { this.setTitle(this.state.current_file, true); } - // Add beforeunload event if "Confirm on close or refresh" option is enabled in the preferences. - if(this.state.is_close_tab_warning){ - window.addEventListener('beforeunload', this.onBeforeUnload); - } else { - window.removeEventListener('beforeunload', this.onBeforeUnload); - } } confirmBeforeClose() { let bodyObj = this; - this.context.showModal(gettext('Save changes?'), (closeModal)=>( - { - bodyObj.closePanel(); - }} - onSave={()=>{ - bodyObj.onSaveDiagram(false, true); - }} - /> - )); - return false; + if(this.state.dirty) { + this.closeOnSave = false; + this.context.showModal(gettext('Save changes?'), (closeModal)=>( + { + bodyObj.forceClose(); + }} + onSave={()=>{ + bodyObj.onSaveDiagram(false, true); + }} + /> + )); + return false; + } else { + this.forceClose(); + } } closePanel() { @@ -463,15 +447,6 @@ export default class ERDTool extends React.Component { } } - onBeforeUnload(e) { - if(this.state.dirty) { - e.preventDefault(); - e.returnValue = 'prevent'; - } else { - delete e['returnValue']; - } - } - onDropNode(e) { let nodeDropData = JSON.parse(e.dataTransfer.getData('text')); if(nodeDropData.objUrl && nodeDropData.nodeType === 'table') { @@ -627,7 +602,7 @@ export default class ERDTool extends React.Component { this.setTitle(fileName); this.setLoading(null); if(this.closeOnSave) { - this.closePanel(); + this.forceClose(); } }).catch((err)=>{ this.setLoading(null); @@ -910,6 +885,15 @@ export default class ERDTool extends React.Component { return ( + { + this.confirmBeforeClose(); + }} + closePanel={this.closePanel} + getForceClose={(fn)=>this.forceClose = fn} + /> { - e.preventDefault(); - e.returnValue = 'prevent'; - eventBus.current.fireEvent(QUERY_TOOL_EVENTS.WARN_SAVE_DATA_CLOSE); - }; + const {forceClose} = useBeforeUnload({ + enabled: qtState.preferences.browser.confirm_on_refresh_close, + isNewTab: qtState.is_new_tab, + beforeClose: ()=>{ + eventBus.current.fireEvent(QUERY_TOOL_EVENTS.WARN_SAVE_DATA_CLOSE); + }, + closePanel: ()=>{ + qtPanelDocker.close(qtPanelId, true); + } + }); useEffect(()=>{ getSQLScript(); @@ -424,19 +424,11 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN }); eventBus.current.registerListener(QUERY_TOOL_EVENTS.FORCE_CLOSE_PANEL, ()=>{ - if(getBrowser().name == 'Electron' && qtState.is_new_tab) { - window.removeEventListener('beforeunload', onBeforeUnloadElectron); - // somehow window.close was not working may becuase the removeEventListener - // was not completely executed. Add timeout. - setTimeout(()=>window.close(), 50); - } else { - qtPanelDocker.close(qtPanelId, true); - } + forceClose(); }); qtPanelDocker.eventBus.registerListener(LAYOUT_EVENTS.CLOSING, (id)=>{ if(qtPanelId == id) { - window.removeEventListener('beforeunload', onBeforeUnload); eventBus.current.fireEvent(QUERY_TOOL_EVENTS.WARN_SAVE_DATA_CLOSE); } }); @@ -643,24 +635,6 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN }); }; - useEffect(()=> { - // Add beforeunload event if "Confirm on close or refresh" option is enabled in the preferences. - if(getBrowser().name == 'Electron') { - window.addEventListener('beforeunload', onBeforeUnloadElectron); - } else { - if(qtState.preferences.browser.confirm_on_refresh_close){ - window.addEventListener('beforeunload', onBeforeUnload); - } else { - window.removeEventListener('beforeunload', onBeforeUnload); - } - } - - return () => { - window.removeEventListener('beforeunload', onBeforeUnloadElectron); - window.removeEventListener('beforeunload', onBeforeUnload); - }; - }, [qtState.preferences.browser]); - const updateQueryToolConnection = (connectionData, isNew=false)=>{ let currSelectedConn = _.find(qtState.connection_list, (c)=>c.is_selected); let currConnected = qtState.connected; diff --git a/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx b/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx index bd0330b1f..181703f62 100644 --- a/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx +++ b/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx @@ -317,7 +317,7 @@ function UserManagementDialog({onClose}) { const [roles, setRoles] = React.useState([]); const api = getApiInstance(); - React.useEffect(async ()=>{ + const fetchData = async ()=>{ try { api.get(url_for('user_management.auth_sources')) .then(res=>{ @@ -337,6 +337,10 @@ function UserManagementDialog({onClose}) { } catch (error) { pgAdmin.Browser.notifier.error(parseApiError(error)); } + }; + + React.useEffect(()=>{ + fetchData(); }, []); const onSaveClick = (_isNew, changeData)=>{