diff --git a/eslint.config.js b/eslint.config.js index 3196d25..7d75b3c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -55,7 +55,7 @@ export default tseslint.config( "comma-spacing": "off", "@typescript-eslint/comma-spacing": "warn", "@typescript-eslint/no-unused-vars": [ - "error", + "warn", { "argsIgnorePattern": "^_" } diff --git a/src/components/apps/file-explorer/FileExplorer.tsx b/src/components/apps/file-explorer/FileExplorer.tsx index b980c3d..39b6c1b 100644 --- a/src/components/apps/file-explorer/FileExplorer.tsx +++ b/src/components/apps/file-explorer/FileExplorer.tsx @@ -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(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) => - -

This folder is protected.

- -
- }); + // openWindowedModal({ + // title: "Error", + // iconUrl: AppsManager.getAppIconUrl(APPS.FILE_EXPLORER), + // size: new Vector2(300, 150), + // Modal: (props) => + // + //

This folder is protected.

+ // + //
+ // }); // 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..." /> + diff --git a/src/components/apps/file-explorer/ImportButton.tsx b/src/components/apps/file-explorer/ImportButton.tsx new file mode 100644 index 0000000..e12320a --- /dev/null +++ b/src/components/apps/file-explorer/ImportButton.tsx @@ -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 ; +} \ No newline at end of file diff --git a/src/components/apps/file-explorer/directory-list/DirectoryList.module.css b/src/components/apps/file-explorer/directory-list/DirectoryList.module.css index 49d0616..adb6c8a 100644 --- a/src/components/apps/file-explorer/directory-list/DirectoryList.module.css +++ b/src/components/apps/file-explorer/directory-list/DirectoryList.module.css @@ -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 { diff --git a/src/components/apps/file-explorer/directory-list/ImagePreview.module.css b/src/components/apps/file-explorer/directory-list/ImagePreview.module.css index 918f47f..af14250 100644 --- a/src/components/apps/file-explorer/directory-list/ImagePreview.module.css +++ b/src/components/apps/file-explorer/directory-list/ImagePreview.module.css @@ -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; } \ No newline at end of file diff --git a/src/features/storage/storageManager.ts b/src/features/storage/storageManager.ts index 7d229cc..5e63b8e 100644 --- a/src/features/storage/storageManager.ts +++ b/src/features/storage/storageManager.ts @@ -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); } diff --git a/src/features/virtual-drive/root/virtualRoot.ts b/src/features/virtual-drive/root/virtualRoot.ts index a14dab8..a5a6733 100644 --- a/src/features/virtual-drive/root/virtualRoot.ts +++ b/src/features/virtual-drive/root/virtualRoot.ts @@ -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" + }); + } } /**