FEAT: Creation of files and directories!
This commit is contained in:
parent
07952933b2
commit
0466a9c33a
@ -12,7 +12,7 @@ import {verifyToken} from "./authenicate";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import {config} from "dotenv";
|
import {config} from "dotenv";
|
||||||
import Multer from "multer";
|
import Multer from "multer";
|
||||||
import {readFileSync, rmSync, writeFileSync} from "fs";
|
import {mkdirSync, readFileSync, rmSync, writeFileSync} from "fs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App details
|
* App details
|
||||||
@ -207,7 +207,7 @@ v1.post("/update", (req: Request, res: Response): void => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(path, content);
|
fs.writeFileSync(path, content);
|
||||||
res.status(204);
|
res.status(200).json({code: 200, message: "Success"});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({code: 500, 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
|
// Directory to upload the files to
|
||||||
const cwd: string[] = JSON.parse(req.body.path);
|
const cwd: string[] = JSON.parse(req.body.path);
|
||||||
|
|
||||||
const files = (req as any).files as UploadedFile[];
|
const files = (req as any).files as UploadedFile[];
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
try {
|
try {
|
||||||
@ -272,6 +271,38 @@ v1.post("/upload", upload.array("files"), (req: Request, res: Response) => {
|
|||||||
|
|
||||||
res.status(200).json({code: 200, message: "Success"});
|
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
|
* Apply the routes to the server
|
||||||
*/
|
*/
|
||||||
|
|||||||
55
frontend/src/components/CreateDirectory.jsx
Normal file
55
frontend/src/components/CreateDirectory.jsx
Normal file
@ -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 (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
|
<div className="fixed -inset-10 bg-black opacity-50 blur-lg"></div>
|
||||||
|
<div className="relative z-50 bg-white p-8 rounded-lg shadow-lg w-2/5 border-1 border-gray-400">
|
||||||
|
<h2 className="text-2xl font-semibold mb-2 text-blue-400">Create a Directory</h2>
|
||||||
|
<p className="text-sm">
|
||||||
|
Create a file or directory in the current directory. To create a directory, include a <span
|
||||||
|
className="font-mono text-black bg-gray-300 p-1 rounded-md">/</span> at the end of the name.
|
||||||
|
Otherwise, the entry created will be a file.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input className="border-b-2 border-blue-400 w-full p-2 my-4" type="text" name="directoryName"
|
||||||
|
value={dirName}
|
||||||
|
onInput={updateDirName}
|
||||||
|
placeholder="Directory name"/>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={closeWithoutSaving}
|
||||||
|
title="Close without creating"
|
||||||
|
className="bg-red-500 hover:bg-red-600 duration-100 text-white text-sm font-semibold py-1.5 px-3 mt-2 mx-2 rounded hover:cursor-pointer">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={createDirectory}
|
||||||
|
title="Create directory"
|
||||||
|
className="bg-blue-400 hover:bg-blue-500 duration-100 text-white text-sm font-semibold py-1.5 px-3 mt-2 rounded hover:cursor-pointer">
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -34,6 +34,22 @@ function BackButton({onClick, enabled}) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CreateButton({onClick}) {
|
||||||
|
return (
|
||||||
|
<button onClick={onClick}
|
||||||
|
title="Create a file/directory"
|
||||||
|
className="hover:bg-gray-200 p-1.5 mr-1 rounded-full transition-colors duration-150 disabled:text-gray-500 disabled:hover:bg-red-300 disabled:cursor-not-allowed text-black">
|
||||||
|
<svg className="h-5" viewBox="0 0 24 24" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M13 3H8.2C7.0799 3 6.51984 3 6.09202 3.21799C5.71569 3.40973 5.40973 3.71569 5.21799 4.09202C5 4.51984 5 5.0799 5 6.2V17.8C5 18.9201 5 19.4802 5.21799 19.908C5.40973 20.2843 5.71569 20.5903 6.09202 20.782C6.51984 21 7.0799 21 8.2 21H10M13 3L19 9M13 3V7.4C13 7.96005 13 8.24008 13.109 8.45399C13.2049 8.64215 13.3578 8.79513 13.546 8.89101C13.7599 9 14.0399 9 14.6 9H19M19 9V10M14 21L16.025 20.595C16.2015 20.5597 16.2898 20.542 16.3721 20.5097C16.4452 20.4811 16.5147 20.4439 16.579 20.399C16.6516 20.3484 16.7152 20.2848 16.8426 20.1574L21 16C21.5523 15.4477 21.5523 14.5523 21 14C20.4477 13.4477 19.5523 13.4477 19 14L14.8426 18.1574C14.7152 18.2848 14.6516 18.3484 14.601 18.421C14.5561 18.4853 14.5189 18.5548 14.4903 18.6279C14.458 18.7102 14.4403 18.7985 14.405 18.975L14 21Z"
|
||||||
|
stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param name {string}
|
* @param name {string}
|
||||||
@ -56,16 +72,20 @@ function PathElement({name, index, onClick}) {
|
|||||||
* @param backHome {function}
|
* @param backHome {function}
|
||||||
* @param backArrow {function}
|
* @param backArrow {function}
|
||||||
* @param enabled {boolean}
|
* @param enabled {boolean}
|
||||||
|
* @param create {function}
|
||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export default function PathDisplay({path, updatePath, backHome, backArrow, enabled}) {
|
export default function PathDisplay({path, updatePath, backHome, backArrow, enabled, create}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-2/3 mt-8 border-b-1 border-gray-400 bg-white flex items-center truncate">
|
className="w-2/3 mt-8 border-b-1 border-gray-400 bg-white flex items-center truncate">
|
||||||
<HomeButton onClick={backHome} enabled={enabled}/>
|
<HomeButton onClick={backHome} enabled={enabled}/>
|
||||||
<BackButton onClick={backArrow} enabled={enabled}/>
|
<BackButton onClick={backArrow} enabled={enabled}/>
|
||||||
{path.map((seg, idx) => <PathElement name={seg} key={idx} index={idx} onClick={updatePath}/>)}
|
{path.map((seg, idx) => <PathElement name={seg} key={idx} index={idx} onClick={updatePath}/>)}
|
||||||
|
<div className="ml-auto h-full flex items-center">
|
||||||
|
<CreateButton onClick={create}/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ export default function Uploader({close, upload}) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={uploadFiles}
|
onClick={uploadFiles}
|
||||||
title="Upload times"
|
title="Upload files"
|
||||||
className="bg-blue-400 hover:bg-blue-500 duration-100 text-white text-sm font-semibold py-1.5 px-3 mt-2 rounded hover:cursor-pointer">
|
className="bg-blue-400 hover:bg-blue-500 duration-100 text-white text-sm font-semibold py-1.5 px-3 mt-2 rounded hover:cursor-pointer">
|
||||||
Upload
|
Upload
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import Editor from "../components/Editor.jsx";
|
|||||||
import ChildrenLoading from "../components/ChildrenLoading.jsx";
|
import ChildrenLoading from "../components/ChildrenLoading.jsx";
|
||||||
import DownloadLoading from "../components/DownloadLoading.jsx";
|
import DownloadLoading from "../components/DownloadLoading.jsx";
|
||||||
import Uploader from "../components/Uploader.jsx";
|
import Uploader from "../components/Uploader.jsx";
|
||||||
|
import CreateDirectory from "../components/CreateDirectory.jsx";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
// Store the default path
|
// Store the default path
|
||||||
@ -30,6 +31,7 @@ export default function Dashboard() {
|
|||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [editing, setEditing] = useState("");
|
const [editing, setEditing] = useState("");
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [creating, setCreating] = useState(false);
|
||||||
const [childrenLoading, setChildrenLoading] = useState(false);
|
const [childrenLoading, setChildrenLoading] = useState(false);
|
||||||
const [downloadLoading, setDownloadLoading] = useState(false);
|
const [downloadLoading, setDownloadLoading] = useState(false);
|
||||||
const [contentLoading, setContentLoading] = useState(false);
|
const [contentLoading, setContentLoading] = useState(false);
|
||||||
@ -84,7 +86,7 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
setSelected([]);
|
setSelected([]);
|
||||||
|
|
||||||
}, [path, uploading]);
|
}, [path, uploading, creating]);
|
||||||
|
|
||||||
|
|
||||||
// Redirect if the user isn't logged in, otherwise update the state.
|
// 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
|
// Send request to server to update the file. This will return nothing
|
||||||
// so no need for any promise handling.
|
// so no need for any promise handling.
|
||||||
updateContent(editing, newContent).finally(() => {
|
updateContent(editing, newContent).then((data) => {
|
||||||
setEditing("");
|
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 (
|
return (
|
||||||
<div className="w-full min-h-screen h-screen pb-8">
|
<div className="w-full min-h-screen h-screen pb-8">
|
||||||
<Navbar downloadFiles={downloadFiles} uploadFiles={toggleUploading}/>
|
<Navbar downloadFiles={downloadFiles} uploadFiles={toggleUploading}/>
|
||||||
<div className="h-full w-full flex flex-col items-center justify-center pb-8">
|
<div className="h-full w-full flex flex-col items-center justify-center pb-8">
|
||||||
{downloadLoading && <DownloadLoading/>}
|
{downloadLoading && <DownloadLoading/>}
|
||||||
|
{creating && <CreateDirectory close={closeCreate} create={createDirectory}/>}
|
||||||
{uploading && <Uploader close={toggleUploading} upload={upload}/>}
|
{uploading && <Uploader close={toggleUploading} upload={upload}/>}
|
||||||
|
|
||||||
{error && <Error error={error} clear={clearError}/>}
|
{error && <Error error={error} clear={clearError}/>}
|
||||||
@ -352,7 +398,7 @@ export default function Dashboard() {
|
|||||||
loading={contentLoading}/>}
|
loading={contentLoading}/>}
|
||||||
|
|
||||||
<PathDisplay path={path} updatePath={updatePath} backHome={backHome} backArrow={backArrow}
|
<PathDisplay path={path} updatePath={updatePath} backHome={backHome} backArrow={backArrow}
|
||||||
enabled={path.length > defaultPath.length}/>
|
enabled={path.length > defaultPath.length} create={createDir}/>
|
||||||
<div className="w-2/3 h-5/6 overflow-y-auto border-1 border-gray-300">
|
<div className="w-2/3 h-5/6 overflow-y-auto border-1 border-gray-300">
|
||||||
{childrenLoading && <ChildrenLoading/>}
|
{childrenLoading && <ChildrenLoading/>}
|
||||||
<DirectoryList dirs={files} showHidden={showHidden} appendPath={appendPath}
|
<DirectoryList dirs={files} showHidden={showHidden} appendPath={appendPath}
|
||||||
|
|||||||
Reference in New Issue
Block a user