public inbox for [email protected]
help / color / mirror / Atom feedFrom: Aditya Toshniwal <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgAdmin][RM4139] Drag and drop object names in Query Editor from Browser Tree
Date: Wed, 26 Jun 2019 11:53:57 +0530
Message-ID: <CAM9w-_mxRdG6peLx3VdF1z-+yY11qQEPNTQ0ekT5TNaEWT05QQ@mail.gmail.com> (raw)
Hi Hackers,
Attached is the patch to allow tree nodes to be dragged and dropped in
query editor wherever you take the drop cursor. The drop text will be fully
qualified and double quoted if required.
For functions/procedures it will drop the the label stripping away the
parameter names, with empty pair of round brackets. It will also set the
focus cursor inside the brackets if there were params otherwiser to the end
of text.
For adding a node type, you need to register it in browser.js along with a
callback function which will return drop text along with cursor positioning
and selection. Currently, I have registered for "table partition type
sequence package view mview foreign_table edbvar schema column edbfunc
function edbproc procedure".
Please note, the drag design customisation is not supported in IE.
Kindly review.
--
Thanks and Regards,
Aditya Toshniwal
Software Engineer | EnterpriseDB India | Pune
"Don't Complain about Heat, Plant a TREE"
Attachments:
[application/octet-stream] RM4139.patch (10.6K, 3-RM4139.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index 29c11c2e..8c0f9a5a 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -12,7 +12,7 @@ define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'underscore.string',
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
'sources/check_node_visibility', './toolbar', 'pgadmin.help',
- 'sources/csrf', 'pgadmin.browser.utils',
+ 'sources/csrf', 'sources/utils', 'pgadmin.browser.utils',
'wcdocker', 'jquery.contextmenu', 'jquery.aciplugin', 'jquery.acitree',
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin.browser.layout',
@@ -24,7 +24,7 @@ define('pgadmin.browser', [
tree,
gettext, url_for, require, $, _, S,
Bootstrap, pgAdmin, Alertify, codemirror,
- checkNodeVisibility, toolBar, help, csrfToken
+ checkNodeVisibility, toolBar, help, csrfToken, pgadminUtils,
) {
window.jQuery = window.$ = $;
// Some scripts do export their object in the window only.
@@ -102,6 +102,38 @@ define('pgadmin.browser', [
b.tree = $('#tree').aciTree('api');
b.treeMenu.register($('#tree'));
+
+ b.treeMenu.registerDraggableType({
+ 'table partition type sequence package view mview foreign_table edbvar' : (data, item)=>{
+ return pgadminUtils.fully_qualify(b, data, item);
+ },
+ 'schema column' : (data)=>{
+ return pgadminUtils.quote_ident(data.label);
+ },
+ 'edbfunc function edbproc procedure' : (data, item)=>{
+ let newData = {
+ ...data,
+ },
+ bracketPos = newData.label.lastIndexOf('(');
+
+ /* Strip the bracket */
+ newData.label = newData.label.substr(0, bracketPos);
+ let dropVal = pgadminUtils.fully_qualify(b, newData, item);
+ dropVal = dropVal + '()';
+
+ let curPos = dropVal.length;
+ /* If it has params */
+ if(data.label.substr(bracketPos).length > 2) {
+ curPos = curPos - 1;
+ }
+ return {
+ text: dropVal,
+ cur: {
+ from: curPos, to: curPos,
+ },
+ };
+ },
+ });
};
// Extend the browser class attributes
diff --git a/web/pgadmin/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js
index 782f4d59..99397ef4 100644
--- a/web/pgadmin/static/js/tree/tree.js
+++ b/web/pgadmin/static/js/tree/tree.js
@@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////////////////
import {isValidData} from 'sources/utils';
+import $ from 'jquery';
export class TreeNode {
constructor(id, data, domNode, parent) {
@@ -97,6 +98,87 @@ export class Tree {
constructor() {
this.rootNode = new TreeNode(undefined, {});
this.aciTreeApi = undefined;
+ this.draggableTypes = {};
+ }
+
+ /*
+ *
+ * The dropDetailsFunc should return an object of sample
+ * {text: 'xyz', cur: {from:0, to:0} where text is the drop text and
+ * cur is selection range of text after dropping. If returned as
+ * string, by default cursor will be set to the end of text
+ */
+ registerDraggableType(typeOrTypeDict, dropDetailsFunc=null) {
+ if(typeof typeOrTypeDict == 'object') {
+ Object.keys(typeOrTypeDict).forEach((type)=>{
+ this.registerDraggableType(type, typeOrTypeDict[type]);
+ });
+ } else {
+ if(dropDetailsFunc != null) {
+ typeOrTypeDict.replace(/ +/, ' ').split(' ').forEach((type)=>{
+ this.draggableTypes[type] = dropDetailsFunc;
+ });
+ }
+ }
+ }
+
+ getDraggable(type) {
+ if(this.draggableTypes[type]) {
+ return this.draggableTypes[type];
+ } else {
+ return null;
+ }
+ }
+
+ prepareDraggable(data, item) {
+ let dropDetailsFunc = this.getDraggable(data._type);
+
+ if(dropDetailsFunc != null) {
+ item.find('.aciTreeItem')
+ .attr('draggable', true)
+ .on('dragstart', (e)=> {
+ let dropDetails = dropDetailsFunc(data, item);
+ let origEvent = e.originalEvent;
+
+ if(typeof dropDetails == 'string') {
+ dropDetails = {
+ text:dropDetails,
+ cur:{
+ from:dropDetails.length,
+ to: dropDetails.length,
+ },
+ };
+ } else {
+ if(!dropDetails.cur) {
+ dropDetails = {
+ ...dropDetails,
+ cur:{
+ from:dropDetails.text.length,
+ to: dropDetails.text.length,
+ },
+ };
+ }
+ }
+
+ origEvent.dataTransfer.setData('text', JSON.stringify(dropDetails));
+
+ /* setDragImage is not supported in IE. We leave it to
+ * its default look and feel
+ */
+ if(origEvent.dataTransfer.setDragImage) {
+ let dragItem = $(`
+ <div class="drag-tree-node">
+ <span>${dropDetails.text}</span>
+ </div>`
+ );
+
+ $('body .drag-tree-node').remove();
+ $('body').append(dragItem);
+
+ origEvent.dataTransfer.setDragImage(dragItem[0], 0, 0);
+ }
+ });
+ }
}
addNewNode(id, data, domNode, parentPath) {
@@ -163,6 +245,9 @@ export class Tree {
if (eventName === 'added') {
const id = api.getId(item);
const data = api.itemData(item);
+
+ this.prepareDraggable(data, item);
+
const parentId = this.translateTreeNodeIdFromACITree(api.parent(item));
this.addNewNode(id, data, item, parentId);
}
diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js
index 1c58a9eb..5c908afb 100644
--- a/web/pgadmin/static/js/utils.js
+++ b/web/pgadmin/static/js/utils.js
@@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////////////////
import _ from 'underscore';
+import { getTreeNodeHierarchyFromIdentifier } from 'sources/tree/pgadmin_tree_node';
export function parseShortcutValue(obj) {
var shortcut = '';
@@ -83,3 +84,50 @@ export function getGCD(inp_arr) {
export function getMod(no, divisor) {
return ((no % divisor) + divisor) % divisor;
}
+
+export function quote_ident(value) {
+ /* check if the string is number or not */
+ let quoteIt = false;
+ if (!isNaN(parseInt(value))){
+ quoteIt = true;
+ }
+
+ if(value.search(/[^a-z0-9_]/g) > -1) {
+ quoteIt = true;
+ }
+
+ if(quoteIt) {
+ return `"${value}"`;
+ } else {
+ return value;
+ }
+}
+
+export function fully_qualify(pgBrowser, data, item) {
+ const parentData = getTreeNodeHierarchyFromIdentifier.call(pgBrowser, item);
+ let namespace = '';
+
+ if (parentData.schema !== undefined) {
+ namespace = quote_ident(parentData.schema.label);
+ }
+ else if (parentData.view !== undefined) {
+ namespace = quote_ident(parentData.view.label);
+ }
+ else if (parentData.catalog !== undefined) {
+ namespace = quote_ident(parentData.catalog.label);
+ }
+
+ if (parentData.package !== undefined && data._type != 'package') {
+ if(namespace == '') {
+ namespace = quote_ident(parentData.package.label);
+ } else {
+ namespace += '.' + quote_ident(parentData.package.label);
+ }
+ }
+
+ if(namespace != '') {
+ return namespace + '.' + quote_ident(data.label);
+ } else {
+ return quote_ident(data.label);
+ }
+}
diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss
index ed426545..f5a8877c 100644
--- a/web/pgadmin/static/scss/_pgadmin.style.scss
+++ b/web/pgadmin/static/scss/_pgadmin.style.scss
@@ -983,3 +983,15 @@ table.table-empty-rows{
padding: 0px !important;
position: absolute;
}
+
+.drag-tree-node {
+ position: absolute;
+ top:-100px;
+ left:0;
+ z-index: 99999;
+ color: $input-focus-color;
+ background: $input-bg;
+ border: $input-border-width solid $input-focus-border-color;
+ border-radius: $input-border-radius;
+ padding: $input-btn-padding-y $input-btn-padding-x;
+}
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index a8560574..ef468f63 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -341,8 +341,31 @@ define('tools.querytool', [
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
extraKeys: pgBrowser.editor_shortcut_keys,
scrollbarStyle: 'simple',
+ dragDrop: false,
});
+ if(self.handler.is_query_tool) {
+ self.query_tool_obj.setOption('dragDrop', true);
+ self.query_tool_obj.on('drop', (editor, e) => {
+ var cursor = editor.coordsChar({
+ left: e.x,
+ top: e.y,
+ });
+ var dropDetails = JSON.parse(e.dataTransfer.getData('text'));
+ e.codemirrorIgnore = true;
+ e.dataTransfer.clearData('text');
+ editor.replaceRange(dropDetails.text, cursor);
+ editor.focus();
+ editor.setSelection({
+ ...cursor,
+ ch: cursor.ch + dropDetails.cur.from,
+ },{
+ ...cursor,
+ ch: cursor.ch +dropDetails.cur.to,
+ });
+ });
+ }
+
pgBrowser.Events.on('pgadmin:query_tool:sql_panel:focus', ()=>{
self.query_tool_obj.focus();
});
diff --git a/web/regression/javascript/pgadmin_utils_spec.js b/web/regression/javascript/pgadmin_utils_spec.js
index 02bd5478..7df78d29 100644
--- a/web/regression/javascript/pgadmin_utils_spec.js
+++ b/web/regression/javascript/pgadmin_utils_spec.js
@@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
-import { getEpoch, getGCD, getMod } from 'sources/utils';
+import { getEpoch, getGCD, getMod, quote_ident } from 'sources/utils';
describe('getEpoch', function () {
it('should return non zero', function () {
@@ -51,3 +51,20 @@ describe('getMod', function () {
expect(getMod(-7,5)).toEqual(3);
});
});
+
+describe('quote_ident', function () {
+ it('normal string', function () {
+ expect(quote_ident('abcd')).toEqual('abcd');
+ });
+
+ it('contains certain characters string', function () {
+ expect(quote_ident('Abcd')).toEqual('"Abcd"');
+ expect(quote_ident('abc$d')).toEqual('"abc$d"');
+ expect(quote_ident('ab cd')).toEqual('"ab cd"');
+ });
+
+ it('starts with number', function () {
+ expect(quote_ident('1a')).toEqual('"1a"');
+ expect(quote_ident('a1')).toEqual('a1');
+ });
+});
view thread (8+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [pgAdmin][RM4139] Drag and drop object names in Query Editor from Browser Tree
In-Reply-To: <CAM9w-_mxRdG6peLx3VdF1z-+yY11qQEPNTQ0ekT5TNaEWT05QQ@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox