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 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';
|
import styles from './FolderTree.module.css';
|
||||||
|
|
||||||
/* ── Public types ──────────────────────────────────────────────────────── */
|
/* ── Public types ──────────────────────────────────────────────────────── */
|
||||||
|
|
@ -61,6 +61,7 @@ export interface FolderTreeProps {
|
||||||
onDeleteFile?: (fileId: string) => Promise<void>;
|
onDeleteFile?: (fileId: string) => Promise<void>;
|
||||||
onDeleteFiles?: (fileIds: string[]) => Promise<void>;
|
onDeleteFiles?: (fileIds: string[]) => Promise<void>;
|
||||||
onDeleteFolders?: (folderIds: string[]) => Promise<void>;
|
onDeleteFolders?: (folderIds: string[]) => Promise<void>;
|
||||||
|
onDownloadFolder?: (folderId: string, folderName: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Helpers ───────────────────────────────────────────────────────────── */
|
/* ── Helpers ───────────────────────────────────────────────────────────── */
|
||||||
|
|
@ -285,12 +286,14 @@ interface TreeNodeProps {
|
||||||
onMoveFolders?: (folderIds: string[], targetParentId: string | null) => Promise<void>;
|
onMoveFolders?: (folderIds: string[], targetParentId: string | null) => Promise<void>;
|
||||||
onMoveFile?: (fileId: string, targetFolderId: string | null) => Promise<void>;
|
onMoveFile?: (fileId: string, targetFolderId: string | null) => Promise<void>;
|
||||||
onMoveFiles?: (fileIds: string[], targetFolderId: string | null) => Promise<void>;
|
onMoveFiles?: (fileIds: string[], targetFolderId: string | null) => Promise<void>;
|
||||||
|
onDownloadFolder?: (folderId: string, folderName: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _TreeNode({
|
function _TreeNode({
|
||||||
node, depth, selectedFolderId, expandedIds, showFiles, filesByFolder, sel,
|
node, depth, selectedFolderId, expandedIds, showFiles, filesByFolder, sel,
|
||||||
onToggle, onSelect,
|
onToggle, onSelect,
|
||||||
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
||||||
|
onDownloadFolder,
|
||||||
}: TreeNodeProps) {
|
}: TreeNodeProps) {
|
||||||
const [renaming, setRenaming] = useState(false);
|
const [renaming, setRenaming] = useState(false);
|
||||||
const [renameValue, setRenameValue] = useState(node.name);
|
const [renameValue, setRenameValue] = useState(node.name);
|
||||||
|
|
@ -436,6 +439,11 @@ function _TreeNode({
|
||||||
<span className={styles.folderName}>{node.name}</span>
|
<span className={styles.folderName}>{node.name}</span>
|
||||||
)}
|
)}
|
||||||
<span className={styles.actions}>
|
<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) && (
|
{onCreateFolder && !(isMultiSelected && sel.selectedItemIds.size > 1) && (
|
||||||
<button className={styles.actionBtn} onClick={_handleAdd} title="Neuer Unterordner">
|
<button className={styles.actionBtn} onClick={_handleAdd} title="Neuer Unterordner">
|
||||||
<FaPlus />
|
<FaPlus />
|
||||||
|
|
@ -489,6 +497,7 @@ function _TreeNode({
|
||||||
onMoveFolders={onMoveFolders}
|
onMoveFolders={onMoveFolders}
|
||||||
onMoveFile={onMoveFile}
|
onMoveFile={onMoveFile}
|
||||||
onMoveFiles={onMoveFiles}
|
onMoveFiles={onMoveFiles}
|
||||||
|
onDownloadFolder={onDownloadFolder}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{folderFiles.map((file) => (
|
{folderFiles.map((file) => (
|
||||||
|
|
@ -507,7 +516,7 @@ export default function FolderTree({
|
||||||
selectedItemIds: externalSelectedIds, onSelectionChange,
|
selectedItemIds: externalSelectedIds, onSelectionChange,
|
||||||
expandedIds: externalExpandedIds, onToggleExpand,
|
expandedIds: externalExpandedIds, onToggleExpand,
|
||||||
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
||||||
onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onRefresh,
|
onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onRefresh, onDownloadFolder,
|
||||||
}: FolderTreeProps) {
|
}: FolderTreeProps) {
|
||||||
const [internalExpandedIds, setInternalExpandedIds] = useState<Set<string>>(new Set());
|
const [internalExpandedIds, setInternalExpandedIds] = useState<Set<string>>(new Set());
|
||||||
const [rootDropOver, setRootDropOver] = useState(false);
|
const [rootDropOver, setRootDropOver] = useState(false);
|
||||||
|
|
@ -720,6 +729,7 @@ export default function FolderTree({
|
||||||
onMoveFolders={onMoveFolders}
|
onMoveFolders={onMoveFolders}
|
||||||
onMoveFile={onMoveFile}
|
onMoveFile={onMoveFile}
|
||||||
onMoveFiles={onMoveFiles}
|
onMoveFiles={onMoveFiles}
|
||||||
|
onDownloadFolder={onDownloadFolder}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{rootFiles.map((file) => (
|
{rootFiles.map((file) => (
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ interface FileContextType {
|
||||||
handleMoveFile: (fileId: string, targetFolderId: string | null) => Promise<void>;
|
handleMoveFile: (fileId: string, targetFolderId: string | null) => Promise<void>;
|
||||||
handleMoveFiles: (fileIds: string[], targetFolderId: string | null) => Promise<void>;
|
handleMoveFiles: (fileIds: string[], targetFolderId: string | null) => Promise<void>;
|
||||||
handleMoveFolders: (folderIds: string[], targetParentId: string | null) => Promise<void>;
|
handleMoveFolders: (folderIds: string[], targetParentId: string | null) => Promise<void>;
|
||||||
|
handleDownloadFolder: (folderId: string, folderName: string) => Promise<void>;
|
||||||
expandedFolderIds: Set<string>;
|
expandedFolderIds: Set<string>;
|
||||||
toggleFolderExpanded: (id: string) => void;
|
toggleFolderExpanded: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +123,24 @@ export function FileProvider({ children }: { children: React.ReactNode }) {
|
||||||
await refreshFolders();
|
await refreshFolders();
|
||||||
}, [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 ────────────────────────────────────────────────────
|
// ── File operations ────────────────────────────────────────────────────
|
||||||
|
|
||||||
const handleFileUpload = useCallback(async (file: File, workflowId?: string) => {
|
const handleFileUpload = useCallback(async (file: File, workflowId?: string) => {
|
||||||
|
|
@ -174,6 +193,7 @@ export function FileProvider({ children }: { children: React.ReactNode }) {
|
||||||
handleMoveFile,
|
handleMoveFile,
|
||||||
handleMoveFiles,
|
handleMoveFiles,
|
||||||
handleMoveFolders,
|
handleMoveFolders,
|
||||||
|
handleDownloadFolder,
|
||||||
expandedFolderIds,
|
expandedFolderIds,
|
||||||
toggleFolderExpanded,
|
toggleFolderExpanded,
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ export const FilesPage: React.FC = () => {
|
||||||
handleMoveFolders,
|
handleMoveFolders,
|
||||||
handleMoveFile,
|
handleMoveFile,
|
||||||
handleMoveFiles: contextMoveFiles,
|
handleMoveFiles: contextMoveFiles,
|
||||||
|
handleDownloadFolder,
|
||||||
expandedFolderIds,
|
expandedFolderIds,
|
||||||
toggleFolderExpanded,
|
toggleFolderExpanded,
|
||||||
} = useFileContext();
|
} = useFileContext();
|
||||||
|
|
@ -367,6 +368,7 @@ export const FilesPage: React.FC = () => {
|
||||||
onDeleteFile={_handleDeleteTreeFile}
|
onDeleteFile={_handleDeleteTreeFile}
|
||||||
onDeleteFiles={_handleDeleteTreeFiles}
|
onDeleteFiles={_handleDeleteTreeFiles}
|
||||||
onDeleteFolders={_handleDeleteTreeFolders}
|
onDeleteFolders={_handleDeleteTreeFolders}
|
||||||
|
onDownloadFolder={handleDownloadFolder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export const FileBrowser: React.FC<FileBrowserProps> = ({
|
||||||
handleMoveFile,
|
handleMoveFile,
|
||||||
handleMoveFiles: contextMoveFiles,
|
handleMoveFiles: contextMoveFiles,
|
||||||
handleFileDelete,
|
handleFileDelete,
|
||||||
|
handleDownloadFolder,
|
||||||
expandedFolderIds,
|
expandedFolderIds,
|
||||||
toggleFolderExpanded,
|
toggleFolderExpanded,
|
||||||
} = useFileContext();
|
} = useFileContext();
|
||||||
|
|
@ -238,6 +239,7 @@ export const FileBrowser: React.FC<FileBrowserProps> = ({
|
||||||
onDeleteFile={_onDeleteFile}
|
onDeleteFile={_onDeleteFile}
|
||||||
onDeleteFiles={_onDeleteFiles}
|
onDeleteFiles={_onDeleteFiles}
|
||||||
onDeleteFolders={_onDeleteFolders}
|
onDeleteFolders={_onDeleteFolders}
|
||||||
|
onDownloadFolder={handleDownloadFolder}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{_fileNodes.length === 0 && (
|
{_fileNodes.length === 0 && (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue