Skip to content

Commit

Permalink
Ensure double click event is not ignored in the browser tree
Browse files Browse the repository at this point in the history
  • Loading branch information
yogeshmahajan-1903 committed Jan 14, 2025
1 parent 36df6d5 commit 3ce9797
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -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(
<div
className={cn('file-entry', {
renaming: isRenamePrompt,
prompt: isRenamePrompt || isNewPrompt,
new: isNewPrompt,
}, fileOrDir, decorations ? decorations.classlist : null, `depth-${item.depth}`, extraClasses)}
data-depth={item.depth}
onContextMenu={handleContextMenu}
onClick={onClick}
onDragStart={handleDragStartItem}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onKeyDown={()=>{/* taken care by parent */}}
// required for rendering context menus when opened through context menu button on keyboard
ref={handleDivRef}
draggable={true}>

{!isNewPrompt && fileOrDir === 'directory' ?
<i className={cn('directory-toggle', isDirExpanded ? 'open' : '')} />
: null
}

<span className='file-label'>
{
item._metadata?.data?.icon ?
<i className={cn('file-icon', item._metadata?.data?.icon ? item._metadata.data.icon : fileOrDir)} /> : null
}
<span className='file-name'>
{ _.unescape(item._metadata?.data._label)}
</span>
<span className='children-count'>{itemChildren}</span>
{tags.map((tag)=>(
<div key={tag.text} className='file-tag' style={{'--tag-color': tag.color}}>
{tag.text}
</div>
))}
</span>
</div>);

}

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

};
86 changes: 17 additions & 69 deletions web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -58,71 +56,21 @@ 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
? (item as Directory).expanded
: itemType === ItemType.RenamePrompt && (item as RenamePromptHandle).target.type === FileType.Directory
? ((item as RenamePromptHandle).target as Directory).expanded
: false;

const fileOrDir =
(itemType === ItemType.File ||
itemType === ItemType.NewFilePrompt ||
(itemType === ItemType.RenamePrompt && (item as RenamePromptHandle).target.constructor === FileEntry))
? 'file'
: 'directory';

if (this.props.item.parent?.parent && this.props.item.parent?.path) {
this.props.item.resolvedPathCache = this.props.item.parent.path + '/' + this.props.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 (
<div
className={cn('file-entry', {
renaming: isRenamePrompt,
prompt: isRenamePrompt || isNewPrompt,
new: isNewPrompt,
}, fileOrDir, decorations ? decorations.classlist : null, `depth-${item.depth}`, extraClasses)}
data-depth={item.depth}
onContextMenu={this.handleContextMenu}
onClick={this.handleClick}
onDoubleClick={this.handleDoubleClick}
onDragStart={this.handleDragStartItem}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onKeyDown={()=>{/* 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' ?
<i className={cn('directory-toggle', isDirExpanded ? 'open' : '')} />
: null
}

<span className='file-label'>
{
item._metadata?.data?.icon ?
<i className={cn('file-icon', item._metadata?.data?.icon ? item._metadata.data.icon : fileOrDir)} /> : null
}
<span className='file-name'>
{ _.unescape(this.props.item.getMetadata('data')._label)}
</span>
<span className='children-count'>{itemChildren}</span>
{tags.map((tag)=>(
<div key={tag.text} className='file-tag' style={{'--tag-color': tag.color} as React.CSSProperties}>
{tag.text}
</div>
))}
</span>
</div>);
return(
<div>
<FileTreeItemComponent
item={item}
itemType={itemType}
decorations={decorations}
handleContextMenu={this.handleContextMenu}
handleDragStartItem={this.handleDragStartItem}
handleMouseEnter={this.handleMouseEnter}
handleMouseLeave={this.handleMouseLeave}
handleItemClicked={this.handleClick}
handleItemDoubleClicked={this.handleDoubleClick}
handleDivRef={this.handleDivRef}/>
</div>
);
}

public componentDidMount() {
Expand Down
27 changes: 27 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,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(() => {
Expand Down

0 comments on commit 3ce9797

Please sign in to comment.