Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure double click event is not ignored in the browser tree. #8361

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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(
<div onClick={(e)=>onClick(e)}>
{children}
</div>
);
}
DoubleClickHandler.propTypes = {
onSingleClick: PropTypes.func,
onDoubleClick: PropTypes.func,
children: CustomPropTypes.children
};
13 changes: 6 additions & 7 deletions web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -58,7 +58,6 @@ export class FileTreeItem extends React.Component<IItemRendererXProps & IItemRen

public render() {
const { item, itemType, decorations } = this.props;

const isRenamePrompt = itemType === ItemType.RenamePrompt;
const isNewPrompt = itemType === ItemType.NewDirectoryPrompt || itemType === ItemType.NewFilePrompt;
const isDirExpanded = itemType === ItemType.Directory
Expand Down Expand Up @@ -93,7 +92,6 @@ export class FileTreeItem extends React.Component<IItemRendererXProps & IItemRen
data-depth={item.depth}
onContextMenu={this.handleContextMenu}
onClick={this.handleClick}
onDoubleClick={this.handleDoubleClick}
onDragStart={this.handleDragStartItem}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
Expand All @@ -107,8 +105,8 @@ export class FileTreeItem extends React.Component<IItemRendererXProps & IItemRen
: null
}

<span className='file-label'>
{
<DoubleClickHandler onDoubleClick={this.handleDoubleClick} onSingleClick={this.handleClick} >
<span className='file-label'>{
item._metadata?.data?.icon ?
<i className={cn('file-icon', item._metadata?.data?.icon ? item._metadata.data.icon : fileOrDir)} /> : null
}
Expand All @@ -121,7 +119,8 @@ export class FileTreeItem extends React.Component<IItemRendererXProps & IItemRen
{tag.text}
</div>
))}
</span>
</span>
</DoubleClickHandler>
</div>);
}

Expand Down
26 changes: 26 additions & 0 deletions web/pgadmin/static/js/custom_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
43 changes: 22 additions & 21 deletions web/regression/feature_utils/tree_area_locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading