Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 14 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,29 @@ npm i @cubone/react-file-manager
Here’s a basic example of how to use the File Manager Component in your React application:

```jsx
import { useState } from 'react';
import { FileManager } from '@cubone/react-file-manager';
import '@cubone/react-file-manager/dist/style.css';
import { useState } from "react";
import { FileManager } from "@cubone/react-file-manager";
import "@cubone/react-file-manager/dist/style.css";

function App() {
const [files, setFiles] = useState([
{
name: 'Documents',
name: "Documents",
isDirectory: true, // Folder
path: '/Documents', // Located in Root directory
updatedAt: '2024-09-09T10:30:00Z', // Last updated time
path: "/Documents", // Located in Root directory
updatedAt: "2024-09-09T10:30:00Z", // Last updated time
},
{
name: 'Pictures',
name: "Pictures",
isDirectory: true,
path: '/Pictures', // Located in Root directory as well
updatedAt: '2024-09-09T11:00:00Z',
path: "/Pictures", // Located in Root directory as well
updatedAt: "2024-09-09T11:00:00Z",
},
{
name: 'Pic.png',
name: "Pic.png",
isDirectory: false, // File
path: '/Pictures/Pic.png', // Located inside the "Pictures" folder
updatedAt: '2024-09-08T16:45:00Z',
path: "/Pictures/Pic.png", // Located inside the "Pictures" folder
updatedAt: "2024-09-08T16:45:00Z",
size: 2048, // File size in bytes (example: 2 KB)
},
]);
Expand Down Expand Up @@ -125,6 +125,7 @@ type File = {
| `onDownload` | (files: Array<[File](#-file-structure)>) => void | A callback function triggered when one or more files or folders are downloaded. |
| `onError` | (error: { type: string, message: string }, file: [File](#-file-structure)) => void | A callback function triggered whenever there is an error in the file manager. Where error is an object containing `type` ("upload", etc.) and a descriptive error `message`. |
| `onFileOpen` | (file: [File](#-file-structure)) => void | A callback function triggered when a file or folder is opened. |
| `onFolderChange` | (path: string) => void | A callback function triggered when the active folder changes. Receives the full path of the current folder as a string parameter. Useful for tracking the active folder path. |
| `onFileUploaded` | (response: { [key: string]: any }) => void | A callback function triggered after a file is successfully uploaded. Provides JSON `response` holding uploaded file details, use it to extract the uploaded file details and add it to the `files` state e.g. `setFiles((prev) => [...prev, JSON.parse(response)]);` |
| `onFileUploading` | (file: [File](#-file-structure), parentFolder: [File](#-file-structure)) => { [key: string]: any } | A callback function triggered during the file upload process. You can also return an object with key-value pairs that will be appended to the `FormData` along with the file being uploaded. The object can contain any number of valid properties. |
| `onLayoutChange` | (layout: "list" \| "grid") => void | A callback function triggered when the layout of the file manager is changed. |
Expand Down Expand Up @@ -189,12 +190,7 @@ as an argument and must return a valid React node, JSX element, or HTML.

```jsx
const CustomImagePreviewer = ({ file }) => {
return (
<img
src={`${file.path}`}
alt={file.name}
/>
);
return <img src={`${file.path}`} alt={file.name} />;
};

