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",
|
||||
"@typescript-eslint/comma-spacing": "warn",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ import { WindowProps } from "../../windows/WindowView";
|
|||
import { VirtualFolder } from "../../../features/virtual-drive/folder/virtualFolder";
|
||||
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
|
||||
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 {
|
||||
startPath?: string;
|
||||
|
|
@ -44,6 +47,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
|||
const windowsManager = useWindowsManager();
|
||||
const [showHidden] = useState(true);
|
||||
const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory<string>(currentDirectory.path);
|
||||
const { alert } = useAlert();
|
||||
|
||||
const { openWindowedModal } = useWindowedModal();
|
||||
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
|
||||
|
|
@ -119,6 +123,24 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
|||
}
|
||||
}, [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) => {
|
||||
setPath((event.target as HTMLInputElement).value);
|
||||
};
|
||||
|
|
@ -188,16 +210,16 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
|||
tabIndex={0}
|
||||
className={styles["Icon-button"]}
|
||||
onClick={() => {
|
||||
openWindowedModal({
|
||||
title: "Error",
|
||||
iconUrl: AppsManager.getAppIconUrl(APPS.FILE_EXPLORER),
|
||||
size: new Vector2(300, 150),
|
||||
Modal: (props) =>
|
||||
<DialogBox {...props}>
|
||||
<p>This folder is protected.</p>
|
||||
<button data-type={DIALOG_CONTENT_TYPES.CloseButton}>Ok</button>
|
||||
</DialogBox>
|
||||
});
|
||||
// openWindowedModal({
|
||||
// title: "Error",
|
||||
// iconUrl: AppsManager.getAppIconUrl(APPS.FILE_EXPLORER),
|
||||
// size: new Vector2(300, 150),
|
||||
// Modal: (props) =>
|
||||
// <DialogBox {...props}>
|
||||
// <p>This folder is protected.</p>
|
||||
// <button data-type={DIALOG_CONTENT_TYPES.CloseButton}>Ok</button>
|
||||
// </DialogBox>
|
||||
// });
|
||||
|
||||
// if (currentDirectory.canBeEdited) {
|
||||
// onNew(event);
|
||||
|
|
@ -219,6 +241,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
|||
onKeyDown={onKeyDown as unknown as KeyboardEventHandler}
|
||||
placeholder="Enter a path..."
|
||||
/>
|
||||
<ImportButton directory={currentDirectory}/>
|
||||
<button title="Search" tabIndex={0} className={styles["Icon-button"]}>
|
||||
<FontAwesomeIcon icon={faSearch}/>
|
||||
</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 {
|
||||
--gap: 0.25rem;
|
||||
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
gap: var(--gap);
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -41,9 +43,16 @@
|
|||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.File-icon, .Folder-icon {
|
||||
max-height: calc(100% - 1rem - var(--gap));
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.File-icon div, .Folder-icon div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.File-icon svg, .Folder-icon svg {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
.Image-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
|
@ -16,5 +13,7 @@
|
|||
.Image-preview > img {
|
||||
height: auto;
|
||||
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)
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ export class VirtualRoot extends VirtualFolder {
|
|||
initiated: boolean = false;
|
||||
loadedDefaultData: boolean = false;
|
||||
|
||||
static EVENT_NAMES = {
|
||||
ERROR: "error"
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super("root");
|
||||
this.root = this;
|
||||
|
|
@ -141,7 +145,13 @@ export class VirtualRoot extends VirtualFolder {
|
|||
if (data == null)
|
||||
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