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:
@@ -360,12 +360,13 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
|
|||||||
|
|
||||||
const handleMoveSelectionToFolder = useCallback(
|
const handleMoveSelectionToFolder = useCallback(
|
||||||
async (folderId: DocumentId | null) => {
|
async (folderId: DocumentId | null) => {
|
||||||
if (!documentIdList.length || !onMoveDocumentsToFolder) {
|
const itemsToMove = [...documentIdList, ...folderIdList];
|
||||||
|
if (!itemsToMove.length || !onMoveDocumentsToFolder) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await onMoveDocumentsToFolder(documentIdList, folderId);
|
await onMoveDocumentsToFolder(itemsToMove, folderId);
|
||||||
},
|
},
|
||||||
[documentIdList, onMoveDocumentsToFolder],
|
[documentIdList, folderIdList, onMoveDocumentsToFolder],
|
||||||
);
|
);
|
||||||
|
|
||||||
const summaryNode = totalCount > 0 ? (
|
const summaryNode = totalCount > 0 ? (
|
||||||
@@ -395,7 +396,7 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
|
|||||||
placeholder="Search folders…"
|
placeholder="Search folders…"
|
||||||
emptyMessage="No folders"
|
emptyMessage="No folders"
|
||||||
onSelectFolder={handleMoveSelectionToFolder}
|
onSelectFolder={handleMoveSelectionToFolder}
|
||||||
disabled={!documentCount}
|
disabled={!documentCount && !folderCount}
|
||||||
onOpenMenu={handleMoveMenuOpen}
|
onOpenMenu={handleMoveMenuOpen}
|
||||||
rootTitle={rootTitle}
|
rootTitle={rootTitle}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const useDocumentDragHandlers = ({
|
|||||||
useEffect(() => destroyDragPreview, [destroyDragPreview]);
|
useEffect(() => destroyDragPreview, [destroyDragPreview]);
|
||||||
|
|
||||||
const createDragPreview = useCallback(
|
const createDragPreview = useCallback(
|
||||||
({ documents = [], folders = [] }: { documents?: Document[]; folders?: FolderIdentifier[] } = {}) => {
|
({ documents = [], folders = [], prioritizeFolders = false }: { documents?: Document[]; folders?: FolderIdentifier[]; prioritizeFolders?: boolean } = {}) => {
|
||||||
destroyDragPreview();
|
destroyDragPreview();
|
||||||
|
|
||||||
const docEntries = (documents || []).filter(Boolean);
|
const docEntries = (documents || []).filter(Boolean);
|
||||||
@@ -72,15 +72,44 @@ const useDocumentDragHandlers = ({
|
|||||||
const size = 64;
|
const size = 64;
|
||||||
const canvasSize = Math.round(size * 1.6);
|
const canvasSize = Math.round(size * 1.6);
|
||||||
|
|
||||||
const visibleItems = [];
|
const visibleItems: Array<{ type: 'document' | 'folder'; payload: any }> = [];
|
||||||
docEntries.slice(0, maxVisible).forEach((doc) => {
|
|
||||||
visibleItems.push({ type: 'document', payload: doc });
|
|
||||||
});
|
|
||||||
|
|
||||||
if (visibleItems.length < maxVisible) {
|
let takeDocs = 0;
|
||||||
folderEntries
|
let takeFolders = 0;
|
||||||
.slice(0, maxVisible - visibleItems.length)
|
|
||||||
.forEach((folderId) => visibleItems.push({ type: 'folder', payload: folderId }));
|
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');
|
const wrapper = document.createElement('div');
|
||||||
@@ -152,16 +181,18 @@ const useDocumentDragHandlers = ({
|
|||||||
layer.style.height = `${size}px`;
|
layer.style.height = `${size}px`;
|
||||||
layer.classList.add('document-drag-preview__item--folder');
|
layer.classList.add('document-drag-preview__item--folder');
|
||||||
|
|
||||||
let content: HTMLElement | null = null;
|
let content: HTMLElement | SVGElement | null = null;
|
||||||
if (iconEl instanceof HTMLElement) {
|
if (iconEl instanceof HTMLElement) {
|
||||||
const cloneSource = iconEl.classList.contains('folder-card__icon')
|
const cloneSource = iconEl.classList.contains('folder-card__icon')
|
||||||
? iconEl.querySelector('svg') || iconEl
|
? iconEl.querySelector('svg') || iconEl
|
||||||
: iconEl;
|
: iconEl;
|
||||||
const clone = cloneSource.cloneNode(true);
|
const clone = cloneSource.cloneNode(true);
|
||||||
if (clone instanceof HTMLElement) {
|
if (clone instanceof HTMLElement || clone instanceof SVGElement) {
|
||||||
content = clone;
|
content = clone as HTMLElement | SVGElement;
|
||||||
content.classList.add('document-drag-preview__folder-thumb');
|
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) {
|
if (svg) {
|
||||||
svg.setAttribute('width', '48');
|
svg.setAttribute('width', '48');
|
||||||
svg.setAttribute('height', '48');
|
svg.setAttribute('height', '48');
|
||||||
@@ -213,7 +244,11 @@ const useDocumentDragHandlers = ({
|
|||||||
const selection: Identifier[] = isAlreadySelected
|
const selection: Identifier[] = isAlreadySelected
|
||||||
? [...selectedDocumentIds]
|
? [...selectedDocumentIds]
|
||||||
: [documentId];
|
: [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) {
|
if (!isAlreadySelected) {
|
||||||
applySelection([documentKey], {
|
applySelection([documentKey], {
|
||||||
@@ -228,6 +263,7 @@ const useDocumentDragHandlers = ({
|
|||||||
const previewNode = createDragPreview({
|
const previewNode = createDragPreview({
|
||||||
documents: previewDocs,
|
documents: previewDocs,
|
||||||
folders: folderSelection,
|
folders: folderSelection,
|
||||||
|
prioritizeFolders: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
setDraggedDocumentIds(selection);
|
setDraggedDocumentIds(selection);
|
||||||
@@ -266,6 +302,7 @@ const useDocumentDragHandlers = ({
|
|||||||
createDragPreview,
|
createDragPreview,
|
||||||
setDraggedFolderId,
|
setDraggedFolderId,
|
||||||
setDraggedDocumentIds,
|
setDraggedDocumentIds,
|
||||||
|
normalizedFolderIds,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -331,6 +368,7 @@ const useDocumentDragHandlers = ({
|
|||||||
.map((id) => documentLookup.get(id) || documentLookup.get(String(id)) || null)
|
.map((id) => documentLookup.get(id) || documentLookup.get(String(id)) || null)
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
folders: uniqueFolders,
|
folders: uniqueFolders,
|
||||||
|
prioritizeFolders: true,
|
||||||
});
|
});
|
||||||
event.currentTarget.classList.add('dragging');
|
event.currentTarget.classList.add('dragging');
|
||||||
|
|
||||||
|
|||||||
@@ -852,4 +852,9 @@
|
|||||||
.menu .selection-assignment__item--empty:focus-visible {
|
.menu .selection-assignment__item--empty:focus-visible {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-assignment__item--empty .selection-assignment__item-content {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
@@ -254,8 +254,8 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documents-panel--view-grid {
|
.documents-panel .documents-grid {
|
||||||
padding: 0.35rem 0.5rem 1rem;
|
padding: 1rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documents-panel th.thumb-column,
|
.documents-panel th.thumb-column,
|
||||||
|
|||||||
Reference in New Issue
Block a user