1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

feat: implemented functionality for adding file/folder from sidebar context menu

This commit is contained in:
2022-07-20 16:45:45 +05:00
parent 941917e508
commit 0ce94a553e
4 changed files with 232 additions and 7 deletions

View File

@@ -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<React.SetStateAction<boolean>>
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<HTMLInputElement>) => {
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 (
<BootstrapDialog fullWidth onClose={() => setOpen(false)} open={open}>
<BootstrapDialogTitle id="abort-modal" handleOpen={setOpen}>
{isFolder ? 'Add Folder' : 'Add File'}
</BootstrapDialogTitle>
<DialogContent dividers>
<TextField
fullWidth
variant="outlined"
label={isFolder ? 'Folder Name' : 'File Name'}
value={name}
onChange={handleChange}
error={hasError}
helperText={errorText}
/>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button
variant="contained"
onClick={() => {
add(name)
}}
disabled={hasError || !name}
>
Add
</Button>
</DialogActions>
</BootstrapDialog>
)
}
export default NameInputModal

View File

@@ -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}
/>
</ul>
@@ -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 (
<div onContextMenu={handleContextMenu} style={{ cursor: 'context-menu' }}>
<li style={{ display: 'list-item' }}>
@@ -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}
/>
<NameInputModal
open={nameInputModalOpen}
setOpen={setNameInputModalOpen}
isFolder={nameInputModalForFolder}
add={addFileFolder}
/>
<Menu
open={contextMenu !== null}
onClose={() => setContextMenu(null)}
@@ -153,7 +191,16 @@ const TreeViewNode = ({
>
{node.isFolder &&
['Add Folder', 'Add File'].map((item) => (
<MenuItem key={item}>{item}</MenuItem>
<MenuItem
key={item}
onClick={() =>
item === 'Add Folder'
? handleNewFolderItemClick()
: handleNewFileItemClick()
}
>
{item}
</MenuItem>
))}
<MenuItem>Rename</MenuItem>
<MenuItem disabled={!node.relativePath} onClick={handleDeleteItemClick}>

View File

@@ -88,6 +88,7 @@ const Studio = () => {
directoryData={directoryData}
handleSelect={handleSelect}
removeFileFromTree={removeFileFromTree}
refreshSideBar={fetchDirectoryData}
/>
<SASjsEditor
selectedFilePath={selectedFilePath}

View File

@@ -1,8 +1,10 @@
import React, { useMemo } from 'react'
import React, { useState, useMemo } from 'react'
import axios from 'axios'
import { Box, Drawer, Toolbar } from '@mui/material'
import { Backdrop, Box, CircularProgress, Drawer, Toolbar } from '@mui/material'
import TreeView from '../../components/tree'
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
import Modal from '../../components/modal'
import { TreeNode } from '../../utils/types'
const drawerWidth = 240
@@ -12,14 +14,25 @@ type Props = {
directoryData: TreeNode | null
handleSelect: (filePath: string) => 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>(
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' }
}}
>
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={isLoading}
>
<CircularProgress color="inherit" />
</Backdrop>
<Toolbar />
<Box sx={{ overflow: 'auto' }}>
{directoryData && (
@@ -64,10 +145,24 @@ const SideBar = ({
selectedFilePath={selectedFilePath}
handleSelect={handleSelect}
deleteNode={deleteNode}
addFile={addFile}
addFolder={addFolder}
defaultExpanded={defaultExpanded}
/>
)}
</Box>
<BootstrapSnackbar
open={openSnackbar}
setOpen={setOpenSnackbar}
message={snackbarMessage}
severity={snackbarSeverity}
/>
<Modal
open={openModal}
setOpen={setOpenModal}
title={modalTitle}
payload={modalPayload}
/>
</Drawer>
)
}