import { h, Component } from 'preact'; import { FileIcon } from './FileIcon'; import { getParentPath, getFileFromPath } from '../fileUtils'; import { trackEvent } from '../analytics'; const ENTER_KEY = 13; const ESCAPE_KEY = 27; function File({ file, selectedFile, fileBeingRenamed, onFileSelect, onRenameBtnClick, onRemoveBtnClick, onNameInputBlur, onNameInputKeyUp, onFileDrop }) { function focusInput(el) { el && setTimeout(() => { el.focus(); }, 1); } function dragStartHandler(e) { e.dataTransfer.setData('text/plain', file.path); } function dragOverHandler(e) { if (file.isFolder) { e.preventDefault(); // e.stopPropagation(); e.currentTarget.classList.add('is-being-dragged-over'); e.currentTarget.style.outline = '1px dashed'; } } function dragLeaveHandler(e) { if (file.isFolder) { e.preventDefault(); e.currentTarget.style.outline = null; } } function dropHandler(e) { e.stopPropagation(); if (file.isFolder) { e.preventDefault(); onFileDrop(e.dataTransfer.getData('text/plain'), file); e.currentTarget.style.outline = null; } } return (
{file === fileBeingRenamed ? ( ) : (
)} ); } function Folder(props) { return (
); } export class SidePane extends Component { addFileButtonClickHandler() { this.setState({ isAddingFile: true }); trackEvent('ui', 'fileAddBtnClick'); } addFolderButtonClickHandler() { this.setState({ isAddingFolder: true }); trackEvent('ui', 'folderAddBtnClick'); } /** * Checks if the passed filename already exists and if so, warns the user. * Also it passes false if the validation fails. * @param {string} newFileName New file name to validate */ warnForExistingFileName(newFileName) { // We also check for fileBeingRenamed !== file because when a file being renamed is // asked to be set as same name, then that should not match and warn here. let newPath = this.state.fileBeingRenamed ? `${getParentPath(this.state.fileBeingRenamed.path)}/${newFileName}` : newFileName; // remove first slash newPath = newPath.replace(/^\//, ''); const match = getFileFromPath(this.props.files, newPath); if (match.file && this.state.fileBeingRenamed !== match.file) { alert(`A file with name ${newFileName} already exists.`); return false; } return true; } addFile(e) { // This gets called twice when enter is pressed, because blur also fires. // So check `isAddingFile` before proceeding. if (!this.state.isAddingFile && !this.state.isAddingFolder) { return; } const newFileName = e.target.value; if (!this.warnForExistingFileName(newFileName)) { return; } if (newFileName) { this.props.onAddFile(newFileName, this.state.isAddingFolder); } this.setState({ isAddingFile: false, isAddingFolder: false }); } newFileNameInputKeyDownHandler(e) { if (e.which === ENTER_KEY) { this.addFile(e); } else if (e.which === ESCAPE_KEY) { this.setState({ isAddingFile: false, isAddingFolder: false }); } } removeFileClickHandler(file, e) { e.stopPropagation(); const answer = confirm(`Are you sure you want to delete "${file.name}"?`); if (answer) { this.props.onRemoveFile(file.path); } trackEvent('ui', 'fileRemoveBtnClick'); } renameFile(e) { // This gets called twice when enter is pressed, because blur also fires. if (!e.target || !this.state.fileBeingRenamed) { return; } const newFileName = e.target.value; if (!this.warnForExistingFileName(newFileName)) { return; } if (newFileName) { this.props.onRenameFile(this.state.fileBeingRenamed.path, newFileName); } this.setState({ fileBeingRenamed: null }); } renameFileNameInputKeyUpHandler(e) { if (e.which === ENTER_KEY) { this.renameFile(e); } else if (e.which === ESCAPE_KEY) { this.setState({ fileBeingRenamed: null }); } } renameFileClickHandler(file, e) { e.stopPropagation(); this.setState({ fileBeingRenamed: file }); trackEvent('ui', 'fileRenameBtnClick'); } dragOverHandler(e) { e.preventDefault(); } dropHandler(e) { e.preventDefault(); // Object with `children` key is to simulate a folder structure for root folder this.props.onFileDrop(e.dataTransfer.getData('text/plain'), { children: this.props.files }); // e.currentTarget.style.outline = null; } render() { const { files, onFileSelect, selectedFile, onRemoveFile } = this.props; const moreProps = { onRemoveBtnClick: this.removeFileClickHandler.bind(this), onRenameBtnClick: this.renameFileClickHandler.bind(this), onNameInputBlur: this.renameFile.bind(this), onNameInputKeyUp: this.renameFileNameInputKeyUpHandler.bind(this), fileBeingRenamed: this.state.fileBeingRenamed }; return ( ); } }