From 0466a9c33a6e1f150641b1f778100ab937f056ba Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Mon, 10 Mar 2025 22:01:53 -0700 Subject: [PATCH] FEAT: Creation of files and directories! --- backend/src/server.ts | 37 ++++++++++++-- frontend/src/components/CreateDirectory.jsx | 55 +++++++++++++++++++++ frontend/src/components/PathDisplay.jsx | 22 ++++++++- frontend/src/components/Uploader.jsx | 2 +- frontend/src/pages/Dashboard.jsx | 54 ++++++++++++++++++-- 5 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/CreateDirectory.jsx diff --git a/backend/src/server.ts b/backend/src/server.ts index 0e06968..9e28add 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -12,7 +12,7 @@ import {verifyToken} from "./authenicate"; import jwt from "jsonwebtoken"; import {config} from "dotenv"; import Multer from "multer"; -import {readFileSync, rmSync, writeFileSync} from "fs"; +import {mkdirSync, readFileSync, rmSync, writeFileSync} from "fs"; /** * App details @@ -207,7 +207,7 @@ v1.post("/update", (req: Request, res: Response): void => { try { fs.writeFileSync(path, content); - res.status(204); + res.status(200).json({code: 200, message: "Success"}); } catch (error) { res.status(500).json({code: 500, error}) } @@ -242,7 +242,6 @@ v1.post("/upload", upload.array("files"), (req: Request, res: Response) => { // Directory to upload the files to const cwd: string[] = JSON.parse(req.body.path); - const files = (req as any).files as UploadedFile[]; files.forEach((file) => { try { @@ -272,6 +271,38 @@ v1.post("/upload", upload.array("files"), (req: Request, res: Response) => { res.status(200).json({code: 200, message: "Success"}); }); + +v1.post("/create", (req: Request, res: Response): void => { + // Generate the path to create + const {cwd, name} = req.body; + + try { + const newPath: string = path.join("/", ...cwd, name); + if (name.endsWith("/")) { + mkdirSync(newPath, {mode: "644"}) + } else { + console.log("NOT DIR"); + writeFileSync(newPath, "", {mode: "644"}) + } + } catch (error: any) { + if (error.code === 'EACCES') { + res.status(403).json({code: 403, error: "Permission denied."}); // Specific error + return; + } else if (error.code === 'ENOSPC') { + res.status(507).json({code: 507, error: "Insufficient storage."}); // Specific error + return; + } else if (error instanceof TypeError) { + res.status(400).json({code: 400, error: "Invalid data type."}); // Example of instance check + return; + } else { + res.status(500).json({code: 500, error: "Error processing directory."}); // Generic error + return; + } + } + + res.status(201).json({code: 201, message: "Success"}); +}); + /** * Apply the routes to the server */ diff --git a/frontend/src/components/CreateDirectory.jsx b/frontend/src/components/CreateDirectory.jsx new file mode 100644 index 0000000..f552b10 --- /dev/null +++ b/frontend/src/components/CreateDirectory.jsx @@ -0,0 +1,55 @@ +import {useState} from "react"; + +/** + * Create a file or directory, end with a / for a directory. + * @constructor + */ +export default function CreateDirectory({close, create}) { + const [dirName, setDirName] = useState(""); + + const createDirectory = () => { + create(dirName); + } + + const updateDirName = (e) => { + setDirName(e.target.value); + } + + const closeWithoutSaving = () => { + setDirName(""); + close(); + } + + return ( +
+
+
+

Create a Directory

+

+ Create a file or directory in the current directory. To create a directory, include a / at the end of the name. + Otherwise, the entry created will be a file. +

+ + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/PathDisplay.jsx b/frontend/src/components/PathDisplay.jsx index 5e7fd51..0927f46 100644 --- a/frontend/src/components/PathDisplay.jsx +++ b/frontend/src/components/PathDisplay.jsx @@ -34,6 +34,22 @@ function BackButton({onClick, enabled}) { ) } + +function CreateButton({onClick}) { + return ( + + ) +} + /** * * @param name {string} @@ -56,16 +72,20 @@ function PathElement({name, index, onClick}) { * @param backHome {function} * @param backArrow {function} * @param enabled {boolean} + * @param create {function} * @returns {JSX.Element} * @constructor */ -export default function PathDisplay({path, updatePath, backHome, backArrow, enabled}) { +export default function PathDisplay({path, updatePath, backHome, backArrow, enabled, create}) { return (
{path.map((seg, idx) => )} +
+ +
) } \ No newline at end of file diff --git a/frontend/src/components/Uploader.jsx b/frontend/src/components/Uploader.jsx index d6feaa3..eefcc18 100644 --- a/frontend/src/components/Uploader.jsx +++ b/frontend/src/components/Uploader.jsx @@ -107,7 +107,7 @@ export default function Uploader({close, upload}) { diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 8cb94ec..6e87205 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -8,6 +8,7 @@ import Editor from "../components/Editor.jsx"; import ChildrenLoading from "../components/ChildrenLoading.jsx"; import DownloadLoading from "../components/DownloadLoading.jsx"; import Uploader from "../components/Uploader.jsx"; +import CreateDirectory from "../components/CreateDirectory.jsx"; export default function Dashboard() { // Store the default path @@ -30,6 +31,7 @@ export default function Dashboard() { const [error, setError] = useState(null); const [editing, setEditing] = useState(""); const [uploading, setUploading] = useState(false); + const [creating, setCreating] = useState(false); const [childrenLoading, setChildrenLoading] = useState(false); const [downloadLoading, setDownloadLoading] = useState(false); const [contentLoading, setContentLoading] = useState(false); @@ -84,7 +86,7 @@ export default function Dashboard() { setSelected([]); - }, [path, uploading]); + }, [path, uploading, creating]); // Redirect if the user isn't logged in, otherwise update the state. @@ -239,8 +241,10 @@ export default function Dashboard() { // Send request to server to update the file. This will return nothing // so no need for any promise handling. - updateContent(editing, newContent).finally(() => { - setEditing(""); + updateContent(editing, newContent).then((data) => { + if (data.code === 200) { + setEditing(""); + } }); }; @@ -339,11 +343,53 @@ export default function Dashboard() { }); }; + const createDir = () => { + setCreating(true); + }; + + const closeCreate = () => { + setCreating(false); + }; + + /** + * Create a directory or file in the backend + * @param name {string} Name of new directory/file + */ + const createDirectory = (name) => { + const create = async (name, cwd) => { + const resp = await fetch(`${backendUrl}/v1/create`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "authorization": `Bearer ${token}`, + }, + body: JSON.stringify({name, cwd}) + }); + + if (!resp.ok) { + const data = await resp.json() + setError(data.error); + return data; + } + + return await resp.json(); + }; + + create(name, path).then((data) => { + if (data.code === 201) { + setCreating(false); + } + }).catch((error) => { + setError(error); + }); + }; + return (
{downloadLoading && } + {creating && } {uploading && } {error && } @@ -352,7 +398,7 @@ export default function Dashboard() { loading={contentLoading}/>} defaultPath.length}/> + enabled={path.length > defaultPath.length} create={createDir}/>
{childrenLoading && }