(REFACTOR): Moved all the code around and organized it.
It was a mess in here, holy shit.
This commit is contained in:
parent
1dd9f4e38f
commit
d8873e08f2
@ -8,6 +8,7 @@
|
|||||||
- [ ] Image display (will require some kind of file server)
|
- [ ] Image display (will require some kind of file server)
|
||||||
- [ ] Markdown rendering (quite a reach)
|
- [ ] Markdown rendering (quite a reach)
|
||||||
- [x] Mobile friendly
|
- [x] Mobile friendly
|
||||||
|
- [ ] Fix selection bug
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@ -11,46 +11,66 @@ import Uploader from "../components/Uploader.jsx";
|
|||||||
import CreateDirectory from "../components/CreateDirectory.jsx";
|
import CreateDirectory from "../components/CreateDirectory.jsx";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
// Store the default path
|
// ---- CONSTANTS ---- //
|
||||||
// TODO: BACK TO NORMAL PATH
|
/**
|
||||||
const defaultPath = ["media", "vault"];
|
* Default path
|
||||||
// const defaultPath = ["home", "azpect"];
|
* Uses the .env var in local development, but when
|
||||||
|
* pushed to dockerhub, the .env is ignored and the
|
||||||
|
* default path is used.
|
||||||
|
*/
|
||||||
|
const defaultPath = (import.meta.env.VITE_DEFAULT_PATH || "media,vault").split(',');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL To the backend web server.
|
* URL To the backend web server.
|
||||||
* Uses the .env var in local development, but when
|
* Uses the .env var in local development, but when
|
||||||
* pushed to dockerhub, the .env is ignored and the real
|
* pushed to dockerhub, the .env is ignored and the
|
||||||
* backend URL is used.
|
* real backend URL is used.
|
||||||
* @type {string}
|
|
||||||
*/
|
*/
|
||||||
const backendUrl = import.meta.env.VITE_BACKEND_URL || "https://backend.gophernest.net";
|
const backendUrl = import.meta.env.VITE_BACKEND_URL || "https://backend.gophernest.net";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the value stored in local storage.
|
||||||
|
*/
|
||||||
|
const storage_id = "gophernest_credentials";
|
||||||
|
|
||||||
|
|
||||||
|
// ---- STATE ---- //
|
||||||
|
|
||||||
|
// General state
|
||||||
const [token, setToken] = useState(null);
|
const [token, setToken] = useState(null);
|
||||||
const [path, setPath] = useState([...defaultPath]);
|
const [path, setPath] = useState([...defaultPath]);
|
||||||
|
const [files, setFiles] = useState([]);
|
||||||
|
|
||||||
|
// User settings
|
||||||
const [showHidden, setShowHidden] = useState(false);
|
const [showHidden, setShowHidden] = useState(false);
|
||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState([]);
|
||||||
const [files, setFiles] = useState([]);
|
|
||||||
|
// Modals
|
||||||
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 [creating, setCreating] = useState(false);
|
||||||
|
|
||||||
|
// Loading spinners
|
||||||
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);
|
||||||
|
|
||||||
|
|
||||||
|
// Handle the state for the content being modified in the text editor.
|
||||||
|
const [editingFileContent, setEditingFileContent] = useState("");
|
||||||
|
|
||||||
|
// Other
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// ---- FUNCTIONS ---- //
|
||||||
/**
|
|
||||||
* The name of the value stored in local storage.
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
const storage_id = "gophernest_credentials";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the token from the local or session storage.
|
* Update the token from the local or session storage.
|
||||||
* This function assumes one of them exists.
|
* This function assumes one of them exists.
|
||||||
* If it does not, the token will be null.
|
* If it does not, the token will be null.
|
||||||
* This function will return the token as well, to allow for other usecases.
|
* This function will return the token as well, to allow for other usecases.
|
||||||
|
* @returns {string | null} Token from browser storage
|
||||||
*/
|
*/
|
||||||
const updateToken = () => {
|
const updateToken = () => {
|
||||||
const t = localStorage.getItem(storage_id) ? localStorage.getItem(storage_id) : sessionStorage.getItem(storage_id);
|
const t = localStorage.getItem(storage_id) ? localStorage.getItem(storage_id) : sessionStorage.getItem(storage_id);
|
||||||
@ -58,6 +78,166 @@ export default function Dashboard() {
|
|||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the error in the error state.
|
||||||
|
*/
|
||||||
|
const clearError = () => setError(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle editing of a file.
|
||||||
|
* @param path {string}
|
||||||
|
*/
|
||||||
|
const toggleEditing = (path) => setEditing(path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the file that is being edited.
|
||||||
|
*/
|
||||||
|
const exitFile = () => setEditing("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the hidden files
|
||||||
|
*/
|
||||||
|
const toggleHidden = (e) => setShowHidden(e.target.checked);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the upload modal.
|
||||||
|
* This can be used in the navbar and the close button!
|
||||||
|
*/
|
||||||
|
const toggleUploading = () => setUploading(!uploading);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the creating modal.
|
||||||
|
*/
|
||||||
|
const createDir = () => setCreating(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the creating modal.
|
||||||
|
*/
|
||||||
|
const closeCreate = () => setCreating(false);
|
||||||
|
|
||||||
|
// ---- HANDLERS --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the path by slicing [0:index]
|
||||||
|
* @param index {number} - Index to slice to.
|
||||||
|
*/
|
||||||
|
const updatePath = (index) => {
|
||||||
|
let newPath = path.slice(0, index + 1);
|
||||||
|
if (newPath.length < defaultPath.length) {
|
||||||
|
newPath = [...defaultPath];
|
||||||
|
}
|
||||||
|
setPath(newPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the path back to the default.
|
||||||
|
* TODO: Fix this in production? Not sure why
|
||||||
|
*/
|
||||||
|
const backHome = () => setPath([...defaultPath]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add name to the path.
|
||||||
|
* @param name {string} - Target child
|
||||||
|
*/
|
||||||
|
const appendPath = (name) => setPath([...path, name])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Back arrow, goes back one directory (cd ..)
|
||||||
|
*/
|
||||||
|
const backArrow = () => {
|
||||||
|
if (path.length > defaultPath.length) {
|
||||||
|
setPath(path.slice(0, path.length - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exitAndSaveFile = (newContent) => {
|
||||||
|
// Send request to server to update the file. This will return nothing
|
||||||
|
// so no need for any promise handling.
|
||||||
|
updateContent(editing, newContent).then((data) => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
setEditing("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This isn't fast, but hopefully the use case will be small batches.
|
||||||
|
* @param file {string} - The file to toggle
|
||||||
|
*/
|
||||||
|
const toggleSelected = (file) => {
|
||||||
|
if (!selected.includes(file)) {
|
||||||
|
setSelected([...selected, file]);
|
||||||
|
} else {
|
||||||
|
const idx = selected.indexOf(file);
|
||||||
|
setSelected([...selected.slice(0, idx), ...selected.slice(idx + 1, selected.length)])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function for when the download button is clicked.
|
||||||
|
*/
|
||||||
|
const downloadFiles = () => {
|
||||||
|
// Do not allow empty downloads
|
||||||
|
if (selected.length === 0) {
|
||||||
|
setError("Please select files/directories to download.");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setDownloadLoading(true);
|
||||||
|
|
||||||
|
const targets = selected.map((file) => `/${path.join("/")}/${file}`);
|
||||||
|
|
||||||
|
download(targets).catch((err) => {
|
||||||
|
setError(`Download error: ${err}.`)
|
||||||
|
}).finally(() => {
|
||||||
|
setDownloadLoading(false)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a directory or file in the backend
|
||||||
|
* @param name {string} Name of new file or directory
|
||||||
|
*/
|
||||||
|
const createDirectory = (name) => {
|
||||||
|
create(name, path).then((data) => {
|
||||||
|
if (data.code === 201) {
|
||||||
|
setCreating(false);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
setError(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the selected files.
|
||||||
|
*/
|
||||||
|
const removeSelected = () => {
|
||||||
|
if (selected.length === 0) {
|
||||||
|
setError("Please select files or directories to delete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files are stored as arrays of paths
|
||||||
|
const files = selected.map((file) => [...path, file]);
|
||||||
|
|
||||||
|
remove(files).then((data) => {
|
||||||
|
if (data.code === 201) {
|
||||||
|
console.log(data);
|
||||||
|
setSelected([]);
|
||||||
|
|
||||||
|
// Fetch the new files & deselect everything
|
||||||
|
fetchFiles();
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
setError(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- SERVER FUNCTIONS ---- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a list of files in the current directory.
|
||||||
|
*/
|
||||||
const fetchFiles = () => {
|
const fetchFiles = () => {
|
||||||
const getData = async (token) => {
|
const getData = async (token) => {
|
||||||
const response = await fetch(`${backendUrl}/v1/children?path=/${path.join("/")}`, {
|
const response = await fetch(`${backendUrl}/v1/children?path=/${path.join("/")}`, {
|
||||||
@ -92,81 +272,33 @@ export default function Dashboard() {
|
|||||||
setSelected([]);
|
setSelected([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchFiles();
|
|
||||||
}, [path, uploading, creating]);
|
|
||||||
|
|
||||||
|
|
||||||
// Redirect if the user isn't logged in, otherwise update the state.
|
|
||||||
// Store the token in the storage, it should be attached to every request.
|
|
||||||
useEffect(() => {
|
|
||||||
if (localStorage.getItem(storage_id) == null && sessionStorage.getItem(storage_id) == null) {
|
|
||||||
navigate("/login");
|
|
||||||
} else {
|
|
||||||
updateToken();
|
|
||||||
}
|
|
||||||
}, [navigate]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the path by slicing [0:index]
|
* Update the contents of a file.
|
||||||
* @param index {number} Index to slice to.
|
* @param path {string} - Path to the file.
|
||||||
|
* @param content {string} - New content to add to the file.
|
||||||
|
* @return {any} - Server response, nothing.
|
||||||
*/
|
*/
|
||||||
const updatePath = (index) => {
|
const updateContent = async (path, content) => {
|
||||||
let newPath = path.slice(0, index + 1);
|
const resp = await fetch(`${backendUrl}/v1/update`, {
|
||||||
if (newPath.length < defaultPath.length) {
|
method: "POST",
|
||||||
newPath = [...defaultPath];
|
headers: {
|
||||||
}
|
"Content-Type": "application/json",
|
||||||
setPath(newPath);
|
"Authorization": `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ path, content }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!resp.ok) setError("An error occurred when saving the file. Please try again.");
|
||||||
|
|
||||||
|
const json = resp.json();
|
||||||
|
return json;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the path back to the default.
|
* Download a list of files.
|
||||||
|
* @param paths {string[]} - List of paths to download.
|
||||||
|
* @returns void
|
||||||
*/
|
*/
|
||||||
const backHome = () => {
|
|
||||||
// TODO: Fix this in production
|
|
||||||
setPath([...defaultPath]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add name to the path.
|
|
||||||
* @param name Target child
|
|
||||||
*/
|
|
||||||
const appendPath = (name) => {
|
|
||||||
setPath([...path, name])
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Back arrow, goes back one directory (cd ..)
|
|
||||||
*/
|
|
||||||
const backArrow = () => {
|
|
||||||
if (path.length > defaultPath.length) {
|
|
||||||
setPath(path.slice(0, path.length - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This isn't fast, but hopefully the use case will be small batches.
|
|
||||||
* @param file {string} The file to toggle
|
|
||||||
*/
|
|
||||||
const toggleSelected = (file) => {
|
|
||||||
if (!selected.includes(file)) {
|
|
||||||
setSelected([...selected, file]);
|
|
||||||
} else {
|
|
||||||
const idx = selected.indexOf(file);
|
|
||||||
setSelected([...selected.slice(0, idx), ...selected.slice(idx + 1, selected.length)])
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback function for when the download button is clicked.
|
|
||||||
*/
|
|
||||||
const downloadFiles = () => {
|
|
||||||
// Do not allow empty downloads
|
|
||||||
if (selected.length === 0) {
|
|
||||||
setError("Please select files/directories to download.");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const download = async (paths) => {
|
const download = async (paths) => {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${backendUrl}/v1/download`, {
|
const resp = await fetch(`${backendUrl}/v1/download`, {
|
||||||
@ -197,70 +329,11 @@ export default function Dashboard() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setDownloadLoading(true);
|
|
||||||
|
|
||||||
const targets = [];
|
|
||||||
selected.forEach((file) => {
|
|
||||||
targets.push(`/${path.join("/")}/${file}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Implement UI for errors
|
|
||||||
download(targets).catch((err) => {
|
|
||||||
setError(`Download error: ${err}.`)
|
|
||||||
}).finally(() => {
|
|
||||||
setDownloadLoading(false)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the error in the error state.
|
* Fetch the content of a file.
|
||||||
|
* @param path {string} - File content.
|
||||||
|
* @returns {any} - Server response with content.
|
||||||
*/
|
*/
|
||||||
const clearError = () => {
|
|
||||||
setError(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle editing of a file.
|
|
||||||
* @param path {string}
|
|
||||||
*/
|
|
||||||
const toggleEditing = (path) => {
|
|
||||||
setEditing(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
const exitFile = () => {
|
|
||||||
setEditing("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const exitAndSaveFile = (newContent) => {
|
|
||||||
const updateContent = async (path, content) => {
|
|
||||||
const resp = await fetch(`${backendUrl}/v1/update`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ path, content }),
|
|
||||||
})
|
|
||||||
if (!resp.ok) {
|
|
||||||
setError("An error occurred when saving the file. Please try again.");
|
|
||||||
}
|
|
||||||
return await resp.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send request to server to update the file. This will return nothing
|
|
||||||
// so no need for any promise handling.
|
|
||||||
updateContent(editing, newContent).then((data) => {
|
|
||||||
if (data.code === 200) {
|
|
||||||
setEditing("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the state for the content being modified in the text editor.
|
|
||||||
*/
|
|
||||||
const [editingFileContent, setEditingFileContent] = useState("");
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchContent = async (path) => {
|
const fetchContent = async (path) => {
|
||||||
const resp = await fetch(`${backendUrl}/v1/content?path=${path}`, {
|
const resp = await fetch(`${backendUrl}/v1/content?path=${path}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@ -269,14 +342,85 @@ export default function Dashboard() {
|
|||||||
"Authorization": `Bearer ${token}`,
|
"Authorization": `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!resp.ok) setError("Something went wrong! Failed to get file content.")
|
||||||
|
|
||||||
|
const json = await resp.json();
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a file or directory.
|
||||||
|
* @param name {string} - Name of the file or directory.
|
||||||
|
* @param cwd {string[]} - Current directory as a list of paths.
|
||||||
|
* @return {any} Server response.
|
||||||
|
*/
|
||||||
|
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) {
|
if (!resp.ok) {
|
||||||
// TODO: Add this back in, its broken right now.
|
const data = await resp.json()
|
||||||
setError("Something went wrong! Failed to get file content.")
|
setError(data.error);
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const json = resp.json();
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a list of files.
|
||||||
|
* @param files {string[][]} - List of file paths as list of sub-paths.
|
||||||
|
* @returns {any} Server response.
|
||||||
|
*/
|
||||||
|
const remove = async (files) => {
|
||||||
|
const resp = await fetch(`${backendUrl}/v1/delete`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"authorization": `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ files, root: defaultPath })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resp.ok) {
|
||||||
|
const data = await resp.json()
|
||||||
|
setError(data.error);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
return await resp.json();
|
return await resp.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- EFFECTS ---- //
|
||||||
|
|
||||||
|
// Update the file list from the server each time an action requires
|
||||||
|
// an update.
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFiles();
|
||||||
|
}, [path, uploading, creating]);
|
||||||
|
|
||||||
|
|
||||||
|
// Redirect if the user isn't logged in, otherwise update the state.
|
||||||
|
// Store the token in the storage, it should be attached to every request.
|
||||||
|
useEffect(() => {
|
||||||
|
(localStorage.getItem(storage_id) == null && sessionStorage.getItem(storage_id) == null)
|
||||||
|
? navigate("/login")
|
||||||
|
: updateToken();
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
// Load the content from the file into the editor and allow the user to begin
|
||||||
|
// editing the file.
|
||||||
|
useEffect(() => {
|
||||||
setContentLoading(true);
|
setContentLoading(true);
|
||||||
|
|
||||||
// Prevent running when nothing is being edited. Also prevents a call on mount.
|
// Prevent running when nothing is being edited. Also prevents a call on mount.
|
||||||
if (editing) {
|
if (editing) {
|
||||||
// Fetch the data and handle errors accordingly
|
// Fetch the data and handle errors accordingly
|
||||||
@ -295,17 +439,6 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
}, [editing]);
|
}, [editing]);
|
||||||
|
|
||||||
const toggleHidden = (e) => {
|
|
||||||
setShowHidden(e.target.checked);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the upload modal.
|
|
||||||
* This can be used in the navbar and the close button!
|
|
||||||
*/
|
|
||||||
const toggleUploading = () => {
|
|
||||||
setUploading(!uploading);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will be where the magic happens, where the files are upload
|
* This will be where the magic happens, where the files are upload
|
||||||
@ -351,93 +484,6 @@ 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);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the selected files.
|
|
||||||
*/
|
|
||||||
const removeSelected = () => {
|
|
||||||
if (selected.length === 0) {
|
|
||||||
return setError("Please select files or directories to delete");
|
|
||||||
}
|
|
||||||
|
|
||||||
const remove = async (files) => {
|
|
||||||
const resp = await fetch(`${backendUrl}/v1/delete`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"authorization": `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ files, root: defaultPath })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!resp.ok) {
|
|
||||||
const data = await resp.json()
|
|
||||||
setError(data.error);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await resp.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Files are stored as arrays of paths
|
|
||||||
const files = [];
|
|
||||||
for (const file of selected) {
|
|
||||||
files.push([...path, file]);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(files).then((data) => {
|
|
||||||
if (data.code === 201) {
|
|
||||||
console.log(data);
|
|
||||||
setSelected([]);
|
|
||||||
|
|
||||||
// Fetch the new files & deselect everything
|
|
||||||
fetchFiles();
|
|
||||||
}
|
|
||||||
}).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} />
|
||||||
|
|||||||
Reference in New Issue
Block a user