diff --git a/web/src/components/tree.tsx b/web/src/components/tree.tsx new file mode 100644 index 0000000..e728b51 --- /dev/null +++ b/web/src/components/tree.tsx @@ -0,0 +1,138 @@ +import React, { useEffect, useState } from 'react' +import { Menu, MenuItem } from '@mui/material' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import ChevronRightIcon from '@mui/icons-material/ChevronRight' + +import { TreeNode } from '../utils/types' + +type TreeViewProps = { + node: TreeNode + selectedFilePath: string + handleSelect: (filePath: string) => void + defaultExpanded?: string[] +} + +const TreeView = ({ + node, + selectedFilePath, + handleSelect, + defaultExpanded +}: TreeViewProps) => { + return ( + + ) +} + +export default TreeView + +type TreeViewNodeProps = { + node: TreeNode + selectedFilePath: string + handleSelect: (filePath: string) => void + defaultExpanded?: string[] +} + +const TreeViewNode = ({ + node, + selectedFilePath, + handleSelect, + defaultExpanded +}: TreeViewNodeProps) => { + const [childVisible, setChildVisibility] = useState(false) + const [contextMenu, setContextMenu] = useState<{ + mouseX: number + mouseY: number + } | null>(null) + + const handleContextMenu = (event: React.MouseEvent) => { + event.preventDefault() + event.stopPropagation() + setContextMenu( + contextMenu === null + ? { + mouseX: event.clientX + 2, + mouseY: event.clientY - 6 + } + : null + ) + } + + const hasChild = node.children.length ? true : false + + const handleItemClick = () => { + if (node.children.length) { + setChildVisibility((v) => !v) + return + } + + if (!node.name.includes('.')) return + + handleSelect(node.relativePath) + } + + useEffect(() => { + if (defaultExpanded && defaultExpanded[0] === node.relativePath) { + setChildVisibility(true) + defaultExpanded.shift() + } + }, [defaultExpanded, node.relativePath]) + + return ( +
+
  • +
    handleItemClick()} + > + {hasChild && + (childVisible ? : )} +
    {node.name}
    +
    + + {hasChild && + childVisible && + node.children.map((child, index) => ( + + ))} +
  • + setContextMenu(null)} + anchorReference="anchorPosition" + anchorPosition={ + contextMenu !== null + ? { top: contextMenu.mouseY, left: contextMenu.mouseX } + : undefined + } + > + {hasChild && + ['Add Folder', 'Add File'].map((item) => ( + {item} + ))} + Rename + Delete + +
    + ) +} diff --git a/web/src/index.css b/web/src/index.css index 34d605c..6a506a5 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -25,3 +25,15 @@ code { padding: '5px 10px'; margin-top: '10px'; } + +.tree-item-label { + display: flex; +} + +.tree-item-label.selected { + background: lightgoldenrodyellow; +} + +.tree-item-label:hover { + background: lightgray; +} diff --git a/web/src/utils/types.ts b/web/src/utils/types.ts index 4f0a80a..6d48d9b 100644 --- a/web/src/utils/types.ts +++ b/web/src/utils/types.ts @@ -30,3 +30,9 @@ export interface RegisterPermissionPayload { principalType: string principalId: number } + +export interface TreeNode { + name: string + relativePath: string + children: Array +}