diff --git a/web/pgadmin/static/js/components/PgTree/FileTreeItem/DoubleClickHandler.jsx b/web/pgadmin/static/js/components/PgTree/FileTreeItem/DoubleClickHandler.jsx
new file mode 100644
index 000000000..934d36320
--- /dev/null
+++ b/web/pgadmin/static/js/components/PgTree/FileTreeItem/DoubleClickHandler.jsx
@@ -0,0 +1,18 @@
+import { useSingleAndDoubleClick } from '../../../custom_hooks';
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import CustomPropTypes from '../../../../js/custom_prop_types';
+
+export default function DoubleClickHandler({onSingleClick, onDoubleClick, children}){
+ const onClick = useSingleAndDoubleClick(onSingleClick, onDoubleClick) ;
+ return(
+
onClick(e)}>
+ {children}
+
+ );
+}
+DoubleClickHandler.propTypes = {
+ onSingleClick: PropTypes.func,
+ onDoubleClick: PropTypes.func,
+ children: CustomPropTypes.children
+};
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx b/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx
index e4c04362d..268785156 100644
--- a/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx
+++ b/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx
@@ -12,9 +12,9 @@ import * as React from 'react';
import { ClasslistComposite } from 'aspen-decorations';
import { Directory, FileEntry, IItemRendererProps, ItemType, RenamePromptHandle, FileType, FileOrDir} from 'react-aspen';
import {IFileTreeXTriggerEvents, FileTreeXEvent } from '../types';
-import _ from 'lodash';
import { Notificar } from 'notificar';
-
+import _ from 'lodash';
+import DoubleClickHandler from './DoubleClickHandler';
interface IItemRendererXProps {
/**
* In this implementation, decoration are null when item is `PromptHandle`
@@ -58,7 +58,6 @@ export class FileTreeItem extends React.Component
- {
+
+ {
item._metadata?.data?.icon ?
: null
}
@@ -121,7 +119,8 @@ export class FileTreeItem extends React.Component
))}
-
+
+
);
}
diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js
index 67aaaeed0..fb679a2ef 100644
--- a/web/pgadmin/static/js/custom_hooks.js
+++ b/web/pgadmin/static/js/custom_hooks.js
@@ -29,6 +29,32 @@ export function useInterval(callback, delay) {
}, [delay]);
}
+/* React hook for handling double and single click events */
+export function useSingleAndDoubleClick(handleSingleClick, handleDoubleClick, delay = 250) {
+ const clickCountRef = useRef(0);
+ const timerRef = useRef(null);
+
+ const handleClick = (e) => {
+ // Handle the logic here, no need to pass the event
+ clickCountRef.current += 1;
+
+ // Clear any previous timeout to ensure the double-click logic is triggered only once
+ clearTimeout(timerRef.current);
+
+ // Set the timeout to handle click logic after the delay
+ timerRef.current = setTimeout(() => {
+ if (clickCountRef.current === 1) handleSingleClick(e);
+ else if (clickCountRef.current === 2) handleDoubleClick(e);
+
+ // Reset the click count and props after handling
+ clickCountRef.current = 0;
+ }, delay);
+ };
+
+ return handleClick;
+}
+
+
export function useDelayedCaller(callback) {
let timer;
useEffect(() => {
diff --git a/web/regression/feature_utils/tree_area_locators.py b/web/regression/feature_utils/tree_area_locators.py
index f99718397..d79194df9 100644
--- a/web/regression/feature_utils/tree_area_locators.py
+++ b/web/regression/feature_utils/tree_area_locators.py
@@ -19,7 +19,7 @@ class TreeAreaLocators:
@staticmethod
def server_group_node_exp_status(server_group_name):
return "//i[@class='directory-toggle open']/following-sibling::" \
- "span//span[starts-with(text(),'%s')]" % server_group_name
+ "div//span[starts-with(text(),'%s')]" % server_group_name
# Server Node
@staticmethod
@@ -31,7 +31,7 @@ class TreeAreaLocators:
@staticmethod
def server_node_exp_status(server_name):
return "//i[@class='directory-toggle open']/following-sibling::" \
- "span//span[starts-with(text(),'%s')]" % server_name
+ "div//span[starts-with(text(),'%s')]" % server_name
# Server Connection
@staticmethod
@@ -43,36 +43,37 @@ class TreeAreaLocators:
# Databases Node
@staticmethod
def databases_node(server_name):
- return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
+ return "//div[div[div[span[span[starts-with(text(),'%s')]]]]]/" \
"following-sibling::div//span[text()='Databases']" % server_name
@staticmethod
def databases_node_exp_status(server_name):
- return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
- "following-sibling::div//span[span[text()='Databases']]/" \
+ return "//div[div[div[span[span[starts-with(text(),'%s')]]]]]/" \
+ "following-sibling::div//div[span[span[text()='Databases']]]/" \
"preceding-sibling::i[@class='directory-toggle open']" \
% server_name
# Database Node
@staticmethod
def database_node(database_name):
- return "//div[@data-depth='4']/span/span[text()='%s']" % database_name
+ return "//div[@data-depth='4']/div/span/span[text()='%s']" \
+ % database_name
@staticmethod
def database_node_exp_status(database_name):
return "//i[@class='directory-toggle open']/following-sibling::" \
- "span//span[text()='%s']" % database_name
+ "div//span[text()='%s']" % database_name
# Schemas Node
@staticmethod
def schemas_node(database_name):
- return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
+ return "//div[div[div[span[span[starts-with(text(),'%s')]]]]]/" \
"following-sibling::div//span[text()='Schemas']" % database_name
@staticmethod
def schemas_node_exp_status(database_name):
- return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
- "following-sibling::div//span[span[text()='Schemas']]/" \
+ return "//div[div[div[span[span[starts-with(text(),'%s')]]]]]/" \
+ "following-sibling::div//div[span[span[text()='Schemas']]]/" \
"preceding-sibling::i[@class='directory-toggle open']" \
% database_name
@@ -85,28 +86,28 @@ class TreeAreaLocators:
@staticmethod
def schema_node_exp_status(schema_name):
return "//i[@class='directory-toggle open']/" \
- "following-sibling::span//span[text()='%s']" % schema_name
+ "following-sibling::div//span[text()='%s']" % schema_name
# Tables Node
@staticmethod
def tables_node(schema_name):
- return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
+ return "//div[divdiv[[span[span[starts-with(text(),'%s')]]]]]/" \
"following-sibling::div//span[text()='Tables']" % schema_name
@staticmethod
def tables_node_exp_status(schema_name):
return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
- "following-sibling::div//span[span[text()='Tables']]/" \
+ "following-sibling::div//div[span[span[text()='Tables']]]/" \
"preceding-sibling::i[@class='directory-toggle open']"\
% schema_name
# Schema child
child_node_exp_status = \
- "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
- "following-sibling::div//span[span[text()='%s']]/" \
+ "//div[div[div[span[span[starts-with(text(),'%s')]]]]]/" \
+ "following-sibling::div//div[span[span[text()='%s']]]/" \
"preceding-sibling::i[@class='directory-toggle open']"
- child_node = "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
+ child_node = "//div[div[div[span[span[starts-with(text(),'%s')]]]]]/" \
"following-sibling::div//span[text()='%s']"
@staticmethod
@@ -120,8 +121,8 @@ class TreeAreaLocators:
@staticmethod
def schema_child_node_expand_icon_xpath(schema_name, child_node_name):
- return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \
- "following-sibling::div//span[text()='%s']/../" \
+ return "//div[div[div[span[span[starts-with(text(),'%s')]]]]]/" \
+ "following-sibling::div//div[span[text()='%s']]/../" \
"preceding-sibling::i" % (schema_name, child_node_name)
# Database child
@@ -147,17 +148,17 @@ class TreeAreaLocators:
# Table Node
@staticmethod
def table_node(table_name):
- return "//div[@data-depth='8']/span/span[text()='%s']" % table_name
+ return "//div[@data-depth='8']/div/span/span[text()='%s']" % table_name
# Function Node
@staticmethod
def function_node(table_name):
- return "//div[@data-depth='8']/span/span[text()='%s']" % table_name
+ return "//div[@data-depth='8']/div/span/span[text()='%s']" % table_name
# Role Node
@staticmethod
def role_node(role_name):
- return "//div[@data-depth='4']/span/span[text()='%s']" % role_name
+ return "//div[@data-depth='4']/div/span/span[text()='%s']" % role_name
# Context element option
@staticmethod