From 98f6b1ff12aa55179780e1f49c9c06f328866568 Mon Sep 17 00:00:00 2001 From: Yogesh Mahajan Date: Mon, 20 Jan 2025 11:21:21 +0530 Subject: [PATCH] Ensure the double-click event is not ignored in the browser tree. --- .../FileTreeItem/DoubleClickHandler.jsx | 18 ++++++++ .../components/PgTree/FileTreeItem/index.tsx | 13 +++--- web/pgadmin/static/js/custom_hooks.js | 26 +++++++++++ .../feature_utils/tree_area_locators.py | 43 ++++++++++--------- 4 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 web/pgadmin/static/js/components/PgTree/FileTreeItem/DoubleClickHandler.jsx 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