From 0ce94a553e53bfcdbd6273b26b322095a080a341 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Wed, 20 Jul 2022 16:45:45 +0500 Subject: [PATCH] feat: implemented functionality for adding file/folder from sidebar context menu --- web/src/components/nameInputModal.tsx | 82 ++++++++++++++++++++ web/src/components/tree.tsx | 49 +++++++++++- web/src/containers/Studio/index.tsx | 1 + web/src/containers/Studio/sideBar.tsx | 107 ++++++++++++++++++++++++-- 4 files changed, 232 insertions(+), 7 deletions(-) create mode 100644 web/src/components/nameInputModal.tsx diff --git a/web/src/components/nameInputModal.tsx b/web/src/components/nameInputModal.tsx new file mode 100644 index 0000000..2343b74 --- /dev/null +++ b/web/src/components/nameInputModal.tsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react' + +import { Button, DialogActions, DialogContent, TextField } from '@mui/material' + +import { BootstrapDialogTitle } from './dialogTitle' +import { BootstrapDialog } from './modal' + +type NameInputModalProps = { + open: boolean + setOpen: React.Dispatch> + isFolder: boolean + add: (name: string) => void +} + +const NameInputModal = ({ + open, + setOpen, + isFolder, + add +}: NameInputModalProps) => { + const [name, setName] = useState('') + const [hasError, setHasError] = useState(false) + const [errorText, setErrorText] = useState('') + + const handleChange = (event: React.ChangeEvent) => { + const value = event.target.value + + const folderNameRegex = /[`!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/ + const fileNameRegex = /[`!@#$%^&*()_+\-=[\]{};':"\\|,<>/?~]/ + const fileNameExtensionRegex = /.(exe|sh|htaccess)$/i + + const specialChars = isFolder ? folderNameRegex : fileNameRegex + + if (specialChars.test(value)) { + setHasError(true) + setErrorText('can not have special characters') + } else if (!isFolder && fileNameExtensionRegex.test(value)) { + setHasError(true) + setErrorText('can not add file with extensions [exe, sh, htaccess]') + } else { + setHasError(false) + setErrorText('') + } + + setName(value) + } + + return ( + setOpen(false)} open={open}> + + {isFolder ? 'Add Folder' : 'Add File'} + + + + + + + + + + ) +} + +export default NameInputModal diff --git a/web/src/components/tree.tsx b/web/src/components/tree.tsx index 9b7db0f..b244b05 100644 --- a/web/src/components/tree.tsx +++ b/web/src/components/tree.tsx @@ -4,6 +4,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import ChevronRightIcon from '@mui/icons-material/ChevronRight' import DeleteConfirmationModal from './deleteConfirmationModal' +import NameInputModal from './nameInputModal' import { TreeNode } from '../utils/types' @@ -12,6 +13,8 @@ type Props = { selectedFilePath: string handleSelect: (filePath: string) => void deleteNode: (path: string, isFolder: boolean) => void + addFile: (path: string) => void + addFolder: (path: string) => void defaultExpanded?: string[] } @@ -20,6 +23,8 @@ const TreeView = ({ selectedFilePath, handleSelect, deleteNode, + addFile, + addFolder, defaultExpanded }: Props) => { return ( @@ -35,6 +40,8 @@ const TreeView = ({ selectedFilePath={selectedFilePath} handleSelect={handleSelect} deleteNode={deleteNode} + addFile={addFile} + addFolder={addFolder} defaultExpanded={defaultExpanded} /> @@ -48,12 +55,16 @@ const TreeViewNode = ({ selectedFilePath, handleSelect, deleteNode, + addFile, + addFolder, defaultExpanded }: Props) => { const [deleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false) const [deleteConfirmationModalMessage, setDeleteConfirmationModalMessage] = useState('') + const [nameInputModalOpen, setNameInputModalOpen] = useState(false) + const [nameInputModalForFolder, setNameInputModalForFolder] = useState(false) const [childVisible, setChildVisibility] = useState(false) const [contextMenu, setContextMenu] = useState<{ mouseX: number @@ -108,6 +119,25 @@ const TreeViewNode = ({ deleteNode(node.relativePath, node.isFolder) } + const handleNewFolderItemClick = () => { + setContextMenu(null) + setNameInputModalOpen(true) + setNameInputModalForFolder(true) + } + + const handleNewFileItemClick = () => { + setContextMenu(null) + setNameInputModalOpen(true) + setNameInputModalForFolder(false) + } + + const addFileFolder = (name: string) => { + setNameInputModalOpen(false) + const path = node.relativePath + '/' + name + if (nameInputModalForFolder) addFolder(path) + else addFile(path) + } + return (
  • @@ -131,6 +161,8 @@ const TreeViewNode = ({ selectedFilePath={selectedFilePath} handleSelect={handleSelect} deleteNode={deleteNode} + addFile={addFile} + addFolder={addFolder} defaultExpanded={defaultExpanded} /> ))} @@ -141,6 +173,12 @@ const TreeViewNode = ({ message={deleteConfirmationModalMessage} _delete={deleteConfirm} /> + setContextMenu(null)} @@ -153,7 +191,16 @@ const TreeViewNode = ({ > {node.isFolder && ['Add Folder', 'Add File'].map((item) => ( - {item} + + item === 'Add Folder' + ? handleNewFolderItemClick() + : handleNewFileItemClick() + } + > + {item} + ))} Rename diff --git a/web/src/containers/Studio/index.tsx b/web/src/containers/Studio/index.tsx index 1dd7051..c8f9751 100644 --- a/web/src/containers/Studio/index.tsx +++ b/web/src/containers/Studio/index.tsx @@ -88,6 +88,7 @@ const Studio = () => { directoryData={directoryData} handleSelect={handleSelect} removeFileFromTree={removeFileFromTree} + refreshSideBar={fetchDirectoryData} /> void removeFileFromTree: (filePath: string) => void + refreshSideBar: () => void } const SideBar = ({ selectedFilePath, directoryData, handleSelect, - removeFileFromTree + removeFileFromTree, + refreshSideBar }: Props) => { + const [isLoading, setIsLoading] = useState(false) + const [openModal, setOpenModal] = useState(false) + const [modalTitle, setModalTitle] = useState('') + const [modalPayload, setModalPayload] = useState('') + const [openSnackbar, setOpenSnackbar] = useState(false) + const [snackbarMessage, setSnackbarMessage] = useState('') + const [snackbarSeverity, setSnackbarSeverity] = useState( + AlertSeverityType.Success + ) const defaultExpanded = useMemo(() => { const splittedPath = selectedFilePath.split('/') const arr = [''] @@ -34,6 +47,7 @@ const SideBar = ({ }, [selectedFilePath]) const deleteNode = (path: string, isFolder: boolean) => { + setIsLoading(true) const axiosPromise = axios.delete( `/SASjsApi/drive/${ isFolder ? `folder?_folderPath=${path}` : `file?_filePath=${path}` @@ -41,10 +55,71 @@ const SideBar = ({ ) axiosPromise - .then(() => removeFileFromTree(path)) - .catch((err) => { - console.log(err) + .then(() => { + removeFileFromTree(path) + setSnackbarMessage('Deleted!') + setSnackbarSeverity(AlertSeverityType.Success) + setOpenSnackbar(true) }) + .catch((err) => { + setModalTitle('Abort') + setModalPayload( + typeof err.response.data === 'object' + ? JSON.stringify(err.response.data) + : err.response.data + ) + setOpenModal(true) + }) + .finally(() => setIsLoading(false)) + } + + const addFile = (filePath: string) => { + const formData = new FormData() + const stringBlob = new Blob([''], { type: 'text/plain' }) + formData.append('file', stringBlob) + formData.append('filePath', filePath) + + setIsLoading(true) + axios + .post('/SASjsApi/drive/file', formData) + .then(() => { + setSnackbarMessage('File added!') + setSnackbarSeverity(AlertSeverityType.Success) + setOpenSnackbar(true) + refreshSideBar() + }) + .catch((err) => { + setModalTitle('Abort') + setModalPayload( + typeof err.response.data === 'object' + ? JSON.stringify(err.response.data) + : err.response.data + ) + setOpenModal(true) + }) + .finally(() => setIsLoading(false)) + } + + const addFolder = (folderPath: string) => { + setIsLoading(true) + axios + .post('/SASjsApi/drive/folder', { folderPath }) + .then(() => { + setSnackbarMessage('Folder added!') + setSnackbarSeverity(AlertSeverityType.Success) + setOpenSnackbar(true) + refreshSideBar() + }) + .catch((err) => { + setModalTitle('Abort') + setModalPayload( + typeof err.response.data === 'object' + ? JSON.stringify(err.response.data) + : err.response.data + ) + setOpenModal(true) + }) + .finally(() => setIsLoading(false)) } return ( @@ -56,6 +131,12 @@ const SideBar = ({ [`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box' } }} > + theme.zIndex.drawer + 1 }} + open={isLoading} + > + + {directoryData && ( @@ -64,10 +145,24 @@ const SideBar = ({ selectedFilePath={selectedFilePath} handleSelect={handleSelect} deleteNode={deleteNode} + addFile={addFile} + addFolder={addFolder} defaultExpanded={defaultExpanded} /> )} + + ) }