From 5aadd6fc2b29f194481cff7babd39e8918a052b4 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:10:50 +0330 Subject: [PATCH] Import source: src/components/features/file-manager Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- src/components/features/file-manager.tsx | 411 +++++++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 src/components/features/file-manager.tsx diff --git a/src/components/features/file-manager.tsx b/src/components/features/file-manager.tsx new file mode 100644 index 0000000..7b2f480 --- /dev/null +++ b/src/components/features/file-manager.tsx @@ -0,0 +1,411 @@ +"use client"; + +import { DndContext } from "@dnd-kit/core"; +import { useQuery } from "@tanstack/react-query"; +import type { CSSProperties, PointerEvent as ReactPointerEvent } from "react"; +import { useState } from "react"; +import { + Archive, + ArrowLeft, + ArrowRight, + CheckSquare, + Copy, + Download, + Edit3, + Eye, + File, + FilePlus2, + Folder, + FolderPlus, + Grid2X2, + Home, + Info, + List, + MoreVertical, + RefreshCw, + RotateCcw, + Scissors, + Search, + Settings, + ShieldCheck, + Trash2, + Upload, +} from "lucide-react"; +import type { LucideIcon } from "lucide-react"; +import type { FileEntry } from "@/api/contracts/models"; +import { panelService } from "@/api/services/panel-service"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { CodeEditor } from "@/components/common/code-editor"; +import { formatBytes } from "@/lib/utils"; + +const toolbar: Array<{ label: string; icon: LucideIcon; primary?: boolean }> = [ + { label: "File", icon: FilePlus2 }, + { label: "Folder", icon: FolderPlus }, + { label: "Copy", icon: Copy }, + { label: "Move", icon: Scissors }, + { label: "Upload", icon: Upload, primary: true }, + { label: "Download", icon: Download }, + { label: "Delete", icon: Trash2 }, + { label: "Restore", icon: RotateCcw }, + { label: "Rename", icon: Edit3 }, + { label: "Edit", icon: Edit3 }, + { label: "HTML Editor", icon: File }, + { label: "Permissions", icon: ShieldCheck }, + { label: "Extract", icon: Archive }, + { label: "Compress", icon: Archive }, +]; + +const directories = [ + "/home/coaxys", + "public_html", + "assets", + "cgi-bin", + "logs", + "mail", + "ssl", + "tmp", + "backups", + ".ssh", + ".trash", +]; + +export function FileManager() { + const [treeWidth, setTreeWidth] = useState(276); + const [detailsWidth, setDetailsWidth] = useState(330); + const { data, isLoading } = useQuery({ + queryKey: ["files"], + queryFn: () => panelService.files.list(), + }); + const files = data?.data ?? []; + + function startTreeResize(event: ReactPointerEvent) { + const startX = event.clientX; + const startWidth = treeWidth; + event.currentTarget.setPointerCapture(event.pointerId); + + function onMove(moveEvent: globalThis.PointerEvent) { + setTreeWidth(Math.min(460, Math.max(220, startWidth + moveEvent.clientX - startX))); + } + + function onUp() { + window.removeEventListener("pointermove", onMove); + window.removeEventListener("pointerup", onUp); + } + + window.addEventListener("pointermove", onMove); + window.addEventListener("pointerup", onUp); + } + + function startDetailsResize(event: ReactPointerEvent) { + const startX = event.clientX; + const startWidth = detailsWidth; + event.currentTarget.setPointerCapture(event.pointerId); + + function onMove(moveEvent: globalThis.PointerEvent) { + setDetailsWidth(Math.min(520, Math.max(270, startWidth - (moveEvent.clientX - startX)))); + } + + function onUp() { + window.removeEventListener("pointermove", onMove); + window.removeEventListener("pointerup", onUp); + } + + window.addEventListener("pointermove", onMove); + window.addEventListener("pointerup", onUp); + } + + return ( + +
+
+
+ + File Manager +
+
+ + /home/coaxys/public_html + + +
+
+ +
+
+ {toolbar.map(({ label, icon: Icon, primary }) => ( + + ))} +
+
+ +
+ + +
+ +
+
+
+ + + +
+
+ Path + + +
+
+ + +
+
+ +
+
+ + + Selected: 0 files +
+
+ + +
+
+ + {isLoading ?
: } + +
+ 'mock-only'];\n" + } + /> +
+ + +
+ + File details +
+ {[ + ["Name", "Home page script"], + ["Type", "PHP source"], + ["Permissions", "0644"], + ["Owner", "coaxys"], + ["Checksum", "sha256: 2b4f...91ce"], + ["Storage", "37 GB of 100 GB used"], + ].map(([label, value]) => ( +
+ {label} + {value} +
+ ))} + +
+
+
+
+
+
+ + ); +} + +function FileTable({ files }: { files: FileEntry[] }) { + return ( +
+ + + + + {["Name", "Size", "Last Modified", "Type", "Permissions", "Owner", "Actions"].map( + (head) => ( + + ), + )} + + + + {files.map((file) => { + const Icon = file.type === "directory" ? Folder : File; + return ( + + + + + + + + + + + ); + })} + +
+ + + {head} +
+ + +
+ + {file.name} +
+
+ {file.type === "directory" ? "--" : formatBytes(file.size)} + + {new Date(file.modifiedAt).toLocaleString()} + {file.mime}{file.permissions}{file.owner} +
+ + +
+
+
+ ); +}