????
Current Path : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/components/PgTree/FileTreeItem/ |
Current File : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx |
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 && 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 && item._metadata.data.icon ? <i className={cn('file-icon', item._metadata && 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); } } }; }