refactor: Reorganize frontend by moving UI components, hooks, and utilities to new components, logic, features, and lib directories
This commit is contained in:
@@ -0,0 +1,688 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { DragEvent } from 'react';
|
||||
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../../app/workspaceUtils';
|
||||
import {
|
||||
createFolder,
|
||||
deleteFolder,
|
||||
moveFolder as moveFolderRequest,
|
||||
renameFolder as renameFolderRequest,
|
||||
} from '../../../lib/api/apiClient';
|
||||
import type { FolderId } from '../../../types/identifiers';
|
||||
import type { MessageOptions } from '../../../types/documents';
|
||||
|
||||
type FolderKey = FolderId | 'root';
|
||||
|
||||
interface FolderNode {
|
||||
id: FolderKey;
|
||||
name?: string;
|
||||
parentId?: FolderKey | null;
|
||||
children: FolderId[];
|
||||
expanded?: boolean;
|
||||
loaded?: boolean;
|
||||
hasChildren?: boolean;
|
||||
}
|
||||
|
||||
interface FolderContentsState {
|
||||
folder?: { id: FolderKey; name?: string };
|
||||
subfolders?: any[];
|
||||
documents?: any[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface EnsureFolderOptions {
|
||||
force?: boolean;
|
||||
includeDocuments?: boolean;
|
||||
prefetchDepth?: number;
|
||||
}
|
||||
|
||||
interface LoadFolderOptions {
|
||||
preserveSearch?: boolean;
|
||||
}
|
||||
|
||||
interface SelectFolderOptions {
|
||||
replace?: boolean;
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
interface FolderClickHandlers {
|
||||
onToggle: (folderId: FolderKey) => Promise<void>;
|
||||
onSelect: (folderId: FolderKey, options?: SelectFolderOptions) => Promise<void>;
|
||||
onDrop: (event: DragEvent<HTMLElement>, folderId: FolderKey) => Promise<void>;
|
||||
onDragOver: (event: DragEvent<HTMLElement>, folderId: FolderKey) => void;
|
||||
onDragLeave: (event: DragEvent<HTMLElement>) => void;
|
||||
}
|
||||
|
||||
interface UseFolderTreeActionsOptions {
|
||||
token?: string | null;
|
||||
folderNodes: Map<FolderKey, FolderNode>;
|
||||
setFolderNodes: (updater: (prev: Map<FolderKey, FolderNode>) => Map<FolderKey, FolderNode>) => void;
|
||||
selectedFolder: FolderKey;
|
||||
setSelectedFolder: (folderId: FolderKey) => void;
|
||||
ensureFolderData: (folderId: FolderKey, options?: EnsureFolderOptions) => Promise<any>;
|
||||
ensureFolderAncestorsLoaded: (folderId: FolderKey) => Promise<void>;
|
||||
expandFolderAncestors: (folderId: FolderKey, options?: { includeSelf?: boolean }) => void;
|
||||
applySelectedFolder: (folderId: FolderKey, contents: any) => void;
|
||||
notifyApiError: (error: unknown, message?: string) => void;
|
||||
setStatusMessage: (message: string, level?: string) => void;
|
||||
setFolderContents: (
|
||||
updater: (prev: Map<FolderKey, FolderContentsState>) => Map<FolderKey, FolderContentsState>,
|
||||
) => void;
|
||||
setCurrentFolder: (updater: (prev: any) => any) => void;
|
||||
setSearchResultIds: (value: any) => void;
|
||||
isFilterActive: boolean;
|
||||
navigate?: (path: string, options?: { replace?: boolean }) => void;
|
||||
handleFileDrop: (dataTransfer: DataTransfer, folderId: FolderKey) => Promise<void> | void;
|
||||
moveDocumentsToFolder: (docIds: FolderId[], folderId: FolderKey) => Promise<void>;
|
||||
draggedDocumentIds: FolderId[];
|
||||
draggedFolderId: FolderKey | null;
|
||||
setDraggedDocumentIds: (ids: FolderId[]) => void;
|
||||
setDraggedFolderId: (id: FolderKey | null) => void;
|
||||
isInvalidFolderDrop: (sourceFolderId: FolderKey, targetFolderId: FolderKey) => boolean;
|
||||
setCreatingFolder: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const useFolderTreeActions = ({
|
||||
token,
|
||||
folderNodes,
|
||||
setFolderNodes,
|
||||
selectedFolder,
|
||||
setSelectedFolder,
|
||||
ensureFolderData,
|
||||
ensureFolderAncestorsLoaded,
|
||||
expandFolderAncestors,
|
||||
applySelectedFolder,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
setFolderContents,
|
||||
setCurrentFolder,
|
||||
setSearchResultIds,
|
||||
isFilterActive,
|
||||
navigate,
|
||||
handleFileDrop,
|
||||
moveDocumentsToFolder,
|
||||
draggedDocumentIds,
|
||||
draggedFolderId,
|
||||
setDraggedDocumentIds,
|
||||
setDraggedFolderId,
|
||||
isInvalidFolderDrop,
|
||||
setCreatingFolder,
|
||||
}: UseFolderTreeActionsOptions) => {
|
||||
const moveFolder = useCallback(
|
||||
async (folderId: FolderKey, targetFolderId: FolderKey | null) => {
|
||||
const node = folderNodes.get(folderId);
|
||||
if (!node) {
|
||||
setStatusMessage('Folder metadata unavailable. Try refreshing.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const previousParentKey = node.parentId ?? 'root';
|
||||
const targetKey = targetFolderId && targetFolderId !== 'root' ? targetFolderId : 'root';
|
||||
|
||||
if (previousParentKey === targetKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parent_id = targetKey === 'root' ? null : targetKey;
|
||||
|
||||
try {
|
||||
await moveFolderRequest(folderId, parent_id);
|
||||
|
||||
setFolderNodes((prev) => {
|
||||
const next = new Map(prev);
|
||||
const currentNode = next.get(folderId);
|
||||
if (!currentNode) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
next.set(folderId, { ...currentNode, parentId: parent_id ?? null });
|
||||
|
||||
const previousParent = next.get(previousParentKey);
|
||||
if (previousParent) {
|
||||
const remainingChildren = (previousParent.children || []).filter(
|
||||
(childId) => childId !== folderId,
|
||||
);
|
||||
next.set(previousParentKey, {
|
||||
...previousParent,
|
||||
children: remainingChildren,
|
||||
hasChildren: remainingChildren.length > 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (!next.has(targetKey)) {
|
||||
next.set(targetKey, {
|
||||
id: targetKey,
|
||||
name: targetKey === 'root' ? DEFAULT_FOLDER_NAME : 'Folder',
|
||||
parentId: targetKey === 'root' ? null : null,
|
||||
children: [],
|
||||
expanded: targetKey === 'root',
|
||||
loaded: false,
|
||||
hasChildren: false,
|
||||
});
|
||||
}
|
||||
|
||||
const targetNode = next.get(targetKey);
|
||||
if (targetNode && !targetNode.children.includes(folderId)) {
|
||||
next.set(targetKey, {
|
||||
...targetNode,
|
||||
children: [...targetNode.children, folderId],
|
||||
hasChildren: true,
|
||||
});
|
||||
}
|
||||
|
||||
return next;
|
||||
});
|
||||
|
||||
const refreshTargets = new Set([previousParentKey, targetKey]);
|
||||
await Promise.all(
|
||||
Array.from(refreshTargets).map((key) =>
|
||||
ensureFolderData(key === 'root' ? 'root' : key, { force: true, prefetchDepth: 1 }),
|
||||
),
|
||||
);
|
||||
|
||||
if (selectedFolder === folderId) {
|
||||
await ensureFolderData(folderId, { force: true, prefetchDepth: 1 });
|
||||
setSelectedFolder(folderId);
|
||||
}
|
||||
|
||||
setStatusMessage('Folder moved.', 'success');
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.error || 'Failed to move folder.';
|
||||
notifyApiError(error, message);
|
||||
|
||||
const refreshTargets = new Set([previousParentKey, targetKey]);
|
||||
await Promise.all(
|
||||
Array.from(refreshTargets).map((key) =>
|
||||
ensureFolderData(key === 'root' ? 'root' : key, { force: true, prefetchDepth: 1 }),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
ensureFolderData,
|
||||
folderNodes,
|
||||
notifyApiError,
|
||||
selectedFolder,
|
||||
setFolderNodes,
|
||||
setSelectedFolder,
|
||||
setStatusMessage,
|
||||
],
|
||||
);
|
||||
|
||||
const loadFolder = useCallback(
|
||||
async (folderId: FolderKey | null, { preserveSearch = false }: LoadFolderOptions = {}) => {
|
||||
const targetId = folderId || 'root';
|
||||
setSelectedFolder(targetId);
|
||||
await ensureFolderAncestorsLoaded(targetId);
|
||||
expandFolderAncestors(targetId);
|
||||
try {
|
||||
const contents = await ensureFolderData(targetId, { force: true, prefetchDepth: 1 });
|
||||
if (targetId !== 'root') {
|
||||
try {
|
||||
await ensureFolderData('root', {
|
||||
force: false,
|
||||
includeDocuments: false,
|
||||
prefetchDepth: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Failed to refresh root folder tree', error);
|
||||
}
|
||||
}
|
||||
applySelectedFolder(targetId, contents);
|
||||
if (!preserveSearch) {
|
||||
setSearchResultIds(null);
|
||||
}
|
||||
} catch (error) {
|
||||
notifyApiError(error, 'Failed to load folder contents.');
|
||||
}
|
||||
},
|
||||
[
|
||||
applySelectedFolder,
|
||||
ensureFolderAncestorsLoaded,
|
||||
ensureFolderData,
|
||||
expandFolderAncestors,
|
||||
notifyApiError,
|
||||
setSearchResultIds,
|
||||
setSelectedFolder,
|
||||
],
|
||||
);
|
||||
|
||||
const selectFolder = useCallback(
|
||||
async (folderId: FolderKey | null, { replace = false, immediate = false }: SelectFolderOptions = {}) => {
|
||||
const targetId = folderId && folderId !== 'root' ? folderId : 'root';
|
||||
|
||||
await ensureFolderAncestorsLoaded(targetId);
|
||||
expandFolderAncestors(targetId);
|
||||
|
||||
if (!navigate || immediate) {
|
||||
await loadFolder(targetId, { preserveSearch: isFilterActive });
|
||||
setSelectedFolder(targetId);
|
||||
return;
|
||||
}
|
||||
|
||||
const path = targetId === 'root' ? '/documents' : `/documents/folder/${targetId}`;
|
||||
navigate(path, { replace });
|
||||
},
|
||||
[
|
||||
ensureFolderAncestorsLoaded,
|
||||
expandFolderAncestors,
|
||||
isFilterActive,
|
||||
loadFolder,
|
||||
navigate,
|
||||
setSelectedFolder,
|
||||
],
|
||||
);
|
||||
|
||||
const handleFolderRename = useCallback(
|
||||
async (folderId: FolderKey, nextName: string) => {
|
||||
if (!token) {
|
||||
setStatusMessage('Log in to rename folders.', 'error');
|
||||
return false;
|
||||
}
|
||||
const trimmed = nextName?.trim?.() || '';
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Folder name cannot be empty.', 'error');
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await renameFolderRequest(folderId, trimmed);
|
||||
|
||||
setFolderNodes((prev) => {
|
||||
const next = new Map(prev);
|
||||
const node = next.get(folderId);
|
||||
if (node) {
|
||||
next.set(folderId, { ...node, name: trimmed });
|
||||
}
|
||||
return next;
|
||||
});
|
||||
|
||||
setFolderContents((prev) => {
|
||||
if (!prev.has(folderId)) {
|
||||
return prev;
|
||||
}
|
||||
const next = new Map(prev);
|
||||
const existing = next.get(folderId) || {};
|
||||
const folderInfo = existing.folder
|
||||
? { ...existing.folder, name: trimmed }
|
||||
: { id: folderId, name: trimmed };
|
||||
next.set(folderId, { ...existing, folder: folderInfo });
|
||||
return next;
|
||||
});
|
||||
|
||||
setCurrentFolder((prev) => (prev?.id === folderId ? { ...prev, name: trimmed } : prev));
|
||||
setStatusMessage('Folder renamed.', 'success');
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.error || 'Failed to rename folder.';
|
||||
notifyApiError(error, message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[
|
||||
notifyApiError,
|
||||
setCurrentFolder,
|
||||
setFolderContents,
|
||||
setFolderNodes,
|
||||
setStatusMessage,
|
||||
token,
|
||||
],
|
||||
);
|
||||
|
||||
const handleFolderCreate = useCallback(
|
||||
async (name: string, parentId?: FolderKey | null) => {
|
||||
if (!token) {
|
||||
setStatusMessage('Log in to create folders.', 'error');
|
||||
return false;
|
||||
}
|
||||
if (!name.trim()) {
|
||||
setStatusMessage('Folder name cannot be empty.', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetParentId = parentId !== undefined
|
||||
? (parentId === 'root' ? null : parentId)
|
||||
: (selectedFolder === 'root' ? null : selectedFolder);
|
||||
|
||||
const payload = {
|
||||
name: name.trim(),
|
||||
parent_id: targetParentId,
|
||||
};
|
||||
setCreatingFolder(true);
|
||||
let succeeded = false;
|
||||
try {
|
||||
const data = await createFolder(payload);
|
||||
const folderData = (data as { folder?: { id?: FolderKey; name?: string; parent_id?: FolderKey | null; children?: FolderKey[] } }).folder;
|
||||
if (!folderData?.id) {
|
||||
throw new Error('Folder creation failed.');
|
||||
}
|
||||
setStatusMessage('Folder created.', 'success');
|
||||
setFolderNodes((prev) => {
|
||||
const next = new Map(prev);
|
||||
const parentId = folderData.parent_id ?? payload.parent_id ?? 'root';
|
||||
const parentNode = next.get(parentId);
|
||||
if (parentNode) {
|
||||
next.set(parentId, {
|
||||
...parentNode,
|
||||
children: parentNode.children.concat([folderData.id]),
|
||||
loaded: true,
|
||||
hasChildren: true,
|
||||
});
|
||||
}
|
||||
next.set(folderData.id, {
|
||||
id: folderData.id,
|
||||
name: folderData.name ?? payload.name,
|
||||
parentId: parentId,
|
||||
children: folderData.children || [],
|
||||
expanded: false,
|
||||
loaded: false,
|
||||
hasChildren: Array.isArray(folderData.children) ? folderData.children.length > 0 : false,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
|
||||
// Refresh the parent folder to ensure consistency
|
||||
const refreshTarget = targetParentId || 'root';
|
||||
await ensureFolderData(refreshTarget, { force: true, prefetchDepth: 1 });
|
||||
|
||||
await selectFolder(folderData.id, { immediate: true });
|
||||
succeeded = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.error || 'Failed to create folder.';
|
||||
notifyApiError(error, message);
|
||||
return false;
|
||||
} finally {
|
||||
setCreatingFolder(false);
|
||||
if (!succeeded) {
|
||||
setStatusMessage('Folder creation failed.', 'error');
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
ensureFolderData,
|
||||
notifyApiError,
|
||||
selectFolder,
|
||||
selectedFolder,
|
||||
setCreatingFolder,
|
||||
setFolderNodes,
|
||||
setStatusMessage,
|
||||
token,
|
||||
],
|
||||
);
|
||||
|
||||
const handleFolderDelete = useCallback(
|
||||
async (folderId: FolderKey, { showMessage = true }: MessageOptions = {}) => {
|
||||
if (!token) {
|
||||
if (showMessage) {
|
||||
setStatusMessage('Log in to manage folders.', 'error');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!folderId || folderId === 'root') {
|
||||
if (showMessage) {
|
||||
setStatusMessage('The root folder cannot be removed.', 'error');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const contents = await ensureFolderData(folderId, {
|
||||
force: true,
|
||||
prefetchDepth: 1,
|
||||
});
|
||||
const hasChildren = (contents.subfolders || []).length > 0;
|
||||
const hasDocs = (contents.documents || []).length > 0;
|
||||
if (hasChildren || hasDocs) {
|
||||
if (showMessage) {
|
||||
setStatusMessage('Folder must be empty before it can be deleted.', 'error');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
await deleteFolder(folderId);
|
||||
|
||||
setFolderNodes((prev) => {
|
||||
const next = new Map(prev);
|
||||
const node = next.get(folderId);
|
||||
next.delete(folderId);
|
||||
if (node) {
|
||||
const parentId = node.parentId || 'root';
|
||||
const parentNode = next.get(parentId);
|
||||
if (parentNode) {
|
||||
const remaining = parentNode.children.filter((id) => id !== folderId);
|
||||
next.set(parentId, {
|
||||
...parentNode,
|
||||
children: remaining,
|
||||
hasChildren: remaining.length > 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
|
||||
setFolderContents((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(folderId);
|
||||
return next;
|
||||
});
|
||||
|
||||
if (selectedFolder === folderId) {
|
||||
const node = folderNodes.get(folderId);
|
||||
const parentId = node?.parentId || 'root';
|
||||
setSelectedFolder(parentId);
|
||||
const parentContents = await ensureFolderData(parentId, {
|
||||
force: true,
|
||||
prefetchDepth: 1,
|
||||
});
|
||||
applySelectedFolder(parentId, parentContents);
|
||||
} else if (selectedFolder !== 'root') {
|
||||
await ensureFolderData(selectedFolder, { force: true, prefetchDepth: 1 });
|
||||
}
|
||||
|
||||
if (showMessage) {
|
||||
setStatusMessage('Folder deleted.', 'success');
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.error || 'Failed to delete folder.';
|
||||
notifyApiError(error, message);
|
||||
if (showMessage) {
|
||||
setStatusMessage(message, 'error');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[
|
||||
token,
|
||||
applySelectedFolder,
|
||||
ensureFolderData,
|
||||
folderNodes,
|
||||
notifyApiError,
|
||||
selectedFolder,
|
||||
setFolderContents,
|
||||
setFolderNodes,
|
||||
setSelectedFolder,
|
||||
setStatusMessage,
|
||||
],
|
||||
);
|
||||
|
||||
const folderClickHandlers: FolderClickHandlers = useMemo(
|
||||
() => ({
|
||||
onToggle: async (folderId: FolderKey) => {
|
||||
const node = folderNodes.get(folderId);
|
||||
const nextExpanded = !(node?.expanded ?? false);
|
||||
if (nextExpanded) {
|
||||
try {
|
||||
await ensureFolderData(folderId, {
|
||||
includeDocuments: false,
|
||||
prefetchDepth: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
notifyApiError(error, 'Failed to load folder.');
|
||||
}
|
||||
} else if (node && !node.loaded) {
|
||||
try {
|
||||
await ensureFolderData(folderId, {
|
||||
includeDocuments: false,
|
||||
prefetchDepth: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
notifyApiError(error, 'Failed to load folder.');
|
||||
}
|
||||
}
|
||||
setFolderNodes((prev) => {
|
||||
const next = new Map(prev);
|
||||
const current = next.get(folderId);
|
||||
if (!current) return prev;
|
||||
next.set(folderId, { ...current, expanded: nextExpanded });
|
||||
return next;
|
||||
});
|
||||
},
|
||||
onSelect: selectFolder,
|
||||
onDrop: async (event: DragEvent<HTMLElement>, folderId: FolderKey) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.currentTarget.classList.remove('is-drop-target');
|
||||
|
||||
let folderIds: FolderId[] = [];
|
||||
try {
|
||||
const rawFolderList = event.dataTransfer.getData('application/x-papercrate-folder-list');
|
||||
if (rawFolderList) {
|
||||
const parsed = JSON.parse(rawFolderList);
|
||||
if (Array.isArray(parsed)) {
|
||||
folderIds = parsed.filter(Boolean);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[folders] Failed to parse folder list drag payload', error);
|
||||
}
|
||||
|
||||
if (!folderIds.length) {
|
||||
let folderSourceId = draggedFolderId;
|
||||
if (!folderSourceId) {
|
||||
try {
|
||||
if (event.dataTransfer.types?.includes('application/x-papercrate-folder')) {
|
||||
folderSourceId = event.dataTransfer.getData('application/x-papercrate-folder');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[folders] Failed to read folder id from drag payload', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (folderSourceId) {
|
||||
folderIds = [folderSourceId];
|
||||
}
|
||||
}
|
||||
|
||||
folderIds = Array.from(new Set(folderIds.filter(Boolean)));
|
||||
|
||||
if (folderIds.length) {
|
||||
setDraggedFolderId(null);
|
||||
const invalidMove = folderIds.some((sourceId) => isInvalidFolderDrop(sourceId, folderId));
|
||||
if (invalidMove) {
|
||||
setStatusMessage(
|
||||
'Cannot move a folder into itself or one of its descendants.',
|
||||
'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const sourceId of folderIds) {
|
||||
await moveFolder(sourceId, folderId);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFiles(event)) {
|
||||
await handleFileDrop(event.dataTransfer, folderId);
|
||||
return;
|
||||
}
|
||||
|
||||
let docIds: FolderId[] = [];
|
||||
try {
|
||||
const raw = event.dataTransfer.getData('application/x-papercrate-doc-list');
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) {
|
||||
docIds = parsed.filter(Boolean);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[documents] Failed to parse document list drag payload', error);
|
||||
}
|
||||
|
||||
if (!docIds.length) {
|
||||
try {
|
||||
const single = event.dataTransfer.getData('application/x-papercrate-doc');
|
||||
if (single) {
|
||||
docIds = [single];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[documents] Failed to read single document drag payload', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!docIds.length && draggedDocumentIds.length) {
|
||||
docIds = draggedDocumentIds;
|
||||
}
|
||||
|
||||
docIds = Array.from(new Set(docIds));
|
||||
|
||||
if (!docIds.length || folderId === selectedFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDraggedDocumentIds([]);
|
||||
await moveDocumentsToFolder(docIds, folderId);
|
||||
},
|
||||
onDragOver: (event: DragEvent<HTMLElement>, folderId: FolderKey) => {
|
||||
const folderDragActive = Boolean(draggedFolderId);
|
||||
if (folderDragActive && isInvalidFolderDrop(draggedFolderId, folderId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasFiles(event)) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
event.currentTarget.classList.add('is-drop-target');
|
||||
return;
|
||||
}
|
||||
|
||||
if (draggedDocumentIds.length || folderDragActive) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
event.currentTarget.classList.add('is-drop-target');
|
||||
}
|
||||
},
|
||||
onDragLeave: (event: DragEvent<HTMLElement>) => {
|
||||
event.currentTarget.classList.remove('is-drop-target');
|
||||
},
|
||||
}),
|
||||
[
|
||||
draggedDocumentIds,
|
||||
draggedFolderId,
|
||||
ensureFolderData,
|
||||
folderNodes,
|
||||
handleFileDrop,
|
||||
isInvalidFolderDrop,
|
||||
moveDocumentsToFolder,
|
||||
moveFolder,
|
||||
notifyApiError,
|
||||
selectFolder,
|
||||
selectedFolder,
|
||||
setDraggedDocumentIds,
|
||||
setDraggedFolderId,
|
||||
setFolderNodes,
|
||||
setStatusMessage,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
loadFolder,
|
||||
selectFolder,
|
||||
handleFolderRename,
|
||||
handleFolderCreate,
|
||||
handleFolderDelete,
|
||||
folderClickHandlers,
|
||||
};
|
||||
};
|
||||
|
||||
export default useFolderTreeActions;
|
||||
Reference in New Issue
Block a user