From f0b88dc19f971783e572606494dda5df54619e13 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/FileTreeItemComponent.jsx | 85 ++++++++++++++++++ .../components/PgTree/FileTreeItem/index.tsx | 86 ++++--------------- web/pgadmin/static/js/custom_hooks.js | 27 ++++++ 3 files changed, 129 insertions(+), 69 deletions(-) create mode 100644 web/pgadmin/static/js/components/PgTree/FileTreeItem/FileTreeItemComponent.jsx diff --git a/web/pgadmin/static/js/components/PgTree/FileTreeItem/FileTreeItemComponent.jsx b/web/pgadmin/static/js/components/PgTree/FileTreeItem/FileTreeItemComponent.jsx new file mode 100644 index 00000000000..d74fbefbfb0 --- /dev/null +++ b/web/pgadmin/static/js/components/PgTree/FileTreeItem/FileTreeItemComponent.jsx @@ -0,0 +1,85 @@ +import cn from 'classnames'; +import { useSingleAndDoubleClick } from '../../../custom_hooks'; +import { FileEntry, ItemType, FileType} from 'react-aspen'; +import * as React from 'react'; +import PropTypes from 'prop-types'; + +export default function FileTreeItemComponent({item, itemType, decorations, handleContextMenu, handleDragStartItem, handleMouseEnter, handleMouseLeave, handleItemClicked, handleItemDoubleClicked, handleDivRef}){ + const onClick = useSingleAndDoubleClick(handleItemClicked, handleItemDoubleClicked) ; + const isRenamePrompt = itemType === ItemType.RenamePrompt; + const isNewPrompt = itemType === ItemType.NewDirectoryPrompt || itemType === ItemType.NewFilePrompt; + const isDirExpanded = itemType === ItemType.Directory + ? item.expanded + : itemType === ItemType.RenamePrompt && item.target.type === FileType.Directory + ? item.target.expanded + : false; + const fileOrDir = + (itemType === ItemType.File || + itemType === ItemType.NewFilePrompt || + (itemType === ItemType.RenamePrompt && (item).target.constructor === FileEntry)) + ? 'file' + : 'directory'; + + if (item.parent?.parent && item.parent?.path) { + item.resolvedPathCache = item.parent.path + '/' + item._metadata.data.id; + } + + const itemChildren = item.children && item.children.length > 0 && item._metadata.data._type.indexOf('coll-') !== -1 ? '(' + item.children.length + ')' : ''; + const extraClasses = item._metadata.data.extraClasses ? item._metadata.data.extraClasses.join(' ') : ''; + const tags = item._metadata.data?.tags ?? []; + + return( +
{/* taken care by parent */}} + // required for rendering context menus when opened through context menu button on keyboard + ref={handleDivRef} + draggable={true}> + + {!isNewPrompt && fileOrDir === 'directory' ? + + : null + } + + + { + item._metadata?.data?.icon ? + : null + } + + { _.unescape(item._metadata?.data._label)} + + {itemChildren} + {tags.map((tag)=>( +
+ {tag.text} +
+ ))} +
+
); + +} + +FileTreeItemComponent.propTypes = { + item: PropTypes.object, + itemType: PropTypes.number, + decorations: PropTypes.object, + handleContextMenu: PropTypes.func, + handleDragStartItem:PropTypes.func, + handleMouseEnter:PropTypes.func, + handleMouseLeave:PropTypes.func, + handleItemClicked:PropTypes.func, + handleItemDoubleClicked:PropTypes.func, + handleDivRef:PropTypes.func + +}; \ 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..b542c8529f7 100644 --- a/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx +++ b/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx @@ -7,14 +7,12 @@ // ////////////////////////////////////////////////////////////// -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 { Directory, FileEntry, IItemRendererProps, ItemType, FileOrDir} from 'react-aspen'; import {IFileTreeXTriggerEvents, FileTreeXEvent } from '../types'; -import _ from 'lodash'; import { Notificar } from 'notificar'; - +import FileTreeItemComponent from './FileTreeItemComponent'; interface IItemRendererXProps { /** * In this implementation, decoration are null when item is `PromptHandle` @@ -58,71 +56,21 @@ export class FileTreeItem extends React.Component 0 && item._metadata.data._type.indexOf('coll-') !== -1 ? '(' + item.children.length + ')' : ''; - const extraClasses = item._metadata.data.extraClasses ? item._metadata.data.extraClasses.join(' ') : ''; - - const tags = item._metadata.data?.tags ?? []; - - return ( -
{/* taken care by parent */}} - // required for rendering context menus when opened through context menu button on keyboard - ref={this.handleDivRef} - draggable={true}> - - {!isNewPrompt && fileOrDir === 'directory' ? - - : null - } - - - { - item._metadata?.data?.icon ? - : null - } - - { _.unescape(this.props.item.getMetadata('data')._label)} - - {itemChildren} - {tags.map((tag)=>( -
- {tag.text} -
- ))} -
-
); + return( +
+ +
+ ); } public componentDidMount() { 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(() => {