From 55e6811da6eacde8d0fce08e17a4df8ad9c7d563 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Tue, 11 Nov 2025 18:30:58 +0100 Subject: [PATCH 01/43] Started with issue #92 - Add TypeScript Support Renamed all `.js` and `.jsx` files to `.ts` and `.tsx` respectively. Added tsconfig.json. Started defining common types and functions, located in "/src/types". Added type annotations to all contexts. --- backend/.env.example | 3 - frontend/.env.example | 2 - frontend/package-lock.json | 63 +++++--- frontend/package.json | 7 +- frontend/src/{App.jsx => App.tsx} | 0 .../Actions/{Actions.jsx => Actions.tsx} | 0 ...der.action.jsx => CreateFolder.action.tsx} | 0 .../{Delete.action.jsx => Delete.action.tsx} | 0 ...File.action.jsx => PreviewFile.action.tsx} | 0 .../{Rename.action.jsx => Rename.action.tsx} | 0 ...dFile.action.jsx => UploadFile.action.tsx} | 0 .../{UploadItem.jsx => UploadItem.tsx} | 0 .../{BreadCrumb.jsx => BreadCrumb.tsx} | 0 .../FileList/{FileItem.jsx => FileItem.tsx} | 0 .../FileList/{FileList.jsx => FileList.tsx} | 0 .../{FilesHeader.jsx => FilesHeader.tsx} | 0 .../{useFileList.jsx => useFileList.tsx} | 0 .../{FileManager.jsx => FileManager.tsx} | 143 +++++++++++++----- .../{FolderTree.jsx => FolderTree.tsx} | 0 ...{NavigationPane.jsx => NavigationPane.tsx} | 0 .../{LayoutToggler.jsx => LayoutToggler.tsx} | 0 .../Toolbar/{Toolbar.jsx => Toolbar.tsx} | 0 .../src/FileManager/{index.js => index.ts} | 0 frontend/src/api/{api.js => api.ts} | 0 ...{createFolderAPI.js => createFolderAPI.ts} | 0 .../src/api/{deleteAPI.js => deleteAPI.ts} | 0 ...{downloadFileAPI.js => downloadFileAPI.ts} | 0 ...{fileTransferAPI.js => fileTransferAPI.ts} | 0 .../{getAllFilesAPI.js => getAllFilesAPI.ts} | 0 .../src/api/{renameAPI.js => renameAPI.ts} | 0 .../Button/{Button.jsx => Button.tsx} | 0 .../Checkbox/{Checkbox.jsx => Checkbox.tsx} | 0 .../Collapse/{Collapse.jsx => Collapse.tsx} | 0 .../{ContextMenu.jsx => ContextMenu.tsx} | 0 .../ContextMenu/{SubMenu.jsx => SubMenu.tsx} | 0 .../{ErrorTooltip.jsx => ErrorTooltip.tsx} | 0 .../Loader/{Loader.jsx => Loader.tsx} | 7 +- .../components/Modal/{Modal.jsx => Modal.tsx} | 0 .../{NameInput.jsx => NameInput.tsx} | 0 .../Progress/{Progress.jsx => Progress.tsx} | 0 frontend/src/constants/{index.js => index.ts} | 0 frontend/src/contexts/ClipboardContext.jsx | 44 ------ frontend/src/contexts/ClipboardContext.tsx | 64 ++++++++ .../src/contexts/FileNavigationContext.jsx | 60 -------- .../src/contexts/FileNavigationContext.tsx | 82 ++++++++++ frontend/src/contexts/FilesContext.jsx | 25 --- frontend/src/contexts/FilesContext.tsx | 40 +++++ frontend/src/contexts/LayoutContext.jsx | 20 --- frontend/src/contexts/LayoutContext.tsx | 26 ++++ frontend/src/contexts/SelectionContext.jsx | 25 --- frontend/src/contexts/SelectionContext.tsx | 39 +++++ ...onProvider.jsx => TranslationProvider.tsx} | 7 +- ...{useColumnResize.js => useColumnResize.ts} | 0 ...tsideClick.js => useDetectOutsideClick.ts} | 0 .../{useFileIcons.jsx => useFileIcons.tsx} | 0 .../hooks/{useKeyPress.js => useKeyPress.ts} | 0 ...ortcutHandler.js => useShortcutHandler.ts} | 0 ...seTriggerAction.js => useTriggerAction.ts} | 0 frontend/src/{i18n.js => i18n.ts} | 0 frontend/src/{index.js => index.ts} | 0 frontend/src/{main.jsx => main.tsx} | 2 +- frontend/src/types/File.ts | 7 + frontend/src/types/FileManagerFunctions.ts | 37 +++++ frontend/src/types/FileUploadConfiguration.ts | 5 + frontend/src/types/Language.ts | 21 +++ frontend/src/types/Layout.ts | 1 + frontend/src/types/Permissions.ts | 9 ++ frontend/src/types/SortConfiguration.ts | 8 + ...reateFolderTree.js => createFolderTree.ts} | 0 ...NameHandler.js => duplicateNameHandler.ts} | 0 frontend/src/utils/formatDate.js | 15 -- frontend/src/utils/formatDate.ts | 15 ++ .../utils/{getDataSize.js => getDataSize.ts} | 0 ...etFileExtension.js => getFileExtension.ts} | 0 .../{getParentPath.js => getParentPath.ts} | 0 .../src/utils/{shortcuts.js => shortcuts.ts} | 0 .../src/utils/{sortFiles.js => sortFiles.ts} | 0 frontend/src/utils/validateApiCallback.js | 13 -- .../{propValidators.js => propValidators.ts} | 0 frontend/tsconfig.json | 50 ++++++ frontend/vite.config.js | 2 +- 81 files changed, 568 insertions(+), 274 deletions(-) delete mode 100644 backend/.env.example delete mode 100644 frontend/.env.example rename frontend/src/{App.jsx => App.tsx} (100%) rename frontend/src/FileManager/Actions/{Actions.jsx => Actions.tsx} (100%) rename frontend/src/FileManager/Actions/CreateFolder/{CreateFolder.action.jsx => CreateFolder.action.tsx} (100%) rename frontend/src/FileManager/Actions/Delete/{Delete.action.jsx => Delete.action.tsx} (100%) rename frontend/src/FileManager/Actions/PreviewFile/{PreviewFile.action.jsx => PreviewFile.action.tsx} (100%) rename frontend/src/FileManager/Actions/Rename/{Rename.action.jsx => Rename.action.tsx} (100%) rename frontend/src/FileManager/Actions/UploadFile/{UploadFile.action.jsx => UploadFile.action.tsx} (100%) rename frontend/src/FileManager/Actions/UploadFile/{UploadItem.jsx => UploadItem.tsx} (100%) rename frontend/src/FileManager/BreadCrumb/{BreadCrumb.jsx => BreadCrumb.tsx} (100%) rename frontend/src/FileManager/FileList/{FileItem.jsx => FileItem.tsx} (100%) rename frontend/src/FileManager/FileList/{FileList.jsx => FileList.tsx} (100%) rename frontend/src/FileManager/FileList/{FilesHeader.jsx => FilesHeader.tsx} (100%) rename frontend/src/FileManager/FileList/{useFileList.jsx => useFileList.tsx} (100%) rename frontend/src/FileManager/{FileManager.jsx => FileManager.tsx} (70%) rename frontend/src/FileManager/NavigationPane/{FolderTree.jsx => FolderTree.tsx} (100%) rename frontend/src/FileManager/NavigationPane/{NavigationPane.jsx => NavigationPane.tsx} (100%) rename frontend/src/FileManager/Toolbar/{LayoutToggler.jsx => LayoutToggler.tsx} (100%) rename frontend/src/FileManager/Toolbar/{Toolbar.jsx => Toolbar.tsx} (100%) rename frontend/src/FileManager/{index.js => index.ts} (100%) rename frontend/src/api/{api.js => api.ts} (100%) rename frontend/src/api/{createFolderAPI.js => createFolderAPI.ts} (100%) rename frontend/src/api/{deleteAPI.js => deleteAPI.ts} (100%) rename frontend/src/api/{downloadFileAPI.js => downloadFileAPI.ts} (100%) rename frontend/src/api/{fileTransferAPI.js => fileTransferAPI.ts} (100%) rename frontend/src/api/{getAllFilesAPI.js => getAllFilesAPI.ts} (100%) rename frontend/src/api/{renameAPI.js => renameAPI.ts} (100%) rename frontend/src/components/Button/{Button.jsx => Button.tsx} (100%) rename frontend/src/components/Checkbox/{Checkbox.jsx => Checkbox.tsx} (100%) rename frontend/src/components/Collapse/{Collapse.jsx => Collapse.tsx} (100%) rename frontend/src/components/ContextMenu/{ContextMenu.jsx => ContextMenu.tsx} (100%) rename frontend/src/components/ContextMenu/{SubMenu.jsx => SubMenu.tsx} (100%) rename frontend/src/components/ErrorTooltip/{ErrorTooltip.jsx => ErrorTooltip.tsx} (100%) rename frontend/src/components/Loader/{Loader.jsx => Loader.tsx} (64%) rename frontend/src/components/Modal/{Modal.jsx => Modal.tsx} (100%) rename frontend/src/components/NameInput/{NameInput.jsx => NameInput.tsx} (100%) rename frontend/src/components/Progress/{Progress.jsx => Progress.tsx} (100%) rename frontend/src/constants/{index.js => index.ts} (100%) delete mode 100644 frontend/src/contexts/ClipboardContext.jsx create mode 100644 frontend/src/contexts/ClipboardContext.tsx delete mode 100644 frontend/src/contexts/FileNavigationContext.jsx create mode 100644 frontend/src/contexts/FileNavigationContext.tsx delete mode 100644 frontend/src/contexts/FilesContext.jsx create mode 100644 frontend/src/contexts/FilesContext.tsx delete mode 100644 frontend/src/contexts/LayoutContext.jsx create mode 100644 frontend/src/contexts/LayoutContext.tsx delete mode 100644 frontend/src/contexts/SelectionContext.jsx create mode 100644 frontend/src/contexts/SelectionContext.tsx rename frontend/src/contexts/{TranslationProvider.jsx => TranslationProvider.tsx} (53%) rename frontend/src/hooks/{useColumnResize.js => useColumnResize.ts} (100%) rename frontend/src/hooks/{useDetectOutsideClick.js => useDetectOutsideClick.ts} (100%) rename frontend/src/hooks/{useFileIcons.jsx => useFileIcons.tsx} (100%) rename frontend/src/hooks/{useKeyPress.js => useKeyPress.ts} (100%) rename frontend/src/hooks/{useShortcutHandler.js => useShortcutHandler.ts} (100%) rename frontend/src/hooks/{useTriggerAction.js => useTriggerAction.ts} (100%) rename frontend/src/{i18n.js => i18n.ts} (100%) rename frontend/src/{index.js => index.ts} (100%) rename frontend/src/{main.jsx => main.tsx} (71%) create mode 100644 frontend/src/types/File.ts create mode 100644 frontend/src/types/FileManagerFunctions.ts create mode 100644 frontend/src/types/FileUploadConfiguration.ts create mode 100644 frontend/src/types/Language.ts create mode 100644 frontend/src/types/Layout.ts create mode 100644 frontend/src/types/Permissions.ts create mode 100644 frontend/src/types/SortConfiguration.ts rename frontend/src/utils/{createFolderTree.js => createFolderTree.ts} (100%) rename frontend/src/utils/{duplicateNameHandler.js => duplicateNameHandler.ts} (100%) delete mode 100644 frontend/src/utils/formatDate.js create mode 100644 frontend/src/utils/formatDate.ts rename frontend/src/utils/{getDataSize.js => getDataSize.ts} (100%) rename frontend/src/utils/{getFileExtension.js => getFileExtension.ts} (100%) rename frontend/src/utils/{getParentPath.js => getParentPath.ts} (100%) rename frontend/src/utils/{shortcuts.js => shortcuts.ts} (100%) rename frontend/src/utils/{sortFiles.js => sortFiles.ts} (100%) delete mode 100644 frontend/src/utils/validateApiCallback.js rename frontend/src/validators/{propValidators.js => propValidators.ts} (100%) create mode 100644 frontend/tsconfig.json diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 6a6d228..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -PORT=3000 -MONGO_URI=mongodb://localhost:27017/fileManagerDB -CLIENT_URI=http://localhost:5173 \ No newline at end of file diff --git a/frontend/.env.example b/frontend/.env.example deleted file mode 100644 index 2ffab02..0000000 --- a/frontend/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -VITE_API_BASE_URL=http://localhost:3000/api/file-system -VITE_API_FILES_BASE_URL=http://localhost:3000 \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0c55cc1..e140948 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,12 +10,14 @@ "license": "MIT", "dependencies": { "i18next": "^25.0.0", + "prop-types": "^15.8.1", "react-collapsed": "^4.2.0", "react-icons": "^5.4.0" }, "devDependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/node": "^24.10.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", "@vitejs/plugin-react-swc": "^3.5.0", "axios": "^1.7.7", "eslint": "^8.57.0", @@ -26,6 +28,7 @@ "react-dom": "^18.3.1", "sass": "^1.77.6", "semantic-release": "^24.1.0", + "typescript": "^5.9.3", "vite": "^6.3.6" }, "peerDependencies": { @@ -1904,6 +1907,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -1911,32 +1924,24 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { - "version": "18.3.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", - "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", - "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", + "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "dev": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/semver": { @@ -8467,7 +8472,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9038,7 +9042,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -9168,7 +9171,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/read-package-up": { @@ -10606,6 +10608,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -10639,6 +10655,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2f77316..b86b351 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,12 +21,14 @@ }, "dependencies": { "i18next": "^25.0.0", + "prop-types": "^15.8.1", "react-collapsed": "^4.2.0", "react-icons": "^5.4.0" }, "devDependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/node": "^24.10.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", "@vitejs/plugin-react-swc": "^3.5.0", "axios": "^1.7.7", "eslint": "^8.57.0", @@ -37,6 +39,7 @@ "react-dom": "^18.3.1", "sass": "^1.77.6", "semantic-release": "^24.1.0", + "typescript": "^5.9.3", "vite": "^6.3.6" }, "peerDependencies": { diff --git a/frontend/src/App.jsx b/frontend/src/App.tsx similarity index 100% rename from frontend/src/App.jsx rename to frontend/src/App.tsx diff --git a/frontend/src/FileManager/Actions/Actions.jsx b/frontend/src/FileManager/Actions/Actions.tsx similarity index 100% rename from frontend/src/FileManager/Actions/Actions.jsx rename to frontend/src/FileManager/Actions/Actions.tsx diff --git a/frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.jsx b/frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.tsx similarity index 100% rename from frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.jsx rename to frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.tsx diff --git a/frontend/src/FileManager/Actions/Delete/Delete.action.jsx b/frontend/src/FileManager/Actions/Delete/Delete.action.tsx similarity index 100% rename from frontend/src/FileManager/Actions/Delete/Delete.action.jsx rename to frontend/src/FileManager/Actions/Delete/Delete.action.tsx diff --git a/frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.jsx b/frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.tsx similarity index 100% rename from frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.jsx rename to frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.tsx diff --git a/frontend/src/FileManager/Actions/Rename/Rename.action.jsx b/frontend/src/FileManager/Actions/Rename/Rename.action.tsx similarity index 100% rename from frontend/src/FileManager/Actions/Rename/Rename.action.jsx rename to frontend/src/FileManager/Actions/Rename/Rename.action.tsx diff --git a/frontend/src/FileManager/Actions/UploadFile/UploadFile.action.jsx b/frontend/src/FileManager/Actions/UploadFile/UploadFile.action.tsx similarity index 100% rename from frontend/src/FileManager/Actions/UploadFile/UploadFile.action.jsx rename to frontend/src/FileManager/Actions/UploadFile/UploadFile.action.tsx diff --git a/frontend/src/FileManager/Actions/UploadFile/UploadItem.jsx b/frontend/src/FileManager/Actions/UploadFile/UploadItem.tsx similarity index 100% rename from frontend/src/FileManager/Actions/UploadFile/UploadItem.jsx rename to frontend/src/FileManager/Actions/UploadFile/UploadItem.tsx diff --git a/frontend/src/FileManager/BreadCrumb/BreadCrumb.jsx b/frontend/src/FileManager/BreadCrumb/BreadCrumb.tsx similarity index 100% rename from frontend/src/FileManager/BreadCrumb/BreadCrumb.jsx rename to frontend/src/FileManager/BreadCrumb/BreadCrumb.tsx diff --git a/frontend/src/FileManager/FileList/FileItem.jsx b/frontend/src/FileManager/FileList/FileItem.tsx similarity index 100% rename from frontend/src/FileManager/FileList/FileItem.jsx rename to frontend/src/FileManager/FileList/FileItem.tsx diff --git a/frontend/src/FileManager/FileList/FileList.jsx b/frontend/src/FileManager/FileList/FileList.tsx similarity index 100% rename from frontend/src/FileManager/FileList/FileList.jsx rename to frontend/src/FileManager/FileList/FileList.tsx diff --git a/frontend/src/FileManager/FileList/FilesHeader.jsx b/frontend/src/FileManager/FileList/FilesHeader.tsx similarity index 100% rename from frontend/src/FileManager/FileList/FilesHeader.jsx rename to frontend/src/FileManager/FileList/FilesHeader.tsx diff --git a/frontend/src/FileManager/FileList/useFileList.jsx b/frontend/src/FileManager/FileList/useFileList.tsx similarity index 100% rename from frontend/src/FileManager/FileList/useFileList.jsx rename to frontend/src/FileManager/FileList/useFileList.tsx diff --git a/frontend/src/FileManager/FileManager.jsx b/frontend/src/FileManager/FileManager.tsx similarity index 70% rename from frontend/src/FileManager/FileManager.jsx rename to frontend/src/FileManager/FileManager.tsx index e6cff0a..d967d0e 100644 --- a/frontend/src/FileManager/FileManager.jsx +++ b/frontend/src/FileManager/FileManager.tsx @@ -18,46 +18,111 @@ import { useMemo, useState } from "react"; import { defaultPermissions } from "../constants"; import { formatDate as defaultFormatDate } from "../utils/formatDate"; import "./FileManager.scss"; +import { File } from "../types/File"; +import { FileUploadConfiguration } from "../types/FileUploadConfiguration"; +import { Layout } from "../types/Layout"; +import { Language } from "../types/Language"; +import { + OnCreateFolder, + OnFileUploading, + OnFileUploaded, + OnCut, + OnCopy, + OnPaste, + OnRename, + OnDownload, + OnDelete, + OnFolderChange, + OnError, + OnFileOpen, + OnLayoutChange, + OnSelect, + OnSelectionChange, + OnSortChange, + FilePreviewComponent + } from "../types/FileManagerFunctions"; + +export interface FileManagerProps { + files?: File[]; // x + fileUploadConfig?: FileUploadConfiguration; // x + isLoading?: boolean; // x + onCreateFolder?: OnCreateFolder; // x + onFileUploading?: OnFileUploading; // x + onFileUploaded?: OnFileUploaded; // x + onCut?: OnCut; // x + onCopy?: OnCopy; // x + onPaste?: OnPaste; // x + onRename?: OnRename; // x + onDownload?: OnDownload; // x + onDelete?: OnDelete // x + onLayoutChange?: OnLayoutChange // x + onRefresh?: () => void; // x + onFileOpen?: OnFileOpen; // x + onFolderChange?: OnFolderChange; // x + onSelect?: OnSelect; // x (deprecated) + onSelectionChange?: OnSelectionChange; // x + onError?: OnError; // x + onSortChange?: OnSortChange; // x + layout?: Layout; // x + enableFilePreview?: boolean; // x + maxFileSize?: number; // x + filePreviewPath?: string; // x + acceptedFileTypes?: string; // x + height?: string | number; // x + width?: string | number; // x + initialPath?: string; // x + filePreviewComponent?: FilePreviewComponent; // x + primaryColor?: string; // x + fontFamily?: string; // x + language?: Language; // x + permissions?: Permissions; // x + collapsibleNav?: boolean; // x + defaultNavExpanded?: boolean; // x + className?: string; // x + style?: React.CSSProperties; // x + formatDate?: (date: string) => string; // x (Not in official docs but in your code) +} const FileManager = ({ - files, - fileUploadConfig, - isLoading, - onCreateFolder, - onFileUploading = () => {}, - onFileUploaded = () => {}, - onCut, - onCopy, - onPaste, - onRename, - onDownload, - onDelete = () => null, - onLayoutChange = () => {}, - onRefresh, - onFileOpen = () => {}, - onFolderChange = () => {}, - onSelect, - onSelectionChange, - onError = () => {}, - layout = "grid", - enableFilePreview = true, - maxFileSize, - filePreviewPath, - acceptedFileTypes, - height = "600px", - width = "100%", - initialPath = "", - filePreviewComponent, - primaryColor = "#6155b4", - fontFamily = "Nunito Sans, sans-serif", - language = "en-US", - permissions: userPermissions = {}, - collapsibleNav = false, - defaultNavExpanded = true, - className = "", - style = {}, - formatDate = defaultFormatDate, -}) => { + files = [], // x + fileUploadConfig, // x + isLoading, // x + onCreateFolder, // x + onFileUploading, + onFileUploaded, + onCut, // x + onCopy, // x + onPaste, // x + onRename, // x + onDownload, // x + onDelete = () => null, // x + onLayoutChange = () => {}, // x + onRefresh, // x + onFileOpen = () => {}, // x + onFolderChange = () => {}, // x + onSelect, // x + onSelectionChange, // x + onError = () => {}, // x + onSortChange, // x + layout = "grid", // x + enableFilePreview = true, // x + maxFileSize, // x + filePreviewPath, // x + acceptedFileTypes, // x + height = "600px", // x + width = "100%", // x + initialPath = "", // x + filePreviewComponent, // x + primaryColor = "#6155b4", // x + fontFamily = "Nunito Sans, sans-serif", // x + language = "en-US", // x + permissions: userPermissions = {}, // x + collapsibleNav = false, // x + defaultNavExpanded = true, // x + className = "", // x + style = {}, // x + formatDate = defaultFormatDate, // x +}: FileManagerProps) => { const [isNavigationPaneOpen, setNavigationPaneOpen] = useState(defaultNavExpanded); const triggerAction = useTriggerAction(); const { containerRef, colSizes, isDragging, handleMouseMove, handleMouseUp, handleMouseDown } = @@ -84,7 +149,7 @@ const FileManager = ({ - { +interface LoaderProps { + loading?: boolean; + className?: string; +} + +const Loader = ({ loading = false, className } : LoaderProps) => { if (!loading) return null; return ( diff --git a/frontend/src/components/Modal/Modal.jsx b/frontend/src/components/Modal/Modal.tsx similarity index 100% rename from frontend/src/components/Modal/Modal.jsx rename to frontend/src/components/Modal/Modal.tsx diff --git a/frontend/src/components/NameInput/NameInput.jsx b/frontend/src/components/NameInput/NameInput.tsx similarity index 100% rename from frontend/src/components/NameInput/NameInput.jsx rename to frontend/src/components/NameInput/NameInput.tsx diff --git a/frontend/src/components/Progress/Progress.jsx b/frontend/src/components/Progress/Progress.tsx similarity index 100% rename from frontend/src/components/Progress/Progress.jsx rename to frontend/src/components/Progress/Progress.tsx diff --git a/frontend/src/constants/index.js b/frontend/src/constants/index.ts similarity index 100% rename from frontend/src/constants/index.js rename to frontend/src/constants/index.ts diff --git a/frontend/src/contexts/ClipboardContext.jsx b/frontend/src/contexts/ClipboardContext.jsx deleted file mode 100644 index 24e2792..0000000 --- a/frontend/src/contexts/ClipboardContext.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { createContext, useContext, useState } from "react"; -import { useSelection } from "./SelectionContext"; -import { validateApiCallback } from "../utils/validateApiCallback"; - -const ClipBoardContext = createContext(); - -export const ClipBoardProvider = ({ children, onPaste, onCut, onCopy }) => { - const [clipBoard, setClipBoard] = useState(null); - const { selectedFiles, setSelectedFiles } = useSelection(); - - const handleCutCopy = (isMoving) => { - setClipBoard({ - files: selectedFiles, - isMoving: isMoving, - }); - - if (isMoving) { - !!onCut && onCut(selectedFiles); - } else { - !!onCopy && onCopy(selectedFiles); - } - }; - - // Todo: Show error if destination folder already has file(s) with the same name - const handlePasting = (destinationFolder) => { - if (destinationFolder && !destinationFolder.isDirectory) return; - - const copiedFiles = clipBoard.files; - const operationType = clipBoard.isMoving ? "move" : "copy"; - - validateApiCallback(onPaste, "onPaste", copiedFiles, destinationFolder, operationType); - - clipBoard.isMoving && setClipBoard(null); - setSelectedFiles([]); - }; - - return ( - - {children} - - ); -}; - -export const useClipBoard = () => useContext(ClipBoardContext); diff --git a/frontend/src/contexts/ClipboardContext.tsx b/frontend/src/contexts/ClipboardContext.tsx new file mode 100644 index 0000000..f1c9e27 --- /dev/null +++ b/frontend/src/contexts/ClipboardContext.tsx @@ -0,0 +1,64 @@ +import { createContext, ReactNode, useContext, useState } from "react"; +import { useSelection } from "./SelectionContext"; +import { OnPaste, OnCut, OnCopy } from "../types/FileManagerFunctions"; +import { File } from "../types/File"; + +export interface ClipBoardContextType { + +} + +export interface ClipBoardProviderProps { + children: ReactNode; + onPaste: OnPaste; + onCut: OnCut; + onCopy: OnCopy; +} + +interface ClipBoard { + files?: File[]; + isMoving: boolean; +} + +const ClipBoardContext = createContext(undefined); + +export const ClipBoardProvider = ({ children, onPaste, onCut, onCopy } : ClipBoardProviderProps) => { + const [clipBoard, setClipBoard] = useState(undefined); + const selectionContext = useSelection(); + const selectedFiles = selectionContext?.selectedFiles; + const setSelectedFiles = selectionContext?.setSelectedFiles; + + const handleCutCopy = (isMoving: boolean) => { + setClipBoard({ + files: selectedFiles, + isMoving: isMoving, + }); + + if (isMoving) { + !!onCut && onCut(selectedFiles ?? []); + } else { + !!onCopy && onCopy(selectedFiles ?? []); + } + }; + + // Todo: Show error if destination folder already has file(s) with the same name + const handlePasting = (destinationFolder : File) => { + if (destinationFolder && !destinationFolder.isDirectory) return; + + const copiedFiles = clipBoard?.files; + const operationType = clipBoard?.isMoving ? "move" : "copy"; + + onPaste?.(copiedFiles ?? [], destinationFolder, operationType); + + clipBoard?.isMoving && setClipBoard(undefined); + + setSelectedFiles?.([]); + }; + + return ( + + {children} + + ); +}; + +export const useClipBoard = () => useContext(ClipBoardContext); diff --git a/frontend/src/contexts/FileNavigationContext.jsx b/frontend/src/contexts/FileNavigationContext.jsx deleted file mode 100644 index 13546e0..0000000 --- a/frontend/src/contexts/FileNavigationContext.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import { createContext, useContext, useEffect, useRef, useState } from "react"; -import { useFiles } from "./FilesContext"; -import sortFiles from "../utils/sortFiles"; - -const FileNavigationContext = createContext(); - -export const FileNavigationProvider = ({ children, initialPath, onFolderChange }) => { - const { files } = useFiles(); - const isMountRef = useRef(false); - const [currentPath, setCurrentPath] = useState(""); - const [currentFolder, setCurrentFolder] = useState(null); - const [currentPathFiles, setCurrentPathFiles] = useState([]); - const [sortConfig, setSortConfig] = useState({ key: "name", direction: "asc" }); - - useEffect(() => { - if (Array.isArray(files) && files.length > 0) { - setCurrentPathFiles(() => { - const currPathFiles = files.filter((file) => file.path === `${currentPath}/${file.name}`); - return sortFiles(currPathFiles, sortConfig.key, sortConfig.direction); - }); - - setCurrentFolder(() => { - return files.find((file) => file.path === currentPath) ?? null; - }); - } else { - setCurrentPathFiles([]); - setCurrentFolder(null); - } - }, [files, currentPath, sortConfig]); - - useEffect(() => { - if (!isMountRef.current && Array.isArray(files) && files.length > 0) { - const activePath = files.some((file) => file.isDirectory && file.path === initialPath) - ? initialPath - : ""; - setCurrentPath(activePath); - isMountRef.current = true; - } - }, [files]); - - return ( - - {children} - - ); -}; - -export const useFileNavigation = () => useContext(FileNavigationContext); diff --git a/frontend/src/contexts/FileNavigationContext.tsx b/frontend/src/contexts/FileNavigationContext.tsx new file mode 100644 index 0000000..0e64cac --- /dev/null +++ b/frontend/src/contexts/FileNavigationContext.tsx @@ -0,0 +1,82 @@ +import { createContext, ReactNode, useContext, useEffect, useRef, useState } from "react"; +import { useFiles } from "./FilesContext"; +import sortFiles from "../utils/sortFiles"; +import { OnFolderChange } from "../types/functions/OnFolderChange"; +import { File } from "../types/File"; +import { SortConfiguration } from "../types/SortConfiguration"; + +export interface FileNavigationContextType { + currentPath?: string; + setCurrentPath?: (path: string) => void; + currentFolder?: File; + setCurrentFolder: (file : File | undefined) => void; + currentPathFiles: File[]; + setCurrentPathFiles: (files: File[]) => void; + sortConfig: SortConfiguration; + setSortConfig: (config : SortConfiguration) => void; + onFolderChange: OnFolderChange; +} + +export interface FileNavigationProviderProps { + children: ReactNode; + initialPath: string; + onFolderChange: OnFolderChange; +} + +const FileNavigationContext = createContext(undefined); + +export const FileNavigationProvider = ({ children, initialPath, onFolderChange } : FileNavigationProviderProps) => { + const fileContext = useFiles(); + const files = fileContext?.files; + const isMountRef = useRef(false); + const [currentPath, setCurrentPath] = useState(""); + const [currentFolder, setCurrentFolder] = useState(undefined); + const [currentPathFiles, setCurrentPathFiles] = useState([]); + const [sortConfig, setSortConfig] = useState({ key: "name", direction: "asc" }); + + useEffect(() => { + if (Array.isArray(files) && files.length > 0) { + setCurrentPathFiles(() => { + const currPathFiles = files.filter((file) => file.path === `${currentPath}/${file.name}`); + return sortFiles(currPathFiles, sortConfig.key, sortConfig.direction); + }); + + setCurrentFolder(() => { + return files.find((file) => file.path === currentPath); + }); + } else { + setCurrentPathFiles([]); + setCurrentFolder(undefined); + } + }, [files, currentPath, sortConfig]); + + useEffect(() => { + if (!isMountRef.current && Array.isArray(files) && files.length > 0) { + const activePath = files.some((file) => file.isDirectory && file.path === initialPath) + ? initialPath + : ""; + setCurrentPath(activePath); + isMountRef.current = true; + } + }, [files]); + + return ( + + {children} + + ); +}; + +export const useFileNavigation = () => useContext(FileNavigationContext); diff --git a/frontend/src/contexts/FilesContext.jsx b/frontend/src/contexts/FilesContext.jsx deleted file mode 100644 index 00d4266..0000000 --- a/frontend/src/contexts/FilesContext.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { createContext, useContext, useEffect, useState } from "react"; - -const FilesContext = createContext(); - -export const FilesProvider = ({ children, filesData, onError }) => { - const [files, setFiles] = useState([]); - - useEffect(() => { - setFiles(filesData); - }, [filesData]); - - const getChildren = (file) => { - if (!file.isDirectory) return []; - - return files.filter((child) => child.path === `${file.path}/${child.name}`); - }; - - return ( - - {children} - - ); -}; - -export const useFiles = () => useContext(FilesContext); diff --git a/frontend/src/contexts/FilesContext.tsx b/frontend/src/contexts/FilesContext.tsx new file mode 100644 index 0000000..1e17501 --- /dev/null +++ b/frontend/src/contexts/FilesContext.tsx @@ -0,0 +1,40 @@ +import { createContext, ReactNode, useContext, useEffect, useState } from "react"; +import { File } from "../types/File"; +import { OnError } from "../types/functions/OnError"; + +interface FilesContextType { + files: File[], + setFiles: (files : File[]) => void; + getChildren: (file: File) => File[]; + onError: OnError; +} + +interface FilesProviderProps { + children: ReactNode; + filesData: File[]; + onError: OnError; +} + +const FilesContext = createContext(undefined); + +export const FilesProvider = ({ children, filesData, onError } : FilesProviderProps) => { + const [files, setFiles] = useState([]); + + useEffect(() => { + setFiles(filesData); + }, [filesData]); + + const getChildren = (file : File) => { + if (!file.isDirectory) return []; + + return files.filter((child) => child.path === `${file.path}/${child.name}`); + }; + + return ( + + {children} + + ); +}; + +export const useFiles = () => useContext(FilesContext); diff --git a/frontend/src/contexts/LayoutContext.jsx b/frontend/src/contexts/LayoutContext.jsx deleted file mode 100644 index a94adf7..0000000 --- a/frontend/src/contexts/LayoutContext.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { createContext, useContext, useState } from "react"; - -const LayoutContext = createContext(); - -export const LayoutProvider = ({ children, layout }) => { - const [activeLayout, setActiveLayout] = useState(() => validateLayout(layout)); - - function validateLayout(layout) { - const acceptedValue = ["list", "grid"]; - return acceptedValue.includes(layout) ? layout : "grid"; - } - - return ( - - {children} - - ); -}; - -export const useLayout = () => useContext(LayoutContext); diff --git a/frontend/src/contexts/LayoutContext.tsx b/frontend/src/contexts/LayoutContext.tsx new file mode 100644 index 0000000..e7ced2e --- /dev/null +++ b/frontend/src/contexts/LayoutContext.tsx @@ -0,0 +1,26 @@ +import { createContext, ReactNode, useContext, useState } from "react"; +import { Layout } from "../types/Layout"; + +interface LayoutContextType { + activeLayout: Layout; + setActiveLayout: (layout : Layout) => void; +} + +interface LayoutProviderProps { + children: ReactNode; + layout: Layout; +} + +const LayoutContext = createContext(undefined); + +export const LayoutProvider = ({ children, layout } : LayoutProviderProps) => { + const [activeLayout, setActiveLayout] = useState(layout); + + return ( + + {children} + + ); +}; + +export const useLayout = () => useContext(LayoutContext); diff --git a/frontend/src/contexts/SelectionContext.jsx b/frontend/src/contexts/SelectionContext.jsx deleted file mode 100644 index 2f480b2..0000000 --- a/frontend/src/contexts/SelectionContext.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { createContext, useContext, useEffect, useState } from "react"; -import { validateApiCallback } from "../utils/validateApiCallback"; - -const SelectionContext = createContext(); - -export const SelectionProvider = ({ children, onDownload, onSelect, onSelectionChange }) => { - const [selectedFiles, setSelectedFiles] = useState([]); - - useEffect(() => { - onSelect?.(selectedFiles); - onSelectionChange?.(selectedFiles); - }, [selectedFiles]); - - const handleDownload = () => { - validateApiCallback(onDownload, "onDownload", selectedFiles); - }; - - return ( - - {children} - - ); -}; - -export const useSelection = () => useContext(SelectionContext); diff --git a/frontend/src/contexts/SelectionContext.tsx b/frontend/src/contexts/SelectionContext.tsx new file mode 100644 index 0000000..8a0b7b0 --- /dev/null +++ b/frontend/src/contexts/SelectionContext.tsx @@ -0,0 +1,39 @@ +import { createContext, ReactNode, useContext, useEffect, useState } from "react"; +import { File } from "../types/File"; +import { OnDownload, OnSelect, OnSelectionChange } from "../types/FileManagerFunctions"; + +interface SelectionContextType { + selectedFiles: File[]; + setSelectedFiles: (files: File[]) => void; + handleDownload: () => void; +} + +interface SelectionProviderProps { + children: ReactNode; + onDownload?: OnDownload; + onSelect?: OnSelect; + onSelectionChange?: OnSelectionChange; +} + +const SelectionContext = createContext(undefined); + +export const SelectionProvider = ({ children, onDownload, onSelect, onSelectionChange } : SelectionProviderProps) => { + const [selectedFiles, setSelectedFiles] = useState([]); + + useEffect(() => { + onSelect?.(selectedFiles); + onSelectionChange?.(selectedFiles); + }, [selectedFiles]); + + const handleDownload = () => { + onDownload?.(selectedFiles); + }; + + return ( + + {children} + + ); +}; + +export const useSelection = () => useContext(SelectionContext); diff --git a/frontend/src/contexts/TranslationProvider.jsx b/frontend/src/contexts/TranslationProvider.tsx similarity index 53% rename from frontend/src/contexts/TranslationProvider.jsx rename to frontend/src/contexts/TranslationProvider.tsx index 4660bc2..4c285e6 100644 --- a/frontend/src/contexts/TranslationProvider.jsx +++ b/frontend/src/contexts/TranslationProvider.tsx @@ -1,9 +1,10 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, ReactNode, useContext, useEffect, useState } from "react"; import i18n, { initI18n } from "../i18n"; +import { Language } from "../types/Language"; -const I18nContext = createContext(() => (key) => key); +const I18nContext = createContext((key : string) => key); -export const TranslationProvider = ({ children, language }) => { +export const TranslationProvider = ({ children, language } : { children : ReactNode, language: Language }) => { const [t, setT] = useState(() => i18n.t.bind(i18n)); useEffect(() => { diff --git a/frontend/src/hooks/useColumnResize.js b/frontend/src/hooks/useColumnResize.ts similarity index 100% rename from frontend/src/hooks/useColumnResize.js rename to frontend/src/hooks/useColumnResize.ts diff --git a/frontend/src/hooks/useDetectOutsideClick.js b/frontend/src/hooks/useDetectOutsideClick.ts similarity index 100% rename from frontend/src/hooks/useDetectOutsideClick.js rename to frontend/src/hooks/useDetectOutsideClick.ts diff --git a/frontend/src/hooks/useFileIcons.jsx b/frontend/src/hooks/useFileIcons.tsx similarity index 100% rename from frontend/src/hooks/useFileIcons.jsx rename to frontend/src/hooks/useFileIcons.tsx diff --git a/frontend/src/hooks/useKeyPress.js b/frontend/src/hooks/useKeyPress.ts similarity index 100% rename from frontend/src/hooks/useKeyPress.js rename to frontend/src/hooks/useKeyPress.ts diff --git a/frontend/src/hooks/useShortcutHandler.js b/frontend/src/hooks/useShortcutHandler.ts similarity index 100% rename from frontend/src/hooks/useShortcutHandler.js rename to frontend/src/hooks/useShortcutHandler.ts diff --git a/frontend/src/hooks/useTriggerAction.js b/frontend/src/hooks/useTriggerAction.ts similarity index 100% rename from frontend/src/hooks/useTriggerAction.js rename to frontend/src/hooks/useTriggerAction.ts diff --git a/frontend/src/i18n.js b/frontend/src/i18n.ts similarity index 100% rename from frontend/src/i18n.js rename to frontend/src/i18n.ts diff --git a/frontend/src/index.js b/frontend/src/index.ts similarity index 100% rename from frontend/src/index.js rename to frontend/src/index.ts diff --git a/frontend/src/main.jsx b/frontend/src/main.tsx similarity index 71% rename from frontend/src/main.jsx rename to frontend/src/main.tsx index 569fdf2..5773721 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.tsx @@ -2,7 +2,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.jsx"; -ReactDOM.createRoot(document.getElementById("root")).render( +ReactDOM.createRoot(document.getElementById("root")!).render( diff --git a/frontend/src/types/File.ts b/frontend/src/types/File.ts new file mode 100644 index 0000000..7787474 --- /dev/null +++ b/frontend/src/types/File.ts @@ -0,0 +1,7 @@ +export interface File { + name: string; + isDirectory: boolean; + path: string; + updatedAt?: string; + size?: number; +} \ No newline at end of file diff --git a/frontend/src/types/FileManagerFunctions.ts b/frontend/src/types/FileManagerFunctions.ts new file mode 100644 index 0000000..384eb8b --- /dev/null +++ b/frontend/src/types/FileManagerFunctions.ts @@ -0,0 +1,37 @@ +import { File } from "./File"; +import { Layout } from "./Layout"; +import { SortConfiguration } from "./SortConfiguration"; + +export type OnCopy = (files: File[]) => void; + +export type OnCreateFolder = (name: string, parentFolder: File) => void; + +export type OnCut = (files: File[]) => void; + +export type OnDelete = (files: File[]) => void; + +export type OnDownload = (files: File[]) => void; + +export type OnError = (error: { type: string; message: string }, file: File) => void; + +export type OnFileOpen = (file: File) => void; + +export type OnFileUploaded = (response: { [key: string]: any }) => void; + +export type OnFileUploading = (file: File, parentFolder: File) => { [key: string]: any }; + +export type OnFolderChange = (path: string) => void; + +export type OnPaste = (files: File[], destinationFolder: File, operationType: "copy" | "move") => void; + +export type OnRename = (file: File, newName: string) => void; + +export type OnLayoutChange = (layout: Layout) => void; + +export type OnSelect = (files: File[]) => void; + +export type OnSelectionChange = (files: File[]) => void; + +export type OnSortChange = (sortConfig: SortConfiguration) => void; + +export type FilePreviewComponent = (file: File) => React.ReactNode; \ No newline at end of file diff --git a/frontend/src/types/FileUploadConfiguration.ts b/frontend/src/types/FileUploadConfiguration.ts new file mode 100644 index 0000000..4ba25cb --- /dev/null +++ b/frontend/src/types/FileUploadConfiguration.ts @@ -0,0 +1,5 @@ +export interface FileUploadConfiguration { + url: string; + method?: "POST" | "PUT"; + headers?: { [key: string]: string }; +} \ No newline at end of file diff --git a/frontend/src/types/Language.ts b/frontend/src/types/Language.ts new file mode 100644 index 0000000..d01a584 --- /dev/null +++ b/frontend/src/types/Language.ts @@ -0,0 +1,21 @@ +export type Language = + | "ar-SA" // Arabic, Saudi Arabia + | "de-DE" // German, Germany + | "en-US" // English, United States + | "es-ES" // Spanish, Spain + | "fa-IR" // Persian, Iran + | "fr-FR" // French, France + | "he-IL" // Hebrew, Israel + | "hi-IN" // Hindi, India + | "it-IT" // Italian, Italy + | "ja-JP" // Japanese, Japan + | "ko-KR" // Korean, South Korea + | "pt-BR" // Portuguese, Brazil + | "pt-PT" // Portuguese, Portugal + | "ru-RU" // Russian, Russia + | "tr-TR" // Turkish, Turkey + | "uk-UA" // Ukrainian, Ukraine + | "ur-UR" // Urdu, Pakistan + | "vi-VN" // Vietnamese, Vietnam + | "zh-CN" // Chinese, Simplified + | "pl-PL"; // Polish, Poland \ No newline at end of file diff --git a/frontend/src/types/Layout.ts b/frontend/src/types/Layout.ts new file mode 100644 index 0000000..a8dce9b --- /dev/null +++ b/frontend/src/types/Layout.ts @@ -0,0 +1 @@ +export type Layout = "grid" | "list"; \ No newline at end of file diff --git a/frontend/src/types/Permissions.ts b/frontend/src/types/Permissions.ts new file mode 100644 index 0000000..2e1a44d --- /dev/null +++ b/frontend/src/types/Permissions.ts @@ -0,0 +1,9 @@ +export interface Permissions { + create?: boolean; + upload?: boolean; + move?: boolean; + copy?: boolean; + rename?: boolean; + download?: boolean; + delete?: boolean; +} \ No newline at end of file diff --git a/frontend/src/types/SortConfiguration.ts b/frontend/src/types/SortConfiguration.ts new file mode 100644 index 0000000..b9d866f --- /dev/null +++ b/frontend/src/types/SortConfiguration.ts @@ -0,0 +1,8 @@ +export type SortKey = "name" | "modified" | "size"; + +export type SortDirection = "asc" | "desc"; + +export interface SortConfiguration { + key: SortKey; + direction: SortDirection; +} \ No newline at end of file diff --git a/frontend/src/utils/createFolderTree.js b/frontend/src/utils/createFolderTree.ts similarity index 100% rename from frontend/src/utils/createFolderTree.js rename to frontend/src/utils/createFolderTree.ts diff --git a/frontend/src/utils/duplicateNameHandler.js b/frontend/src/utils/duplicateNameHandler.ts similarity index 100% rename from frontend/src/utils/duplicateNameHandler.js rename to frontend/src/utils/duplicateNameHandler.ts diff --git a/frontend/src/utils/formatDate.js b/frontend/src/utils/formatDate.js deleted file mode 100644 index 0bcb3cf..0000000 --- a/frontend/src/utils/formatDate.js +++ /dev/null @@ -1,15 +0,0 @@ -export const formatDate = (date) => { - if (!date || isNaN(Date.parse(date))) return ""; - - date = new Date(date); - let hours = date.getHours(); - const minutes = date.getMinutes(); - const ampm = hours >= 12 ? "PM" : "AM"; - hours = hours % 12; - hours = hours ? hours : 12; - const month = date.getMonth() + 1; - const day = date.getDate(); - const year = date.getFullYear(); - - return `${month}/${day}/${year} ${hours}:${minutes < 10 ? "0" + minutes : minutes} ${ampm}`; -}; diff --git a/frontend/src/utils/formatDate.ts b/frontend/src/utils/formatDate.ts new file mode 100644 index 0000000..b36b84d --- /dev/null +++ b/frontend/src/utils/formatDate.ts @@ -0,0 +1,15 @@ +export const formatDate = (date : string) => { + if (!date || isNaN(Date.parse(date))) return ""; + + const parsedDate = new Date(date); + let hours = parsedDate.getHours(); + const minutes = parsedDate.getMinutes(); + const ampm = hours >= 12 ? "PM" : "AM"; + hours = hours % 12; + hours = hours ? hours : 12; + const month = parsedDate.getMonth() + 1; + const day = parsedDate.getDate(); + const year = parsedDate.getFullYear(); + + return `${month}/${day}/${year} ${hours}:${minutes < 10 ? "0" + minutes : minutes} ${ampm}`; +}; diff --git a/frontend/src/utils/getDataSize.js b/frontend/src/utils/getDataSize.ts similarity index 100% rename from frontend/src/utils/getDataSize.js rename to frontend/src/utils/getDataSize.ts diff --git a/frontend/src/utils/getFileExtension.js b/frontend/src/utils/getFileExtension.ts similarity index 100% rename from frontend/src/utils/getFileExtension.js rename to frontend/src/utils/getFileExtension.ts diff --git a/frontend/src/utils/getParentPath.js b/frontend/src/utils/getParentPath.ts similarity index 100% rename from frontend/src/utils/getParentPath.js rename to frontend/src/utils/getParentPath.ts diff --git a/frontend/src/utils/shortcuts.js b/frontend/src/utils/shortcuts.ts similarity index 100% rename from frontend/src/utils/shortcuts.js rename to frontend/src/utils/shortcuts.ts diff --git a/frontend/src/utils/sortFiles.js b/frontend/src/utils/sortFiles.ts similarity index 100% rename from frontend/src/utils/sortFiles.js rename to frontend/src/utils/sortFiles.ts diff --git a/frontend/src/utils/validateApiCallback.js b/frontend/src/utils/validateApiCallback.js deleted file mode 100644 index 7519a67..0000000 --- a/frontend/src/utils/validateApiCallback.js +++ /dev/null @@ -1,13 +0,0 @@ -export const validateApiCallback = (callback, callbackName, ...args) => { - try { - if (typeof callback === "function") { - callback(...args); - } else { - throw new Error( - ` Missing prop: Callback function "${callbackName}" is required.` - ); - } - } catch (error) { - console.error(error.message); - } -}; diff --git a/frontend/src/validators/propValidators.js b/frontend/src/validators/propValidators.ts similarity index 100% rename from frontend/src/validators/propValidators.js rename to frontend/src/validators/propValidators.ts diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..f71ea51 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,50 @@ +{ + "compilerOptions": { + /* Language and Environment */ + "target": "ES2015", // Modern browsers support, good balance + "lib": ["ES2020", "DOM", "DOM.Iterable"], // Include DOM types for React + "jsx": "react-jsx", // Modern JSX transform (React 17+) + + /* Modules */ + "module": "ESNext", // Modern module system + "moduleResolution": "node", // Node-style module resolution + "resolveJsonModule": true, // Allow importing JSON files + "allowSyntheticDefaultImports": true, // Better import compatibility + "esModuleInterop": true, // Better CommonJS/ESM interop + + /* Emit */ + "declaration": true, // Generate .d.ts files (CRITICAL for TS users) + "declarationMap": true, // Generate source maps for .d.ts files + "sourceMap": true, // Generate source maps for debugging + "outDir": "./dist", // Output directory (changed from ./built) + "removeComments": false, // Keep JSDoc comments for IDE hints + "noEmit": false, // Allow emitting files + + /* Interop Constraints */ + "isolatedModules": true, // Ensure each file can be transpiled independently + "allowJs": true, // Allow JS files during migration + "checkJs": false, // Don't type-check JS files (gradual migration) + + /* Type Checking */ + "strict": true, // Enable all strict type checking + "noUnusedLocals": true, // Error on unused local variables + "noUnusedParameters": true, // Error on unused parameters + "noFallthroughCasesInSwitch": true, // Error on switch fallthrough + "noImplicitReturns": true, // Error when not all code paths return + + /* Completeness */ + "skipLibCheck": true, // Skip type checking of declaration files + "forceConsistentCasingInFileNames": true // Enforce consistent casing in imports + }, + "include": [ + "src/**/*" // Include all files in src + ], + "exclude": [ + "node_modules", // Exclude dependencies + "dist", // Exclude build output + "**/*.spec.ts", // Exclude test files + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx" + ] +} \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 084c178..84ba9f3 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -13,7 +13,7 @@ export default defineConfig({ formats: ["es"], }, rollupOptions: { - external: ["react", "react-dom", "react/jsx-runtime"], + external: ["react", "react-dom", "react/tsx-runtime"], output: { globals: { react: "React", From 28e29a7cbc9e47a9daaf9e77664cecdfbadf9e99 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Tue, 11 Nov 2025 19:11:09 +0100 Subject: [PATCH 02/43] Updated ClipboardContext.tsx Now fully implemented this context, forgot adding that in previos commit. --- frontend/src/contexts/ClipboardContext.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend/src/contexts/ClipboardContext.tsx b/frontend/src/contexts/ClipboardContext.tsx index f1c9e27..0b935b8 100644 --- a/frontend/src/contexts/ClipboardContext.tsx +++ b/frontend/src/contexts/ClipboardContext.tsx @@ -4,14 +4,17 @@ import { OnPaste, OnCut, OnCopy } from "../types/FileManagerFunctions"; import { File } from "../types/File"; export interface ClipBoardContextType { - + clipBoard?: ClipBoard; + setClipBoard: (clipboard? : ClipBoard) => void; + handleCutCopy: (isMoving: boolean) => void; + handlePasting: (destinationFolder: File) => void; } export interface ClipBoardProviderProps { children: ReactNode; - onPaste: OnPaste; - onCut: OnCut; - onCopy: OnCopy; + onPaste?: OnPaste; + onCut?: OnCut; + onCopy?: OnCopy; } interface ClipBoard { @@ -34,9 +37,9 @@ export const ClipBoardProvider = ({ children, onPaste, onCut, onCopy } : ClipBoa }); if (isMoving) { - !!onCut && onCut(selectedFiles ?? []); + onCut?.(selectedFiles ?? []); } else { - !!onCopy && onCopy(selectedFiles ?? []); + onCopy?.(selectedFiles ?? []); } }; From a0253a4138a3967be1a999ee33c0704ab98ab0e9 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Tue, 11 Nov 2025 19:16:08 +0100 Subject: [PATCH 03/43] Removed PropTypes package Does not seem to be working with TypeScript, removed for now. --- frontend/package-lock.json | 4 +- frontend/package.json | 1 - frontend/src/FileManager/FileManager.tsx | 62 ----------------------- frontend/src/validators/propValidators.ts | 22 -------- 4 files changed, 3 insertions(+), 86 deletions(-) delete mode 100644 frontend/src/validators/propValidators.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e140948..b669002 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "i18next": "^25.0.0", - "prop-types": "^15.8.1", "react-collapsed": "^4.2.0", "react-icons": "^5.4.0" }, @@ -8472,6 +8471,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9042,6 +9042,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -9171,6 +9172,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, "license": "MIT" }, "node_modules/read-package-up": { diff --git a/frontend/package.json b/frontend/package.json index b86b351..1262fd4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,6 @@ }, "dependencies": { "i18next": "^25.0.0", - "prop-types": "^15.8.1", "react-collapsed": "^4.2.0", "react-icons": "^5.4.0" }, diff --git a/frontend/src/FileManager/FileManager.tsx b/frontend/src/FileManager/FileManager.tsx index d967d0e..3b3b492 100644 --- a/frontend/src/FileManager/FileManager.tsx +++ b/frontend/src/FileManager/FileManager.tsx @@ -11,8 +11,6 @@ import { ClipBoardProvider } from "../contexts/ClipboardContext"; import { LayoutProvider } from "../contexts/LayoutContext"; import { useTriggerAction } from "../hooks/useTriggerAction"; import { useColumnResize } from "../hooks/useColumnResize"; -import PropTypes from "prop-types"; -import { dateStringValidator, urlValidator } from "../validators/propValidators"; import { TranslationProvider } from "../contexts/TranslationProvider"; import { useMemo, useState } from "react"; import { defaultPermissions } from "../constants"; @@ -228,64 +226,4 @@ const FileManager = ({ FileManager.displayName = "FileManager"; -FileManager.propTypes = { - files: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired, - isDirectory: PropTypes.bool.isRequired, - path: PropTypes.string.isRequired, - updatedAt: dateStringValidator, - size: PropTypes.number, - }) - ).isRequired, - fileUploadConfig: PropTypes.shape({ - url: urlValidator, - headers: PropTypes.objectOf(PropTypes.string), - method: PropTypes.oneOf(["POST", "PUT"]), - }), - isLoading: PropTypes.bool, - onCreateFolder: PropTypes.func, - onFileUploading: PropTypes.func, - onFileUploaded: PropTypes.func, - onRename: PropTypes.func, - onDelete: PropTypes.func, - onCut: PropTypes.func, - onCopy: PropTypes.func, - onPaste: PropTypes.func, - onDownload: PropTypes.func, - onLayoutChange: PropTypes.func, - onRefresh: PropTypes.func, - onFileOpen: PropTypes.func, - onFolderChange: PropTypes.func, - onSelect: PropTypes.func, - onSelectionChange: PropTypes.func, - onError: PropTypes.func, - layout: PropTypes.oneOf(["grid", "list"]), - maxFileSize: PropTypes.number, - enableFilePreview: PropTypes.bool, - filePreviewPath: urlValidator, - acceptedFileTypes: PropTypes.string, - height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - initialPath: PropTypes.string, - filePreviewComponent: PropTypes.func, - primaryColor: PropTypes.string, - fontFamily: PropTypes.string, - language: PropTypes.string, - permissions: PropTypes.shape({ - create: PropTypes.bool, - upload: PropTypes.bool, - move: PropTypes.bool, - copy: PropTypes.bool, - rename: PropTypes.bool, - download: PropTypes.bool, - delete: PropTypes.bool, - }), - collapsibleNav: PropTypes.bool, - defaultNavExpanded: PropTypes.bool, - className: PropTypes.string, - style: PropTypes.object, - formatDate: PropTypes.func, -}; - export default FileManager; diff --git a/frontend/src/validators/propValidators.ts b/frontend/src/validators/propValidators.ts deleted file mode 100644 index c92e96e..0000000 --- a/frontend/src/validators/propValidators.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const dateStringValidator = (props, propName, componentName) => { - const value = props[propName]; - - if (value && isNaN(Date.parse(value))) { - return new Error( - `Invalid prop \`${propName}\` supplied to \`${componentName}\`. Expected a valid date string (ISO 8601) but received \`${value}\`.` - ); - } -}; - -export const urlValidator = (props, propName, componentName) => { - const url = props[propName]; - - try { - new URL(url); - return; - } catch (error) { - return new Error( - `Invalid prop \`${propName}\` supplied to \`${componentName}\`. Expected a valid URL but received \`${url}\`.` - ); - } -}; From ccc7e215b17b3db42f352f5ca77a480b01244f36 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Tue, 11 Nov 2025 19:16:37 +0100 Subject: [PATCH 04/43] Updated createFolderTree.ts Added correct type annotations --- frontend/src/utils/createFolderTree.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/createFolderTree.ts b/frontend/src/utils/createFolderTree.ts index 025c65e..9bea30e 100644 --- a/frontend/src/utils/createFolderTree.ts +++ b/frontend/src/utils/createFolderTree.ts @@ -1,4 +1,10 @@ -export const createFolderTree = (copiedFile, files) => { +import { File } from "../types/File"; + +type FileTree = File & { + children: FileTree[]; +}; + +export const createFolderTree = (copiedFile : File, files : File[]) : FileTree => { const childFiles = files.filter( (f) => f.path === copiedFile.path + "/" + copiedFile.name ); From 877629e1d6b51597bbc7e7edb29e3ad821894431 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Tue, 11 Nov 2025 19:24:17 +0100 Subject: [PATCH 05/43] Updated remaining utility functions Added type annotations. --- frontend/src/utils/duplicateNameHandler.ts | 6 ++++-- frontend/src/utils/getDataSize.ts | 3 ++- frontend/src/utils/getFileExtension.ts | 2 +- frontend/src/utils/getParentPath.ts | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/utils/duplicateNameHandler.ts b/frontend/src/utils/duplicateNameHandler.ts index 891a4a3..fe54311 100644 --- a/frontend/src/utils/duplicateNameHandler.ts +++ b/frontend/src/utils/duplicateNameHandler.ts @@ -1,4 +1,6 @@ -export const duplicateNameHandler = (originalFileName, isDirectory, files) => { +import { File } from "../types/File"; + +export const duplicateNameHandler = (originalFileName : string, isDirectory : boolean, files : File[]) => { if (files.find((f) => f.name === originalFileName)) { const fileExtension = isDirectory ? "" : "." + originalFileName.split(".").pop(); const fileName = isDirectory @@ -13,7 +15,7 @@ export const duplicateNameHandler = (originalFileName, isDirectory, files) => { files.forEach((f) => { const fName = f.isDirectory ? f.name : f.name.split(".").slice(0, -1).join("."); if (fileNameRegex.test(fName)) { - const fileNumStr = fName.split(`${fileName} (`).pop().slice(0, -1); + const fileNumStr = fName.split(`${fileName} (`).pop()!.slice(0, -1); const fileNum = parseInt(fileNumStr); if (!isNaN(fileNum) && fileNum > maxFileNum) { maxFileNum = fileNum; diff --git a/frontend/src/utils/getDataSize.ts b/frontend/src/utils/getDataSize.ts index 019a921..a4fe0c3 100644 --- a/frontend/src/utils/getDataSize.ts +++ b/frontend/src/utils/getDataSize.ts @@ -1,4 +1,4 @@ -export const getDataSize = (size, decimalPlaces = 2) => { +export const getDataSize = (size : number, decimalPlaces = 2) => { if (isNaN(size)) return ""; const KiloBytes = size / 1024; @@ -12,4 +12,5 @@ export const getDataSize = (size, decimalPlaces = 2) => { } else if (MegaBytes >= 1024) { return `${GigaBytes.toFixed(decimalPlaces)} GB`; } + return `${size.toFixed(decimalPlaces)} B`; }; diff --git a/frontend/src/utils/getFileExtension.ts b/frontend/src/utils/getFileExtension.ts index 2f51e74..aa064f6 100644 --- a/frontend/src/utils/getFileExtension.ts +++ b/frontend/src/utils/getFileExtension.ts @@ -1,3 +1,3 @@ -export const getFileExtension = (fileName) => { +export const getFileExtension = (fileName : string) => { return fileName.split(".").pop(); }; diff --git a/frontend/src/utils/getParentPath.ts b/frontend/src/utils/getParentPath.ts index 2cd2eff..f031152 100644 --- a/frontend/src/utils/getParentPath.ts +++ b/frontend/src/utils/getParentPath.ts @@ -1,3 +1,3 @@ -export const getParentPath = (path) => { +export const getParentPath = (path : string) => { return path?.split("/").slice(0, -1).join("/"); }; From 1594381307cd0919283f17000b39d9d8f15052cc Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 09:55:01 +0100 Subject: [PATCH 06/43] Updated TranslationProvider.tsx context Defined `TranslationFunction` which allows for passing parameters to `i18n`. --- frontend/src/contexts/TranslationProvider.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/contexts/TranslationProvider.tsx b/frontend/src/contexts/TranslationProvider.tsx index 4c285e6..b490b0e 100644 --- a/frontend/src/contexts/TranslationProvider.tsx +++ b/frontend/src/contexts/TranslationProvider.tsx @@ -2,10 +2,12 @@ import { createContext, ReactNode, useContext, useEffect, useState } from "react import i18n, { initI18n } from "../i18n"; import { Language } from "../types/Language"; -const I18nContext = createContext((key : string) => key); +const I18nContext = createContext((key : string) => key); + +export type TranslationFunction = (key: string, params?: Record) => string; export const TranslationProvider = ({ children, language } : { children : ReactNode, language: Language }) => { - const [t, setT] = useState(() => i18n.t.bind(i18n)); + const [t, setT] = useState(() => i18n.t.bind(i18n)); useEffect(() => { initI18n(language); From 604d434f78d8e489071fc0177b40c77fa75940e5 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 09:55:36 +0100 Subject: [PATCH 07/43] Updated FileNavigationContxt.tsx Fixed import of the `OnFolderChange` type --- frontend/src/contexts/FileNavigationContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/contexts/FileNavigationContext.tsx b/frontend/src/contexts/FileNavigationContext.tsx index 0e64cac..2fd7cc6 100644 --- a/frontend/src/contexts/FileNavigationContext.tsx +++ b/frontend/src/contexts/FileNavigationContext.tsx @@ -1,9 +1,9 @@ import { createContext, ReactNode, useContext, useEffect, useRef, useState } from "react"; import { useFiles } from "./FilesContext"; import sortFiles from "../utils/sortFiles"; -import { OnFolderChange } from "../types/functions/OnFolderChange"; import { File } from "../types/File"; import { SortConfiguration } from "../types/SortConfiguration"; +import { OnFolderChange } from "../types/FileManagerFunctions"; export interface FileNavigationContextType { currentPath?: string; From a47b0e8fd9433c187b5e097c615692d1f54f4282 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 10:04:35 +0100 Subject: [PATCH 08/43] Updated context implementations Added guard clauses to throw when `undefined` on `use[Context]` calls. This allows to continue using hooks like this: ``` const { files } = useFiles(); ``` --- frontend/src/contexts/ClipboardContext.tsx | 10 +++++++++- frontend/src/contexts/FileNavigationContext.tsx | 13 ++++++++++--- frontend/src/contexts/FilesContext.tsx | 12 ++++++++++-- frontend/src/contexts/LayoutContext.tsx | 10 +++++++++- frontend/src/contexts/SelectionContext.tsx | 10 +++++++++- 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/frontend/src/contexts/ClipboardContext.tsx b/frontend/src/contexts/ClipboardContext.tsx index 0b935b8..3fb643a 100644 --- a/frontend/src/contexts/ClipboardContext.tsx +++ b/frontend/src/contexts/ClipboardContext.tsx @@ -64,4 +64,12 @@ export const ClipBoardProvider = ({ children, onPaste, onCut, onCopy } : ClipBoa ); }; -export const useClipBoard = () => useContext(ClipBoardContext); +export const useClipBoard = () => { + const context = useContext(ClipBoardContext); + + if (context === undefined) { + throw new Error("useClipBoard must be used within a ClipBoardProvider"); + } + + return context; +} diff --git a/frontend/src/contexts/FileNavigationContext.tsx b/frontend/src/contexts/FileNavigationContext.tsx index 2fd7cc6..fda3b16 100644 --- a/frontend/src/contexts/FileNavigationContext.tsx +++ b/frontend/src/contexts/FileNavigationContext.tsx @@ -26,8 +26,7 @@ export interface FileNavigationProviderProps { const FileNavigationContext = createContext(undefined); export const FileNavigationProvider = ({ children, initialPath, onFolderChange } : FileNavigationProviderProps) => { - const fileContext = useFiles(); - const files = fileContext?.files; + const { files } = useFiles(); const isMountRef = useRef(false); const [currentPath, setCurrentPath] = useState(""); const [currentFolder, setCurrentFolder] = useState(undefined); @@ -79,4 +78,12 @@ export const FileNavigationProvider = ({ children, initialPath, onFolderChange } ); }; -export const useFileNavigation = () => useContext(FileNavigationContext); +export const useFileNavigation = () => { + const context = useContext(FileNavigationContext); + + if (context === undefined) { + throw new Error("useFileNavigation must be used within a FileNavigationContext"); + } + + return context; +} \ No newline at end of file diff --git a/frontend/src/contexts/FilesContext.tsx b/frontend/src/contexts/FilesContext.tsx index 1e17501..f4ed8a9 100644 --- a/frontend/src/contexts/FilesContext.tsx +++ b/frontend/src/contexts/FilesContext.tsx @@ -1,6 +1,6 @@ import { createContext, ReactNode, useContext, useEffect, useState } from "react"; import { File } from "../types/File"; -import { OnError } from "../types/functions/OnError"; +import { OnError } from "../types/FileManagerFunctions"; interface FilesContextType { files: File[], @@ -37,4 +37,12 @@ export const FilesProvider = ({ children, filesData, onError } : FilesProviderPr ); }; -export const useFiles = () => useContext(FilesContext); +export const useFiles = () => { + const context = useContext(FilesContext); + + if (context === undefined) { + throw new Error("useFiles must be used within a FilesContext"); + } + + return context; +} diff --git a/frontend/src/contexts/LayoutContext.tsx b/frontend/src/contexts/LayoutContext.tsx index e7ced2e..7bc0abd 100644 --- a/frontend/src/contexts/LayoutContext.tsx +++ b/frontend/src/contexts/LayoutContext.tsx @@ -23,4 +23,12 @@ export const LayoutProvider = ({ children, layout } : LayoutProviderProps) => { ); }; -export const useLayout = () => useContext(LayoutContext); +export const useLayout = () => { + const context = useContext(LayoutContext); + + if (context === undefined) { + throw new Error("useLayout must be used within a LayoutContext"); + } + + return context; +} diff --git a/frontend/src/contexts/SelectionContext.tsx b/frontend/src/contexts/SelectionContext.tsx index 8a0b7b0..1af4258 100644 --- a/frontend/src/contexts/SelectionContext.tsx +++ b/frontend/src/contexts/SelectionContext.tsx @@ -36,4 +36,12 @@ export const SelectionProvider = ({ children, onDownload, onSelect, onSelectionC ); }; -export const useSelection = () => useContext(SelectionContext); +export const useSelection = () => { + const context = useContext(SelectionContext); + + if (context === undefined) { + throw new Error("useSelection must be used within a SelectionContext"); + } + + return context; +} From 634c6d7dd6ac02dc4ba8a59b64f60800e0b2d641 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 10:21:55 +0100 Subject: [PATCH 09/43] Updated FileManagerFunctions.ts Added `OnRefresh` type --- frontend/src/types/FileManagerFunctions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/types/FileManagerFunctions.ts b/frontend/src/types/FileManagerFunctions.ts index 384eb8b..b6d98b5 100644 --- a/frontend/src/types/FileManagerFunctions.ts +++ b/frontend/src/types/FileManagerFunctions.ts @@ -34,4 +34,6 @@ export type OnSelectionChange = (files: File[]) => void; export type OnSortChange = (sortConfig: SortConfiguration) => void; -export type FilePreviewComponent = (file: File) => React.ReactNode; \ No newline at end of file +export type FilePreviewComponent = (file: File) => React.ReactNode; + +export type OnRefresh = () => void; \ No newline at end of file From a4ae61012140659dec917a3955e20468cb3aa334 Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 10:27:22 +0100 Subject: [PATCH 10/43] Updated hooks Added type annotations --- frontend/src/hooks/useColumnResize.ts | 10 +++++----- frontend/src/hooks/useDetectOutsideClick.ts | 11 ++++++----- frontend/src/hooks/useFileIcons.tsx | 2 +- frontend/src/hooks/useKeyPress.ts | 10 +++++----- frontend/src/hooks/useShortcutHandler.ts | 14 +++++++++----- frontend/src/hooks/useTriggerAction.ts | 17 +++++++++++++---- 6 files changed, 39 insertions(+), 25 deletions(-) diff --git a/frontend/src/hooks/useColumnResize.ts b/frontend/src/hooks/useColumnResize.ts index 3e9e8b1..1166409 100644 --- a/frontend/src/hooks/useColumnResize.ts +++ b/frontend/src/hooks/useColumnResize.ts @@ -1,9 +1,9 @@ -import { useRef, useState } from "react"; +import { MouseEvent, useRef, useState } from "react"; -export const useColumnResize = (col1Size, col2Size) => { +export const useColumnResize = (col1Size : number, col2Size : number) => { const [colSizes, setColSizes] = useState({ col1: col1Size, col2: col2Size }); const [isDragging, setIsDragging] = useState(false); - const containerRef = useRef(null); + const containerRef = useRef(null); const handleMouseDown = () => { setIsDragging(true); @@ -13,14 +13,14 @@ export const useColumnResize = (col1Size, col2Size) => { setIsDragging(false); }; - const handleMouseMove = (e) => { + const handleMouseMove = (e : MouseEvent) => { if (!isDragging) return; // Prevent text selection during drag e.preventDefault(); // Calculate new sizes based on mouse movement const container = containerRef.current; - const containerRect = container.getBoundingClientRect(); + const containerRect = container!.getBoundingClientRect(); const newCol1Size = ((e.clientX - containerRect.left) / containerRect.width) * 100; // Limiting the resizing to 15% to 60% for better UX diff --git a/frontend/src/hooks/useDetectOutsideClick.ts b/frontend/src/hooks/useDetectOutsideClick.ts index d259538..7934e7c 100644 --- a/frontend/src/hooks/useDetectOutsideClick.ts +++ b/frontend/src/hooks/useDetectOutsideClick.ts @@ -1,11 +1,12 @@ -import { useEffect, useRef, useState } from "react"; +import { RefObject, useEffect, useRef, useState } from "react"; -export const useDetectOutsideClick = (handleOutsideClick = () => {}) => { +export const useDetectOutsideClick = ( + handleOutsideClick : (event : MouseEvent, ref: RefObject) => void) => { const [isClicked, setIsClicked] = useState(false); - const ref = useRef(null); + const ref = useRef(null); - const handleClick = (event) => { - if (!ref.current?.contains(event.target)) { + const handleClick = (event : MouseEvent) => { + if (ref.current && event.target instanceof Node && !ref.current?.contains(event.target)) { setIsClicked(true); handleOutsideClick(event, ref); } else { diff --git a/frontend/src/hooks/useFileIcons.tsx b/frontend/src/hooks/useFileIcons.tsx index 6cba295..42de4c8 100644 --- a/frontend/src/hooks/useFileIcons.tsx +++ b/frontend/src/hooks/useFileIcons.tsx @@ -12,7 +12,7 @@ import { FaRegFileZipper, } from "react-icons/fa6"; -export const useFileIcons = (size) => { +export const useFileIcons = (size?: number) => { const fileIcons = { pdf: , jpg: , diff --git a/frontend/src/hooks/useKeyPress.ts b/frontend/src/hooks/useKeyPress.ts index 0f3f84f..aed7a46 100644 --- a/frontend/src/hooks/useKeyPress.ts +++ b/frontend/src/hooks/useKeyPress.ts @@ -1,16 +1,16 @@ import { useEffect, useMemo, useRef } from "react"; -const normalizeKey = (key) => { +const normalizeKey = (key : string) => { return key.toLowerCase(); }; -export const useKeyPress = (keys, callback, disable = false) => { - const lastKeyPressed = useRef(new Set([])); +export const useKeyPress = (keys : string[], callback: (event : KeyboardEvent) => void, disable = false) => { + const lastKeyPressed = useRef>(new Set([])); const keysSet = useMemo(() => { return new Set(keys.map((key) => normalizeKey(key))); }, [keys]); - const handleKeyDown = (e) => { + const handleKeyDown = (e : KeyboardEvent) => { if (e.repeat) return; // To prevent this function from triggering on key hold e.g. Ctrl hold lastKeyPressed.current.add(normalizeKey(e.key)); @@ -22,7 +22,7 @@ export const useKeyPress = (keys, callback, disable = false) => { } }; - const handleKeyUp = (e) => { + const handleKeyUp = (e : KeyboardEvent) => { lastKeyPressed.current.delete(normalizeKey(e.key)); }; diff --git a/frontend/src/hooks/useShortcutHandler.ts b/frontend/src/hooks/useShortcutHandler.ts index 70923a9..88d47d4 100644 --- a/frontend/src/hooks/useShortcutHandler.ts +++ b/frontend/src/hooks/useShortcutHandler.ts @@ -4,9 +4,11 @@ import { useClipBoard } from "../contexts/ClipboardContext"; import { useFileNavigation } from "../contexts/FileNavigationContext"; import { useSelection } from "../contexts/SelectionContext"; import { useLayout } from "../contexts/LayoutContext"; -import { validateApiCallback } from "../utils/validateApiCallback"; +import { TriggerAction } from "./useTriggerAction"; +import { OnRefresh } from "../types/FileManagerFunctions"; +import { Permissions } from "../types/Permissions"; -export const useShortcutHandler = (triggerAction, onRefresh, permissions) => { +export const useShortcutHandler = (triggerAction : TriggerAction, onRefresh : OnRefresh, permissions : Permissions) => { const { setClipBoard, handleCutCopy, handlePasting } = useClipBoard(); const { currentFolder, currentPathFiles } = useFileNavigation(); const { selectedFiles, setSelectedFiles, handleDownload } = useSelection(); @@ -29,7 +31,9 @@ export const useShortcutHandler = (triggerAction, onRefresh, permissions) => { }; const triggerPasteItems = () => { - handlePasting(currentFolder); + if (currentFolder) { + handlePasting(currentFolder); + } }; const triggerRename = () => { @@ -54,7 +58,7 @@ export const useShortcutHandler = (triggerAction, onRefresh, permissions) => { const triggerSelectLast = () => { if (currentPathFiles.length > 0) { - setSelectedFiles([currentPathFiles.at(-1)]); + setSelectedFiles([currentPathFiles.at(-1)!]); } }; @@ -67,7 +71,7 @@ export const useShortcutHandler = (triggerAction, onRefresh, permissions) => { }; const triggerRefresh = () => { - validateApiCallback(onRefresh, "onRefresh"); + onRefresh?.(); setClipBoard(null); }; diff --git a/frontend/src/hooks/useTriggerAction.ts b/frontend/src/hooks/useTriggerAction.ts index 9a7edd7..cb3b7aa 100644 --- a/frontend/src/hooks/useTriggerAction.ts +++ b/frontend/src/hooks/useTriggerAction.ts @@ -1,17 +1,26 @@ import { useState } from "react"; -export const useTriggerAction = () => { +export type TriggerActionType = "createFolder" | "uploadFile" | "rename" | "delete" + +export interface TriggerAction { + isActive: boolean; + actionType?: TriggerActionType; + show: (type: TriggerActionType) => void; + close: () => void; +} + +export const useTriggerAction = () : TriggerAction => { const [isActive, setIsActive] = useState(false); - const [actionType, setActionType] = useState(null); + const [actionType, setActionType] = useState(undefined); - const show = (type) => { + const show = (type : TriggerActionType) => { setIsActive(true); setActionType(type); }; const close = () => { setIsActive(false); - setActionType(null); + setActionType(undefined); }; return { From b12125463e45a4ec92633bdb9ed57b1858777b0a Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 10:28:51 +0100 Subject: [PATCH 11/43] Exported TriggerAction to its own file --- frontend/src/contexts/ClipboardContext.tsx | 8 ++++---- frontend/src/contexts/SelectionContext.tsx | 4 ++-- frontend/src/types/TriggerAction.ts | 8 ++++++++ 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 frontend/src/types/TriggerAction.ts diff --git a/frontend/src/contexts/ClipboardContext.tsx b/frontend/src/contexts/ClipboardContext.tsx index 3fb643a..551b664 100644 --- a/frontend/src/contexts/ClipboardContext.tsx +++ b/frontend/src/contexts/ClipboardContext.tsx @@ -4,8 +4,8 @@ import { OnPaste, OnCut, OnCopy } from "../types/FileManagerFunctions"; import { File } from "../types/File"; export interface ClipBoardContextType { - clipBoard?: ClipBoard; - setClipBoard: (clipboard? : ClipBoard) => void; + clipBoard: ClipBoard | null; + setClipBoard: (clipboard : ClipBoard | null) => void; handleCutCopy: (isMoving: boolean) => void; handlePasting: (destinationFolder: File) => void; } @@ -25,7 +25,7 @@ interface ClipBoard { const ClipBoardContext = createContext(undefined); export const ClipBoardProvider = ({ children, onPaste, onCut, onCopy } : ClipBoardProviderProps) => { - const [clipBoard, setClipBoard] = useState(undefined); + const [clipBoard, setClipBoard] = useState(null); const selectionContext = useSelection(); const selectedFiles = selectionContext?.selectedFiles; const setSelectedFiles = selectionContext?.setSelectedFiles; @@ -52,7 +52,7 @@ export const ClipBoardProvider = ({ children, onPaste, onCut, onCopy } : ClipBoa onPaste?.(copiedFiles ?? [], destinationFolder, operationType); - clipBoard?.isMoving && setClipBoard(undefined); + clipBoard?.isMoving && setClipBoard(null); setSelectedFiles?.([]); }; diff --git a/frontend/src/contexts/SelectionContext.tsx b/frontend/src/contexts/SelectionContext.tsx index 1af4258..611da27 100644 --- a/frontend/src/contexts/SelectionContext.tsx +++ b/frontend/src/contexts/SelectionContext.tsx @@ -1,10 +1,10 @@ -import { createContext, ReactNode, useContext, useEffect, useState } from "react"; +import { createContext, Dispatch, ReactNode, SetStateAction, useContext, useEffect, useState } from "react"; import { File } from "../types/File"; import { OnDownload, OnSelect, OnSelectionChange } from "../types/FileManagerFunctions"; interface SelectionContextType { selectedFiles: File[]; - setSelectedFiles: (files: File[]) => void; + setSelectedFiles: Dispatch>; handleDownload: () => void; } diff --git a/frontend/src/types/TriggerAction.ts b/frontend/src/types/TriggerAction.ts new file mode 100644 index 0000000..9a0d51e --- /dev/null +++ b/frontend/src/types/TriggerAction.ts @@ -0,0 +1,8 @@ +export type TriggerActionType = "createFolder" | "uploadFile" | "rename" | "delete" + +export interface TriggerAction { + isActive: boolean; + actionType?: TriggerActionType; + show: (type: TriggerActionType) => void; + close: () => void; +} \ No newline at end of file From c997573a22404f3f22734ca10669bb0967e6b5ed Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 10:29:17 +0100 Subject: [PATCH 12/43] Updated tsconfig.json Configured lib to be `ESNect` --- frontend/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index f71ea51..88490ee 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { /* Language and Environment */ "target": "ES2015", // Modern browsers support, good balance - "lib": ["ES2020", "DOM", "DOM.Iterable"], // Include DOM types for React + "lib": ["ESNext", "DOM", "DOM.Iterable"], // Include DOM types for React "jsx": "react-jsx", // Modern JSX transform (React 17+) /* Modules */ From d1ba96e0a19c19809909e428c33cd124011c39cb Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 10:31:24 +0100 Subject: [PATCH 13/43] Addendum to useTriggerAction.ts Now uses the `TriggerAction` and `TriggerActionType` defined in a seperate file --- frontend/src/hooks/useTriggerAction.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/frontend/src/hooks/useTriggerAction.ts b/frontend/src/hooks/useTriggerAction.ts index cb3b7aa..c9ea66f 100644 --- a/frontend/src/hooks/useTriggerAction.ts +++ b/frontend/src/hooks/useTriggerAction.ts @@ -1,13 +1,5 @@ import { useState } from "react"; - -export type TriggerActionType = "createFolder" | "uploadFile" | "rename" | "delete" - -export interface TriggerAction { - isActive: boolean; - actionType?: TriggerActionType; - show: (type: TriggerActionType) => void; - close: () => void; -} +import { TriggerAction, TriggerActionType } from "../types/TriggerAction"; export const useTriggerAction = () : TriggerAction => { const [isActive, setIsActive] = useState(false); From a93f90737f70d2e1b456d77e989ede8ce6eac80f Mon Sep 17 00:00:00 2001 From: Maximilian Hollnbuchner Date: Wed, 12 Nov 2025 10:32:38 +0100 Subject: [PATCH 14/43] Updated components Added property types for each component. --- frontend/src/components/Button/Button.tsx | 11 ++++- frontend/src/components/Checkbox/Checkbox.tsx | 13 +++++- frontend/src/components/Collapse/Collapse.tsx | 9 +++- .../components/ContextMenu/ContextMenu.tsx | 46 ++++++++++++++----- .../src/components/ContextMenu/SubMenu.tsx | 18 +++++++- .../components/ErrorTooltip/ErrorTooltip.tsx | 11 ++++- frontend/src/components/Modal/Modal.tsx | 22 ++++++--- .../src/components/NameInput/NameInput.tsx | 14 +++++- frontend/src/components/Progress/Progress.tsx | 8 +++- 9 files changed, 125 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/Button/Button.tsx b/frontend/src/components/Button/Button.tsx index d3c6ec6..e3bb3f3 100644 --- a/frontend/src/components/Button/Button.tsx +++ b/frontend/src/components/Button/Button.tsx @@ -1,6 +1,15 @@ +import { KeyboardEvent, MouseEvent, ReactNode } from "react"; import "./Button.scss"; -const Button = ({ onClick, onKeyDown, type = "primary", padding = "0.4rem 0.8rem", children }) => { +export interface ButtonProps { + onClick?: (event : MouseEvent) => void; + onKeyDown?: (event : KeyboardEvent) => void; + type?: "primary" | "secondary" | "danger"; + padding?: string; + children: ReactNode; +} + +const Button = ({ onClick, onKeyDown, type = "primary", padding = "0.4rem 0.8rem", children } : ButtonProps) => { return (