mirror of
https://github.com/sasjs/server.git
synced 2025-12-11 19:44:35 +00:00
fix: remove drive component
This commit is contained in:
@@ -6,7 +6,6 @@ import { theme } from './theme'
|
|||||||
import Login from './components/login'
|
import Login from './components/login'
|
||||||
import Header from './components/header'
|
import Header from './components/header'
|
||||||
import Home from './components/home'
|
import Home from './components/home'
|
||||||
import Drive from './containers/Drive'
|
|
||||||
import Studio from './containers/Studio'
|
import Studio from './containers/Studio'
|
||||||
import Settings from './containers/Settings'
|
import Settings from './containers/Settings'
|
||||||
|
|
||||||
@@ -36,7 +35,6 @@ function App() {
|
|||||||
<Header />
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/SASjsDrive" element={<Drive />} />
|
|
||||||
<Route path="/SASjsStudio" element={<Studio />} />
|
<Route path="/SASjsStudio" element={<Studio />} />
|
||||||
<Route path="/SASjsSettings" element={<Settings />} />
|
<Route path="/SASjsSettings" element={<Settings />} />
|
||||||
<Route path="/SASjsLogon" element={<AuthCode />} />
|
<Route path="/SASjsLogon" element={<AuthCode />} />
|
||||||
|
|||||||
@@ -83,12 +83,6 @@ const Header = (props: any) => {
|
|||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
>
|
>
|
||||||
<Tab label="Home" value="/" to="/" component={Link} />
|
<Tab label="Home" value="/" to="/" component={Link} />
|
||||||
<Tab
|
|
||||||
label="Drive"
|
|
||||||
value="/SASjsDrive"
|
|
||||||
to="/SASjsDrive"
|
|
||||||
component={Link}
|
|
||||||
/>
|
|
||||||
<Tab
|
<Tab
|
||||||
label="Studio"
|
label="Studio"
|
||||||
value="/SASjsStudio"
|
value="/SASjsStudio"
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react'
|
|
||||||
import { useLocation } from 'react-router-dom'
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import CssBaseline from '@mui/material/CssBaseline'
|
|
||||||
import Box from '@mui/material/Box'
|
|
||||||
|
|
||||||
import SideBar from './sideBar'
|
|
||||||
import Main from './main'
|
|
||||||
|
|
||||||
export interface TreeNode {
|
|
||||||
name: string
|
|
||||||
relativePath: string
|
|
||||||
absolutePath: string
|
|
||||||
children: Array<TreeNode>
|
|
||||||
}
|
|
||||||
|
|
||||||
const Drive = () => {
|
|
||||||
const location = useLocation()
|
|
||||||
const baseUrl = window.location.origin
|
|
||||||
|
|
||||||
const [selectedFilePath, setSelectedFilePath] = useState('')
|
|
||||||
const [directoryData, setDirectoryData] = useState<TreeNode | null>(null)
|
|
||||||
|
|
||||||
const setFilePathOnMount = useCallback(() => {
|
|
||||||
const queryParams = new URLSearchParams(location.search)
|
|
||||||
setSelectedFilePath(queryParams.get('filePath') ?? '')
|
|
||||||
}, [location.search])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
axios
|
|
||||||
.get(`/SASjsApi/drive/fileTree`)
|
|
||||||
.then((res: any) => {
|
|
||||||
if (res.data && res.data?.status === 'success') {
|
|
||||||
setDirectoryData(res.data.tree)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
setFilePathOnMount()
|
|
||||||
}, [setFilePathOnMount])
|
|
||||||
|
|
||||||
const handleSelect = (node: TreeNode) => {
|
|
||||||
if (node.children.length) return
|
|
||||||
|
|
||||||
if (!node.name.includes('.')) return
|
|
||||||
|
|
||||||
window.history.pushState(
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
`${baseUrl}/#/SASjsDrive?filePath=${node.relativePath}`
|
|
||||||
)
|
|
||||||
setSelectedFilePath(node.relativePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeFileFromTree = (path: string) => {
|
|
||||||
if (directoryData) {
|
|
||||||
const newTree = JSON.parse(JSON.stringify(directoryData)) as TreeNode
|
|
||||||
findAndRemoveNode(newTree, newTree, path)
|
|
||||||
setDirectoryData(newTree)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const findAndRemoveNode = (
|
|
||||||
node: TreeNode,
|
|
||||||
parentNode: TreeNode,
|
|
||||||
path: string
|
|
||||||
) => {
|
|
||||||
if (node.relativePath === path) {
|
|
||||||
removeNodeFromParent(parentNode, path)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (Array.isArray(node.children)) {
|
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
|
||||||
if (findAndRemoveNode(node.children[i], node, path)) return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeNodeFromParent = (parent: TreeNode, path: string) => {
|
|
||||||
const index = parent.children.findIndex(
|
|
||||||
(node) => node.relativePath === path
|
|
||||||
)
|
|
||||||
if (index !== -1) {
|
|
||||||
parent.children.splice(index, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: 'flex' }}>
|
|
||||||
<CssBaseline />
|
|
||||||
<SideBar
|
|
||||||
selectedFilePath={selectedFilePath}
|
|
||||||
directoryData={directoryData}
|
|
||||||
handleSelect={handleSelect}
|
|
||||||
/>
|
|
||||||
<Main
|
|
||||||
selectedFilePath={selectedFilePath}
|
|
||||||
removeFileFromTree={removeFileFromTree}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Drive
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import Editor from 'react-monaco-editor'
|
|
||||||
|
|
||||||
import Box from '@mui/material/Box'
|
|
||||||
import Paper from '@mui/material/Paper'
|
|
||||||
import Stack from '@mui/material/Stack'
|
|
||||||
import Button from '@mui/material/Button'
|
|
||||||
import Toolbar from '@mui/material/Toolbar'
|
|
||||||
import CircularProgress from '@mui/material/CircularProgress'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
selectedFilePath: string
|
|
||||||
removeFileFromTree: (path: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const Main = (props: Props) => {
|
|
||||||
const baseUrl = window.location.origin
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const [fileContentBeforeEdit, setFileContentBeforeEdit] = useState('')
|
|
||||||
const [fileContent, setFileContent] = useState('')
|
|
||||||
const [editMode, setEditMode] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.selectedFilePath) {
|
|
||||||
setIsLoading(true)
|
|
||||||
axios
|
|
||||||
.get(`/SASjsApi/drive/file?_filePath=${props.selectedFilePath}`)
|
|
||||||
.then((res: any) => {
|
|
||||||
setFileContent(res.data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [props.selectedFilePath])
|
|
||||||
|
|
||||||
const handleDeleteBtnClick = () => {
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
const filePath = props.selectedFilePath
|
|
||||||
|
|
||||||
axios
|
|
||||||
.delete(`/SASjsApi/drive/file?_filePath=${filePath}`)
|
|
||||||
.then((res) => {
|
|
||||||
setFileContent('')
|
|
||||||
props.removeFileFromTree(filePath)
|
|
||||||
window.history.pushState('', '', `${baseUrl}/#/SASjsDrive`)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEditSaveBtnClick = () => {
|
|
||||||
if (!editMode) {
|
|
||||||
setFileContentBeforeEdit(fileContent)
|
|
||||||
setEditMode(true)
|
|
||||||
} else {
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
const formData = new FormData()
|
|
||||||
|
|
||||||
const stringBlob = new Blob([fileContent], { type: 'text/plain' })
|
|
||||||
formData.append('file', stringBlob, 'filename.sas')
|
|
||||||
formData.append('filePath', props.selectedFilePath)
|
|
||||||
|
|
||||||
axios
|
|
||||||
.patch(`/SASjsApi/drive/file`, formData)
|
|
||||||
.then((res) => {
|
|
||||||
setEditMode(false)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCancelExecuteBtnClick = () => {
|
|
||||||
if (editMode) {
|
|
||||||
setFileContent(fileContentBeforeEdit)
|
|
||||||
setEditMode(false)
|
|
||||||
} else {
|
|
||||||
window.open(
|
|
||||||
`${baseUrl}/SASjsApi/stp/execute?_program=${props.selectedFilePath}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
|
||||||
<Toolbar />
|
|
||||||
<Paper
|
|
||||||
sx={{
|
|
||||||
height: '75vh',
|
|
||||||
padding: '10px',
|
|
||||||
overflow: 'auto',
|
|
||||||
position: 'relative'
|
|
||||||
}}
|
|
||||||
elevation={3}
|
|
||||||
>
|
|
||||||
{isLoading && (
|
|
||||||
<CircularProgress
|
|
||||||
style={{ position: 'absolute', left: '50%', top: '50%' }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isLoading && props?.selectedFilePath && !editMode && (
|
|
||||||
<code style={{ whiteSpace: 'break-spaces' }}>{fileContent}</code>
|
|
||||||
)}
|
|
||||||
{!isLoading && props?.selectedFilePath && editMode && (
|
|
||||||
<Editor
|
|
||||||
height="95%"
|
|
||||||
language="sas"
|
|
||||||
value={fileContent}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (val) setFileContent(val)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
<Stack
|
|
||||||
spacing={3}
|
|
||||||
direction="row"
|
|
||||||
sx={{ justifyContent: 'center', marginTop: '20px' }}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleDeleteBtnClick}
|
|
||||||
disabled={isLoading || !props?.selectedFilePath}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleEditSaveBtnClick}
|
|
||||||
disabled={isLoading || !props?.selectedFilePath}
|
|
||||||
>
|
|
||||||
{!editMode ? 'Edit' : 'Save'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleCancelExecuteBtnClick}
|
|
||||||
disabled={isLoading || !props?.selectedFilePath}
|
|
||||||
>
|
|
||||||
{editMode ? 'Cancel' : 'Execute'}
|
|
||||||
</Button>
|
|
||||||
{props?.selectedFilePath && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
component={Link}
|
|
||||||
to={`/SASjsStudio?_program=${props.selectedFilePath}`}
|
|
||||||
>
|
|
||||||
Open in Studio
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Main
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import React, { useMemo } from 'react'
|
|
||||||
|
|
||||||
import { makeStyles } from '@mui/styles'
|
|
||||||
|
|
||||||
import Box from '@mui/material/Box'
|
|
||||||
import Drawer from '@mui/material/Drawer'
|
|
||||||
import Toolbar from '@mui/material/Toolbar'
|
|
||||||
import ListItem from '@mui/material/ListItem'
|
|
||||||
import ListItemText from '@mui/material/ListItemText'
|
|
||||||
|
|
||||||
import TreeView from '@mui/lab/TreeView'
|
|
||||||
import TreeItem from '@mui/lab/TreeItem'
|
|
||||||
|
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
|
||||||
|
|
||||||
import { TreeNode } from '.'
|
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
|
||||||
root: {
|
|
||||||
'& .MuiTreeItem-content': {
|
|
||||||
width: 'auto'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
listItem: {
|
|
||||||
padding: 0
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const drawerWidth = 240
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
selectedFilePath: string
|
|
||||||
directoryData: TreeNode | null
|
|
||||||
handleSelect: (node: TreeNode) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const SideBar = ({ selectedFilePath, directoryData, handleSelect }: Props) => {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const defaultExpanded = useMemo(() => {
|
|
||||||
const splittedPath = selectedFilePath.split('/')
|
|
||||||
const arr = ['']
|
|
||||||
let nodeId = ''
|
|
||||||
splittedPath.forEach((path) => {
|
|
||||||
if (path !== '') {
|
|
||||||
nodeId += '/' + path
|
|
||||||
arr.push(nodeId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return arr
|
|
||||||
}, [selectedFilePath])
|
|
||||||
|
|
||||||
const renderTree = (nodes: TreeNode) => (
|
|
||||||
<TreeItem
|
|
||||||
classes={{ root: classes.root }}
|
|
||||||
key={nodes.relativePath}
|
|
||||||
nodeId={nodes.relativePath}
|
|
||||||
label={
|
|
||||||
<ListItem
|
|
||||||
className={classes.listItem}
|
|
||||||
onClick={() => handleSelect(nodes)}
|
|
||||||
>
|
|
||||||
<ListItemText primary={nodes.name} />
|
|
||||||
</ListItem>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{Array.isArray(nodes.children)
|
|
||||||
? nodes.children.map((node) => renderTree(node))
|
|
||||||
: null}
|
|
||||||
</TreeItem>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer
|
|
||||||
variant="permanent"
|
|
||||||
sx={{
|
|
||||||
width: drawerWidth,
|
|
||||||
flexShrink: 0,
|
|
||||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box' }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Toolbar />
|
|
||||||
<Box sx={{ overflow: 'auto' }}>
|
|
||||||
{directoryData && (
|
|
||||||
<TreeView
|
|
||||||
defaultCollapseIcon={<ExpandMoreIcon />}
|
|
||||||
defaultExpandIcon={<ChevronRightIcon />}
|
|
||||||
defaultExpanded={defaultExpanded}
|
|
||||||
selected={defaultExpanded.slice(-1)}
|
|
||||||
>
|
|
||||||
{renderTree(directoryData)}
|
|
||||||
</TreeView>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Drawer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SideBar
|
|
||||||
Reference in New Issue
Block a user