Updated file explorer + added file importing
This commit is contained in:
parent
e74765e2c8
commit
35c06deb3c
7 changed files with 106 additions and 17 deletions
|
|
@ -55,7 +55,7 @@ export default tseslint.config(
|
||||||
"comma-spacing": "off",
|
"comma-spacing": "off",
|
||||||
"@typescript-eslint/comma-spacing": "warn",
|
"@typescript-eslint/comma-spacing": "warn",
|
||||||
"@typescript-eslint/no-unused-vars": [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
"error",
|
"warn",
|
||||||
{
|
{
|
||||||
"argsIgnorePattern": "^_"
|
"argsIgnorePattern": "^_"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ import { WindowProps } from "../../windows/WindowView";
|
||||||
import { VirtualFolder } from "../../../features/virtual-drive/folder/virtualFolder";
|
import { VirtualFolder } from "../../../features/virtual-drive/folder/virtualFolder";
|
||||||
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
|
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
|
||||||
import { VirtualFolderLink } from "../../../features/virtual-drive/folder/virtualFolderLink";
|
import { VirtualFolderLink } from "../../../features/virtual-drive/folder/virtualFolderLink";
|
||||||
|
import ImportButton from "./ImportButton";
|
||||||
|
import { useAlert } from "../../../hooks/modals/alert";
|
||||||
|
import { VirtualRoot } from "../../../features/virtual-drive/root/virtualRoot";
|
||||||
|
|
||||||
interface FileExplorerProps extends WindowProps {
|
interface FileExplorerProps extends WindowProps {
|
||||||
startPath?: string;
|
startPath?: string;
|
||||||
|
|
@ -44,6 +47,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const [showHidden] = useState(true);
|
const [showHidden] = useState(true);
|
||||||
const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory<string>(currentDirectory.path);
|
const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory<string>(currentDirectory.path);
|
||||||
|
const { alert } = useAlert();
|
||||||
|
|
||||||
const { openWindowedModal } = useWindowedModal();
|
const { openWindowedModal } = useWindowedModal();
|
||||||
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
|
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
|
||||||
|
|
@ -119,6 +123,24 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
}
|
}
|
||||||
}, [history, stateIndex, virtualRoot]);
|
}, [history, stateIndex, virtualRoot]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onError = (error: { message: string }) => {
|
||||||
|
alert({
|
||||||
|
title: error.message,
|
||||||
|
text: "You have exceeded the virtual drive capacity. Files and folders will not be saved until more storage is freed.",
|
||||||
|
iconUrl: AppsManager.getAppIconUrl(APPS.FILE_EXPLORER),
|
||||||
|
size: new Vector2(300, 200),
|
||||||
|
single: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualRoot.on(VirtualRoot.EVENT_NAMES.ERROR, onError);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
virtualRoot.off(VirtualRoot.EVENT_NAMES.ERROR, onError);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onPathChange = (event: Event) => {
|
const onPathChange = (event: Event) => {
|
||||||
setPath((event.target as HTMLInputElement).value);
|
setPath((event.target as HTMLInputElement).value);
|
||||||
};
|
};
|
||||||
|
|
@ -188,16 +210,16 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={styles["Icon-button"]}
|
className={styles["Icon-button"]}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openWindowedModal({
|
// openWindowedModal({
|
||||||
title: "Error",
|
// title: "Error",
|
||||||
iconUrl: AppsManager.getAppIconUrl(APPS.FILE_EXPLORER),
|
// iconUrl: AppsManager.getAppIconUrl(APPS.FILE_EXPLORER),
|
||||||
size: new Vector2(300, 150),
|
// size: new Vector2(300, 150),
|
||||||
Modal: (props) =>
|
// Modal: (props) =>
|
||||||
<DialogBox {...props}>
|
// <DialogBox {...props}>
|
||||||
<p>This folder is protected.</p>
|
// <p>This folder is protected.</p>
|
||||||
<button data-type={DIALOG_CONTENT_TYPES.CloseButton}>Ok</button>
|
// <button data-type={DIALOG_CONTENT_TYPES.CloseButton}>Ok</button>
|
||||||
</DialogBox>
|
// </DialogBox>
|
||||||
});
|
// });
|
||||||
|
|
||||||
// if (currentDirectory.canBeEdited) {
|
// if (currentDirectory.canBeEdited) {
|
||||||
// onNew(event);
|
// onNew(event);
|
||||||
|
|
@ -219,6 +241,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
onKeyDown={onKeyDown as unknown as KeyboardEventHandler}
|
onKeyDown={onKeyDown as unknown as KeyboardEventHandler}
|
||||||
placeholder="Enter a path..."
|
placeholder="Enter a path..."
|
||||||
/>
|
/>
|
||||||
|
<ImportButton directory={currentDirectory}/>
|
||||||
<button title="Search" tabIndex={0} className={styles["Icon-button"]}>
|
<button title="Search" tabIndex={0} className={styles["Icon-button"]}>
|
||||||
<FontAwesomeIcon icon={faSearch}/>
|
<FontAwesomeIcon icon={faSearch}/>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
42
src/components/apps/file-explorer/ImportButton.tsx
Normal file
42
src/components/apps/file-explorer/ImportButton.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { FormEventHandler, ReactElement } from "react";
|
||||||
|
import styles from "./FileExplorer.module.css";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
|
||||||
|
import { VirtualFolder } from "../../../features/virtual-drive/folder";
|
||||||
|
|
||||||
|
interface ImportButtonProps {
|
||||||
|
directory: VirtualFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImportButton({ directory }: ImportButtonProps): ReactElement {
|
||||||
|
const onChange = (event: InputEvent) => {
|
||||||
|
const files = (event.target as HTMLInputElement).files;
|
||||||
|
|
||||||
|
Array.from(files).forEach((file: File) => {
|
||||||
|
const { name, extension } = VirtualFile.convertId(file.name);
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event: Event) => {
|
||||||
|
const { result } = event.target as FileReader;
|
||||||
|
|
||||||
|
// Create a file with the same name and extension, with a base64 string as a source
|
||||||
|
directory.createFile(name, extension, (virtualFile) => {
|
||||||
|
virtualFile.setSource(result as string);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return <label title="Import" tabIndex={0} className={styles["Icon-button"]}>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="import"
|
||||||
|
multiple
|
||||||
|
style={{ display: "none" }}
|
||||||
|
onChange={onChange as unknown as FormEventHandler}
|
||||||
|
/>
|
||||||
|
<FontAwesomeIcon icon={faDownload}/>
|
||||||
|
</label>;
|
||||||
|
}
|
||||||
|
|
@ -7,8 +7,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.File-button, .Folder-button {
|
.File-button, .Folder-button {
|
||||||
|
--gap: 0.25rem;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.25rem;
|
gap: var(--gap);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -41,9 +43,16 @@
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.File-icon, .Folder-icon {
|
||||||
|
max-height: calc(100% - 1rem - var(--gap));
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.File-icon div, .Folder-icon div {
|
.File-icon div, .Folder-icon div {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.File-icon svg, .Folder-icon svg {
|
.File-icon svg, .Folder-icon svg {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
.Image-preview {
|
.Image-preview {
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
@ -16,5 +13,7 @@
|
||||||
.Image-preview > img {
|
.Image-preview > img {
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
border-radius: 0.5rem;
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,12 @@ export class StorageManager {
|
||||||
if (key == null || value == null)
|
if (key == null || value == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
const exceededMaxStorage = this.getByteSize(value) > this.MAX_BYTES;
|
||||||
|
|
||||||
|
if (exceededMaxStorage) {
|
||||||
|
throw new Error("Failed to store value: storage capacity exceeded.");
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.setItem(key, value);
|
localStorage.setItem(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
initiated: boolean = false;
|
initiated: boolean = false;
|
||||||
loadedDefaultData: boolean = false;
|
loadedDefaultData: boolean = false;
|
||||||
|
|
||||||
|
static EVENT_NAMES = {
|
||||||
|
ERROR: "error"
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super("root");
|
super("root");
|
||||||
this.root = this;
|
this.root = this;
|
||||||
|
|
@ -141,7 +145,13 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
if (data == null)
|
if (data == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
StorageManager.store("data", data);
|
try {
|
||||||
|
StorageManager.store("data", data);
|
||||||
|
} catch (error) {
|
||||||
|
this.emit(VirtualRoot.EVENT_NAMES.ERROR, {
|
||||||
|
message: "Failed to save data"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue