diff --git a/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/fileTreeItem.tsx b/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/fileTreeItem.tsx index 78fb186..109c98f 100644 --- a/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/fileTreeItem.tsx +++ b/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/fileTreeItem.tsx @@ -1,3 +1,4 @@ +import { FileTreeItemContextMenu, FileTreeItemLabel } from '@/features/editor/components/fileTreeView/fileTreeItem'; import { useWorkspaceContext } from '@/features/editor/hooks'; import { getIconFromFileType, isExpandable } from '@/features/editor/utils'; import { FileTypes } from '@/types'; @@ -12,8 +13,7 @@ import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDr import { unstable_useTreeItem2 as useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; import { animated, useSpring } from '@react-spring/web'; import clsx from 'clsx'; -import React from 'react'; -import { FileTreeItemLabel } from '.'; +import React, { useState } from 'react'; const StyledFileTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ //color: theme.palette.mode === 'light' ? theme.palette.grey[800] : theme.palette.grey[400], @@ -136,6 +136,11 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem( } const Workspace = useWorkspaceContext(); + const [contextMenu, setContextMenu] = useState<(EventTarget & HTMLDivElement) | null>(null); + const [contextMenuPosition, setContextMenuPosition] = useState<{ top: number; left: number }>({ + top: 0, + left: 0, + }); const handleClick = (newId: string, newLabel: string, newType: FileTypes) => { if (newType === FileTypes.FOLDER) return; @@ -143,6 +148,21 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem( Workspace.update(newId, newLabel, newType); }; + const handleOpenContextMenu = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + setContextMenu(event.currentTarget); + setContextMenuPosition({ + top: event.clientY, + left: event.clientX, + }); + }; + + const handleCloseContextMenu = () => { + setContextMenu(null); + setContextMenuPosition({ top: 0, left: 0 }); + }; + return ( @@ -152,6 +172,7 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem( if (getContentProps().onClick) getContentProps().onClick(event); handleClick(item.id, item.label, item.fileType); }, + onContextMenu: (event) => handleOpenContextMenu(event), className: clsx('content', { 'Mui-expanded': status.expanded, 'Mui-selected': status.selected, @@ -168,6 +189,12 @@ export const FileTreeItem = React.forwardRef(function CustomTreeItem( + {children && } diff --git a/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/index.ts b/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/index.ts index 5180b58..d7190c2 100644 --- a/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/index.ts +++ b/app/front-end/src/features/editor/components/fileTreeView/fileTreeItem/index.ts @@ -1,2 +1,9 @@ export { FileTreeItem } from './fileTreeItem'; +export { FileTreeItemContextMenu } from './fileTreeItemContextMenu'; +export type { FileTreeItemContextMenuProps } from './fileTreeItemContextMenu'; +export { FileTreeItemContextMenuConfirmationDialog } from './fileTreeItemContextMenuConfirmationDialog'; +export type { FileTreeItemContextMenuConfirmationDialogProps } from './fileTreeItemContextMenuConfirmationDialog'; +export { FileTreeItemContextMenuStyledDialog } from './fileTreeItemContextMenuStyledDialog'; +export { FileTreeItemContextMenuTextfieldDialog } from './fileTreeItemContextMenuTextfieldDialog'; +export type { FileTreeItemContextMenuTextfieldDialogProps } from './fileTreeItemContextMenuTextfieldDialog'; export { FileTreeItemLabel } from './fileTreeItemLabel'; diff --git a/app/front-end/src/features/editor/components/fileTreeView/fileTreeView.tsx b/app/front-end/src/features/editor/components/fileTreeView/fileTreeView.tsx index 105dfb5..01067d6 100644 --- a/app/front-end/src/features/editor/components/fileTreeView/fileTreeView.tsx +++ b/app/front-end/src/features/editor/components/fileTreeView/fileTreeView.tsx @@ -1,12 +1,12 @@ +import { FileTreeItem, FileTreeItemContextMenu } from '@/features/editor/components/fileTreeView/fileTreeItem'; import { FileTreeViewItemProps } from '@/features/editor/types'; import { useSessionContext } from '@/hooks'; -import { axios } from '@/lib'; -import { Endpoints } from '@/types'; -import { Box, LinearProgress } from '@mui/material'; +import { axios, socket } from '@/lib'; +import { Endpoints, Events } from '@/types'; +import { Box, Button, LinearProgress } from '@mui/material'; import { TreeViewBaseItem } from '@mui/x-tree-view'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useEffect, useState } from 'react'; -import { FileTreeItem } from './fileTreeItem'; +import { useCallback, useEffect, useState } from 'react'; declare module 'react' { interface CSSProperties { @@ -40,23 +40,51 @@ export const FileTreeView: React.FC = () => { const [fileTreeViewData, setFileTreeViewData] = useState[]>([]); const [isLoading, setIsLoading] = useState(false); + const [contextMenu, setContextMenu] = useState<(EventTarget & HTMLButtonElement) | null>(null); + const [contextMenuPosition, setContextMenuPosition] = useState<{ top: number; left: number }>({ + top: 0, + left: 0, + }); + + const handleOpenContextMenu = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + setContextMenu(event.currentTarget); + setContextMenuPosition({ + top: event.clientY, + left: event.clientX, + }); + }; + + const handleCloseContextMenu = () => { + setContextMenu(null); + setContextMenuPosition({ top: 0, left: 0 }); + }; + + const getWorkspace = useCallback(async () => { + setIsLoading(true); + + try { + const response = await axios.get(Endpoints.WORKSPACE); + setFileTreeViewData(response.data); + } catch (error) { + console.error('Failed to fetch workspace data:', error); + } finally { + setIsLoading(false); + } + }, []); useEffect(() => { - const getWorkspace = async () => { - setIsLoading(true); + if (connected) { + getWorkspace(); + } - try { - const response = await axios.get(Endpoints.WORKSPACE); - setFileTreeViewData(response.data); - } catch (error) { - console.error('Failed to fetch workspace data:', error); - } finally { - setIsLoading(false); - } - }; + socket.on(Events.WORKSPACE_UPDATE_FEEDBACK_EVENT, getWorkspace); - if (connected) getWorkspace(); - }, [connected]); + return () => { + socket.off(Events.WORKSPACE_UPDATE_FEEDBACK_EVENT); + }; + }, [connected, getWorkspace]); return ( <> @@ -65,11 +93,22 @@ export const FileTreeView: React.FC = () => { ) : ( - + <> + + + + )} );