<FileManager
Expand Down
1 change: 1 addition & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type File = {
| `onDownload` | (files: Array<[File](#-file-structure)>) => void | A callback function triggered when one or more files or folders are downloaded. |
| `onError` | (error: { type: string, message: string }, file: [File](#-file-structure)) => void | A callback function triggered whenever there is an error in the file manager. Where error is an object containing `type` ("upload", etc.) and a descriptive error `message`. |
| `onFileOpen` | (file: [File](#-file-structure)) => void | A callback function triggered when a file or folder is opened. |
| `onFolderChange` | (path: string) => void | A callback function triggered when the active folder changes. Receives the full path of the current folder as a string parameter. Useful for tracking the active folder path. |
| `onFileUploaded` | (response: { [key: string]: any }) => void | A callback function triggered after a file is successfully uploaded. Provides JSON `response` holding uploaded file details, use it to extract the uploaded file details and add it to the `files` state e.g. `setFiles((prev) => [...prev, JSON.parse(response)]);` |
| `onFileUploading` | (file: [File](#-file-structure), parentFolder: [File](#-file-structure)) => { [key: string]: any } | A callback function triggered during the file upload process. You can also return an object with key-value pairs that will be appended to the `FormData` along with the file being uploaded. The object can contain any number of valid properties. |
| `onLayoutChange` | (layout: "list" \| "grid") => void | A callback function triggered when the layout of the file manager is changed. |
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/FileManager/BreadCrumb/BreadCrumb.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpe
const [hiddenFoldersWidth, setHiddenFoldersWidth] = useState([]);
const [showHiddenFolders, setShowHiddenFolders] = useState(false);

const { currentPath, setCurrentPath } = useFileNavigation();
const { currentPath, setCurrentPath, onFolderChange } = useFileNavigation();
const breadCrumbRef = useRef(null);
const foldersRef = useRef([]);
const moreBtnRef = useRef(null);
Expand All @@ -39,6 +39,7 @@ const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpe

const switchPath = (path) => {
setCurrentPath(path);
onFolderChange?.(path);
};

const getBreadCrumbWidth = () => {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/FileManager/FileList/FileItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const FileItem = ({
const { activeLayout } = useLayout();
const iconSize = activeLayout === "grid" ? 48 : 20;
const fileIcons = useFileIcons(iconSize);
const { setCurrentPath, currentPathFiles } = useFileNavigation();
const { setCurrentPath, currentPathFiles, onFolderChange } = useFileNavigation();
const { setSelectedFiles } = useSelection();
const { clipBoard, handleCutCopy, setClipBoard, handlePasting } = useClipBoard();
const dragIconRef = useRef(null);
Expand All @@ -50,6 +50,7 @@ const FileItem = ({
onFileOpen(file);
if (file.isDirectory) {
setCurrentPath(file.path);
onFolderChange?.(file.path);
setSelectedFiles([]);
} else {
enableFilePreview && triggerAction.show("previewFile");
Expand Down
14 changes: 5 additions & 9 deletions frontend/src/FileManager/FileList/FileList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ const FileList = ({
selectedFileIndexes,
clickPosition,
isSelectionCtx,
} = useFileList(onRefresh, enableFilePreview, triggerAction, permissions);
} = useFileList(onRefresh, enableFilePreview, triggerAction, permissions, onFileOpen);

const contextMenuRef = useDetectOutsideClick(() => setVisible(false));

const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
let direction = "asc";
if (sortConfig.key === key && sortConfig.direction === "asc") {
direction = "desc";
}
setSortConfig({ key, direction });
};
Expand All @@ -54,11 +54,7 @@ const FileList = ({
onClick={unselectFiles}
>
{activeLayout === "list" && (
<FilesHeader
unselectFiles={unselectFiles}
onSort={handleSort}
sortConfig={sortConfig}
/>
<FilesHeader unselectFiles={unselectFiles} onSort={handleSort} sortConfig={sortConfig} />
)}

{currentPathFiles?.length > 0 ? (
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/FileManager/FileList/useFileList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { duplicateNameHandler } from "../../utils/duplicateNameHandler";
import { validateApiCallback } from "../../utils/validateApiCallback";
import { useTranslation } from "../../contexts/TranslationProvider";

const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions) => {
const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, onFileOpen) => {
const [selectedFileIndexes, setSelectedFileIndexes] = useState([]);
const [visible, setVisible] = useState(false);
const [isSelectionCtx, setIsSelectionCtx] = useState(false);
Expand All @@ -22,15 +22,17 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions) =

const { clipBoard, setClipBoard, handleCutCopy, handlePasting } = useClipBoard();
const { selectedFiles, setSelectedFiles, handleDownload } = useSelection();
const { currentPath, setCurrentPath, currentPathFiles, setCurrentPathFiles } =
const { currentPath, setCurrentPath, currentPathFiles, setCurrentPathFiles, onFolderChange } =
useFileNavigation();
const { activeLayout, setActiveLayout } = useLayout();
const t = useTranslation();

// Context Menu
const handleFileOpen = () => {
onFileOpen(lastSelectedFile);
if (lastSelectedFile.isDirectory) {
setCurrentPath(lastSelectedFile.path);
onFolderChange?.(lastSelectedFile.path);
setSelectedFileIndexes([]);
setSelectedFiles([]);
} else {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/FileManager/FileManager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const FileManager = ({
onLayoutChange = () => {},
onRefresh,
onFileOpen = () => {},
onFolderChange = () => {},
onSelect,
onError = () => {},
layout = "grid",
Expand Down Expand Up @@ -73,7 +74,7 @@ const FileManager = ({
<Loader loading={isLoading} />
<TranslationProvider language={language}>
<FilesProvider filesData={files} onError={onError}>
<FileNavigationProvider initialPath={initialPath}>
<FileNavigationProvider initialPath={initialPath} onFolderChange={onFolderChange}>
<SelectionProvider onDownload={onDownload} onSelect={onSelect}>
<ClipBoardProvider onPaste={onPaste} onCut={onCut} onCopy={onCopy}>
<LayoutProvider layout={layout}>
Expand Down Expand Up @@ -176,6 +177,7 @@ FileManager.propTypes = {
onLayoutChange: PropTypes.func,
onRefresh: PropTypes.func,
onFileOpen: PropTypes.func,
onFolderChange: PropTypes.func,
onSelect: PropTypes.func,
onError: PropTypes.func,
layout: PropTypes.oneOf(["grid", "list"]),
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/FileManager/NavigationPane/FolderTree.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { useFileNavigation } from "../../contexts/FileNavigationContext";
const FolderTree = ({ folder, onFileOpen }) => {
const [isOpen, setIsOpen] = useState(false);
const [isActive, setIsActive] = useState(false);
const { currentPath, setCurrentPath } = useFileNavigation();
const { currentPath, setCurrentPath, onFolderChange } = useFileNavigation();

const handleFolderSwitch = () => {
setIsActive(true);
onFileOpen(folder);
setCurrentPath(folder.path);
onFolderChange?.(folder.path);
};

const handleCollapseChange = (e) => {
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/contexts/FileNavigationContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import sortFiles from "../utils/sortFiles";

const FileNavigationContext = createContext();

export const FileNavigationProvider = ({ children, initialPath }) => {
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' });
const [sortConfig, setSortConfig] = useState({ key: "name", direction: "asc" });

useEffect(() => {
if (Array.isArray(files) && files.length > 0) {
Expand All @@ -27,7 +27,9 @@ export const FileNavigationProvider = ({ children, initialPath }) => {

useEffect(() => {
if (!isMountRef.current && Array.isArray(files) && files.length > 0) {
setCurrentPath(files.some((file) => file.path === initialPath) ? initialPath : "");
const activePath = files.some((file) => file.path === initialPath) ? initialPath : "";
setCurrentPath(activePath);
onFolderChange?.(activePath);
isMountRef.current = true;
}
}, [initialPath, files]);
Expand All @@ -43,6 +45,7 @@ export const FileNavigationProvider = ({ children, initialPath }) => {
setCurrentPathFiles,
sortConfig,
setSortConfig,
onFolderChange,
}}
>
{children}
Expand Down