(FEAT): Mobile friendly and code cleanup!
Cleaned the formatting with Nvim and created mobile friendly styles.
This commit is contained in:
parent
79c438aa3a
commit
7a583ec1d6
@ -20,8 +20,8 @@ import Multer from "multer";
|
||||
const PORT = 5000;
|
||||
const APP: Express = express();
|
||||
// TODO: BACK TO NORMAL PATH
|
||||
const ROOT: string = "/media/vault";
|
||||
// const ROOT: string = "/home/azpect";
|
||||
// const ROOT: string = "/media/vault";
|
||||
const ROOT: string = "/home/azpect";
|
||||
|
||||
/**
|
||||
* Configure the .env file, this is for testing only, should be ignored in production.
|
||||
|
||||
@ -6,12 +6,12 @@ import "../index.css"
|
||||
* @constructor
|
||||
*/
|
||||
export default function ChildrenLoading() {
|
||||
return (
|
||||
<div className="size-full flex items-center justify-center">
|
||||
<div
|
||||
className="animate-spin rounded-full border-blue-500 border-3 border-t-transparent size-6 mx-2 ">
|
||||
</div>
|
||||
<p className="text-lg text-black opacity-90">Content loading...</p>
|
||||
</div>
|
||||
);
|
||||
return <>
|
||||
<div className="size-full flex items-center justify-center">
|
||||
<div
|
||||
className="animate-spin rounded-full border-blue-500 border-3 border-t-transparent size-6 mx-2 ">
|
||||
</div>
|
||||
<p className="text-lg text-black opacity-90">Content loading...</p>
|
||||
</div>
|
||||
</>
|
||||
};
|
||||
@ -6,15 +6,15 @@ import "../index.css"
|
||||
* @constructor
|
||||
*/
|
||||
export default function ContentLoading() {
|
||||
return (
|
||||
<div className="h-9/10 w-full flex flex-col items-center justify-center">
|
||||
<div className="flex">
|
||||
<div
|
||||
className="animate-spin rounded-full border-blue-500 border-3 border-t-transparent size-6 mr-2 ">
|
||||
</div>
|
||||
<p className="text-lg text-black opacity-90">Content loading...</p>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 my-2">For large files, this may take a while.</p>
|
||||
return <>
|
||||
<div className="h-9/10 w-full flex flex-col items-center justify-center">
|
||||
<div className="flex">
|
||||
<div
|
||||
className="animate-spin rounded-full border-blue-500 border-3 border-t-transparent size-6 mr-2 ">
|
||||
</div>
|
||||
);
|
||||
<p className="text-lg text-black opacity-90">Content loading...</p>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 my-2">For large files, this may take a while.</p>
|
||||
</div>
|
||||
</>
|
||||
};
|
||||
|
||||
@ -5,51 +5,50 @@ import {useState} from "react";
|
||||
* @constructor
|
||||
*/
|
||||
export default function CreateDirectory({close, create}) {
|
||||
const [dirName, setDirName] = useState("");
|
||||
const [dirName, setDirName] = useState("");
|
||||
|
||||
const createDirectory = () => {
|
||||
create(dirName);
|
||||
}
|
||||
const createDirectory = () => create(dirName);
|
||||
const updateDirName = (e) => setDirName(e.target.value);
|
||||
|
||||
const updateDirName = (e) => {
|
||||
setDirName(e.target.value);
|
||||
}
|
||||
const closeWithoutSaving = () => {
|
||||
setDirName("");
|
||||
close();
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
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>
|
||||
<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">
|
||||
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>
|
||||
<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>
|
||||
</>
|
||||
}
|
||||
@ -1,22 +1,26 @@
|
||||
import "../index.css"
|
||||
import {useState} from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
function FileIcon() {
|
||||
return <svg className="text-black h-5 mx-2" stroke="currentColor" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M19 9V17.8C19 18.9201 19 19.4802 18.782 19.908C18.5903 20.2843 18.2843 20.5903 17.908 20.782C17.4802 21 16.9201 21 15.8 21H8.2C7.07989 21 6.51984 21 6.09202 20.782C5.71569 20.5903 5.40973 20.2843 5.21799 19.908C5 19.4802 5 18.9201 5 17.8V6.2C5 5.07989 5 4.51984 5.21799 4.09202C5.40973 3.71569 5.71569 3.40973 6.09202 3.21799C6.51984 3 7.0799 3 8.2 3H13M19 9L13 3M19 9H14C13.4477 9 13 8.55228 13 8V3"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
return <>
|
||||
<svg className="text-black h-5 mx-2" stroke="currentColor" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M19 9V17.8C19 18.9201 19 19.4802 18.782 19.908C18.5903 20.2843 18.2843 20.5903 17.908 20.782C17.4802 21 16.9201 21 15.8 21H8.2C7.07989 21 6.51984 21 6.09202 20.782C5.71569 20.5903 5.40973 20.2843 5.21799 19.908C5 19.4802 5 18.9201 5 17.8V6.2C5 5.07989 5 4.51984 5.21799 4.09202C5.40973 3.71569 5.71569 3.40973 6.09202 3.21799C6.51984 3 7.0799 3 8.2 3H13M19 9L13 3M19 9H14C13.4477 9 13 8.55228 13 8V3"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</>
|
||||
}
|
||||
|
||||
function DirectoryIcon() {
|
||||
return <svg className="text-black h-5 mx-2" stroke="currentColor" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M3 8.2C3 7.07989 3 6.51984 3.21799 6.09202C3.40973 5.71569 3.71569 5.40973 4.09202 5.21799C4.51984 5 5.0799 5 6.2 5H9.67452C10.1637 5 10.4083 5 10.6385 5.05526C10.8425 5.10425 11.0376 5.18506 11.2166 5.29472C11.4184 5.4184 11.5914 5.59135 11.9373 5.93726L12.0627 6.06274C12.4086 6.40865 12.5816 6.5816 12.7834 6.70528C12.9624 6.81494 13.1575 6.89575 13.3615 6.94474C13.5917 7 13.8363 7 14.3255 7H17.8C18.9201 7 19.4802 7 19.908 7.21799C20.2843 7.40973 20.5903 7.71569 20.782 8.09202C21 8.51984 21 9.0799 21 10.2V15.8C21 16.9201 21 17.4802 20.782 17.908C20.5903 18.2843 20.2843 18.5903 19.908 18.782C19.4802 19 18.9201 19 17.8 19H6.2C5.07989 19 4.51984 19 4.09202 18.782C3.71569 18.5903 3.40973 18.2843 3.21799 17.908C3 17.4802 3 16.9201 3 15.8V8.2Z"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
return <>
|
||||
<svg className="text-black h-5 mx-2" stroke="currentColor" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M3 8.2C3 7.07989 3 6.51984 3.21799 6.09202C3.40973 5.71569 3.71569 5.40973 4.09202 5.21799C4.51984 5 5.0799 5 6.2 5H9.67452C10.1637 5 10.4083 5 10.6385 5.05526C10.8425 5.10425 11.0376 5.18506 11.2166 5.29472C11.4184 5.4184 11.5914 5.59135 11.9373 5.93726L12.0627 6.06274C12.4086 6.40865 12.5816 6.5816 12.7834 6.70528C12.9624 6.81494 13.1575 6.89575 13.3615 6.94474C13.5917 7 13.8363 7 14.3255 7H17.8C18.9201 7 19.4802 7 19.908 7.21799C20.2843 7.40973 20.5903 7.71569 20.782 8.09202C21 8.51984 21 9.0799 21 10.2V15.8C21 16.9201 21 17.4802 20.782 17.908C20.5903 18.2843 20.2843 18.5903 19.908 18.782C19.4802 19 18.9201 19 17.8 19H6.2C5.07989 19 4.51984 19 4.09202 18.782C3.71569 18.5903 3.40973 18.2843 3.21799 17.908C3 17.4802 3 16.9201 3 15.8V8.2Z"
|
||||
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,40 +32,43 @@ function DirectoryIcon() {
|
||||
* @returns {{}}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Directory({entry, showHidden, appendPath, toggleSelected, toggleEditing}) {
|
||||
const [selected, setSelected] = useState(false);
|
||||
export default function Directory({ entry, showHidden, appendPath, toggleSelected, toggleEditing }) {
|
||||
const [selected, setSelected] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
if (entry.directory) {
|
||||
appendPath(entry.name);
|
||||
} else {
|
||||
toggleEditing(entry.path);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheck = () => {
|
||||
toggleSelected(entry.name);
|
||||
setSelected(!selected);
|
||||
const handleClick = () => {
|
||||
if (entry.directory) {
|
||||
appendPath(entry.name);
|
||||
} else {
|
||||
toggleEditing(entry.path);
|
||||
}
|
||||
};
|
||||
|
||||
// This is temporary, eventually I will have a real data model that stores
|
||||
// directory vs file status.
|
||||
// const isDirectory = !name.endsWith(".html");
|
||||
if (entry.name.startsWith(".") && !showHidden) {
|
||||
return <></>;
|
||||
}
|
||||
const handleCheck = () => {
|
||||
toggleSelected(entry.name);
|
||||
setSelected(!selected);
|
||||
}
|
||||
|
||||
return (
|
||||
<label className={`w-full hover:bg-gray-300 flex ${selected ? "bg-blue-300" : "bg-gray-100"}`}>
|
||||
<input className="mx-2 peer checked:bg-blue-300" type="checkbox" checked={selected}
|
||||
onChange={handleCheck}/>
|
||||
<div className="w-full flex bg-gray-100 peer-checked:bg-blue-300 hover:bg-gray-300 peer-hover:bg-gray-300">
|
||||
<button className="flex items-center" onClick={handleClick}>
|
||||
{entry.directory ? <DirectoryIcon/> : <FileIcon/>}
|
||||
<p className="p-2 hover:underline hover:text-blue-400">{entry.name}{entry.directory ? "/" : ""}</p>
|
||||
</button>
|
||||
</div>
|
||||
// This is temporary, eventually I will have a real data model that stores
|
||||
// directory vs file status.
|
||||
// const isDirectory = !name.endsWith(".html");
|
||||
if (entry.name.startsWith(".") && !showHidden) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
</label>
|
||||
);
|
||||
return <>
|
||||
<label className={`w-full hover:bg-gray-300 flex ${selected ? "bg-blue-300" : "bg-gray-100"}`}>
|
||||
<input
|
||||
className="mx-2 peer checked:bg-blue-300"
|
||||
type="checkbox"
|
||||
checked={selected}
|
||||
onChange={handleCheck} />
|
||||
<div className="w-full flex bg-gray-100 peer-checked:bg-blue-300 hover:bg-gray-300 peer-hover:bg-gray-300">
|
||||
<button className="flex items-center" onClick={handleClick}>
|
||||
{entry.directory ? <DirectoryIcon /> : <FileIcon />}
|
||||
<p className="p-2 hover:underline hover:text-blue-400">{entry.name}{entry.directory ? "/" : ""}</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</label>
|
||||
</>
|
||||
}
|
||||
@ -8,13 +8,16 @@ import Directory from "./Directory.jsx";
|
||||
* @param toggleSelected {function(string)} Function to toggle selection status.
|
||||
* @constructor
|
||||
*/
|
||||
export default function DirectoryList({dirs, showHidden, appendPath, toggleSelected, toggleEditing}) {
|
||||
return (
|
||||
<>
|
||||
{dirs.map((dir, idx) => <Directory key={idx} entry={dir} showHidden={showHidden} appendPath={appendPath}
|
||||
toggleSelected={toggleSelected} toggleEditing={toggleEditing}/>)}
|
||||
</>
|
||||
)
|
||||
|
||||
|
||||
export default function DirectoryList({ dirs, showHidden, appendPath, toggleSelected, toggleEditing }) {
|
||||
return <>
|
||||
{dirs.map((dir, idx) =>
|
||||
<Directory
|
||||
key={idx}
|
||||
entry={dir}
|
||||
showHidden={showHidden}
|
||||
appendPath={appendPath}
|
||||
toggleSelected={toggleSelected}
|
||||
toggleEditing={toggleEditing} />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
@ -6,13 +6,13 @@ import "../index.css"
|
||||
* @constructor
|
||||
*/
|
||||
export default function DownloadLoading() {
|
||||
return (
|
||||
<div className="absolute size-full flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-black opacity-25 blur-lg"></div>
|
||||
<div
|
||||
className="animate-spin rounded-full border-blue-500 border-3 border-t-transparent size-6 mx-2">
|
||||
</div>
|
||||
<p className="text-lg text-black opacity-90">Preparing files...</p>
|
||||
</div>
|
||||
);
|
||||
return <>
|
||||
<div className="absolute size-full flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-black opacity-25 blur-lg"></div>
|
||||
<div
|
||||
className="animate-spin rounded-full border-blue-500 border-3 border-t-transparent size-6 mx-2">
|
||||
</div>
|
||||
<p className="text-lg text-black opacity-90">Preparing files...</p>
|
||||
</div>
|
||||
</>
|
||||
};
|
||||
|
||||
@ -1,86 +1,87 @@
|
||||
import {useEffect, useRef, useState} from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import ContentLoading from "./ContentLoading.jsx";
|
||||
|
||||
export default function Editor({content, path, exit, saveExit, loading}) {
|
||||
const [text, setText] = useState("");
|
||||
/**
|
||||
export default function Editor({ content, path, exit, saveExit, loading }) {
|
||||
const [text, setText] = useState("");
|
||||
/**
|
||||
* Store a reference to the text area object
|
||||
* @type {React.RefObject<null>}
|
||||
*/
|
||||
const textareaRef = useRef(null);
|
||||
const textareaRef = useRef(null);
|
||||
|
||||
const updateText = (event) => {
|
||||
setText(event.target.value);
|
||||
};
|
||||
const updateText = (event) => setText(event.target.value);
|
||||
|
||||
useEffect(() => {
|
||||
setText(content);
|
||||
}, [content])
|
||||
useEffect(() => {
|
||||
setText(content);
|
||||
}, [content])
|
||||
|
||||
const handleKeyPress = (event) => {
|
||||
// Override tab changing focus
|
||||
// Uses 2 space indents
|
||||
// TODO: Allow toggle for two and four space indents
|
||||
if (event.key === "Tab") {
|
||||
event.preventDefault();
|
||||
const handleKeyPress = (event) => {
|
||||
// Override tab changing focus
|
||||
// Uses 2 space indents
|
||||
// TODO: Allow toggle for two and four space indents
|
||||
if (event.key === "Tab") {
|
||||
event.preventDefault();
|
||||
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) return;
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) return;
|
||||
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const value = textarea.value;
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const value = textarea.value;
|
||||
|
||||
// Insert tab character
|
||||
// Update textarea value and cursor position
|
||||
textarea.value = value.substring(0, start) + " " + value.substring(end);
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 2; // Move the cursor after the tab
|
||||
// Insert tab character
|
||||
// Update textarea value and cursor position
|
||||
textarea.value = value.substring(0, start) + " " + value.substring(end);
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 2; // Move the cursor after the tab
|
||||
|
||||
// Trigger a change event so React knows the value changed.
|
||||
textarea.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
}
|
||||
};
|
||||
// Trigger a change event so React knows the value changed.
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Call the parent function with the new content which is
|
||||
* stored in the text state.
|
||||
*/
|
||||
const saveAndExit = () => {
|
||||
saveExit(text);
|
||||
};
|
||||
const saveAndExit = () => saveExit(text);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-black opacity-50 blur-lg"></div>
|
||||
<div className="relative z-10 bg-white p-8 rounded-lg shadow-lg w-3/4 h-5/6 border-1 border-gray-400">
|
||||
<h2 className="text-lg font-semibold mb-4 text-blue-400">Editing File: <span
|
||||
className="font-mono text-black bg-gray-300 p-1 rounded-md">{path}</span></h2>
|
||||
{loading && <ContentLoading/>}
|
||||
{loading ||
|
||||
<textarea
|
||||
onKeyDown={handleKeyPress}
|
||||
tabIndex={-1}
|
||||
ref={textareaRef}
|
||||
onInput={updateText}
|
||||
value={text}
|
||||
className="border-1 border-gray-300 rounded-md w-full h-9/10 p-1 resize-none text-sm font-mono">
|
||||
</textarea>
|
||||
}
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
title="Exit without saving"
|
||||
onClick={exit}
|
||||
className="bg-red-500 hover:bg-red-600 text-white text-sm font-semibold py-1.5 px-3 rounded hover:cursor-pointer mx-2">
|
||||
Exit
|
||||
</button>
|
||||
<button
|
||||
title="Save changes and exit"
|
||||
onClick={saveAndExit}
|
||||
className="bg-blue-400 hover:bg-blue-500 text-white text-sm font-semibold py-1.5 px-3 rounded hover:cursor-pointer">
|
||||
Save & Exit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
return <>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
{/* Blured backdrop */}
|
||||
<div className="fixed inset-0 bg-black opacity-50 blur-lg"></div>
|
||||
|
||||
<div className="w-9/10 lg:w-3/4 h-5/6 relative z-10 bg-white p-4 lg:p-8 rounded-lg shadow-lg border-1 border-gray-400 flex flex-col">
|
||||
<h2 className="text-lg font-semibold mb-4 text-blue-400">
|
||||
Editing File: <span className="font-mono text-black bg-gray-300 p-1 rounded-md">
|
||||
{path}
|
||||
</span>
|
||||
</h2>
|
||||
{loading && <ContentLoading />}
|
||||
{loading ||
|
||||
<textarea
|
||||
onKeyDown={handleKeyPress}
|
||||
tabIndex={-1}
|
||||
ref={textareaRef}
|
||||
onInput={updateText}
|
||||
value={text}
|
||||
className="border-1 border-gray-300 rounded-md w-full flex-grow p-1 resize-none text-sm font-mono">
|
||||
</textarea>
|
||||
}
|
||||
<div className="flex justify-end mt-4">
|
||||
<button
|
||||
title="Exit without saving"
|
||||
onClick={exit}
|
||||
className="bg-red-500 hover:bg-red-600 text-white text-sm font-semibold py-1.5 px-3 rounded hover:cursor-pointer mx-2">
|
||||
Exit
|
||||
</button>
|
||||
<button
|
||||
title="Save changes and exit"
|
||||
onClick={saveAndExit}
|
||||
className="bg-blue-400 hover:bg-blue-500 text-white text-sm font-semibold py-1.5 px-3 rounded hover:cursor-pointer">
|
||||
Save & Exit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -8,21 +8,21 @@ import "../index.css";
|
||||
* @constructor
|
||||
*/
|
||||
export default function Error({error, clear}) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-black opacity-50 blur-lg"></div>
|
||||
<div className="relative z-50 bg-white p-8 rounded-lg shadow-lg w-96 border-1 border-gray-400">
|
||||
<h2 className="text-2xl font-semibold mb-4 text-red-600">An Error Occurred!</h2>
|
||||
<p className="mb-4">{error}</p>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={clear}
|
||||
className="bg-red-500 hover:bg-red-600 text-white text-sm font-semibold py-1.5 px-3 rounded hover:cursor-pointer"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
return <>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-black opacity-50 blur-lg"></div>
|
||||
<div className="relative z-50 bg-white p-8 rounded-lg shadow-lg w-96 border-1 border-gray-400">
|
||||
<h2 className="text-2xl font-semibold mb-4 text-red-600">An Error Occurred!</h2>
|
||||
<p className="mb-4">{error}</p>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={clear}
|
||||
className="bg-red-500 hover:bg-red-600 text-white text-sm font-semibold py-1.5 px-3 rounded hover:cursor-pointer"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -1,132 +1,134 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import UserInput from "./UserInput.jsx";
|
||||
import PasswordInput from "./PasswordInput.jsx";
|
||||
import RememberMe from "./RememberMe.jsx";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
|
||||
export default function LoginForm() {
|
||||
/**
|
||||
* URL To the backend web server.
|
||||
* Uses the .env var in local development, but when
|
||||
* pushed to dockerhub, the .env is ignored and the real
|
||||
* backend URL is used.
|
||||
* @type {string}
|
||||
*/
|
||||
const backendUrl = import.meta.env.VITE_BACKEND_URL || "https://backend.gophernest.net";
|
||||
/**
|
||||
* URL To the backend web server.
|
||||
* Uses the .env var in local development, but when
|
||||
* pushed to dockerhub, the .env is ignored and the real
|
||||
* backend URL is used.
|
||||
* @type {string}
|
||||
*/
|
||||
const backendUrl = import.meta.env.VITE_BACKEND_URL || "https://backend.gophernest.net";
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [remember, setRemember] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [remember, setRemember] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
/**
|
||||
* The name of the value stored in local storage.
|
||||
* @type {string}
|
||||
*/
|
||||
const storage_id = "gophernest_credentials";
|
||||
/**
|
||||
* The name of the value stored in local storage.
|
||||
* @type {string}
|
||||
*/
|
||||
const storage_id = "gophernest_credentials";
|
||||
|
||||
/**
|
||||
* Set the email in the form state.
|
||||
* @param newUsername
|
||||
*/
|
||||
const updateUsername = (newUsername) => {
|
||||
setUsername(newUsername);
|
||||
/**
|
||||
* Set the email in the form state.
|
||||
* @param newUsername
|
||||
*/
|
||||
const updateUsername = (newUsername) => {
|
||||
setUsername(newUsername);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the 'remember me' in the form start.
|
||||
* @param newRemember
|
||||
*/
|
||||
const updateRemember = (newRemember) => {
|
||||
setRemember(newRemember);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the password in the form state.
|
||||
* @param newPassword
|
||||
*/
|
||||
const updatePassword = (newPassword) => {
|
||||
setPassword(newPassword);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the login submission, data is stored in the local storage.
|
||||
* @param event {SubmitEvent}
|
||||
*/
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
const sendAuthReq = async (username, password) => {
|
||||
const resp = await fetch(`${backendUrl}/v1/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const data = await resp.json();
|
||||
console.error(data.message);
|
||||
setError(data.message);
|
||||
return data;
|
||||
}
|
||||
return await resp.json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the 'remember me' in the form start.
|
||||
* @param newRemember
|
||||
*/
|
||||
const updateRemember = (newRemember) => {
|
||||
setRemember(newRemember);
|
||||
};
|
||||
sendAuthReq(username, password).then((data) => {
|
||||
const { code, token } = data;
|
||||
|
||||
/**
|
||||
* Set the password in the form state.
|
||||
* @param newPassword
|
||||
*/
|
||||
const updatePassword = (newPassword) => {
|
||||
setPassword(newPassword);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the login submission, data is stored in the local storage.
|
||||
* @param event {SubmitEvent}
|
||||
*/
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
const sendAuthReq = async (username, password) => {
|
||||
const resp = await fetch(`${backendUrl}/v1/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({username, password}),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const data = await resp.json();
|
||||
console.error(data.message);
|
||||
setError(data.message);
|
||||
return data;
|
||||
}
|
||||
return await resp.json();
|
||||
};
|
||||
|
||||
sendAuthReq(username, password).then((data) => {
|
||||
const {code, token} = data;
|
||||
|
||||
// Should always be 200, but just make sure it is
|
||||
if (code === 200) {
|
||||
// Store JWT in session if the user does not want to be remembered
|
||||
remember ? localStorage.setItem(storage_id, token) : sessionStorage.setItem(storage_id, token);
|
||||
navigate("/login");
|
||||
} else {
|
||||
console.error(data);
|
||||
setError(data.message);
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
setError(err.toString());
|
||||
}).finally(() => {
|
||||
// Disable loading bar
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
// Redirect if the user is logged in
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem(storage_id) != null || sessionStorage.getItem(storage_id) != null) {
|
||||
navigate("/dashboard");
|
||||
}
|
||||
// Should always be 200, but just make sure it is
|
||||
if (code === 200) {
|
||||
// Store JWT in session if the user does not want to be remembered
|
||||
remember ? localStorage.setItem(storage_id, token) : sessionStorage.setItem(storage_id, token);
|
||||
navigate("/login");
|
||||
} else {
|
||||
console.error(data);
|
||||
setError(data.message);
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
setError(err.toString());
|
||||
}).finally(() => {
|
||||
// Disable loading bar
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`loading: ${loading}`);
|
||||
}, [loading]);
|
||||
// Redirect if the user is logged in
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem(storage_id) != null || sessionStorage.getItem(storage_id) != null) {
|
||||
navigate("/dashboard");
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`loading: ${loading}`);
|
||||
}, [loading]);
|
||||
|
||||
|
||||
return <form onSubmit={handleSubmit} className="w-full flex flex-col items-center justify-center">
|
||||
<UserInput onChange={updateUsername}/>
|
||||
<PasswordInput onChange={updatePassword}/>
|
||||
<RememberMe onChange={updateRemember}/>
|
||||
return <>
|
||||
<form onSubmit={handleSubmit} className="w-full flex flex-col items-center justify-center">
|
||||
<UserInput onChange={updateUsername} />
|
||||
<PasswordInput onChange={updatePassword} />
|
||||
<RememberMe onChange={updateRemember} />
|
||||
|
||||
{error && <p className="w-full text-red-500 text-sm my-2">{error}</p>}
|
||||
{error && <p className="w-full text-red-500 text-sm my-2">{error}</p>}
|
||||
|
||||
<button type="submit"
|
||||
disabled={loading}
|
||||
className="mt-8 bg-blue-400 py-2 text-white w-full rounded-sm disabled:bg-gray-400 disabled:cursor-not-allowed">
|
||||
{loading ? "Loading..." : "Login"}
|
||||
</button>
|
||||
<button type="submit"
|
||||
disabled={loading}
|
||||
className="mt-8 bg-blue-400 py-2 text-white w-full rounded-sm disabled:bg-gray-400 disabled:cursor-not-allowed">
|
||||
{loading ? "Loading..." : "Login"}
|
||||
</button>
|
||||
|
||||
<p className="text-gray-400 text-xs text-center mt-6">
|
||||
If you do not have an account, you're in the wrong place!
|
||||
</p>
|
||||
<p className="text-gray-400 text-xs text-center mt-6">
|
||||
If you do not have an account, you're in the wrong place!
|
||||
</p>
|
||||
</form>
|
||||
</>
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import "../index.css"
|
||||
import {useState} from "react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
/**
|
||||
* Main navbar icon
|
||||
@ -9,11 +9,13 @@ import {useNavigate} from "react-router-dom";
|
||||
* @constructor
|
||||
*/
|
||||
function MainIcon() {
|
||||
return <svg className="h-10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12 9.5V15.5M12 9.5L10 11.5M12 9.5L14 11.5M8.4 19C5.41766 19 3 16.6044 3 13.6493C3 11.2001 4.8 8.9375 7.5 8.5C8.34694 6.48637 10.3514 5 12.6893 5C15.684 5 18.1317 7.32251 18.3 10.25C19.8893 10.9449 21 12.6503 21 14.4969C21 16.9839 18.9853 19 16.5 19L8.4 19Z"
|
||||
stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
return <>
|
||||
<svg className="h-8 lg:h-10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12 9.5V15.5M12 9.5L10 11.5M12 9.5L14 11.5M8.4 19C5.41766 19 3 16.6044 3 13.6493C3 11.2001 4.8 8.9375 7.5 8.5C8.34694 6.48637 10.3514 5 12.6893 5C15.684 5 18.1317 7.32251 18.3 10.25C19.8893 10.9449 21 12.6503 21 14.4969C21 16.9839 18.9853 19 16.5 19L8.4 19Z"
|
||||
stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -22,19 +24,19 @@ function MainIcon() {
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
function DownloadButton({downloadFiles}) {
|
||||
return (
|
||||
<button className="text-black" title="Download files" onClick={downloadFiles}>
|
||||
<svg className="hover:bg-gray-300 mx-1 transition-colors duration-200 p-1.5 rounded-full h-8"
|
||||
viewBox="0 0 24 24" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.5535 16.5061C12.4114 16.6615 12.2106 16.75 12 16.75C11.7894 16.75 11.5886 16.6615 11.4465 16.5061L7.44648 12.1311C7.16698 11.8254 7.18822 11.351 7.49392 11.0715C7.79963 10.792 8.27402 10.8132 8.55352 11.1189L11.25 14.0682V3C11.25 2.58579 11.5858 2.25 12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V14.0682L15.4465 11.1189C15.726 10.8132 16.2004 10.792 16.5061 11.0715C16.8118 11.351 16.833 11.8254 16.5535 12.1311L12.5535 16.5061Z"/>
|
||||
<path
|
||||
d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
function DownloadButton({ downloadFiles }) {
|
||||
return <>
|
||||
<button className="text-black" title="Download files" onClick={downloadFiles}>
|
||||
<svg className="hover:bg-gray-300 mx-1 transition-colors duration-200 p-1.5 rounded-full h-8"
|
||||
viewBox="0 0 24 24" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.5535 16.5061C12.4114 16.6615 12.2106 16.75 12 16.75C11.7894 16.75 11.5886 16.6615 11.4465 16.5061L7.44648 12.1311C7.16698 11.8254 7.18822 11.351 7.49392 11.0715C7.79963 10.792 8.27402 10.8132 8.55352 11.1189L11.25 14.0682V3C11.25 2.58579 11.5858 2.25 12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V14.0682L15.4465 11.1189C15.726 10.8132 16.2004 10.792 16.5061 11.0715C16.8118 11.351 16.833 11.8254 16.5535 12.1311L12.5535 16.5061Z" />
|
||||
<path
|
||||
d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,116 +45,117 @@ function DownloadButton({downloadFiles}) {
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
function UploadButton({uploadFiles}) {
|
||||
return (
|
||||
<button className="text-black" title="Upload files" onClick={uploadFiles}>
|
||||
<svg className="hover:bg-gray-300 mx-1 transition-colors duration-200 p-1.5 rounded-full font-semibold h-8"
|
||||
viewBox="0 0 24 24" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.5535 2.49392C12.4114 2.33852 12.2106 2.25 12 2.25C11.7894 2.25 11.5886 2.33852 11.4465 2.49392L7.44648 6.86892C7.16698 7.17462 7.18822 7.64902 7.49392 7.92852C7.79963 8.20802 8.27402 8.18678 8.55352 7.88108L11.25 4.9318V16C11.25 16.4142 11.5858 16.75 12 16.75C12.4142 16.75 12.75 16.4142 12.75 16V4.9318L15.4465 7.88108C15.726 8.18678 16.2004 8.20802 16.5061 7.92852C16.8118 7.64902 16.833 7.17462 16.5535 6.86892L12.5535 2.49392Z"/>
|
||||
<path
|
||||
d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
function UploadButton({ uploadFiles }) {
|
||||
return <>
|
||||
<button className="text-black" title="Upload files" onClick={uploadFiles}>
|
||||
<svg className="hover:bg-gray-300 mx-1 transition-colors duration-200 p-1.5 rounded-full font-semibold h-8"
|
||||
viewBox="0 0 24 24" fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.5535 2.49392C12.4114 2.33852 12.2106 2.25 12 2.25C11.7894 2.25 11.5886 2.33852 11.4465 2.49392L7.44648 6.86892C7.16698 7.17462 7.18822 7.64902 7.49392 7.92852C7.79963 8.20802 8.27402 8.18678 8.55352 7.88108L11.25 4.9318V16C11.25 16.4142 11.5858 16.75 12 16.75C12.4142 16.75 12.75 16.4142 12.75 16V4.9318L15.4465 7.88108C15.726 8.18678 16.2004 8.20802 16.5061 7.92852C16.8118 7.64902 16.833 7.17462 16.5535 6.86892L12.5535 2.49392Z" />
|
||||
<path
|
||||
d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
/**
|
||||
* Information button. Not sure what this is going to do...
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
* @constructor
|
||||
*/
|
||||
function InfoButton() {
|
||||
return (
|
||||
<button className="text-black" title="Filesystem information">
|
||||
<svg className="hover:bg-gray-300 mx-1 transition-colors duration-200 p-1.5 rounded-full font-semibold h-8"
|
||||
viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12 17.75C12.4142 17.75 12.75 17.4142 12.75 17V11C12.75 10.5858 12.4142 10.25 12 10.25C11.5858 10.25 11.25 10.5858 11.25 11V17C11.25 17.4142 11.5858 17.75 12 17.75Z"/>
|
||||
<path
|
||||
d="M12 7C12.5523 7 13 7.44772 13 8C13 8.55228 12.5523 9 12 9C11.4477 9 11 8.55228 11 8C11 7.44772 11.4477 7 12 7Z"/>
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M1.25 12C1.25 6.06294 6.06294 1.25 12 1.25C17.9371 1.25 22.75 6.06294 22.75 12C22.75 17.9371 17.9371 22.75 12 22.75C6.06294 22.75 1.25 17.9371 1.25 12ZM12 2.75C6.89137 2.75 2.75 6.89137 2.75 12C2.75 17.1086 6.89137 21.25 12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
return <>
|
||||
<button className="text-black" title="Filesystem information">
|
||||
<svg className="hover:bg-gray-300 mx-1 transition-colors duration-200 p-1.5 rounded-full font-semibold h-8"
|
||||
viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12 17.75C12.4142 17.75 12.75 17.4142 12.75 17V11C12.75 10.5858 12.4142 10.25 12 10.25C11.5858 10.25 11.25 10.5858 11.25 11V17C11.25 17.4142 11.5858 17.75 12 17.75Z" />
|
||||
<path
|
||||
d="M12 7C12.5523 7 13 7.44772 13 8C13 8.55228 12.5523 9 12 9C11.4477 9 11 8.55228 11 8C11 7.44772 11.4477 7 12 7Z" />
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M1.25 12C1.25 6.06294 6.06294 1.25 12 1.25C17.9371 1.25 22.75 6.06294 22.75 12C22.75 17.9371 17.9371 22.75 12 22.75C6.06294 22.75 1.25 17.9371 1.25 12ZM12 2.75C6.89137 2.75 2.75 6.89137 2.75 12C2.75 17.1086 6.89137 21.25 12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logout button, which clears the user from the storage's.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
* @constructor
|
||||
*/
|
||||
function LogoutButton() {
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
/**
|
||||
/**
|
||||
* The name of the value stored in local storage.
|
||||
* @type {string}
|
||||
*/
|
||||
const storage_id = "gophernest_credentials";
|
||||
*/
|
||||
const storage_id = "gophernest_credentials";
|
||||
|
||||
const handleClick = () => {
|
||||
localStorage.removeItem(storage_id);
|
||||
sessionStorage.removeItem(storage_id);
|
||||
navigate("/login");
|
||||
};
|
||||
const handleClick = () => {
|
||||
localStorage.removeItem(storage_id);
|
||||
sessionStorage.removeItem(storage_id);
|
||||
navigate("/login");
|
||||
};
|
||||
|
||||
return (
|
||||
<button className="text-red-500" title="Logout" onClick={handleClick}>
|
||||
<svg
|
||||
className="text-red-500 hover:bg-red-200 mx-1 transition-colors duration-200 p-1.5 rounded-full font-semibold h-8"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M15 4H18C19.1046 4 20 4.89543 20 6V18C20 19.1046 19.1046 20 18 20H15M8 8L4 12M4 12L8 16M4 12L16 12"
|
||||
strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
return <>
|
||||
<button className="text-red-500" title="Logout" onClick={handleClick}>
|
||||
<svg
|
||||
className="text-red-500 hover:bg-red-200 mx-1 transition-colors duration-200 p-1.5 rounded-full font-semibold h-8"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M15 4H18C19.1046 4 20 4.89543 20 6V18C20 19.1046 19.1046 20 18 20H15M8 8L4 12M4 12L8 16M4 12L16 12"
|
||||
strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
/**
|
||||
* Search bar input, with controlled state.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
* @constructor
|
||||
*/
|
||||
function SearchBar() {
|
||||
const [search, setSearch] = useState("");
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
/**
|
||||
/**
|
||||
* Update the controlled state.
|
||||
* @param event {InputEvent}
|
||||
*/
|
||||
const updateSearch = (event) => {
|
||||
setSearch(event.target.value);
|
||||
};
|
||||
*/
|
||||
const updateSearch = (event) => setSearch(event.target.value);
|
||||
|
||||
return (
|
||||
<div className="mx-4 w-1/4">
|
||||
<input
|
||||
className="px-2 py-1 w-full text-sm focus:outline-none focus:shadow-sm shadow-blue-300 transition-shadow duration-200 rounded-sm border-1 border-gray-400"
|
||||
type="search"
|
||||
value={search}
|
||||
onInput={updateSearch}
|
||||
placeholder="Search filesystem"/>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className="mx-4 w-1/2 lg:w-1/4">
|
||||
<input
|
||||
className="px-2 py-1 w-full text-sm focus:outline-none focus:shadow-sm shadow-blue-300 transition-shadow duration-200 rounded-sm border-1 border-gray-400"
|
||||
type="search"
|
||||
value={search}
|
||||
onInput={updateSearch}
|
||||
placeholder="Search filesystem" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Navbar({downloadFiles, uploadFiles}) {
|
||||
return <nav className="absolute w-full p-2 flex items-center border-b-1 border-gray-400 bg-gray-100">
|
||||
<MainIcon/>
|
||||
export default function Navbar({ downloadFiles, uploadFiles }) {
|
||||
return <>
|
||||
<nav className="absolute w-full p-2 flex items-center border-b-1 border-gray-400 bg-gray-100">
|
||||
<MainIcon />
|
||||
|
||||
<h3 className="text-xl font-mono px-3">file.gophernest.net</h3>
|
||||
<SearchBar/>
|
||||
<h3 className="hidden lg:block text-xl font-mono px-3">file.gophernest.net</h3>
|
||||
|
||||
<div className="min-h-fit ml-auto flex">
|
||||
<DownloadButton downloadFiles={downloadFiles}/>
|
||||
<UploadButton uploadFiles={uploadFiles}/>
|
||||
<InfoButton/>
|
||||
<LogoutButton/>
|
||||
</div>
|
||||
<SearchBar />
|
||||
|
||||
<div className="min-h-fit ml-auto flex">
|
||||
<DownloadButton downloadFiles={downloadFiles} />
|
||||
<UploadButton uploadFiles={uploadFiles} />
|
||||
<InfoButton />
|
||||
<LogoutButton />
|
||||
</div>
|
||||
</nav>
|
||||
</>;
|
||||
}
|
||||
@ -8,63 +8,61 @@ import "../index.css"
|
||||
* @constructor
|
||||
*/
|
||||
export default function PasswordInput({onChange}) {
|
||||
// Example of the controlled input state pattern
|
||||
const [password, setPassword] = useState("");
|
||||
const [hidden, setHidden] = useState(true);
|
||||
// Example of the controlled input state pattern
|
||||
const [password, setPassword] = useState("");
|
||||
const [hidden, setHidden] = useState(true);
|
||||
|
||||
// Generate ID for the input element
|
||||
const id = useId();
|
||||
// Generate ID for the input element
|
||||
const id = useId();
|
||||
|
||||
/**
|
||||
/**
|
||||
* Toggle the hidden state which will determine the type of the input.
|
||||
*/
|
||||
const toggleHidden = () => {
|
||||
setHidden(!hidden);
|
||||
}
|
||||
const toggleHidden = () => setHidden(!hidden);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Update the password
|
||||
* @param event {InputEvent}
|
||||
*/
|
||||
const updatePassword = (event) => {
|
||||
const newPassword = event.target.value;
|
||||
setPassword(newPassword);
|
||||
const updatePassword = (event) => {
|
||||
const newPassword = event.target.value;
|
||||
setPassword(newPassword);
|
||||
|
||||
// Do not rely on other state, otherwise we get one render behind
|
||||
onChange(newPassword);
|
||||
}
|
||||
// Do not rely on other state, otherwise we get one render behind
|
||||
onChange(newPassword);
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className="relative w-full my-2">
|
||||
<input
|
||||
type={hidden ? "password" : "text"}
|
||||
value={password}
|
||||
onInput={updatePassword}
|
||||
name={id}
|
||||
required={true}
|
||||
placeholder="Password"
|
||||
className="border border-gray-300 rounded-sm py-2 px-4 placeholder:italic w-full"/>
|
||||
<button
|
||||
onClick={toggleHidden}
|
||||
type="button"
|
||||
className="absolute inset-y-0 right-0 px-3 h-full my-auto">
|
||||
{hidden ?
|
||||
<svg className="h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M2.68936 6.70456C2.52619 6.32384 2.08528 6.14747 1.70456 6.31064C1.32384 6.47381 1.14747 6.91472 1.31064 7.29544L2.68936 6.70456ZM15.5872 13.3287L15.3125 12.6308L15.5872 13.3287ZM9.04145 13.7377C9.26736 13.3906 9.16904 12.926 8.82185 12.7001C8.47466 12.4742 8.01008 12.5725 7.78417 12.9197L9.04145 13.7377ZM6.37136 15.091C6.14545 15.4381 6.24377 15.9027 6.59096 16.1286C6.93815 16.3545 7.40273 16.2562 7.62864 15.909L6.37136 15.091ZM22.6894 7.29544C22.8525 6.91472 22.6762 6.47381 22.2954 6.31064C21.9147 6.14747 21.4738 6.32384 21.3106 6.70456L22.6894 7.29544ZM19 11.1288L18.4867 10.582V10.582L19 11.1288ZM19.9697 13.1592C20.2626 13.4521 20.7374 13.4521 21.0303 13.1592C21.3232 12.8663 21.3232 12.3914 21.0303 12.0985L19.9697 13.1592ZM11.25 16.5C11.25 16.9142 11.5858 17.25 12 17.25C12.4142 17.25 12.75 16.9142 12.75 16.5H11.25ZM16.3714 15.909C16.5973 16.2562 17.0619 16.3545 17.409 16.1286C17.7562 15.9027 17.8545 15.4381 17.6286 15.091L16.3714 15.909ZM5.53033 11.6592C5.82322 11.3663 5.82322 10.8914 5.53033 10.5985C5.23744 10.3056 4.76256 10.3056 4.46967 10.5985L5.53033 11.6592ZM2.96967 12.0985C2.67678 12.3914 2.67678 12.8663 2.96967 13.1592C3.26256 13.4521 3.73744 13.4521 4.03033 13.1592L2.96967 12.0985ZM12 13.25C8.77611 13.25 6.46133 11.6446 4.9246 9.98966C4.15645 9.16243 3.59325 8.33284 3.22259 7.71014C3.03769 7.3995 2.90187 7.14232 2.8134 6.96537C2.76919 6.87696 2.73689 6.80875 2.71627 6.76411C2.70597 6.7418 2.69859 6.7254 2.69411 6.71533C2.69187 6.7103 2.69036 6.70684 2.68957 6.70503C2.68917 6.70413 2.68896 6.70363 2.68892 6.70355C2.68891 6.70351 2.68893 6.70357 2.68901 6.70374C2.68904 6.70382 2.68913 6.70403 2.68915 6.70407C2.68925 6.7043 2.68936 6.70456 2 7C1.31064 7.29544 1.31077 7.29575 1.31092 7.29609C1.31098 7.29624 1.31114 7.2966 1.31127 7.2969C1.31152 7.29749 1.31183 7.2982 1.31218 7.299C1.31287 7.30062 1.31376 7.30266 1.31483 7.30512C1.31698 7.31003 1.31988 7.31662 1.32353 7.32483C1.33083 7.34125 1.34115 7.36415 1.35453 7.39311C1.38127 7.45102 1.42026 7.5332 1.47176 7.63619C1.57469 7.84206 1.72794 8.13175 1.93366 8.47736C2.34425 9.16716 2.96855 10.0876 3.8254 11.0103C5.53867 12.8554 8.22389 14.75 12 14.75V13.25ZM15.3125 12.6308C14.3421 13.0128 13.2417 13.25 12 13.25V14.75C13.4382 14.75 14.7246 14.4742 15.8619 14.0266L15.3125 12.6308ZM7.78417 12.9197L6.37136 15.091L7.62864 15.909L9.04145 13.7377L7.78417 12.9197ZM22 7C21.3106 6.70456 21.3107 6.70441 21.3108 6.70427C21.3108 6.70423 21.3108 6.7041 21.3109 6.70402C21.3109 6.70388 21.311 6.70376 21.311 6.70368C21.3111 6.70352 21.3111 6.70349 21.3111 6.7036C21.311 6.7038 21.3107 6.70452 21.3101 6.70576C21.309 6.70823 21.307 6.71275 21.3041 6.71924C21.2983 6.73223 21.2889 6.75309 21.2758 6.78125C21.2495 6.83757 21.2086 6.92295 21.1526 7.03267C21.0406 7.25227 20.869 7.56831 20.6354 7.9432C20.1669 8.69516 19.4563 9.67197 18.4867 10.582L19.5133 11.6757C20.6023 10.6535 21.3917 9.56587 21.9085 8.73646C22.1676 8.32068 22.36 7.9668 22.4889 7.71415C22.5533 7.58775 22.602 7.48643 22.6353 7.41507C22.6519 7.37939 22.6647 7.35118 22.6737 7.33104C22.6782 7.32097 22.6818 7.31292 22.6844 7.30696C22.6857 7.30398 22.6867 7.30153 22.6876 7.2996C22.688 7.29864 22.6883 7.29781 22.6886 7.29712C22.6888 7.29677 22.6889 7.29646 22.689 7.29618C22.6891 7.29604 22.6892 7.29585 22.6892 7.29578C22.6893 7.29561 22.6894 7.29544 22 7ZM18.4867 10.582C17.6277 11.3882 16.5739 12.1343 15.3125 12.6308L15.8619 14.0266C17.3355 13.4466 18.5466 12.583 19.5133 11.6757L18.4867 10.582ZM18.4697 11.6592L19.9697 13.1592L21.0303 12.0985L19.5303 10.5985L18.4697 11.6592ZM11.25 14V16.5H12.75V14H11.25ZM14.9586 13.7377L16.3714 15.909L17.6286 15.091L16.2158 12.9197L14.9586 13.7377ZM4.46967 10.5985L2.96967 12.0985L4.03033 13.1592L5.53033 11.6592L4.46967 10.5985Z"
|
||||
fill="#1C274C"/>
|
||||
</svg>
|
||||
:
|
||||
<svg className="h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M9 4.45962C9.91153 4.16968 10.9104 4 12 4C16.1819 4 19.028 6.49956 20.7251 8.70433C21.575 9.80853 22 10.3606 22 12C22 13.6394 21.575 14.1915 20.7251 15.2957C19.028 17.5004 16.1819 20 12 20C7.81811 20 4.97196 17.5004 3.27489 15.2957C2.42496 14.1915 2 13.6394 2 12C2 10.3606 2.42496 9.80853 3.27489 8.70433C3.75612 8.07914 4.32973 7.43025 5 6.82137"
|
||||
stroke="#1C274C" strokeWidth="1.5" strokeLinecap="round"/>
|
||||
<path
|
||||
d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z"
|
||||
stroke="#1C274C" strokeWidth="1.5"/>
|
||||
</svg>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
return <>
|
||||
<div className="relative w-full my-2">
|
||||
<input
|
||||
type={hidden ? "password" : "text"}
|
||||
value={password}
|
||||
onInput={updatePassword}
|
||||
name={id}
|
||||
required={true}
|
||||
placeholder="Password"
|
||||
className="border border-gray-300 rounded-sm py-2 px-4 placeholder:italic w-full"/>
|
||||
<button
|
||||
onClick={toggleHidden}
|
||||
type="button"
|
||||
className="absolute inset-y-0 right-0 px-3 h-full my-auto">
|
||||
{hidden ?
|
||||
<svg className="h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M2.68936 6.70456C2.52619 6.32384 2.08528 6.14747 1.70456 6.31064C1.32384 6.47381 1.14747 6.91472 1.31064 7.29544L2.68936 6.70456ZM15.5872 13.3287L15.3125 12.6308L15.5872 13.3287ZM9.04145 13.7377C9.26736 13.3906 9.16904 12.926 8.82185 12.7001C8.47466 12.4742 8.01008 12.5725 7.78417 12.9197L9.04145 13.7377ZM6.37136 15.091C6.14545 15.4381 6.24377 15.9027 6.59096 16.1286C6.93815 16.3545 7.40273 16.2562 7.62864 15.909L6.37136 15.091ZM22.6894 7.29544C22.8525 6.91472 22.6762 6.47381 22.2954 6.31064C21.9147 6.14747 21.4738 6.32384 21.3106 6.70456L22.6894 7.29544ZM19 11.1288L18.4867 10.582V10.582L19 11.1288ZM19.9697 13.1592C20.2626 13.4521 20.7374 13.4521 21.0303 13.1592C21.3232 12.8663 21.3232 12.3914 21.0303 12.0985L19.9697 13.1592ZM11.25 16.5C11.25 16.9142 11.5858 17.25 12 17.25C12.4142 17.25 12.75 16.9142 12.75 16.5H11.25ZM16.3714 15.909C16.5973 16.2562 17.0619 16.3545 17.409 16.1286C17.7562 15.9027 17.8545 15.4381 17.6286 15.091L16.3714 15.909ZM5.53033 11.6592C5.82322 11.3663 5.82322 10.8914 5.53033 10.5985C5.23744 10.3056 4.76256 10.3056 4.46967 10.5985L5.53033 11.6592ZM2.96967 12.0985C2.67678 12.3914 2.67678 12.8663 2.96967 13.1592C3.26256 13.4521 3.73744 13.4521 4.03033 13.1592L2.96967 12.0985ZM12 13.25C8.77611 13.25 6.46133 11.6446 4.9246 9.98966C4.15645 9.16243 3.59325 8.33284 3.22259 7.71014C3.03769 7.3995 2.90187 7.14232 2.8134 6.96537C2.76919 6.87696 2.73689 6.80875 2.71627 6.76411C2.70597 6.7418 2.69859 6.7254 2.69411 6.71533C2.69187 6.7103 2.69036 6.70684 2.68957 6.70503C2.68917 6.70413 2.68896 6.70363 2.68892 6.70355C2.68891 6.70351 2.68893 6.70357 2.68901 6.70374C2.68904 6.70382 2.68913 6.70403 2.68915 6.70407C2.68925 6.7043 2.68936 6.70456 2 7C1.31064 7.29544 1.31077 7.29575 1.31092 7.29609C1.31098 7.29624 1.31114 7.2966 1.31127 7.2969C1.31152 7.29749 1.31183 7.2982 1.31218 7.299C1.31287 7.30062 1.31376 7.30266 1.31483 7.30512C1.31698 7.31003 1.31988 7.31662 1.32353 7.32483C1.33083 7.34125 1.34115 7.36415 1.35453 7.39311C1.38127 7.45102 1.42026 7.5332 1.47176 7.63619C1.57469 7.84206 1.72794 8.13175 1.93366 8.47736C2.34425 9.16716 2.96855 10.0876 3.8254 11.0103C5.53867 12.8554 8.22389 14.75 12 14.75V13.25ZM15.3125 12.6308C14.3421 13.0128 13.2417 13.25 12 13.25V14.75C13.4382 14.75 14.7246 14.4742 15.8619 14.0266L15.3125 12.6308ZM7.78417 12.9197L6.37136 15.091L7.62864 15.909L9.04145 13.7377L7.78417 12.9197ZM22 7C21.3106 6.70456 21.3107 6.70441 21.3108 6.70427C21.3108 6.70423 21.3108 6.7041 21.3109 6.70402C21.3109 6.70388 21.311 6.70376 21.311 6.70368C21.3111 6.70352 21.3111 6.70349 21.3111 6.7036C21.311 6.7038 21.3107 6.70452 21.3101 6.70576C21.309 6.70823 21.307 6.71275 21.3041 6.71924C21.2983 6.73223 21.2889 6.75309 21.2758 6.78125C21.2495 6.83757 21.2086 6.92295 21.1526 7.03267C21.0406 7.25227 20.869 7.56831 20.6354 7.9432C20.1669 8.69516 19.4563 9.67197 18.4867 10.582L19.5133 11.6757C20.6023 10.6535 21.3917 9.56587 21.9085 8.73646C22.1676 8.32068 22.36 7.9668 22.4889 7.71415C22.5533 7.58775 22.602 7.48643 22.6353 7.41507C22.6519 7.37939 22.6647 7.35118 22.6737 7.33104C22.6782 7.32097 22.6818 7.31292 22.6844 7.30696C22.6857 7.30398 22.6867 7.30153 22.6876 7.2996C22.688 7.29864 22.6883 7.29781 22.6886 7.29712C22.6888 7.29677 22.6889 7.29646 22.689 7.29618C22.6891 7.29604 22.6892 7.29585 22.6892 7.29578C22.6893 7.29561 22.6894 7.29544 22 7ZM18.4867 10.582C17.6277 11.3882 16.5739 12.1343 15.3125 12.6308L15.8619 14.0266C17.3355 13.4466 18.5466 12.583 19.5133 11.6757L18.4867 10.582ZM18.4697 11.6592L19.9697 13.1592L21.0303 12.0985L19.5303 10.5985L18.4697 11.6592ZM11.25 14V16.5H12.75V14H11.25ZM14.9586 13.7377L16.3714 15.909L17.6286 15.091L16.2158 12.9197L14.9586 13.7377ZM4.46967 10.5985L2.96967 12.0985L4.03033 13.1592L5.53033 11.6592L4.46967 10.5985Z"
|
||||
fill="#1C274C"/>
|
||||
</svg>
|
||||
:
|
||||
<svg className="h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M9 4.45962C9.91153 4.16968 10.9104 4 12 4C16.1819 4 19.028 6.49956 20.7251 8.70433C21.575 9.80853 22 10.3606 22 12C22 13.6394 21.575 14.1915 20.7251 15.2957C19.028 17.5004 16.1819 20 12 20C7.81811 20 4.97196 17.5004 3.27489 15.2957C2.42496 14.1915 2 13.6394 2 12C2 10.3606 2.42496 9.80853 3.27489 8.70433C3.75612 8.07914 4.32973 7.43025 5 6.82137"
|
||||
stroke="#1C274C" strokeWidth="1.5" strokeLinecap="round"/>
|
||||
<path
|
||||
d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z"
|
||||
stroke="#1C274C" strokeWidth="1.5"/>
|
||||
</svg>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -7,63 +7,63 @@
|
||||
* @constructor
|
||||
*/
|
||||
function HomeButton({onClick, enabled}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={!enabled}
|
||||
className="hover:bg-gray-200 p-1.5 rounded-full transition-colors duration-150 disabled:text-gray-500 disabled:hover:bg-red-300 disabled:cursor-not-allowed text-black">
|
||||
<svg className="h-4"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor">
|
||||
<path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
return <>
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={!enabled}
|
||||
className="hover:bg-gray-200 p-1.5 rounded-full transition-colors duration-150 disabled:text-gray-500 disabled:hover:bg-red-300 disabled:cursor-not-allowed text-black">
|
||||
<svg className="h-4"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor">
|
||||
<path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
function BackButton({onClick, enabled}) {
|
||||
return (
|
||||
<button onClick={onClick}
|
||||
disabled={!enabled}
|
||||
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="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M4 10L3.29289 10.7071L2.58579 10L3.29289 9.29289L4 10ZM21 18C21 18.5523 20.5523 19 20 19C19.4477 19 19 18.5523 19 18L21 18ZM8.29289 15.7071L3.29289 10.7071L4.70711 9.29289L9.70711 14.2929L8.29289 15.7071ZM3.29289 9.29289L8.29289 4.29289L9.70711 5.70711L4.70711 10.7071L3.29289 9.29289ZM4 9L14 9L14 11L4 11L4 9ZM21 16L21 18L19 18L19 16L21 16ZM14 9C17.866 9 21 12.134 21 16L19 16C19 13.2386 16.7614 11 14 11L14 9Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
return <>
|
||||
<button onClick={onClick}
|
||||
disabled={!enabled}
|
||||
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="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M4 10L3.29289 10.7071L2.58579 10L3.29289 9.29289L4 10ZM21 18C21 18.5523 20.5523 19 20 19C19.4477 19 19 18.5523 19 18L21 18ZM8.29289 15.7071L3.29289 10.7071L4.70711 9.29289L9.70711 14.2929L8.29289 15.7071ZM3.29289 9.29289L8.29289 4.29289L9.70711 5.70711L4.70711 10.7071L3.29289 9.29289ZM4 9L14 9L14 11L4 11L4 9ZM21 16L21 18L19 18L19 16L21 16ZM14 9C17.866 9 21 12.134 21 16L19 16C19 13.2386 16.7614 11 14 11L14 9Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
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>
|
||||
)
|
||||
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>
|
||||
</>
|
||||
}
|
||||
|
||||
function DeleteButton({onClick, enabled}) {
|
||||
return (
|
||||
<button onClick={onClick}
|
||||
disabled={!enabled}
|
||||
title="Delete selected files/directories"
|
||||
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="M18 6L17.1991 18.0129C17.129 19.065 17.0939 19.5911 16.8667 19.99C16.6666 20.3412 16.3648 20.6235 16.0011 20.7998C15.588 21 15.0607 21 14.0062 21H9.99377C8.93927 21 8.41202 21 7.99889 20.7998C7.63517 20.6235 7.33339 20.3412 7.13332 19.99C6.90607 19.5911 6.871 19.065 6.80086 18.0129L6 6M4 6H20M16 6L15.7294 5.18807C15.4671 4.40125 15.3359 4.00784 15.0927 3.71698C14.8779 3.46013 14.6021 3.26132 14.2905 3.13878C13.9376 3 13.523 3 12.6936 3H11.3064C10.477 3 10.0624 3 9.70951 3.13878C9.39792 3.26132 9.12208 3.46013 8.90729 3.71698C8.66405 4.00784 8.53292 4.40125 8.27064 5.18807L8 6"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
return <>
|
||||
<button onClick={onClick}
|
||||
disabled={!enabled}
|
||||
title="Delete selected files/directories"
|
||||
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="M18 6L17.1991 18.0129C17.129 19.065 17.0939 19.5911 16.8667 19.99C16.6666 20.3412 16.3648 20.6235 16.0011 20.7998C15.588 21 15.0607 21 14.0062 21H9.99377C8.93927 21 8.41202 21 7.99889 20.7998C7.63517 20.6235 7.33339 20.3412 7.13332 19.99C6.90607 19.5911 6.871 19.065 6.80086 18.0129L6 6M4 6H20M16 6L15.7294 5.18807C15.4671 4.40125 15.3359 4.00784 15.0927 3.71698C14.8779 3.46013 14.6021 3.26132 14.2905 3.13878C13.9376 3 13.523 3 12.6936 3H11.3064C10.477 3 10.0624 3 9.70951 3.13878C9.39792 3.26132 9.12208 3.46013 8.90729 3.71698C8.66405 4.00784 8.53292 4.40125 8.27064 5.18807L8 6"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,10 +75,14 @@ function DeleteButton({onClick, enabled}) {
|
||||
* @constructor
|
||||
*/
|
||||
function PathElement({name, index, onClick}) {
|
||||
const handleClick = () => {
|
||||
onClick(index);
|
||||
};
|
||||
return <button onClick={handleClick}>/<span className="hover:underline cursor-pointer">{name}</span></button>
|
||||
const handleClick = () => onClick(index);
|
||||
return <>
|
||||
<button onClick={handleClick}>
|
||||
/<span className="hover:underline cursor-pointer">
|
||||
{name}
|
||||
</span>
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,18 +99,17 @@ function PathElement({name, index, onClick}) {
|
||||
* @constructor
|
||||
*/
|
||||
export default function PathDisplay({path, updatePath, backHome, backArrow, enabled, create, remove, removeEnable}) {
|
||||
return (
|
||||
<div
|
||||
className="w-2/3 mt-8 border-b-1 border-gray-400 bg-white flex items-center truncate">
|
||||
<HomeButton onClick={backHome} enabled={enabled}/>
|
||||
<BackButton onClick={backArrow} enabled={enabled}/>
|
||||
{path.map((seg, idx) => <PathElement name={seg} key={idx} index={idx} onClick={updatePath}/>)}
|
||||
<div className="ml-auto h-full flex items-center">
|
||||
<DeleteButton onClick={remove} enabled={removeEnable}/>
|
||||
</div>
|
||||
<div className="h-full flex items-center">
|
||||
<CreateButton onClick={create}/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
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}/>
|
||||
<BackButton onClick={backArrow} enabled={enabled}/>
|
||||
{path.map((seg, idx) => <PathElement name={seg} key={idx} index={idx} onClick={updatePath}/>)}
|
||||
<div className="ml-auto h-full flex items-center">
|
||||
<DeleteButton onClick={remove} enabled={removeEnable}/>
|
||||
</div>
|
||||
<div className="h-full flex items-center">
|
||||
<CreateButton onClick={create}/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -7,21 +7,23 @@ import {useId, useState} from "react";
|
||||
* @constructor
|
||||
*/
|
||||
export default function RememberMe({onChange}) {
|
||||
const [remember, setRemember] = useState(false);
|
||||
const rememberMeId = useId();
|
||||
const [remember, setRemember] = useState(false);
|
||||
const rememberMeId = useId();
|
||||
|
||||
/**
|
||||
/**
|
||||
* Toggle the value of the 'remember me' toggle.
|
||||
* It also calls the 'onChange' function which should
|
||||
* be used to store the state in the parent component.
|
||||
*/
|
||||
const toggleRemember = () => {
|
||||
onChange(!remember);
|
||||
setRemember(!remember);
|
||||
};
|
||||
const toggleRemember = () => {
|
||||
onChange(!remember);
|
||||
setRemember(!remember);
|
||||
};
|
||||
|
||||
return <div className="w-full flex items-center my-2" onClick={toggleRemember}>
|
||||
<input name={rememberMeId} checked={remember} type="checkbox" className="mx-1"/>
|
||||
<label htmlFor={rememberMeId} className="text-sm select-none cursor-pointer">Remember me for 30 days</label>
|
||||
return <>
|
||||
<div className="w-full flex items-center my-2" onClick={toggleRemember}>
|
||||
<input name={rememberMeId} checked={remember} type="checkbox" className="mx-1"/>
|
||||
<label htmlFor={rememberMeId} className="text-sm select-none cursor-pointer">Remember me for 30 days</label>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -8,111 +8,109 @@ import {useRef, useState} from "react";
|
||||
* @constructor
|
||||
*/
|
||||
function FileList({files}) {
|
||||
return (
|
||||
<ul className="text-xs overflow-auto max-h-[200px] italic">
|
||||
{files.map((file) => <li className="py-0.5">{file.name}</li>)}
|
||||
</ul>
|
||||
);
|
||||
return (
|
||||
<ul className="text-xs overflow-auto max-h-[200px] italic">
|
||||
{files.map((file) => <li className="py-0.5">{file.name}</li>)}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Uploader({close, upload}) {
|
||||
const [files, setFiles] = useState([]);
|
||||
const [files, setFiles] = useState([]);
|
||||
|
||||
/**
|
||||
/**
|
||||
* References to the elements.
|
||||
* @type {React.RefObject<null>}
|
||||
*/
|
||||
const inputElement = useRef(null);
|
||||
const inputElement = useRef(null);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Prevent the default drag behavior and apply visual classes.
|
||||
* @param e {object} Event object from the div wrapper.
|
||||
*/
|
||||
const dragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
e.target.classList.add('border-blue-500', 'bg-blue-100');
|
||||
};
|
||||
const dragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
e.target.classList.add('border-blue-500', 'bg-blue-100');
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Prevent the default drag behavior and apply visual classes.
|
||||
* @param e {object} Event object from the div wrapper.
|
||||
*/
|
||||
const dragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
e.target.classList.remove('border-blue-500', 'bg-blue-100');
|
||||
};
|
||||
const dragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
e.target.classList.remove('border-blue-500', 'bg-blue-100');
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Prevent the default drag behavior.
|
||||
* @param e {object} Event object from the div wrapper.
|
||||
*/
|
||||
const dragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
const dragOver = (e) => e.preventDefault();
|
||||
|
||||
/**
|
||||
/**
|
||||
* When files are dropped into the div, this will be called, to append the files.
|
||||
* @param e {object} Event object from the div wrapper.
|
||||
*/
|
||||
const drop = (e) => {
|
||||
e.preventDefault();
|
||||
e.target.classList.remove('border-blue-500', 'bg-blue-100');
|
||||
setFiles([...files, ...e.dataTransfer.files]);
|
||||
};
|
||||
const drop = (e) => {
|
||||
e.preventDefault();
|
||||
e.target.classList.remove('border-blue-500', 'bg-blue-100');
|
||||
setFiles([...files, ...e.dataTransfer.files]);
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Click event for the input element.
|
||||
* Uses the ref hook to access the element.
|
||||
*/
|
||||
const click = () => {
|
||||
inputElement.current.click();
|
||||
};
|
||||
const click = () => inputElement.current.click();
|
||||
|
||||
/**
|
||||
/**
|
||||
* When the input changes, append the new files.
|
||||
* @param e {object} Event object from the input.
|
||||
*/
|
||||
const inputChange = (e) => {
|
||||
setFiles([...files, ...e.target.files]);
|
||||
};
|
||||
const inputChange = (e) => setFiles([...files, ...e.target.files]);
|
||||
|
||||
const uploadFiles = () => {
|
||||
upload(files);
|
||||
};
|
||||
const uploadFiles = () => upload(files);
|
||||
|
||||
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">Upload Files</h2>
|
||||
<p className="text-sm">
|
||||
Files uploaded will be added to the current directory. Currently, directory
|
||||
uploads are not supported. If you want to upload a directory you can create a new one and upload the
|
||||
files into it.
|
||||
</p>
|
||||
<div
|
||||
className="my-5 border-2 border-dashed p-8 rounded-md text-center cursor-pointer border-gray-400 hover:bg-blue-100 hover:border-blue-500 transition-all duration-100"
|
||||
onDragEnter={dragEnter} onDragLeave={dragLeave} onDragOver={dragOver} onDrop={drop} onClick={click}
|
||||
>
|
||||
<input multiple type="file" className="hidden" ref={inputElement} onChange={inputChange}/>
|
||||
<p className="italic">Drag and drop files here or click to select</p>
|
||||
</div>
|
||||
<FileList files={files}/>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={close}
|
||||
title="Close without uploading"
|
||||
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={uploadFiles}
|
||||
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">
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
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-9/10 lg:w-2/5 border-1 border-gray-400">
|
||||
<h2 className="text-2xl font-semibold mb-2 text-blue-400">Upload Files</h2>
|
||||
<p className="text-sm">
|
||||
Files uploaded will be added to the current directory. Currently, directory
|
||||
uploads are not supported. If you want to upload a directory you can create a new one and upload the
|
||||
files into it.
|
||||
</p>
|
||||
<div
|
||||
className="my-5 border-2 border-dashed p-8 rounded-md text-center cursor-pointer border-gray-400 hover:bg-blue-100 hover:border-blue-500 transition-all duration-100"
|
||||
onDragEnter={dragEnter} onDragLeave={dragLeave} onDragOver={dragOver} onDrop={drop} onClick={click}
|
||||
>
|
||||
<input
|
||||
multiple
|
||||
type="file"
|
||||
className="hidden"
|
||||
ref={inputElement}
|
||||
onChange={inputChange}
|
||||
/>
|
||||
<p className="italic">Drag and drop files here or click to select</p>
|
||||
</div>
|
||||
);
|
||||
<FileList files={files}/>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={close}
|
||||
title="Close without uploading"
|
||||
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={uploadFiles}
|
||||
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">
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -9,29 +9,29 @@ import "../index.css"
|
||||
* @constructor
|
||||
*/
|
||||
export default function UserInput({onChange}) {
|
||||
// Example of the controlled input state pattern
|
||||
const [email, setEmail] = useState("");
|
||||
// Example of the controlled input state pattern
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
// Generate ID for the input element
|
||||
const id = useId();
|
||||
// Generate ID for the input element
|
||||
const id = useId();
|
||||
|
||||
/**
|
||||
/**
|
||||
* Update the password controlled state
|
||||
* @param event {InputEvent}
|
||||
*/
|
||||
const updateEmail = (event) => {
|
||||
const email = event.target.value;
|
||||
setEmail(email);
|
||||
onChange(email);
|
||||
}
|
||||
const updateEmail = (event) => {
|
||||
const email = event.target.value;
|
||||
setEmail(email);
|
||||
onChange(email);
|
||||
}
|
||||
|
||||
return <input
|
||||
type="text"
|
||||
name={id}
|
||||
value={email}
|
||||
onInput={updateEmail}
|
||||
placeholder="Username"
|
||||
required={true}
|
||||
className="border border-gray-300 rounded-sm py-2 px-4 placeholder:italic w-full my-2"
|
||||
/>
|
||||
return <input
|
||||
type="text"
|
||||
name={id}
|
||||
value={email}
|
||||
onInput={updateEmail}
|
||||
placeholder="Username"
|
||||
required={true}
|
||||
className="border border-gray-300 rounded-sm py-2 px-4 placeholder:italic w-full my-2"
|
||||
/>
|
||||
}
|
||||
@ -13,8 +13,8 @@ import CreateDirectory from "../components/CreateDirectory.jsx";
|
||||
export default function Dashboard() {
|
||||
// Store the default path
|
||||
// TODO: BACK TO NORMAL PATH
|
||||
const defaultPath = ["media", "vault"];
|
||||
// const defaultPath = ["home", "azpect"];
|
||||
// const defaultPath = ["media", "vault"];
|
||||
const defaultPath = ["home", "azpect"];
|
||||
|
||||
/**
|
||||
* URL To the backend web server.
|
||||
@ -83,11 +83,11 @@ export default function Dashboard() {
|
||||
getData(tkn).then((data) => {
|
||||
setFiles(data);
|
||||
}).finally(() => {
|
||||
setChildrenLoading(false);
|
||||
}).catch((err) => {
|
||||
setError("Failed to fetch data from server.");
|
||||
console.error(err);
|
||||
});
|
||||
setChildrenLoading(false);
|
||||
}).catch((err) => {
|
||||
setError("Failed to fetch data from server.");
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
setSelected([]);
|
||||
};
|
||||
@ -208,8 +208,8 @@ export default function Dashboard() {
|
||||
download(targets).catch((err) => {
|
||||
setError(`Download error: ${err}.`)
|
||||
}).finally(() => {
|
||||
setDownloadLoading(false)
|
||||
});
|
||||
setDownloadLoading(false)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -289,8 +289,8 @@ export default function Dashboard() {
|
||||
setError(data.error);
|
||||
}
|
||||
}).finally(() => {
|
||||
setContentLoading(false)
|
||||
});
|
||||
setContentLoading(false)
|
||||
});
|
||||
}
|
||||
|
||||
}, [editing]);
|
||||
@ -388,8 +388,8 @@ export default function Dashboard() {
|
||||
setCreating(false);
|
||||
}
|
||||
}).catch((error) => {
|
||||
setError(error);
|
||||
});
|
||||
setError(error);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -434,33 +434,54 @@ export default function Dashboard() {
|
||||
fetchFiles();
|
||||
}
|
||||
}).catch((error) => {
|
||||
setError(error);
|
||||
});
|
||||
setError(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen h-screen pb-8">
|
||||
<Navbar downloadFiles={downloadFiles} uploadFiles={toggleUploading} />
|
||||
<div className="h-full w-full flex flex-col items-center justify-center pb-8">
|
||||
|
||||
{downloadLoading && <DownloadLoading />}
|
||||
{creating && <CreateDirectory close={closeCreate} create={createDirectory} />}
|
||||
{uploading && <Uploader close={toggleUploading} upload={upload} />}
|
||||
|
||||
{error && <Error error={error} clear={clearError} />}
|
||||
{(editing !== "" && !error) &&
|
||||
<Editor content={editingFileContent} path={editing} exit={exitFile} saveExit={exitAndSaveFile}
|
||||
loading={contentLoading} />}
|
||||
{
|
||||
(editing !== "" && !error) &&
|
||||
<Editor
|
||||
content={editingFileContent}
|
||||
path={editing}
|
||||
exit={exitFile}
|
||||
saveExit={exitAndSaveFile}
|
||||
loading={contentLoading}
|
||||
/>
|
||||
}
|
||||
|
||||
<PathDisplay path={path} updatePath={updatePath} backHome={backHome} backArrow={backArrow}
|
||||
enabled={path.length > defaultPath.length} create={createDir} remove={removeSelected}
|
||||
removeEnable={selected.length > 0} />
|
||||
<div className="w-2/3 h-5/6 overflow-y-auto border-1 border-gray-300">
|
||||
<PathDisplay
|
||||
path={path}
|
||||
updatePath={updatePath}
|
||||
backHome={backHome}
|
||||
backArrow={backArrow}
|
||||
enabled={path.length > defaultPath.length}
|
||||
create={createDir}
|
||||
remove={removeSelected}
|
||||
removeEnable={selected.length > 0}
|
||||
/>
|
||||
|
||||
<div className="w-9/10 lg:w-2/3 h-5/6 overflow-y-auto border-1 border-gray-300">
|
||||
{childrenLoading && <ChildrenLoading />}
|
||||
<DirectoryList dirs={files} showHidden={showHidden} appendPath={appendPath}
|
||||
// TODO: Rework the toggleSelected functionality
|
||||
toggleSelected={toggleSelected} toggleEditing={toggleEditing} />
|
||||
<DirectoryList
|
||||
dirs={files}
|
||||
showHidden={showHidden}
|
||||
appendPath={appendPath}
|
||||
toggleSelected={toggleSelected} // TODO: Rework the toggleSelected functionality
|
||||
toggleEditing={toggleEditing}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-2/3 flex justify-end items-center">
|
||||
|
||||
<div className="w-9/10 lg:w-2/3 flex justify-end items-center">
|
||||
<label className="text-sm mx-2" htmlFor="showHiddenItems">Show Hidden Items</label>
|
||||
<input
|
||||
className="p-2"
|
||||
|
||||
@ -2,22 +2,23 @@ import "../index.css";
|
||||
import LoginForm from "../components/LoginForm.jsx";
|
||||
|
||||
function Login() {
|
||||
/**
|
||||
* This is just for allowing easy changes to the domain, if a change occurs.
|
||||
* @type {string}
|
||||
*/
|
||||
const domain = "files.gophernest.net"
|
||||
/**
|
||||
* This is just for allowing easy changes to the domain, if a change occurs.
|
||||
* @type {string}
|
||||
*/
|
||||
const domain = "files.gophernest.net"
|
||||
|
||||
return <>
|
||||
<div className="min-h-screen h-screen w-full bg-gray-200 flex items-center justify-center">
|
||||
<h2 className="absolute top-0 left-0 font-[550] font-mono italic text-xl m-6">{domain}</h2>
|
||||
<div className="w-2/7 h-fit bg-white border-1 rounded-sm border-gray-300 p-12">
|
||||
<p className="text-gray-400">Please enter your details</p>
|
||||
<h1 className="text-3xl font-semibold mt-2 mb-12"> Welcome Back </h1>
|
||||
<LoginForm/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
return <>
|
||||
<div className="min-h-screen h-screen w-full bg-gray-200 flex items-center justify-center">
|
||||
<h2 className="absolute top-0 left-0 font-[550] font-mono italic text-xl m-6">{domain}</h2>
|
||||
|
||||
<div className="w-9/10 lg:w-2/7 h-fit bg-white border rounded-sm border-gray-300 p-12">
|
||||
<p className="text-gray-400">Please enter your details</p>
|
||||
<h1 className="text-3xl font-semibold mt-2 mb-12"> Welcome Back </h1>
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
export default Login;
|
||||
@ -1,12 +1,12 @@
|
||||
import "../index.css"
|
||||
|
||||
export default function NotFound() {
|
||||
return <>
|
||||
<div className="min-h-screen h-screen w-full bg-gray-200 flex items-center justify-center">
|
||||
<div className="w-2/7 h-fit bg-white border-1 rounded-sm border-gray-300 p-12">
|
||||
<h1 className="text-6xl font-semibold mt-2 mb-12 opacity-50 text-gray-400"> 404 </h1>
|
||||
<p className="text-sm text-black"> This page could not be found or does not exist.</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
return <>
|
||||
<div className="min-h-screen h-screen w-full bg-gray-200 flex items-center justify-center">
|
||||
<div className="w-2/7 h-fit bg-white border-1 rounded-sm border-gray-300 p-12">
|
||||
<h1 className="text-6xl font-semibold mt-2 mb-12 opacity-50 text-gray-400"> 404 </h1>
|
||||
<p className="text-sm text-black"> This page could not be found or does not exist.</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
Reference in New Issue
Block a user