mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
1) Use the "RegExp.exec()" method instead. 2) Remove parameter form or provide default value. 3) Extract this nested ternary operation into an independent statement. 4) Replace this character class by the character itself. 5) Unnecessary use of conditional expression for default assignment. 6) Prefer using an optional chain expression instead, as it's more concise and easier to read.
199 lines
7.9 KiB
TypeScript
199 lines
7.9 KiB
TypeScript
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 {IFileTreeXTriggerEvents, FileTreeXEvent } from '../types';
|
|
import _ from 'lodash';
|
|
import { Notificar } from 'notificar';
|
|
|
|
interface IItemRendererXProps {
|
|
/**
|
|
* In this implementation, decoration are null when item is `PromptHandle`
|
|
*
|
|
* If you would like decorations for `PromptHandle`s, then get them using `DecorationManager#getDecorations(<target>)`.
|
|
* Where `<target>` can be either `NewFilePromptHandle.parent` or `RenamePromptHandle.target` depending on type of `PromptHandle`
|
|
*
|
|
* To determine the type of `PromptHandle`, use `IItemRendererProps.itemType`
|
|
*/
|
|
decorations: ClasslistComposite
|
|
onClick: (ev: React.MouseEvent, item: FileEntry | Directory, type: ItemType) => void
|
|
onContextMenu: (ev: React.MouseEvent, item: FileEntry | Directory) => void
|
|
onMouseEnter: (ev: React.MouseEvent, item: FileEntry | Directory) => void
|
|
onMouseLeave: (ev: React.MouseEvent, item: FileEntry | Directory) => void
|
|
onItemHovered: (ev: React.MouseEvent, item: FileEntry | Directory, type: ItemType) => void
|
|
events: Notificar<FileTreeXEvent>
|
|
}
|
|
|
|
// DO NOT EXTEND FROM PureComponent!!! You might miss critical changes made deep within `item` prop
|
|
// as far as efficiency is concerned, `react-aspen` works hard to ensure unnecessary updates are ignored
|
|
export class FileTreeItem extends React.Component<IItemRendererXProps & IItemRendererProps> {
|
|
public static getBoundingClientRectForItem(item: FileEntry | Directory): DOMRect {
|
|
const divRef = FileTreeItem.itemIdToRefMap.get(item.id);
|
|
if (divRef) {
|
|
return divRef.getBoundingClientRect();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ensure this syncs up with what goes in CSS, (em, px, % etc.) and what ultimately renders on the page
|
|
public static readonly renderHeight: number = 24;
|
|
private static readonly itemIdToRefMap: Map<number, HTMLDivElement> = new Map();
|
|
private static readonly refToItemIdMap: Map<number, HTMLDivElement> = new Map();
|
|
private fileTreeEvent: IFileTreeXTriggerEvents;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
// used to apply decoration changes, you're welcome to use setState or other mechanisms as you see fit
|
|
this.forceUpdate = this.forceUpdate.bind(this);
|
|
}
|
|
|
|
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?.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(' ') : '';
|
|
|
|
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 className='children-count'>{itemChildren}</span>
|
|
</span>
|
|
|
|
</span>
|
|
</div>);
|
|
}
|
|
|
|
public componentDidMount() {
|
|
this.events = this.props.events;
|
|
this.props.item.resolvedPathCache = this.props.item.parent.path + '/' + this.props.item._metadata.data.id;
|
|
if (this.props.decorations) {
|
|
this.props.decorations.addChangeListener(this.forceUpdate);
|
|
}
|
|
this.setActiveFile(this.props.item);
|
|
}
|
|
|
|
private setActiveFile = async (FileOrDir): Promise<void> => {
|
|
this.props.changeDirectoryCount(FileOrDir.parent);
|
|
if(FileOrDir._loaded !== true) {
|
|
this.events.dispatch(FileTreeXEvent.onTreeEvents, window.event, 'added', FileOrDir);
|
|
}
|
|
FileOrDir._loaded = true;
|
|
};
|
|
|
|
public componentWillUnmount() {
|
|
if (this.props.decorations) {
|
|
this.props.decorations.removeChangeListener(this.forceUpdate);
|
|
}
|
|
}
|
|
|
|
public componentDidUpdate(prevProps: IItemRendererXProps) {
|
|
if (prevProps.decorations) {
|
|
prevProps.decorations.removeChangeListener(this.forceUpdate);
|
|
}
|
|
if (this.props.decorations) {
|
|
this.props.decorations.addChangeListener(this.forceUpdate);
|
|
}
|
|
}
|
|
|
|
private handleDivRef = (r: HTMLDivElement) => {
|
|
if (r === null) {
|
|
FileTreeItem.itemIdToRefMap.delete(this.props.item.id);
|
|
} else {
|
|
FileTreeItem.itemIdToRefMap.set(this.props.item.id, r);
|
|
FileTreeItem.refToItemIdMap.set(r, this.props.item);
|
|
}
|
|
};
|
|
|
|
private handleContextMenu = (ev: React.MouseEvent) => {
|
|
const { item, itemType, onContextMenu } = this.props;
|
|
if (itemType === ItemType.File || itemType === ItemType.Directory) {
|
|
onContextMenu(ev, item as FileOrDir);
|
|
}
|
|
};
|
|
|
|
private handleClick = (ev: React.MouseEvent) => {
|
|
const { item, itemType, onClick } = this.props;
|
|
if (itemType === ItemType.File || itemType === ItemType.Directory) {
|
|
onClick(ev, item as FileEntry, itemType);
|
|
}
|
|
};
|
|
|
|
private handleDoubleClick = (ev: React.MouseEvent) => {
|
|
const { item, itemType, onDoubleClick } = this.props;
|
|
if (itemType === ItemType.File || itemType === ItemType.Directory) {
|
|
onDoubleClick(ev, item as FileEntry, itemType);
|
|
}
|
|
};
|
|
|
|
private handleMouseEnter = (ev: React.MouseEvent) => {
|
|
const { item, itemType, onMouseEnter } = this.props;
|
|
if (itemType === ItemType.File || itemType === ItemType.Directory) {
|
|
onMouseEnter?.(ev, item as FileEntry);
|
|
}
|
|
};
|
|
|
|
private handleMouseLeave = (ev: React.MouseEvent) => {
|
|
const { item, itemType, onMouseLeave } = this.props;
|
|
if (itemType === ItemType.File || itemType === ItemType.Directory) {
|
|
onMouseLeave?.(ev, item as FileEntry);
|
|
}
|
|
};
|
|
|
|
private handleDragStartItem = (e: React.DragEvent) => {
|
|
const { item, itemType, events } = this.props;
|
|
if (itemType === ItemType.File || itemType === ItemType.Directory) {
|
|
const ref = FileTreeItem.itemIdToRefMap.get(item.id);
|
|
if (ref) {
|
|
events.dispatch(FileTreeXEvent.onTreeEvents, e, 'dragstart', item);
|
|
}
|
|
}
|
|
};
|
|
}
|