-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from AElfProject/feature/file-explorer-v2
Feature/file explorer v2
- Loading branch information
Showing
22 changed files
with
998 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { | ||
ContextMenu, | ||
ContextMenuContent, | ||
ContextMenuItem, | ||
ContextMenuShortcut, | ||
ContextMenuTrigger, | ||
} from "@/components/ui/context-menu"; | ||
import { Pencil, Delete, FilePlus, FolderPlus } from "lucide-react"; | ||
import React from "react"; | ||
|
||
export enum IAction { | ||
RENAME, | ||
DELETE, | ||
NEW_FILE, | ||
NEW_FOLDER, | ||
} | ||
|
||
interface IContextMenuProps extends React.PropsWithChildren { | ||
handleClick: (action: IAction) => void; | ||
} | ||
|
||
export function FileContextMenu({ children, handleClick }: IContextMenuProps) { | ||
return ( | ||
<ContextMenu> | ||
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger> | ||
<ContextMenuContent | ||
className="w-64" | ||
onClick={(e) => { | ||
e.stopPropagation(); | ||
}} | ||
> | ||
<RenameItem onClick={() => handleClick(IAction.RENAME)} /> | ||
<DeleteItem onClick={() => handleClick(IAction.DELETE)} /> | ||
</ContextMenuContent> | ||
</ContextMenu> | ||
); | ||
} | ||
|
||
export function FolderContextMenu({ | ||
children, | ||
handleClick, | ||
}: IContextMenuProps) { | ||
return ( | ||
<ContextMenu> | ||
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger> | ||
<ContextMenuContent | ||
className="w-64" | ||
onClick={(e) => { | ||
e.stopPropagation(); | ||
}} | ||
> | ||
<ContextMenuItem onClick={() => handleClick(IAction.NEW_FILE)}> | ||
<FilePlus className="w-4 h-4 mr-2" /> | ||
<span>New File</span> | ||
<ContextMenuShortcut>Ctrl+N</ContextMenuShortcut> | ||
</ContextMenuItem> | ||
<ContextMenuItem onClick={() => handleClick(IAction.NEW_FOLDER)}> | ||
<FolderPlus className="w-4 h-4 mr-2" /> | ||
<span>New Folder</span> | ||
<ContextMenuShortcut>Ctrl+Shift+N</ContextMenuShortcut> | ||
</ContextMenuItem> | ||
<RenameItem onClick={() => handleClick(IAction.RENAME)} /> | ||
<DeleteItem onClick={() => handleClick(IAction.DELETE)} /> | ||
</ContextMenuContent> | ||
</ContextMenu> | ||
); | ||
} | ||
|
||
interface IMenuItemProps { | ||
onClick: () => void; | ||
} | ||
|
||
const DeleteItem = ({ onClick }: IMenuItemProps) => ( | ||
<ContextMenuItem onClick={onClick}> | ||
<Delete className="w-4 h-4 mr-2" /> | ||
<span>Delete</span> | ||
<ContextMenuShortcut>Del</ContextMenuShortcut> | ||
</ContextMenuItem> | ||
); | ||
|
||
const RenameItem = ({ onClick }: IMenuItemProps) => ( | ||
<ContextMenuItem onClick={onClick}> | ||
<Pencil className="w-4 h-4 mr-2" /> | ||
<span>Rename</span> | ||
<ContextMenuShortcut>F2</ContextMenuShortcut> | ||
</ContextMenuItem> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
"use client"; | ||
|
||
import { | ||
Dialog, | ||
DialogContent, | ||
DialogDescription, | ||
DialogFooter, | ||
DialogHeader, | ||
DialogTitle, | ||
} from "@/components/ui/dialog"; | ||
import { Button } from "../ui/button"; | ||
import { db } from "@/data/db"; | ||
import { usePathname } from "next/navigation"; | ||
import { useRefreshFileExplorer } from "./"; | ||
|
||
export default function Delete({ | ||
type, | ||
path, | ||
isOpen, | ||
setIsOpen, | ||
}: React.PropsWithChildren<{ | ||
type?: "file" | "folder"; | ||
path?: string; | ||
isOpen: boolean; | ||
setIsOpen: (open: boolean) => void; | ||
}>) { | ||
const pathname = usePathname(); | ||
const refreshFileExplorer = useRefreshFileExplorer(); | ||
|
||
if (!path) return null; | ||
|
||
return ( | ||
<Dialog open={isOpen} onOpenChange={setIsOpen}> | ||
<DialogContent> | ||
<DialogHeader> | ||
<DialogTitle>Delete {type}</DialogTitle> | ||
<DialogDescription> | ||
Are you sure you want to delete {path}? This action is not | ||
reversible! | ||
</DialogDescription> | ||
</DialogHeader> | ||
<DialogFooter> | ||
<Button | ||
variant="destructive" | ||
onClick={async () => { | ||
if (type === "file") { | ||
await db.files.delete( | ||
`${pathname}/${encodeURIComponent(path)}` | ||
); | ||
} else { | ||
const all = ( | ||
await db.files | ||
.filter((file) => | ||
file.path.startsWith( | ||
`${pathname}/${encodeURIComponent(path + "/")}` | ||
) | ||
) | ||
.toArray() | ||
).map((i) => i.path); | ||
await db.files.bulkDelete(all); | ||
} | ||
|
||
await refreshFileExplorer(); | ||
setIsOpen(false); | ||
}} | ||
> | ||
Confirm | ||
</Button> | ||
<Button onClick={() => setIsOpen(false)}>Cancel</Button> | ||
</DialogFooter> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
"use client"; | ||
|
||
import { createContext, useContext } from "react"; | ||
import { initialState, useFileExplorerReducer } from "./file-explorer-reducer"; | ||
|
||
const FileExplorerContext = createContext< | ||
ReturnType<typeof useFileExplorerReducer> | ||
>([initialState, () => {}]); | ||
|
||
const useFileExplorerContext = () => { | ||
return useContext(FileExplorerContext); | ||
}; | ||
|
||
export { FileExplorerContext, useFileExplorerContext }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
"use client"; | ||
|
||
import { INode } from "react-accessible-treeview"; | ||
import { FileIcon } from "./file-icon"; | ||
import { FileContextMenu, FolderContextMenu, IAction } from "./context-menu"; | ||
import { IFlatMetadata } from "react-accessible-treeview/dist/TreeView/utils"; | ||
import { FolderIcon } from "./folder-icon"; | ||
import { IFileExplorerActionKind } from "./file-explorer-reducer"; | ||
import { useFileExplorerContext } from "./file-explorer-context"; | ||
|
||
type Element = INode<IFlatMetadata>; | ||
|
||
export const NodeRenderer = ({ | ||
isBranch, | ||
isExpanded, | ||
element, | ||
}: { | ||
isBranch: boolean; | ||
isExpanded: boolean; | ||
element: Element; | ||
}) => { | ||
const [_, dispatch] = useFileExplorerContext(); | ||
const { name } = element; | ||
|
||
const handleClick = (action: IAction) => { | ||
switch (action) { | ||
case IAction.DELETE: | ||
dispatch({ | ||
type: IFileExplorerActionKind.DELETE, | ||
payload: { | ||
type: isBranch ? "folder" : "file", | ||
path: element.id as string, | ||
}, | ||
}); | ||
break; | ||
case IAction.RENAME: | ||
dispatch({ | ||
type: IFileExplorerActionKind.RENAME, | ||
payload: { | ||
type: isBranch ? "folder" : "file", | ||
path: element.id as string, | ||
}, | ||
}); | ||
break; | ||
case IAction.NEW_FILE: | ||
dispatch({ | ||
type: IFileExplorerActionKind.ADD, | ||
payload: { | ||
type: "file", | ||
path: element.id as string, | ||
}, | ||
}); | ||
break; | ||
case IAction.NEW_FOLDER: | ||
dispatch({ | ||
type: IFileExplorerActionKind.ADD, | ||
payload: { | ||
type: "folder", | ||
path: element.id as string, | ||
}, | ||
}); | ||
break; | ||
} | ||
}; | ||
|
||
if (name.startsWith(".")) return null; | ||
|
||
return isBranch ? ( | ||
<FolderContextMenu handleClick={handleClick}> | ||
<span className="flex px-2"> | ||
<span className="my-1"> | ||
<FolderIcon isOpen={isExpanded} /> | ||
</span> | ||
<span className="ml-2 line-clamp-1">{name}</span> | ||
</span> | ||
</FolderContextMenu> | ||
) : ( | ||
<FileContextMenu handleClick={handleClick}> | ||
<span className="flex px-2"> | ||
<span className="my-1"> | ||
<FileIcon filename={name} /> | ||
</span> | ||
<span className="ml-2 line-clamp-1">{name}</span> | ||
</span> | ||
</FileContextMenu> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
"use client"; | ||
|
||
import { useReducer } from "react"; | ||
|
||
// An enum with all the types of actions to use in our reducer | ||
export enum IFileExplorerActionKind { | ||
RENAME, | ||
DELETE, | ||
SELECT, | ||
CLOSE_MODAL, | ||
ADD, | ||
} | ||
|
||
type FileOrFolder = "file" | "folder"; | ||
|
||
// An interface for our actions | ||
interface IFileExplorerAction { | ||
type: IFileExplorerActionKind; | ||
payload?: { path?: string; type?: FileOrFolder }; | ||
} | ||
|
||
// An interface for our state | ||
interface IFileExplorerState { | ||
showRename: boolean; | ||
showDelete: boolean; | ||
path?: string; | ||
type?: FileOrFolder; | ||
focusedId?: string; | ||
showAdd: boolean; | ||
addType?: FileOrFolder; | ||
} | ||
|
||
export const initialState: IFileExplorerState = { | ||
showRename: false, | ||
showDelete: false, | ||
showAdd: false, | ||
}; | ||
|
||
// Our reducer function that uses a switch statement to handle our actions | ||
function reducer(state: IFileExplorerState, action: IFileExplorerAction) { | ||
const { type, payload } = action; | ||
switch (type) { | ||
case IFileExplorerActionKind.RENAME: | ||
return { | ||
...state, | ||
...(payload || {}), | ||
showRename: true, | ||
}; | ||
case IFileExplorerActionKind.DELETE: | ||
return { | ||
...state, | ||
...(payload || {}), | ||
showDelete: true, | ||
}; | ||
case IFileExplorerActionKind.SELECT: | ||
return { | ||
...state, | ||
...(payload || {}), | ||
}; | ||
case IFileExplorerActionKind.CLOSE_MODAL: | ||
return { | ||
...state, | ||
showRename: false, | ||
showDelete: false, | ||
showAdd: false, | ||
}; | ||
case IFileExplorerActionKind.ADD: | ||
return { | ||
...state, | ||
addType: payload?.type, | ||
showAdd: true, | ||
}; | ||
default: | ||
return state; | ||
} | ||
} | ||
|
||
export const useFileExplorerReducer = () => { | ||
return useReducer(reducer, initialState); | ||
}; |
File renamed without changes.
Oops, something went wrong.