From 2201c682928b163991e37a55facea944d8672983 Mon Sep 17 00:00:00 2001 From: yogeshmahajan-1903 Date: Tue, 14 Jan 2025 21:37:57 +0530 Subject: [PATCH] Ensure double click event is not ignored in the browser tree --- .../FileTreeItem/DoubleClickHandler.jsx | 18 ++++++++ .../components/PgTree/FileTreeItem/index.tsx | 15 ++++--- web/pgadmin/static/js/custom_hooks.js | 27 ++++++++++++ .../feature_utils/tree_area_locators.py | 43 ++++++++++--------- 4 files changed, 75 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 00000000000..088b6c4fe81 --- /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( +
+ {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 e4c04362dae..f84cb696d1e 100644 --- a/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx +++ b/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx @@ -7,14 +7,16 @@ // ////////////////////////////////////////////////////////////// -import cn from 'classnames'; +import cn from 'classnames' 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 cn from 'classnames'; +import DoubleClickHandler from './DoubleClickHandler'; interface IItemRendererXProps { /** * In this implementation, decoration are null when item is `PromptHandle` @@ -58,7 +60,6 @@ export class FileTreeItem extends React.Component - { + + { item._metadata?.data?.icon ? : null } @@ -121,7 +121,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 67aaaeed0f6..0ec586982db 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -29,6 +29,33 @@ export function useInterval(callback, delay) { }, [delay]); } +/* React hook for handling double and single click events */ +export function useSingleAndDoubleClick(handleSingleClick, handleDoubleClick, delay = 250) { + const [state, setState] = useState({ click: 0, props: undefined }); + + useEffect(() => { + const timer = setTimeout(() => { + // simple click + if (state.click === 1){ + handleSingleClick(state.props); + setState({ click: 0, props: state.props }); + } + }, delay); + + if (state.click === 2) { + handleDoubleClick(state.props); + setState({ click: 0, props: state.props }); + } + + return () => clearTimeout(timer); + }, [state, handleSingleClick, handleDoubleClick, delay ]); + + return (props) => { + setState((prevState) => ({ click: prevState.click + 1, props })); + }; +} + + 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 f99718397a2..d79194df98d 100644 --- a/web/regression/feature_utils/tree_area_locators.py +++ b/web/regression/feature_utils/tree_area_locators.py @@ -19,7 +19,7 @@ def server_group_node(server_group_name): @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 @@ def server_node(server_name): @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 @@ def server_connection_status_element(server_name): # 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 @@ def schema_node(schema_name): @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 @@ def schema_child_node(schema_name, child_node_name): @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 @@ def server_child_node(server_name, child_node_name): # 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