delete file ui + logic
This commit is contained in:
parent
268dcb61f2
commit
894de9990a
13
backend/server/dist/index.js
vendored
13
backend/server/dist/index.js
vendored
@ -129,6 +129,19 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
});
|
||||
yield (0, utils_1.renameFile)(fileId, newFileId, file.data);
|
||||
}));
|
||||
socket.on("deleteFile", (fileId, callback) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
||||
if (!file)
|
||||
return;
|
||||
fs_1.default.unlink(path_1.default.join(dirName, fileId), function (err) {
|
||||
if (err)
|
||||
throw err;
|
||||
});
|
||||
sandboxFiles.fileData = sandboxFiles.fileData.filter((f) => f.id !== fileId);
|
||||
yield (0, utils_1.deleteFile)(fileId);
|
||||
const newFiles = yield (0, utils_1.getSandboxFiles)(data.id);
|
||||
callback(newFiles.files);
|
||||
}));
|
||||
socket.on("createTerminal", ({ id }) => {
|
||||
console.log("creating terminal, id=" + id);
|
||||
const pty = (0, node_pty_1.spawn)(os_1.default.platform() === "win32" ? "cmd.exe" : "bash", [], {
|
||||
|
13
backend/server/dist/utils.js
vendored
13
backend/server/dist/utils.js
vendored
@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.saveFile = exports.renameFile = exports.createFile = exports.getSandboxFiles = void 0;
|
||||
exports.deleteFile = exports.saveFile = exports.renameFile = exports.createFile = exports.getSandboxFiles = void 0;
|
||||
const getSandboxFiles = (id) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const sandboxRes = yield fetch(`https://storage.ishaan1013.workers.dev/api?sandboxId=${id}`);
|
||||
const sandboxData = yield sandboxRes.json();
|
||||
@ -110,3 +110,14 @@ const saveFile = (fileId, data) => __awaiter(void 0, void 0, void 0, function* (
|
||||
return res.ok;
|
||||
});
|
||||
exports.saveFile = saveFile;
|
||||
const deleteFile = (fileId) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const res = yield fetch(`https://storage.ishaan1013.workers.dev/api`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ fileId }),
|
||||
});
|
||||
return res.ok;
|
||||
});
|
||||
exports.deleteFile = deleteFile;
|
||||
|
@ -8,7 +8,13 @@ import { Server } from "socket.io"
|
||||
|
||||
import { z } from "zod"
|
||||
import { User } from "./types"
|
||||
import { createFile, getSandboxFiles, renameFile, saveFile } from "./utils"
|
||||
import {
|
||||
createFile,
|
||||
deleteFile,
|
||||
getSandboxFiles,
|
||||
renameFile,
|
||||
saveFile,
|
||||
} from "./utils"
|
||||
import { IDisposable, IPty, spawn } from "node-pty"
|
||||
|
||||
dotenv.config()
|
||||
@ -148,6 +154,21 @@ io.on("connection", async (socket) => {
|
||||
await renameFile(fileId, newFileId, file.data)
|
||||
})
|
||||
|
||||
socket.on("deleteFile", async (fileId: string, callback) => {
|
||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||
if (!file) return
|
||||
|
||||
fs.unlink(path.join(dirName, fileId), function (err) {
|
||||
if (err) throw err
|
||||
})
|
||||
sandboxFiles.fileData = sandboxFiles.fileData.filter((f) => f.id !== fileId)
|
||||
|
||||
await deleteFile(fileId)
|
||||
|
||||
const newFiles = await getSandboxFiles(data.id)
|
||||
callback(newFiles.files)
|
||||
})
|
||||
|
||||
socket.on("createTerminal", ({ id }: { id: string }) => {
|
||||
console.log("creating terminal, id=" + id)
|
||||
const pty = spawn(os.platform() === "win32" ? "cmd.exe" : "bash", [], {
|
||||
|
@ -123,3 +123,14 @@ export const saveFile = async (fileId: string, data: string) => {
|
||||
})
|
||||
return res.ok
|
||||
}
|
||||
|
||||
export const deleteFile = async (fileId: string) => {
|
||||
const res = await fetch(`https://storage.ishaan1013.workers.dev/api`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ fileId }),
|
||||
})
|
||||
return res.ok
|
||||
}
|
||||
|
@ -50,6 +50,17 @@ export default {
|
||||
|
||||
await env.R2.put(fileId, '');
|
||||
|
||||
return success;
|
||||
} else if (method === 'DELETE') {
|
||||
const deleteSchema = z.object({
|
||||
fileId: z.string(),
|
||||
});
|
||||
|
||||
const body = await request.json();
|
||||
const { fileId } = deleteSchema.parse(body);
|
||||
|
||||
await env.R2.delete(fileId);
|
||||
|
||||
return success;
|
||||
} else return methodNotAllowed;
|
||||
} else if (path === '/api/rename' && method === 'POST') {
|
||||
|
@ -27,9 +27,6 @@ import { processFileType, validateName } from "@/lib/utils"
|
||||
import { toast } from "sonner"
|
||||
import EditorTerminal from "./terminal"
|
||||
|
||||
import { Terminal } from "@xterm/xterm"
|
||||
import { FitAddon } from "@xterm/addon-fit"
|
||||
|
||||
export default function CodeEditor({
|
||||
userId,
|
||||
sandboxId,
|
||||
@ -135,6 +132,9 @@ export default function CodeEditor({
|
||||
const closeTab = (tab: TFile) => {
|
||||
const numTabs = tabs.length
|
||||
const index = tabs.findIndex((t) => t.id === tab.id)
|
||||
|
||||
if (index === -1) return
|
||||
|
||||
const nextId =
|
||||
activeId === tab.id
|
||||
? numTabs === 1
|
||||
@ -156,9 +156,12 @@ export default function CodeEditor({
|
||||
oldName: string,
|
||||
type: "file" | "folder"
|
||||
) => {
|
||||
if (!validateName(newName, oldName, type)) return false
|
||||
if (!validateName(newName, oldName, type)) {
|
||||
toast.error("Invalid file name.")
|
||||
console.log("invalid name")
|
||||
return false
|
||||
}
|
||||
|
||||
// Action
|
||||
socket.emit("renameFile", id, newName)
|
||||
setTabs((prev) =>
|
||||
prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab))
|
||||
@ -167,12 +170,27 @@ export default function CodeEditor({
|
||||
return true
|
||||
}
|
||||
|
||||
const handleDeleteFile = (file: TFile) => {
|
||||
socket.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => {
|
||||
setFiles(response)
|
||||
})
|
||||
closeTab(file)
|
||||
}
|
||||
|
||||
const handleDeleteFolder = (folder: TFolder) => {
|
||||
// socket.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => {
|
||||
// setFiles(response)
|
||||
// })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sidebar
|
||||
files={files}
|
||||
selectFile={selectFile}
|
||||
handleRename={handleRename}
|
||||
handleDeleteFile={handleDeleteFile}
|
||||
handleDeleteFolder={handleDeleteFolder}
|
||||
socket={socket}
|
||||
addNew={(name, type) => {
|
||||
if (type === "file") {
|
||||
|
@ -4,11 +4,19 @@ import Image from "next/image"
|
||||
import { getIconForFile } from "vscode-icons-js"
|
||||
import { TFile, TTab } from "./types"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu"
|
||||
import { Loader2, Pencil, Trash2 } from "lucide-react"
|
||||
|
||||
export default function SidebarFile({
|
||||
data,
|
||||
selectFile,
|
||||
handleRename,
|
||||
handleDeleteFile,
|
||||
}: {
|
||||
data: TFile
|
||||
selectFile: (file: TTab) => void
|
||||
@ -18,16 +26,21 @@ export default function SidebarFile({
|
||||
oldName: string,
|
||||
type: "file" | "folder"
|
||||
) => boolean
|
||||
handleDeleteFile: (file: TFile) => void
|
||||
}) {
|
||||
const [imgSrc, setImgSrc] = useState(`/icons/${getIconForFile(data.name)}`)
|
||||
const [editing, setEditing] = useState(false)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [pendingDelete, setPendingDelete] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (editing) {
|
||||
inputRef.current?.focus()
|
||||
setTimeout(() => inputRef.current?.focus(), 0)
|
||||
}
|
||||
}, [editing])
|
||||
if (!inputRef.current) {
|
||||
console.log("no input ref")
|
||||
}
|
||||
}, [editing, inputRef.current])
|
||||
|
||||
const renameFile = () => {
|
||||
const renamed = handleRename(
|
||||
@ -43,37 +56,71 @@ export default function SidebarFile({
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => selectFile({ ...data, saved: true })}
|
||||
onDoubleClick={() => {
|
||||
setEditing(true)
|
||||
}}
|
||||
className="w-full flex items-center h-7 px-1 hover:bg-secondary rounded-sm cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
>
|
||||
<Image
|
||||
src={imgSrc}
|
||||
alt="File Icon"
|
||||
width={18}
|
||||
height={18}
|
||||
className="mr-2"
|
||||
onError={() => setImgSrc("/icons/default_file.svg")}
|
||||
/>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
renameFile()
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger
|
||||
disabled={pendingDelete}
|
||||
onClick={() => {
|
||||
if (!editing && !pendingDelete) selectFile({ ...data, saved: true })
|
||||
}}
|
||||
// onDoubleClick={() => {
|
||||
// setEditing(true)
|
||||
// }}
|
||||
className="data-[state=open]:bg-secondary/50 w-full flex items-center h-7 px-1 hover:bg-secondary rounded-sm cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={`bg-transparent transition-all focus-visible:outline-none focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-ring rounded-sm w-full ${
|
||||
editing ? "" : "pointer-events-none"
|
||||
}`}
|
||||
disabled={!editing}
|
||||
defaultValue={data.name}
|
||||
onBlur={() => renameFile()}
|
||||
<Image
|
||||
src={imgSrc}
|
||||
alt="File Icon"
|
||||
width={18}
|
||||
height={18}
|
||||
className="mr-2"
|
||||
onError={() => setImgSrc("/icons/default_file.svg")}
|
||||
/>
|
||||
</form>
|
||||
</button>
|
||||
{pendingDelete ? (
|
||||
<>
|
||||
<Loader2 className="text-muted-foreground w-4 h-4 animate-spin mr-2" />
|
||||
<div className="text-muted-foreground">Deleting...</div>
|
||||
</>
|
||||
) : (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
renameFile()
|
||||
}}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={`bg-transparent transition-all focus-visible:outline-none focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-ring rounded-sm w-full ${
|
||||
editing ? "" : "pointer-events-none"
|
||||
}`}
|
||||
disabled={!editing}
|
||||
defaultValue={data.name}
|
||||
onBlur={() => renameFile()}
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
console.log("rename")
|
||||
setEditing(true)
|
||||
}}
|
||||
>
|
||||
<Pencil className="w-4 h-4 mr-2" />
|
||||
Rename
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
disabled={pendingDelete}
|
||||
onClick={() => {
|
||||
console.log("delete")
|
||||
setPendingDelete(true)
|
||||
handleDeleteFile(data)
|
||||
}}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
)
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ export default function SidebarFolder({
|
||||
data,
|
||||
selectFile,
|
||||
handleRename,
|
||||
handleDeleteFile,
|
||||
handleDeleteFolder,
|
||||
}: {
|
||||
data: TFolder
|
||||
selectFile: (file: TTab) => void
|
||||
@ -19,6 +21,8 @@ export default function SidebarFolder({
|
||||
oldName: string,
|
||||
type: "file" | "folder"
|
||||
) => boolean
|
||||
handleDeleteFile: (file: TFile) => void
|
||||
handleDeleteFolder: (folder: TFolder) => void
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const folder = isOpen
|
||||
@ -82,6 +86,7 @@ export default function SidebarFolder({
|
||||
data={child}
|
||||
selectFile={selectFile}
|
||||
handleRename={handleRename}
|
||||
handleDeleteFile={handleDeleteFile}
|
||||
/>
|
||||
) : (
|
||||
<SidebarFolder
|
||||
@ -89,6 +94,8 @@ export default function SidebarFolder({
|
||||
data={child}
|
||||
selectFile={selectFile}
|
||||
handleRename={handleRename}
|
||||
handleDeleteFile={handleDeleteFile}
|
||||
handleDeleteFolder={handleDeleteFolder}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
@ -12,6 +12,8 @@ export default function Sidebar({
|
||||
files,
|
||||
selectFile,
|
||||
handleRename,
|
||||
handleDeleteFile,
|
||||
handleDeleteFolder,
|
||||
socket,
|
||||
addNew,
|
||||
}: {
|
||||
@ -23,6 +25,8 @@ export default function Sidebar({
|
||||
oldName: string,
|
||||
type: "file" | "folder"
|
||||
) => boolean
|
||||
handleDeleteFile: (file: TFile) => void
|
||||
handleDeleteFolder: (folder: TFolder) => void
|
||||
socket: Socket
|
||||
addNew: (name: string, type: "file" | "folder") => void
|
||||
}) {
|
||||
@ -66,6 +70,7 @@ export default function Sidebar({
|
||||
data={child}
|
||||
selectFile={selectFile}
|
||||
handleRename={handleRename}
|
||||
handleDeleteFile={handleDeleteFile}
|
||||
/>
|
||||
) : (
|
||||
<SidebarFolder
|
||||
@ -73,6 +78,8 @@ export default function Sidebar({
|
||||
data={child}
|
||||
selectFile={selectFile}
|
||||
handleRename={handleRename}
|
||||
handleDeleteFile={handleDeleteFile}
|
||||
handleDeleteFolder={handleDeleteFolder}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
204
frontend/components/ui/context-menu.tsx
Normal file
204
frontend/components/ui/context-menu.tsx
Normal file
@ -0,0 +1,204 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronRightIcon,
|
||||
DotFilledIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ContextMenu = ContextMenuPrimitive.Root
|
||||
|
||||
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
|
||||
|
||||
const ContextMenuGroup = ContextMenuPrimitive.Group
|
||||
|
||||
const ContextMenuPortal = ContextMenuPrimitive.Portal
|
||||
|
||||
const ContextMenuSub = ContextMenuPrimitive.Sub
|
||||
|
||||
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
|
||||
|
||||
const ContextMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
))
|
||||
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const ContextMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
|
||||
|
||||
const ContextMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Portal>
|
||||
<ContextMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuPrimitive.Portal>
|
||||
))
|
||||
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
|
||||
|
||||
const ContextMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
|
||||
|
||||
const ContextMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
ContextMenuCheckboxItem.displayName =
|
||||
ContextMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const ContextMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<DotFilledIcon className="h-4 w-4 fill-current" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.RadioItem>
|
||||
))
|
||||
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const ContextMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
|
||||
|
||||
const ContextMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
|
||||
|
||||
const ContextMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuGroup,
|
||||
ContextMenuPortal,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuRadioGroup,
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { toast } from "sonner"
|
||||
// import { toast } from "sonner"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
@ -21,18 +21,15 @@ export function validateName(
|
||||
oldName: string,
|
||||
type: "file" | "folder"
|
||||
) {
|
||||
if (newName === oldName || newName.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
newName === oldName ||
|
||||
newName.length === 0 ||
|
||||
newName.includes("/") ||
|
||||
newName.includes("\\") ||
|
||||
newName.includes(" ") ||
|
||||
(type === "file" && !newName.includes(".")) ||
|
||||
(type === "folder" && newName.includes("."))
|
||||
) {
|
||||
toast.error("Invalid file name.")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
29
frontend/package-lock.json
generated
29
frontend/package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-context-menu": "^2.1.5",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
@ -707,6 +708,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-context-menu": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.1.5.tgz",
|
||||
"integrity": "sha512-R5XaDj06Xul1KGb+WP8qiOh7tKJNz2durpLBXAGZjSVtctcRFCuEvy2gtMwRJGePwQQE5nV77gs4FwRi8T+r2g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-menu": "2.0.6",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz",
|
||||
|
@ -14,6 +14,7 @@
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-context-menu": "^2.1.5",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user