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(() => {