feat folder download as zip
This commit is contained in:
parent
365b188fa2
commit
c6d43340ff
4 changed files with 36 additions and 2 deletions
|
|
@ -12,7 +12,7 @@
|
|||
*/
|
||||
|
||||
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
||||
import { FaFolder, FaFolderOpen, FaPlus, FaPen, FaTrash, FaChevronRight, FaGlobe, FaSyncAlt } from 'react-icons/fa';
|
||||
import { FaFolder, FaFolderOpen, FaPlus, FaPen, FaTrash, FaChevronRight, FaGlobe, FaSyncAlt, FaDownload } from 'react-icons/fa';
|
||||
import styles from './FolderTree.module.css';
|
||||
|
||||
/* ── Public types ──────────────────────────────────────────────────────── */
|
||||
|
|
@ -61,6 +61,7 @@ export interface FolderTreeProps {
|
|||
onDeleteFile?: (fileId: string) => Promise<void>;
|
||||
onDeleteFiles?: (fileIds: string[]) => Promise<void>;
|
||||
onDeleteFolders?: (folderIds: string[]) => Promise<void>;
|
||||
onDownloadFolder?: (folderId: string, folderName: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/* ── Helpers ───────────────────────────────────────────────────────────── */
|
||||
|
|
@ -285,12 +286,14 @@ interface TreeNodeProps {
|
|||
onMoveFolders?: (folderIds: string[], targetParentId: string | null) => Promise<void>;
|
||||
onMoveFile?: (fileId: string, targetFolderId: string | null) => Promise<void>;
|
||||
onMoveFiles?: (fileIds: string[], targetFolderId: string | null) => Promise<void>;
|
||||
onDownloadFolder?: (folderId: string, folderName: string) => Promise<void>;
|
||||
}
|
||||
|
||||
function _TreeNode({
|
||||
node, depth, selectedFolderId, expandedIds, showFiles, filesByFolder, sel,
|
||||
onToggle, onSelect,
|
||||
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
||||
onDownloadFolder,
|
||||
}: TreeNodeProps) {
|
||||
const [renaming, setRenaming] = useState(false);
|
||||
const [renameValue, setRenameValue] = useState(node.name);
|
||||
|
|
@ -436,6 +439,11 @@ function _TreeNode({
|
|||
<span className={styles.folderName}>{node.name}</span>
|
||||
)}
|
||||
<span className={styles.actions}>
|
||||
{onDownloadFolder && !(isMultiSelected && sel.selectedItemIds.size > 1) && (
|
||||
<button className={styles.actionBtn} onClick={(e) => { e.stopPropagation(); onDownloadFolder(node.id, node.name); }} title="Ordner herunterladen (ZIP)">
|
||||
<FaDownload />
|
||||
</button>
|
||||
)}
|
||||
{onCreateFolder && !(isMultiSelected && sel.selectedItemIds.size > 1) && (
|
||||
<button className={styles.actionBtn} onClick={_handleAdd} title="Neuer Unterordner">
|
||||
<FaPlus />
|
||||
|
|
@ -489,6 +497,7 @@ function _TreeNode({
|
|||
onMoveFolders={onMoveFolders}
|
||||
onMoveFile={onMoveFile}
|
||||
onMoveFiles={onMoveFiles}
|
||||
onDownloadFolder={onDownloadFolder}
|
||||
/>
|
||||
))}
|
||||
{folderFiles.map((file) => (
|
||||
|
|
@ -507,7 +516,7 @@ export default function FolderTree({
|
|||
selectedItemIds: externalSelectedIds, onSelectionChange,
|
||||
expandedIds: externalExpandedIds, onToggleExpand,
|
||||
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
||||
onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onRefresh,
|
||||
onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onRefresh, onDownloadFolder,
|
||||
}: FolderTreeProps) {
|
||||
const [internalExpandedIds, setInternalExpandedIds] = useState<Set<string>>(new Set());
|
||||
const [rootDropOver, setRootDropOver] = useState(false);
|
||||
|
|
@ -720,6 +729,7 @@ export default function FolderTree({
|
|||
onMoveFolders={onMoveFolders}
|
||||
onMoveFile={onMoveFile}
|
||||
onMoveFiles={onMoveFiles}
|
||||
onDownloadFolder={onDownloadFolder}
|
||||
/>
|
||||
))}
|
||||
{rootFiles.map((file) => (
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ interface FileContextType {
|
|||
handleMoveFile: (fileId: string, targetFolderId: string | null) => Promise<void>;
|
||||
handleMoveFiles: (fileIds: string[], targetFolderId: string | null) => Promise<void>;
|
||||
handleMoveFolders: (folderIds: string[], targetParentId: string | null) => Promise<void>;
|
||||
handleDownloadFolder: (folderId: string, folderName: string) => Promise<void>;
|
||||
expandedFolderIds: Set<string>;
|
||||
toggleFolderExpanded: (id: string) => void;
|
||||
}
|
||||
|
|
@ -122,6 +123,24 @@ export function FileProvider({ children }: { children: React.ReactNode }) {
|
|||
await refreshFolders();
|
||||
}, [refreshFolders]);
|
||||
|
||||
const handleDownloadFolder = useCallback(async (folderId: string, folderName: string) => {
|
||||
try {
|
||||
const response = await api.get(`/api/files/folders/${folderId}/download`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
const url = window.URL.createObjectURL(response.data);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', `${folderName}.zip`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (err) {
|
||||
console.error('Failed to download folder:', err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ── File operations ────────────────────────────────────────────────────
|
||||
|
||||
const handleFileUpload = useCallback(async (file: File, workflowId?: string) => {
|
||||
|
|
@ -174,6 +193,7 @@ export function FileProvider({ children }: { children: React.ReactNode }) {
|
|||
handleMoveFile,
|
||||
handleMoveFiles,
|
||||
handleMoveFolders,
|
||||
handleDownloadFolder,
|
||||
expandedFolderIds,
|
||||
toggleFolderExpanded,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export const FilesPage: React.FC = () => {
|
|||
handleMoveFolders,
|
||||
handleMoveFile,
|
||||
handleMoveFiles: contextMoveFiles,
|
||||
handleDownloadFolder,
|
||||
expandedFolderIds,
|
||||
toggleFolderExpanded,
|
||||
} = useFileContext();
|
||||
|
|
@ -367,6 +368,7 @@ export const FilesPage: React.FC = () => {
|
|||
onDeleteFile={_handleDeleteTreeFile}
|
||||
onDeleteFiles={_handleDeleteTreeFiles}
|
||||
onDeleteFolders={_handleDeleteTreeFolders}
|
||||
onDownloadFolder={handleDownloadFolder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export const FileBrowser: React.FC<FileBrowserProps> = ({
|
|||
handleMoveFile,
|
||||
handleMoveFiles: contextMoveFiles,
|
||||
handleFileDelete,
|
||||
handleDownloadFolder,
|
||||
expandedFolderIds,
|
||||
toggleFolderExpanded,
|
||||
} = useFileContext();
|
||||
|
|
@ -238,6 +239,7 @@ export const FileBrowser: React.FC<FileBrowserProps> = ({
|
|||
onDeleteFile={_onDeleteFile}
|
||||
onDeleteFiles={_onDeleteFiles}
|
||||
onDeleteFolders={_onDeleteFolders}
|
||||
onDownloadFolder={handleDownloadFolder}
|
||||
/>
|
||||
|
||||
{_fileNodes.length === 0 && (
|
||||
|
|
|
|||
Loading…
Reference in a new issue