feat: Enable moving and dragging selected folders, including mixed document and folder selections, with updated drag preview visuals and minor UI styling.

This commit is contained in:
2025-11-28 20:12:56 +01:00
parent 4fd1b712fa
commit e0d63d94b4
4 changed files with 64 additions and 20 deletions
@@ -360,12 +360,13 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
const handleMoveSelectionToFolder = useCallback(
async (folderId: DocumentId | null) => {
if (!documentIdList.length || !onMoveDocumentsToFolder) {
const itemsToMove = [...documentIdList, ...folderIdList];
if (!itemsToMove.length || !onMoveDocumentsToFolder) {
return;
}
await onMoveDocumentsToFolder(documentIdList, folderId);
await onMoveDocumentsToFolder(itemsToMove, folderId);
},
[documentIdList, onMoveDocumentsToFolder],
[documentIdList, folderIdList, onMoveDocumentsToFolder],
);
const summaryNode = totalCount > 0 ? (
@@ -395,7 +396,7 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
placeholder="Search folders…"
emptyMessage="No folders"
onSelectFolder={handleMoveSelectionToFolder}
disabled={!documentCount}
disabled={!documentCount && !folderCount}
onOpenMenu={handleMoveMenuOpen}
rootTitle={rootTitle}
/>
@@ -58,7 +58,7 @@ const useDocumentDragHandlers = ({
useEffect(() => destroyDragPreview, [destroyDragPreview]);
const createDragPreview = useCallback(
({ documents = [], folders = [] }: { documents?: Document[]; folders?: FolderIdentifier[] } = {}) => {
({ documents = [], folders = [], prioritizeFolders = false }: { documents?: Document[]; folders?: FolderIdentifier[]; prioritizeFolders?: boolean } = {}) => {
destroyDragPreview();
const docEntries = (documents || []).filter(Boolean);
@@ -72,15 +72,44 @@ const useDocumentDragHandlers = ({
const size = 64;
const canvasSize = Math.round(size * 1.6);
const visibleItems = [];
docEntries.slice(0, maxVisible).forEach((doc) => {
visibleItems.push({ type: 'document', payload: doc });
});
const visibleItems: Array<{ type: 'document' | 'folder'; payload: any }> = [];
if (visibleItems.length < maxVisible) {
folderEntries
.slice(0, maxVisible - visibleItems.length)
.forEach((folderId) => visibleItems.push({ type: 'folder', payload: folderId }));
let takeDocs = 0;
let takeFolders = 0;
if (docEntries.length > 0 && folderEntries.length > 0) {
if (prioritizeFolders) {
// Folders on top (added last)
takeDocs = Math.min(docEntries.length, maxVisible - 1);
takeFolders = Math.min(folderEntries.length, maxVisible - takeDocs);
} else {
// Docs on top (added last)
takeFolders = Math.min(folderEntries.length, maxVisible - 1);
takeDocs = Math.min(docEntries.length, maxVisible - takeFolders);
}
} else {
takeDocs = Math.min(docEntries.length, maxVisible);
takeFolders = Math.min(folderEntries.length, maxVisible - takeDocs);
}
if (prioritizeFolders) {
// Docs at bottom
docEntries.slice(0, takeDocs).forEach((doc) => {
visibleItems.push({ type: 'document', payload: doc });
});
// Folders at top
folderEntries.slice(0, takeFolders).forEach((folderId) => {
visibleItems.push({ type: 'folder', payload: folderId });
});
} else {
// Folders at bottom
folderEntries.slice(0, takeFolders).forEach((folderId) => {
visibleItems.push({ type: 'folder', payload: folderId });
});
// Docs at top
docEntries.slice(0, takeDocs).forEach((doc) => {
visibleItems.push({ type: 'document', payload: doc });
});
}
const wrapper = document.createElement('div');
@@ -152,16 +181,18 @@ const useDocumentDragHandlers = ({
layer.style.height = `${size}px`;
layer.classList.add('document-drag-preview__item--folder');
let content: HTMLElement | null = null;
let content: HTMLElement | SVGElement | null = null;
if (iconEl instanceof HTMLElement) {
const cloneSource = iconEl.classList.contains('folder-card__icon')
? iconEl.querySelector('svg') || iconEl
: iconEl;
const clone = cloneSource.cloneNode(true);
if (clone instanceof HTMLElement) {
content = clone;
if (clone instanceof HTMLElement || clone instanceof SVGElement) {
content = clone as HTMLElement | SVGElement;
content.classList.add('document-drag-preview__folder-thumb');
const svg = content.querySelector('svg');
const svg = content.nodeName.toLowerCase() === 'svg'
? content
: content.querySelector('svg');
if (svg) {
svg.setAttribute('width', '48');
svg.setAttribute('height', '48');
@@ -213,7 +244,11 @@ const useDocumentDragHandlers = ({
const selection: Identifier[] = isAlreadySelected
? [...selectedDocumentIds]
: [documentId];
const folderSelection: FolderIdentifier[] = [];
// If the document is part of the selection, we also want to include any selected folders
const folderSelection: FolderIdentifier[] = isAlreadySelected
? normalizedFolderIds
: [];
if (!isAlreadySelected) {
applySelection([documentKey], {
@@ -228,6 +263,7 @@ const useDocumentDragHandlers = ({
const previewNode = createDragPreview({
documents: previewDocs,
folders: folderSelection,
prioritizeFolders: false,
});
setDraggedDocumentIds(selection);
@@ -266,6 +302,7 @@ const useDocumentDragHandlers = ({
createDragPreview,
setDraggedFolderId,
setDraggedDocumentIds,
normalizedFolderIds,
],
);
@@ -331,6 +368,7 @@ const useDocumentDragHandlers = ({
.map((id) => documentLookup.get(id) || documentLookup.get(String(id)) || null)
.filter(Boolean),
folders: uniqueFolders,
prioritizeFolders: true,
});
event.currentTarget.classList.add('dragging');
@@ -852,4 +852,9 @@
.menu .selection-assignment__item--empty:focus-visible {
background: transparent;
cursor: default;
}
.selection-assignment__item--empty .selection-assignment__item-content {
opacity: 0.5;
cursor: default;
}
+2 -2
View File
@@ -254,8 +254,8 @@
text-align: left;
}
.documents-panel--view-grid {
padding: 0.35rem 0.5rem 1rem;
.documents-panel .documents-grid {
padding: 1rem 0.5rem;
}
.documents-panel th.thumb-column,