From 5330121b36e0e2157cc62aaaaa3436dc0be4c53e Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Wed, 29 Mar 2023 21:45:09 +0530 Subject: [PATCH] On pressing Ctrl+C on a tree object, copy the fully qualified name to clipboard. #5854 --- web/pgadmin/browser/static/js/browser.js | 49 +++++++++++++------ .../js/components/PgTree/FileTreeX/index.tsx | 2 +- .../PgTree/services/keyboardHotkeys.ts | 17 +++++-- web/pgadmin/static/js/tree/tree.js | 10 ++++ 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index 33a12ea59..a01e713d1 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -17,6 +17,7 @@ import { pgHandleItemError } from '../../../static/js/utils'; import { Search } from './quick_search/trigger_search'; import { send_heartbeat, stop_heartbeat } from './heartbeat'; import getApiInstance from '../../../static/js/api_instance'; +import { copyToClipboard } from '../../../static/js/clipboard'; define('pgadmin.browser', [ 'sources/gettext', 'sources/url_for', 'jquery', @@ -70,10 +71,31 @@ define('pgadmin.browser', [ let initializeBrowserTree = pgAdmin.Browser.initializeBrowserTree = function(b) { + const draggableTypes = [ + 'collation domain domain_constraints fts_configuration fts_dictionary fts_parser fts_template synonym table partition type sequence package view mview foreign_table edbvar', + 'schema column database cast event_trigger extension language foreign_data_wrapper foreign_server user_mapping compound_trigger index index_constraint primary_key unique_constraint check_constraint exclusion_constraint foreign_key rule', + 'trigger trigger_function', + 'edbfunc function edbproc procedure' + ]; InitTree.initBrowserTree(b).then(() => { + const getQualifiedName = (data, item)=>{ + if(draggableTypes[0].includes(data._type)) { + return pgadminUtils.fully_qualify(b, data, item); + } else if(draggableTypes[1].includes(data._type)) { + return pgadminUtils.quote_ident(data._label); + } else if(draggableTypes[3].includes(data._type)) { + let newData = {...data}; + let parsedFunc = pgadminUtils.parseFuncParams(newData._label); + newData._label = parsedFunc.func_name; + return pgadminUtils.fully_qualify(b, newData, item); + } else { + return data._label; + } + }; + b.tree.registerDraggableType({ - 'collation domain domain_constraints fts_configuration fts_dictionary fts_parser fts_template synonym table partition type sequence package view mview foreign_table edbvar' : (data, item, treeNodeInfo)=>{ - let text = pgadminUtils.fully_qualify(b, data, item); + [draggableTypes[0]] : (data, item, treeNodeInfo)=>{ + let text = getQualifiedName(data, item); return { text: text, objUrl: generateNodeUrl.call(pgBrowser.Nodes[data._type], treeNodeInfo, 'properties', data, true), @@ -84,22 +106,17 @@ define('pgadmin.browser', [ }, }; }, - 'schema column database cast event_trigger extension language foreign_data_wrapper foreign_server user_mapping compound_trigger index index_constraint primary_key unique_constraint check_constraint exclusion_constraint foreign_key rule' : (data)=>{ - return pgadminUtils.quote_ident(data._label); + [draggableTypes[1]] : (data)=>{ + return getQualifiedName(data); }, - 'trigger trigger_function' : (data)=>{ - return data._label; + [draggableTypes[2]] : (data)=>{ + return getQualifiedName(data); }, - 'edbfunc function edbproc procedure' : (data, item)=>{ - let newData = {...data}, - parsedFunc = null, - dropVal = '', + [draggableTypes[3]] : (data, item)=>{ + let parsedFunc = pgadminUtils.parseFuncParams(data._label), + dropVal = getQualifiedName(data, item), curPos = {from: 0, to: 0}; - parsedFunc = pgadminUtils.parseFuncParams(newData._label); - newData._label = parsedFunc.func_name; - dropVal = pgadminUtils.fully_qualify(b, newData, item); - if(parsedFunc.params.length > 0) { dropVal = dropVal + '('; curPos.from = dropVal.length; @@ -122,6 +139,10 @@ define('pgadmin.browser', [ }; }, }); + + b.tree.onNodeCopy((data, item)=>{ + copyToClipboard(getQualifiedName(data, item)); + }); }, () => {console.warn('Tree Load Error');}); }; diff --git a/web/pgadmin/static/js/components/PgTree/FileTreeX/index.tsx b/web/pgadmin/static/js/components/PgTree/FileTreeX/index.tsx index a90755bfd..e6ef069cc 100644 --- a/web/pgadmin/static/js/components/PgTree/FileTreeX/index.tsx +++ b/web/pgadmin/static/js/components/PgTree/FileTreeX/index.tsx @@ -159,7 +159,7 @@ export class FileTreeX extends React.Component { this.setPseudoActiveFile(null); })); - this.keyboardHotkeys = new KeyboardHotkeys(this.fileTreeHandle); + this.keyboardHotkeys = new KeyboardHotkeys(this.fileTreeHandle, this.events); if (typeof onReady === 'function') { onReady(this.fileTreeHandle); diff --git a/web/pgadmin/static/js/components/PgTree/services/keyboardHotkeys.ts b/web/pgadmin/static/js/components/PgTree/services/keyboardHotkeys.ts index 4f0ccf925..27d219e92 100644 --- a/web/pgadmin/static/js/components/PgTree/services/keyboardHotkeys.ts +++ b/web/pgadmin/static/js/components/PgTree/services/keyboardHotkeys.ts @@ -1,5 +1,6 @@ +import { Notificar } from 'notificar'; import { FileEntry, Directory, FileType } from 'react-aspen'; -import { IFileTreeXHandle } from '../types'; +import { FileTreeXEvent, IFileTreeXHandle } from '../types'; export class KeyboardHotkeys { private hotkeyActions = { @@ -12,15 +13,20 @@ export class KeyboardHotkeys { 'Home': () => this.jumpToFirstItem(), 'End': () => this.jumpToLastItem(), 'Escape': () => this.resetSteppedOrSelectedItem(), + 'Ctrl+KeyC': () => this.copyEntry(), }; - constructor(private readonly fileTreeX: IFileTreeXHandle) { } + constructor(private readonly fileTreeX: IFileTreeXHandle, private readonly events: Notificar) { } public handleKeyDown = (ev: React.KeyboardEvent) => { if (!this.fileTreeX.hasDirectFocus()) { return false; } - const { code } = ev.nativeEvent; + let { code } = ev.nativeEvent; + + if((ev.nativeEvent.ctrlKey || ev.nativeEvent.metaKey) && ev.nativeEvent.key !== 'Control') { + code = `Ctrl+${code}`; + } if (code in this.hotkeyActions) { ev.preventDefault(); this.hotkeyActions[code](); @@ -126,4 +132,9 @@ export class KeyboardHotkeys { private resetSteppedItem = () => { this.fileTreeX.setActiveFile(null); }; + + private copyEntry = () => { + const currentPseudoActive = this.fileTreeX.getActiveFile(); + this.events.dispatch(FileTreeXEvent.onTreeEvents, null, 'copied', currentPseudoActive); + }; } diff --git a/web/pgadmin/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js index 79b17a315..5f058f2fc 100644 --- a/web/pgadmin/static/js/tree/tree.js +++ b/web/pgadmin/static/js/tree/tree.js @@ -43,6 +43,9 @@ function manageTreeEvents(event, eventName, item) { if (eventName == 'added' || eventName == 'beforeopen' || eventName == 'loaded') { obj.tree.addNewNode(item.getMetadata('data').id, item.getMetadata('data'), item, item.parent.path); } + if(eventName == 'copied') { + obj.tree.copyHandler?.(item.getMetadata('data'), item); + } if (_.isObject(node.callbacks) && eventName in node.callbacks && typeof node.callbacks[eventName] == 'function' && @@ -545,6 +548,13 @@ export class Tree { e.dataTransfer.setDragImage(dragItem, 0, 0); } } + else { + e.preventDefault(); + } + } + + onNodeCopy(copyCallback) { + this.copyHandler = copyCallback; } }