Skip to content
Closed
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,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
Loading