diff --git a/docs/en_US/keyboard_shortcuts.rst b/docs/en_US/keyboard_shortcuts.rst index 836ccec53..099471e03 100644 --- a/docs/en_US/keyboard_shortcuts.rst +++ b/docs/en_US/keyboard_shortcuts.rst @@ -26,10 +26,6 @@ When using main browser window, the following keyboard shortcuts are available: +----------------------------+-------------------------------------------------------+ | Shift+Alt+d | Delete object | +----------------------------+-------------------------------------------------------+ - | Shift+Ctrl+[ | Dialog tab backward | - +----------------------------+-------------------------------------------------------+ - | Shift+Ctrl+] | Dialog tab forward | - +----------------------------+-------------------------------------------------------+ | Shift+Alt+g | Direct debugging | +----------------------------+-------------------------------------------------------+ | Shift+Alt+e | Edit object properties | @@ -59,23 +55,6 @@ When using main browser window, the following keyboard shortcuts are available: | Shift+Alt+v | View data | +----------------------------+-------------------------------------------------------+ -Dialog Tabs -*********** - -Use the shortcuts below to navigate the tabsets on dialogs: - -.. table:: - :class: longtable - :widths: 2 3 - - +----------------------------+-------------------------------------------------------+ - | Shortcut for all platforms | Function | - +============================+=======================================================+ - | Control+Shift+[ | Dialog tab backward | - +----------------------------+-------------------------------------------------------+ - | Control+Shift+] | Dialog tab forward | - +----------------------------+-------------------------------------------------------+ - Property Grid Controls ********************** diff --git a/web/package.json b/web/package.json index 4f16f385d..65a7ec005 100644 --- a/web/package.json +++ b/web/package.json @@ -108,6 +108,7 @@ "dagre": "^0.8.4", "date-fns": "^2.24.0", "diff-arrays-of-objects": "^1.1.8", + "hotkeys-js": "^3.13.3", "html-to-image": "^1.11.11", "immutability-helper": "^3.0.0", "insert-if": "^1.1.0", @@ -119,7 +120,6 @@ "ml-matrix": "^6.5.0", "moment": "^2.29.4", "moment-timezone": "^0.5.34", - "mousetrap": "^1.6.3", "notificar": "^1.0.1", "notistack": "^1.0.10", "path-fx": "^2.0.0", diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py index c4d56779c..b6ed871a4 100644 --- a/web/pgadmin/browser/register_browser_preferences.py +++ b/web/pgadmin/browser/register_browser_preferences.py @@ -352,36 +352,6 @@ def register_browser_preferences(self): fields=fields ) - self.preference.register( - 'keyboard_shortcuts', - 'dialog_tab_forward', - gettext('Dialog tab forward'), - 'keyboardshortcut', - { - 'alt': False, - 'shift': True, - 'control': True, - 'key': {'key_code': 93, 'char': ']'} - }, - category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, - fields=fields - ) - - self.preference.register( - 'keyboard_shortcuts', - 'dialog_tab_backward', - gettext('Dialog tab backward'), - 'keyboardshortcut', - { - 'alt': False, - 'shift': True, - 'control': True, - 'key': {'key_code': 91, 'char': '['} - }, - category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, - fields=fields - ) - self.preference.register( 'keyboard_shortcuts', 'sub_menu_refresh', diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js index 33b2434b5..6fdae505a 100644 --- a/web/pgadmin/browser/static/js/keyboard.js +++ b/web/pgadmin/browser/static/js/keyboard.js @@ -9,7 +9,7 @@ import _ from 'lodash'; import pgAdmin from '../../../static/js/pgadmin'; -import Mousetrap from 'mousetrap'; +import hotkeys from 'hotkeys-js'; import * as commonUtils from '../../../static/js/utils'; import gettext from 'sources/gettext'; import pgWindow from 'sources/window'; @@ -19,30 +19,39 @@ const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {}; pgBrowser.keyboardNavigation = pgBrowser.keyboardNavigation || {}; +hotkeys.filter = function () { + return true; +}; + _.extend(pgBrowser.keyboardNavigation, { + iframeEventsChannel:new BroadcastChannel('iframe-events'), init: function() { + this.iframeEventsChannel.onmessage = (ev) =>{ + hotkeys.trigger(ev.data); + }; + usePreferences.subscribe((prefStore)=>{ - Mousetrap.reset(); + hotkeys.unbind(); if (prefStore.version > 0) { this.keyboardShortcut = { ...(prefStore.getPreferences('browser', 'main_menu_file')?.value) && {'file_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_file')?.value)}, ...(prefStore.getPreferences('browser', 'main_menu_object')?.value) && {'object_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_object')?.value)}, ...(prefStore.getPreferences('browser', 'main_menu_tools')?.value) && {'tools_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_tools')?.value)}, ...(prefStore.getPreferences('browser', 'main_menu_help')?.value) && {'help_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_help')?.value)}, - 'left_tree_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'browser_tree').value), - 'tabbed_panel_backward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_backward').value), - 'tabbed_panel_forward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_forward').value), - 'sub_menu_query_tool': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_query_tool').value), - 'sub_menu_view_data': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_view_data').value), - 'sub_menu_search_objects': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_search_objects').value), - 'sub_menu_properties': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_properties').value), - 'sub_menu_create': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_create').value), - 'sub_menu_delete': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_delete').value), - 'sub_menu_refresh': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_refresh').value), - 'context_menu': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'context_menu').value), - 'direct_debugging': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'direct_debugging').value), - 'add_grid_row': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'add_grid_row').value), - 'open_quick_search': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'open_quick_search').value), + 'left_tree_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'browser_tree')?.value), + 'tabbed_panel_backward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_backward')?.value), + 'tabbed_panel_forward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_forward')?.value), + 'sub_menu_query_tool': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_query_tool')?.value), + 'sub_menu_view_data': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_view_data')?.value), + 'sub_menu_search_objects': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_search_objects')?.value), + 'sub_menu_properties': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_properties')?.value), + 'sub_menu_create': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_create')?.value), + 'sub_menu_delete': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_delete')?.value), + 'sub_menu_refresh': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_refresh')?.value), + 'context_menu': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'context_menu')?.value), + 'direct_debugging': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'direct_debugging')?.value), + 'add_grid_row': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'add_grid_row')?.value), + 'open_quick_search': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'open_quick_search')?.value), }; this.shortcutMethods = { @@ -65,43 +74,58 @@ _.extend(pgBrowser.keyboardNavigation, { 'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row 'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row }; - this.bindShortcuts(); + this.shortcutsString=Object.values(this.shortcutMethods).map(i=>i.shortcuts).join(','); + // Checks if the tab is iframe or not, if iframe then calls the function 'setupIframeEventsBroadcast' + if (window.self != window.top) { + this.setupIframeEventsBroadcast(); + } else { + this.bindShortcuts(); + } } }); }, + //Sends the pressed keyboard shortcut from iframe to parent + triggerIframeEventsBroadcast: function(event,checkShortcuts=false){ + const shortcut = { + alt:event?.altKey, + shift:event?.shiftKey, + control:event?.ctrlKey, + key:{ + char:event?.key + } + }; + const currShortcutString = commonUtils.parseShortcutValue(shortcut); + if (checkShortcuts && !this.shortcutsString.split(',').includes(currShortcutString)){ + return; + } + this.iframeEventsChannel.postMessage(currShortcutString); + }, + //listens to keyboard events and triggers the 'triggerIframeEventsBroadcast' for shortcuts + setupIframeEventsBroadcast:function() { + const self=this; + hotkeys(self.shortcutsString,(event)=>{ + this.triggerIframeEventsBroadcast(event); + }); + }, bindShortcuts: function() { const self = this; _.each(self.shortcutMethods, (keyCombo, callback) => { - self._bindWithMousetrap(keyCombo.shortcuts, self[callback], keyCombo.bindElem); + self._bindWithHotkeys(keyCombo.shortcuts, self[callback]); }); }, - _bindWithMousetrap: function(shortcuts, callback, bindElem) { + _bindWithHotkeys: function(shortcuts, callback) { const self = this; - shortcuts ?? Mousetrap.unbind(shortcuts); - if (bindElem) { - const elem = document.querySelector(bindElem); - Mousetrap(elem).bind(shortcuts, function() { - callback.apply(this, arguments); - }.bind(elem)); - } else { - Mousetrap.bind(shortcuts, function () { - callback.apply(self, arguments); - }); - } - }, - unbindShortcuts: function() { - // Reset previous events on each instance - const self = this; - _.each(self.mousetrapInstances, (obj) => { - obj['instance'].reset(); + hotkeys(shortcuts.toString(), function (event, combo) { + if(!combo){ + combo = this; + } + callback.apply(self, [event, combo]); }); - // Clear already processed events - self.mousetrapInstances = []; }, bindMainMenu: function(event, combo) { const shortcut_obj = this.keyboardShortcut; let menuLabel = null; - switch (combo) { + switch (combo.key) { case shortcut_obj.file_shortcut: menuLabel = gettext('File'); break; @@ -123,35 +147,57 @@ _.extend(pgBrowser.keyboardNavigation, { } }, bindRightPanel: function(event, combo) { - let allPanels = pgAdmin.Browser.docker.findPanels(); - let activePanel = 0; - let nextPanel = allPanels.length - 1; - let prevPanel = 1; - let activePanelId = 0; - let activePanelFlag = false; + const self = this; + let dockLayoutTabs = document.activeElement?.closest('.dock-layout')?.querySelectorAll('.dock-tab-btn'); let shortcut_obj = this.keyboardShortcut; - - _.each(pgAdmin.Browser.docker.findPanels(), (panel, index) => { - if (panel.isVisible() && !activePanelFlag && panel._type !== 'browser') { - activePanelId = index; - activePanelFlag = true; + //if the focus is on the tab button + if (document.activeElement.closest('.dock-tab-btn')) { + let currDockTab = document.activeElement?.closest('.dock-tab-btn'); + if(dockLayoutTabs?.length > 1 && currDockTab) { + for(let i=0; i 0) ? activePanelId - 1 : prevPanel; - else if (combo === shortcut_obj.tabbed_panel_forward) activePanel = (activePanelId < nextPanel) ? activePanelId + 1 : nextPanel; - - pgAdmin.Browser.docker.findPanels()[activePanel].focus(); - setTimeout(() => { - if (document.activeElement instanceof HTMLIFrameElement) { - document.activeElement.blur(); + //if the tab is a iframe or the focus is within the content of tab + } else if (document.activeElement.nodeName === 'IFRAME' || document.activeElement.closest('.dock-tabpane.dock-tabpane-active')?.id) { + let activeTabId = ''; + //if the tab is a iframe + if (document.activeElement.nodeName === 'IFRAME'){ + dockLayoutTabs = document.activeElement?.closest('#root')?.querySelectorAll('.dock-tab-btn'); + activeTabId = document.activeElement?.id; + //if the focus is within the content of tab + } else if (document.activeElement.closest('.dock-tabpane.dock-tabpane-active')?.id){ + activeTabId = document.activeElement.closest('.dock-tabpane.dock-tabpane-active')?.id; } - }, 1000); + if(dockLayoutTabs?.length > 1 && activeTabId) { + for(let i=0; i diff --git a/web/pgadmin/tools/psql/static/js/psql_module.js b/web/pgadmin/tools/psql/static/js/psql_module.js index 0086cb2a1..ad32b5e37 100644 --- a/web/pgadmin/tools/psql/static/js/psql_module.js +++ b/web/pgadmin/tools/psql/static/js/psql_module.js @@ -17,9 +17,8 @@ import pgWindow from 'sources/window'; import { copyToClipboard } from '../../../../static/js/clipboard'; import {generateTitle, refresh_db_node} from 'tools/sqleditor/static/js/sqleditor_title'; import { BROWSER_PANELS } from '../../../../browser/static/js/constants'; -import usePreferences from '../../../../preferences/static/js/store'; - - +import usePreferences,{ listenPreferenceBroadcast } from '../../../../preferences/static/js/store'; +import 'pgadmin.browser.keyboard'; export function setPanelTitle(psqlToolPanel, panelTitle) { psqlToolPanel.title(''+_.escape(panelTitle)+''); } @@ -257,10 +256,12 @@ export function initialize(gettext, url_for, _, pgAdmin, csrfToken, Browser) { navigator.permissions.query({ name: 'clipboard-write' }).then(function(result) { if(result.state === 'granted' || result.state === 'prompt') { copyToClipboard(selected_text); - } else{ + } else { pgAdmin.Browser.notifier.alert(gettext('Clipboard write permission required'), gettext('To copy data from PSQL terminal, Clipboard write permission required.')); } }); + } else { + self.pgAdmin.Browser.keyboardNavigation.triggerIframeEventsBroadcast(e,true); } return !(e.ctrlKey && platform == 'win32'); @@ -297,6 +298,44 @@ export function initialize(gettext, url_for, _, pgAdmin, csrfToken, Browser) { refresh_db_node(message, dbNode); } }, + psql_mount: async function(params){ + self.pgAdmin.Browser.keyboardNavigation.init(); + await listenPreferenceBroadcast(); + const term = self.pgAdmin.Browser.psql.psql_terminal(); + /* Addon for fitAddon, webLinkAddon, SearchAddon */ + const fitAddon = self.pgAdmin.Browser.psql.psql_Addon(term); + /* Update the theme for terminal as per pgAdmin 4 theme. */ + self.pgAdmin.Browser.psql.set_theme(term); + /* Open the terminal */ + term.open(document.getElementById('psql-terminal')); + /* Socket */ + const socket = self.pgAdmin.Browser.psql.psql_socket(); + self.pgAdmin.Browser.psql.psql_socket_io(socket, params.is_enable, params.sid, params.db, params.server_type, fitAddon, term, params.role); + + self.pgAdmin.Browser.psql.psql_terminal_io(term, socket, params.platform); + self.pgAdmin.Browser.psql.check_db_name_change(params.db, params.o_db_name); + /* Set terminal size */ + setTimeout(function(){ + socket.emit('resize', {'cols': term.cols, 'rows': term.rows}); + }, 1000); + + /* Resize the terminal */ + function fitToscreen(){ + fitAddon.fit(); + socket.emit('resize', {'cols': term.cols, 'rows': term.rows}); + } + + function debounce(func, wait_ms) { + let timeout; + return function(...args) { + const context = this; + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(context, args), wait_ms); + }; + } + + window.onresize = debounce(fitToscreen, 25); + } }; return pgBrowser.psql; diff --git a/web/pgadmin/tools/psql/templates/editor_template.html b/web/pgadmin/tools/psql/templates/editor_template.html index 9edc9b784..6dc3cd396 100644 --- a/web/pgadmin/tools/psql/templates/editor_template.html +++ b/web/pgadmin/tools/psql/templates/editor_template.html @@ -19,40 +19,16 @@ require( ['sources/generated/psql_tool'], function(pgBrowser) { - const term = self.pgAdmin.Browser.psql.psql_terminal(); - - const fitAddon = self.pgAdmin.Browser.psql.psql_Addon(term); - - self.pgAdmin.Browser.psql.set_theme(term); - - term.open(document.getElementById('psql-terminal')); - - const socket = self.pgAdmin.Browser.psql.psql_socket(); - self.pgAdmin.Browser.psql.psql_socket_io(socket, '{{is_enable}}', '{{sid}}', '{{db|safe}}', '{{server_type}}', fitAddon, term, '{{role|safe}}'); - self.pgAdmin.Browser.psql.psql_terminal_io(term, socket, '{{platform}}'); - self.pgAdmin.Browser.psql.check_db_name_change('{{db|safe}}', '{{o_db_name|safe}}'); - - setTimeout(function(){ - socket.emit("resize", {"cols": term.cols, "rows": term.rows}) - }, 1000); - - - function fitToscreen(){ - fitAddon.fit() - socket.emit("resize", {"cols": term.cols, "rows": term.rows}) - } - - function debounce(func, wait_ms) { - let timeout - return function(...args) { - const context = this - clearTimeout(timeout) - timeout = setTimeout(() => func.apply(context, args), wait_ms) - } - } - - const wait_ms = 25; - window.onresize = debounce(fitToscreen, wait_ms); + + self.pgAdmin.Browser.psql.psql_mount({ + "is_enable": '{{is_enable}}', + "sid": '{{sid}}', + "db":'{{db|safe}}', + "server_type": '{{server_type}}', + "role": '{{role|safe}}', + "platform": '{{platform}}', + "o_db_name": '{{o_db_name|safe}}' + }) }); {% endblock %} diff --git a/web/pgadmin/tools/schema_diff/static/js/SchemaDiffModule.js b/web/pgadmin/tools/schema_diff/static/js/SchemaDiffModule.js index 0847b607e..92568d040 100644 --- a/web/pgadmin/tools/schema_diff/static/js/SchemaDiffModule.js +++ b/web/pgadmin/tools/schema_diff/static/js/SchemaDiffModule.js @@ -24,7 +24,6 @@ import usePreferences, { listenPreferenceBroadcast } from '../../../../preferenc import pgAdmin from 'sources/pgadmin'; import { PgAdminContext } from '../../../../static/js/BrowserComponent'; - export default class SchemaDiff { static instance; @@ -101,6 +100,7 @@ export default class SchemaDiff { } async load(container, trans_id) { + pgAdmin.Browser.keyboardNavigation.init(); await listenPreferenceBroadcast(); ReactDOM.render( diff --git a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js index 469d4ae7c..3dacfe54d 100644 --- a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js +++ b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js @@ -226,6 +226,7 @@ export default class SQLEditor { let selectedNodeInfo = pgWindow.pgAdmin.Browser.tree.getTreeNodeHierarchy( pgWindow.pgAdmin.Browser.tree.selected() ); + pgAdmin.Browser.keyboardNavigation.init(); await listenPreferenceBroadcast(); ReactDOM.render( diff --git a/web/yarn.lock b/web/yarn.lock index e32d5c91a..b9da3dd08 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -9160,6 +9160,13 @@ __metadata: languageName: node linkType: hard +"hotkeys-js@npm:^3.13.3": + version: 3.13.3 + resolution: "hotkeys-js@npm:3.13.3" + checksum: fdd2b088671dbf2b2434ba40a4d604bf3f5cbe2f121233f8474f493a557d8a1cced0f1cb0c812b62e34ee9c2ad4ec6c4fd25035aa836ebe12da5dfa11f5afc2d + languageName: node + linkType: hard + "html-dom-parser@npm:5.0.4": version: 5.0.4 resolution: "html-dom-parser@npm:5.0.4" @@ -11898,13 +11905,6 @@ __metadata: languageName: node linkType: hard -"mousetrap@npm:^1.6.3": - version: 1.6.5 - resolution: "mousetrap@npm:1.6.5" - checksum: 1ce36af5ac57e1fab687e3da004cc18914275f8ceef33f16d01110edc5126a5dbaace578b1e61179f93a506e71a88fe886f305db537a3673cf9f73affd1dffa6 - languageName: node - linkType: hard - "mozjpeg@npm:^8.0.0": version: 8.0.0 resolution: "mozjpeg@npm:8.0.0" @@ -14867,6 +14867,7 @@ __metadata: eslint-plugin-react: ^7.33.2 eslint-plugin-react-hooks: ^4.3.0 exports-loader: ^4.0.0 + hotkeys-js: ^3.13.3 html-react-parser: ^5.0.6 html-to-image: ^1.11.11 image-minimizer-webpack-plugin: ^3.8.2 @@ -14889,7 +14890,6 @@ __metadata: ml-matrix: ^6.5.0 moment: ^2.29.4 moment-timezone: ^0.5.34 - mousetrap: ^1.6.3 notificar: ^1.0.1 notistack: ^1.0.10 path-fx: ^2.0.0