Skip to content

Commit

Permalink
Add drag and drop folders and files to open new directory.
Browse files Browse the repository at this point in the history
  • Loading branch information
We-Gold committed Aug 7, 2024
1 parent 155f4c8 commit dc6ff70
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@ import Header from '@renderer/components/Header/Header'
import styles from './FileExplorer.module.css'
import FileEntry from './components/FileEntry/FileEntry'
import { DragOverlay, useDroppable } from '@dnd-kit/core'
import { MouseEvent, useCallback, useContext, useEffect, useState } from 'react'
import { MouseEvent, useCallback, useContext, useEffect, useRef, useState } from 'react'
import DraggableEntry from './components/DraggableEntry/DraggableEntry'
import { DragContext } from '@renderer/contexts/DragContext'
import { DirectoryContext, FileSystemNode } from '@renderer/contexts/DirectoryContext'
import useContextMenu from '@renderer/hooks/use-context-menu'
import ContextMenu, { ContextMenuAction } from '@renderer/components/ContextMenu/ContextMenu'
import { deleteFSItem, join, moveFSItem, newFolder, renameFSItem } from '@renderer/interfaces/file'
import {
deleteFSItem,
join,
moveFSItem,
newFolder,
renameFSItem,
isPathFile,
directory
} from '@renderer/interfaces/file'

function FileExplorer(): JSX.Element {
const { clearDragEvent, parentChildData, active } = useContext(DragContext)
const { nodes, directoryName, directoryPath } = useContext(DirectoryContext)
const { nodes, directoryName, directoryPath, setDirectory } = useContext(DirectoryContext)
const { point, clicked, data, handleContextMenu } = useContextMenu<FileSystemNode>()

const [renamePath, setRenamePath] = useState<string | null>(null)
Expand Down Expand Up @@ -144,13 +152,72 @@ function FileExplorer(): JSX.Element {
handleDrop()
}, [isOver, parentChildData, directoryPath])

const dropFileRef = useRef<HTMLDivElement>(null)

// Set the drop node ref
useEffect(() => {
setDropNodeRef(dropFileRef.current)
}, [dropFileRef])

const [customDragOver, setCustomDragOver] = useState(false)

useEffect(() => {
const handleDrop = async (event: DragEvent): Promise<void> => {
event.preventDefault()

setCustomDragOver(false)

if (!event.dataTransfer) return

const files = event.dataTransfer.files

// Determine if the dropped item is a folder
const item = files[0]

const isFolder = item.type === '' || !isPathFile(item.path)

if (isFolder) {
// Send the folder to the main process
setDirectory(item.path)
} else {
// Get the folder path
const folderPath = directory(item.path)

setDirectory(folderPath)
}
}

const handleDragOver = (event: DragEvent): void => {
event.preventDefault()
setCustomDragOver(true)
}
const handleDragLeave = (event: DragEvent): void => {
event.preventDefault()
setCustomDragOver(false)
}

if (dropFileRef.current) {
dropFileRef.current.addEventListener('drop', handleDrop)
dropFileRef.current.addEventListener('dragover', handleDragOver)
dropFileRef.current.addEventListener('dragleave', handleDragLeave)
}

return (): void => {
if (dropFileRef.current) {
dropFileRef.current.removeEventListener('drop', handleDrop)
dropFileRef.current.removeEventListener('dragover', handleDragOver)
dropFileRef.current.removeEventListener('dragleave', handleDragLeave)
}
}
}, [directoryPath, dropFileRef])

return (
<>
{clicked && <ContextMenu {...point} actions={contextActions} />}
<div className={styles.fileExplorerPanel}>
<div
className={styles.fileExplorerInnerPanel}
ref={setDropNodeRef}
ref={dropFileRef}
onContextMenu={(e) => {
if (!directoryName || !directoryPath) return

Expand All @@ -165,12 +232,12 @@ function FileExplorer(): JSX.Element {
>
{directoryName ? (
<>
<Header text={directoryName} highlight={isOver} />
<Header text={directoryName} highlight={isOver || customDragOver} />
<div>{fileEntries}</div>
</>
) : (
<>
<Header text={'Files'} />
<Header text={'Files'} highlight={customDragOver} />
<div className={`poppins-medium ${styles.helpText}`}>
File &gt; Open Folder
</div>
Expand Down
27 changes: 16 additions & 11 deletions src/renderer/src/contexts/DirectoryContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type DirectoryContextValue = {
nodes: NodeChildren
directoryPath: string | null
directoryName: string | null
setDirectory: (directory: string) => void
}

export const DirectoryContext = createContext<DirectoryContextValue>(null as never)
Expand Down Expand Up @@ -37,20 +38,24 @@ function DirectoryProvider({ children }: { children: React.ReactNode }): JSX.Ele
const [directoryPath, setDirectoryPath] = useState<string | null>(null)
const [nodes, setNodes] = useState<NodeChildren>({})

const setDirectory = useCallback((directory: string): void => {
if (!directory || directory.length === 0) return

setDirectoryPath(directory)

// Clean up the directory name
const directorySplit = directory.split(/[/\\]/)
setDirectoryName(directorySplit[directorySplit.length - 1])

// Clear the folder contents
setNodes({})
}, [])

useEffect(() => {
const clearSelectedFolderListener = window.electron.ipcRenderer.on(
'selected-folder',
(_, directory) => {
if (!directory || directory.length === 0) return

setDirectoryPath(directory)

// Clean up the directory name
const directorySplit = directory.split(/[/\\]/)
setDirectoryName(directorySplit[directorySplit.length - 1])

// Clear the folder contents
setNodes({})
setDirectory(directory)
}
)

Expand All @@ -76,7 +81,7 @@ function DirectoryProvider({ children }: { children: React.ReactNode }): JSX.Ele
}, [directoryPath])

return (
<DirectoryContext.Provider value={{ nodes, directoryPath, directoryName }}>
<DirectoryContext.Provider value={{ nodes, directoryPath, directoryName, setDirectory }}>
{children}
</DirectoryContext.Provider>
)
Expand Down
18 changes: 18 additions & 0 deletions src/renderer/src/interfaces/file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,21 @@ export const renameFSItem = async (oldPath: string, newPath: string): Promise<vo
export const moveFSItem = async (oldPath: string, newPath: string): Promise<void> => {
return renameFSItem(oldPath, newPath)
}

export const assumeSeparator = (path: string): string => {
return path.includes('/') ? '/' : '\\'
}

export const basename = (path: string): string => {
return path.split(assumeSeparator(path)).pop() || ''
}

export const directory = (path: string): string => {
const separator = assumeSeparator(path)

return path.split(separator).slice(0, -1).join(separator)
}

export const isPathFile = (path: string): boolean => {
return basename(path).includes('.')
}

0 comments on commit dc6ff70

Please sign in to comment.