pgadmin4/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx
Akshay Joshi df2f3460f0 Fix the following SonarQube code smells:
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.
2024-06-11 18:07:22 +05:30

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);
}
}
};
}