Skip to content
Open
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
1,926 changes: 939 additions & 987 deletions frontend/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"postcss-prefix-selector": "^2.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sass": "^1.77.6",
"semantic-release": "^24.1.0",
"vite": "^6.3.4"
Expand Down
27 changes: 27 additions & 0 deletions frontend/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import prefixSelector from 'postcss-prefix-selector';

export default {
plugins: [
prefixSelector({
prefix: '.file-explorer',
transform(prefix, selector, prefixedSelector) {
// Bỏ qua các selector toàn cục
if (
selector.startsWith('html') ||
selector.startsWith('body') ||
selector.startsWith(':root') ||
selector.startsWith('*')
) {
return selector;
}

// Bỏ qua selector đã có .file-explorer
if (selector.includes('.file-explorer')) {
return selector;
}

return prefixedSelector;
},
}),
],
};
13 changes: 12 additions & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ function App() {
};

// Refresh Files
const handleRefresh = () => {
const handleRefresh = (file) => {
console.log(`Refresh folder: ${file?.name}`);
getFiles();
};
//
Expand Down Expand Up @@ -129,6 +130,14 @@ function App() {
console.log("Selected Files", files);
};

const handleClose = () => {
console.log("Closed");
};

const handlePick = (files) => {
console.log("Picked Files", files);
};

return (
<div className="app">
<div className="file-manager-container">
Expand All @@ -147,8 +156,10 @@ function App() {
onDelete={handleDelete}
onLayoutChange={handleLayoutChange}
onRefresh={handleRefresh}
onClose={handleClose}
onFileOpen={handleFileOpen}
onSelect={handleSelect}
onPick={handlePick}
onError={handleError}
layout="grid"
enableFilePreview
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/FileManager/Actions/Actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Actions = ({
onFileUploaded,
onDelete,
onRefresh,
onPick,
maxFileSize,
filePreviewPath,
filePreviewComponent,
Expand All @@ -25,7 +26,7 @@ const Actions = ({
const t = useTranslation();

// Triggers all the keyboard shortcuts based actions
useShortcutHandler(triggerAction, onRefresh, permissions);
useShortcutHandler(triggerAction, onRefresh, onPick, permissions);

const actionTypes = {
uploadFile: {
Expand Down
20 changes: 16 additions & 4 deletions frontend/src/FileManager/FileList/FileItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useSelection } from "../../contexts/SelectionContext";
import { useClipBoard } from "../../contexts/ClipboardContext";
import { useLayout } from "../../contexts/LayoutContext";
import Checkbox from "../../components/Checkbox/Checkbox";
import { validateApiCallback } from "../../utils/validateApiCallback";

const dragIconSize = 50;

Expand All @@ -20,6 +21,7 @@ const FileItem = ({
onRename,
enableFilePreview,
onFileOpen,
onPick,
filesViewRef,
selectedFileIndexes,
triggerAction,
Expand All @@ -37,7 +39,7 @@ const FileItem = ({
const iconSize = activeLayout === "grid" ? 48 : 20;
const fileIcons = useFileIcons(iconSize);
const { setCurrentPath, currentPathFiles } = useFileNavigation();
const { setSelectedFiles } = useSelection();
const { selectedFiles, setSelectedFiles } = useSelection();
const { clipBoard, handleCutCopy, setClipBoard, handlePasting } = useClipBoard();
const dragIconRef = useRef(null);
const dragIcons = useFileIcons(dragIconSize);
Expand Down Expand Up @@ -94,7 +96,12 @@ const FileItem = ({

const currentTime = new Date().getTime();
if (currentTime - lastClickTime < 300) {
handleFileAccess();
if (onPick && !file.isDirectory) {
validateApiCallback(onPick, "onPick", [file]);
setSelectedFiles([]);
} else {
handleFileAccess();
}
return;
}
setLastClickTime(currentTime);
Expand All @@ -103,8 +110,13 @@ const FileItem = ({
const handleOnKeyDown = (e) => {
if (e.key === "Enter") {
e.stopPropagation();
setSelectedFiles([file]);
handleFileAccess();
if (onPick && selectedFiles.every(item => item.isDirectory === false)) {
validateApiCallback(onPick, "onPick", selectedFiles);
setSelectedFiles([]);
} else {
setSelectedFiles([file]);
handleFileAccess();
}
}
};

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/FileManager/FileList/FileList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const FileList = ({
onRename,
onFileOpen,
onRefresh,
onPick,
enableFilePreview,
triggerAction,
permissions,
Expand Down Expand Up @@ -57,6 +58,7 @@ const FileList = ({
onCreateFolder={onCreateFolder}
onRename={onRename}
onFileOpen={onFileOpen}
onPick={onPick}
enableFilePreview={enableFilePreview}
triggerAction={triggerAction}
filesViewRef={filesViewRef}
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/FileManager/FileList/useFileList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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, currentFolder } =
useFileNavigation();
const { activeLayout, setActiveLayout } = useLayout();
const t = useTranslation();
Expand Down Expand Up @@ -66,7 +66,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions) =

const handleRefresh = () => {
setVisible(false);
validateApiCallback(onRefresh, "onRefresh");
validateApiCallback(onRefresh, "onRefresh", currentFolder);
setClipBoard(null);
};

Expand Down Expand Up @@ -171,8 +171,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions) =
title: t("rename"),
icon: <BiRename size={19} />,
onClick: handleRenaming,
hidden: selectedFiles.length > 1,
hidden: !permissions.rename,
hidden: selectedFiles.length > 1 || !permissions.rename,
},
{
title: t("download"),
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/FileManager/FileManager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ const FileManager = ({
onDelete = () => null,
onLayoutChange = () => {},
onRefresh,
onClose,
onFileOpen = () => {},
onSelect,
onPick,
onError = () => {},
layout = "grid",
enableFilePreview = true,
Expand Down Expand Up @@ -80,6 +82,8 @@ const FileManager = ({
<Toolbar
onLayoutChange={onLayoutChange}
onRefresh={onRefresh}
onClose={onClose}
onPick={onPick}
triggerAction={triggerAction}
permissions={permissions}
/>
Expand Down Expand Up @@ -116,6 +120,7 @@ const FileManager = ({
onRename={onRename}
onFileOpen={onFileOpen}
onRefresh={onRefresh}
onPick={onPick}
enableFilePreview={enableFilePreview}
triggerAction={triggerAction}
permissions={permissions}
Expand All @@ -129,6 +134,7 @@ const FileManager = ({
onFileUploaded={onFileUploaded}
onDelete={onDelete}
onRefresh={onRefresh}
onPick={onPick}
maxFileSize={maxFileSize}
filePreviewPath={filePreviewPath}
filePreviewComponent={filePreviewComponent}
Expand Down Expand Up @@ -175,8 +181,10 @@ FileManager.propTypes = {
onDownload: PropTypes.func,
onLayoutChange: PropTypes.func,
onRefresh: PropTypes.func,
onClose: PropTypes.func,
onFileOpen: PropTypes.func,
onSelect: PropTypes.func,
onPick: PropTypes.func,
onError: PropTypes.func,
layout: PropTypes.oneOf(["grid", "list"]),
maxFileSize: PropTypes.number,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/FileManager/FileManager.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ svg {
width: 100%;
font-family: var(--file-manager-font-family);

* {
box-sizing: unset;
}

button {
font-family: var(--file-manager-font-family);
}
Expand Down
40 changes: 31 additions & 9 deletions frontend/src/FileManager/Toolbar/Toolbar.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useState } from "react";
import { BsCopy, BsFolderPlus, BsGridFill, BsScissors } from "react-icons/bs";
import { FiRefreshCw } from "react-icons/fi";
import { FaCheck } from "react-icons/fa6";
import { AiOutlineClose } from "react-icons/ai";
import {
MdClear,
MdOutlineDelete,
Expand All @@ -18,7 +20,7 @@ import { validateApiCallback } from "../../utils/validateApiCallback";
import { useTranslation } from "../../contexts/TranslationProvider";
import "./Toolbar.scss";

const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions }) => {
const Toolbar = ({ onLayoutChange, onRefresh, onClose, onPick, triggerAction, permissions }) => {
const [showToggleViewMenu, setShowToggleViewMenu] = useState(false);
const { currentFolder } = useFileNavigation();
const { selectedFiles, setSelectedFiles, handleDownload } = useSelection();
Expand Down Expand Up @@ -58,11 +60,19 @@ const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions }) => {
icon: <FiRefreshCw size={16} />,
title: t("refresh"),
onClick: () => {
validateApiCallback(onRefresh, "onRefresh");
validateApiCallback(onRefresh, "onRefresh", currentFolder);
setClipBoard(null);
},
},
];
if (onClose) toolbarRightItems.push({
icon: <AiOutlineClose size={16} />,
title: t("close"),
onClick: () => {
validateApiCallback(onClose, "onClose");
setClipBoard(null);
},
})

function handleFilePasting() {
handlePasting(currentFolder);
Expand All @@ -73,27 +83,39 @@ const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions }) => {
setSelectedFiles([]);
};

const handlePickItems = () => {
if (!onPick || !selectedFiles.every(item => item.isDirectory === false)) return;
validateApiCallback(onPick, "onPick", selectedFiles);
setSelectedFiles([]);
};

// Selected File/Folder Actions
if (selectedFiles.length > 0) {
return (
<div className="toolbar file-selected">
<div className="file-action-container">
<div>
{onPick && selectedFiles.every(item => item.isDirectory === false) && (
<button className="item-action file-action pick-action" onClick={handlePickItems}>
<FaCheck size={18} />
<span>{t("pick")}</span>
</button>
)}
{permissions.move && (
<button className="item-action file-action" onClick={() => handleCutCopy(true)}>
<button className="item-action file-action move-action" onClick={() => handleCutCopy(true)}>
<BsScissors size={18} />
<span>{t("cut")}</span>
</button>
)}
{permissions.copy && (
<button className="item-action file-action" onClick={() => handleCutCopy(false)}>
<button className="item-action file-action copy-action" onClick={() => handleCutCopy(false)}>
<BsCopy strokeWidth={0.1} size={17} />
<span>{t("copy")}</span>
</button>
)}
{clipBoard?.files?.length > 0 && (
<button
className="item-action file-action"
className="item-action file-action paste-action"
onClick={handleFilePasting}
// disabled={!clipBoard}
>
Expand All @@ -103,22 +125,22 @@ const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions }) => {
)}
{selectedFiles.length === 1 && permissions.rename && (
<button
className="item-action file-action"
className="item-action file-action rename-action"
onClick={() => triggerAction.show("rename")}
>
<BiRename size={19} />
<span>{t("rename")}</span>
</button>
)}
{permissions.download && (
<button className="item-action file-action" onClick={handleDownloadItems}>
<button className="item-action file-action download-action" onClick={handleDownloadItems}>
<MdOutlineFileDownload size={19} />
<span>{t("download")}</span>
</button>
)}
{permissions.delete && (
<button
className="item-action file-action"
className="item-action file-action delete-action"
onClick={() => triggerAction.show("delete")}
>
<MdOutlineDelete size={19} />
Expand All @@ -127,7 +149,7 @@ const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions }) => {
)}
</div>
<button
className="item-action file-action"
className="item-action file-action clear-action"
title={t("clearSelection")}
onClick={() => setSelectedFiles([])}
>
Expand Down
Loading