(FEATURE): We can now move files! Heck yeah!
All checks were successful
Docker Deploy / build_and_deploy (push) Successful in 1m3s
All checks were successful
Docker Deploy / build_and_deploy (push) Successful in 1m3s
Hope this works well, didn't test much
This commit is contained in:
parent
92e02a821c
commit
f14e2c4781
@ -3,7 +3,7 @@ import { Healthcheck } from "./healthcheck";
|
||||
import { printEndpoints, validateHash } from "./utils";
|
||||
import { LogRequestMiddleware } from "./log";
|
||||
import * as fs from "node:fs";
|
||||
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, existsSync } from "node:fs";
|
||||
import { entry } from "./entry";
|
||||
import cors from "cors";
|
||||
import archiver from "archiver";
|
||||
@ -362,6 +362,42 @@ v1.post("/remove", (req: Request, res: Response): void => {
|
||||
res.status(201).json({ code: 201, message: `Deleted ${files.length} files.` });
|
||||
});
|
||||
|
||||
v1.post("/move", (req: Request, res: Response): void => {
|
||||
// Get the array of paths and the root
|
||||
const { oldPath, newPath } = req.body;
|
||||
|
||||
try {
|
||||
// Get path of new path
|
||||
const newDirectory = path.dirname(newPath);
|
||||
|
||||
// Ensure the directory exists
|
||||
if (!existsSync(newDirectory)) {
|
||||
mkdirSync(newDirectory, { recursive: true });
|
||||
console.log(`Created directory: ${newDirectory}`);
|
||||
}
|
||||
|
||||
// Move the file
|
||||
renameSync(oldPath, newPath);
|
||||
console.log(`File moved from ${oldPath} to ${newPath}`);
|
||||
|
||||
res.status(200).json({ code: 200 });
|
||||
} catch (err: any) {
|
||||
console.error("Error moving file:", err);
|
||||
// You might want to send a more specific error message based on 'err.code'
|
||||
if (err.code === 'EXDEV') {
|
||||
// This specific error means moving across different file systems.
|
||||
// Synchronous fallback would be more complex (read/write stream synchronously),
|
||||
// which is why async is preferred here. For synchronous, you'd generally
|
||||
// just fail or implement a blocking copy/delete.
|
||||
res.status(500).json({ code: 500, error: "Error: Cannot move across different file systems synchronously. Try copying and deleting." });
|
||||
} else if (err.code === 'ENOENT') {
|
||||
res.status(404).json({ code: 404, error: "Error: Source file or destination path not found." });
|
||||
} else {
|
||||
res.status(500).json({ code: 500, error: `Failed to move file: ${err.message}` });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Apply the routes to the server
|
||||
*/
|
||||
|
||||
57
frontend/src/components/MoveDirectory.jsx
Normal file
57
frontend/src/components/MoveDirectory.jsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
/**
|
||||
* Move a file or directory.
|
||||
* @constructor
|
||||
*/
|
||||
export default function MoveDirectory({ close, move, path }) {
|
||||
const [dirName, setDirName] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setDirName("/" + path.join("/"));
|
||||
}, [path]);
|
||||
|
||||
const moveDirectory = () => move(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">
|
||||
{/* Blured backdrop */}
|
||||
<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-9/10 lg:w-2/5 border-1 border-gray-400">
|
||||
<h2 className="text-2xl font-semibold mb-2 text-blue-400">
|
||||
Move/Rename Directory
|
||||
</h2>
|
||||
<p className="text-sm">
|
||||
Move a file or directory from the current directory to a new location. This can be used to rename files
|
||||
if the base directory remains the same.
|
||||
</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={moveDirectory}
|
||||
title="Move 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">
|
||||
Move
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -66,6 +66,22 @@ function DeleteButton({onClick, enabled}) {
|
||||
</>
|
||||
}
|
||||
|
||||
function MoveButton({ onClick, enabled }) {
|
||||
return <>
|
||||
<button onClick={onClick}
|
||||
disabled={!enabled}
|
||||
title="Moved a selected 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="M12 21V3M12 21L14 19M12 21L10 19M12 3L14 5M12 3L10 5M21 12H3M21 12L19 10M21 12L19 14M3 12L5 10M3 12L5 14"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name {string}
|
||||
@ -98,7 +114,7 @@ function PathElement({name, index, onClick}) {
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function PathDisplay({path, updatePath, backHome, backArrow, enabled, create, remove, removeEnable}) {
|
||||
export default function PathDisplay({ path, updatePath, backHome, backArrow, enabled, create, remove, removeEnable, move, moveEnabled }) {
|
||||
return <>
|
||||
<div className="w-9/10 lg:w-2/3 mt-8 border-b-1 border-gray-400 bg-white flex items-center truncate">
|
||||
<HomeButton onClick={backHome} enabled={enabled} />
|
||||
@ -107,6 +123,9 @@ export default function PathDisplay({path, updatePath, backHome, backArrow, enab
|
||||
<div className="ml-auto h-full flex items-center">
|
||||
<DeleteButton onClick={remove} enabled={removeEnable} />
|
||||
</div>
|
||||
<div className="h-full flex items-center">
|
||||
<MoveButton onClick={move} enabled={moveEnabled} />
|
||||
</div>
|
||||
<div className="h-full flex items-center">
|
||||
<CreateButton onClick={create} />
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,7 @@ import ChildrenLoading from "../components/ChildrenLoading.jsx";
|
||||
import DownloadLoading from "../components/DownloadLoading.jsx";
|
||||
import Uploader from "../components/Uploader.jsx";
|
||||
import CreateDirectory from "../components/CreateDirectory.jsx";
|
||||
import MoveDirectory from "../components/MoveDirectory.jsx";
|
||||
|
||||
export default function Dashboard() {
|
||||
// ---- CONSTANTS ---- //
|
||||
@ -50,6 +51,8 @@ export default function Dashboard() {
|
||||
const [editing, setEditing] = useState("");
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [moving, setMoving] = useState(false);
|
||||
|
||||
|
||||
// Loading spinners
|
||||
const [childrenLoading, setChildrenLoading] = useState(false);
|
||||
@ -118,6 +121,16 @@ export default function Dashboard() {
|
||||
*/
|
||||
const closeCreate = () => setCreating(false);
|
||||
|
||||
/**
|
||||
* Hide the moving modal.
|
||||
*/
|
||||
const closeMoving = () => setMoving(false);
|
||||
|
||||
/**
|
||||
* Show the moving modal.
|
||||
*/
|
||||
const showMoving = () => setMoving(true);
|
||||
|
||||
// ---- HANDLERS --- //
|
||||
|
||||
/**
|
||||
@ -225,7 +238,6 @@ export default function Dashboard() {
|
||||
|
||||
remove(files).then((data) => {
|
||||
if (data.code === 201) {
|
||||
console.log(data);
|
||||
setSelected([]);
|
||||
|
||||
// Fetch the new files & deselect everything
|
||||
@ -236,6 +248,21 @@ export default function Dashboard() {
|
||||
});
|
||||
};
|
||||
|
||||
const moveSelected = (newPath) => {
|
||||
const oldPath = "/" + [...path, selected].join("/");
|
||||
|
||||
console.log("@oldPath", oldPath);
|
||||
console.log("@newPath", newPath);
|
||||
|
||||
move(oldPath, newPath).then((data) => {
|
||||
if (data.code === 200) {
|
||||
setMoving(false);
|
||||
setSelected([]);
|
||||
fetchFiles();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ---- SERVER FUNCTIONS ---- //
|
||||
|
||||
/**
|
||||
@ -399,9 +426,30 @@ export default function Dashboard() {
|
||||
return data;
|
||||
}
|
||||
|
||||
return await resp.json();
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
const move = async (oldPath, newPath) => {
|
||||
const resp = await fetch(`${backendUrl}/v1/move`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ oldPath, newPath })
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const data = await resp.json()
|
||||
setError(data.error);
|
||||
return data;
|
||||
}
|
||||
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
|
||||
}
|
||||
// ---- EFFECTS ---- //
|
||||
|
||||
// Update the file list from the server each time an action requires
|
||||
@ -494,6 +542,7 @@ export default function Dashboard() {
|
||||
{downloadLoading && <DownloadLoading />}
|
||||
{creating && <CreateDirectory close={closeCreate} create={createDirectory} />}
|
||||
{uploading && <Uploader close={toggleUploading} upload={upload} />}
|
||||
{moving && <MoveDirectory close={closeMoving} move={moveSelected} path={[...path, selected[0]]} />}
|
||||
|
||||
{error && <Error error={error} clear={clearError} />}
|
||||
{
|
||||
@ -516,6 +565,8 @@ export default function Dashboard() {
|
||||
create={createDir}
|
||||
remove={removeSelected}
|
||||
removeEnable={selected.length > 0}
|
||||
move={showMoving}
|
||||
moveEnabled={selected.length === 1}
|
||||
/>
|
||||
|
||||
<div className="w-9/10 lg:w-2/3 h-5/6 overflow-y-auto border-1 border-gray-300">
|
||||
|
||||
Reference in New Issue
Block a user