This commit is contained in:
2025-11-01 01:46:14 +01:00
parent 2fba3236ad
commit ac91efc421
3 changed files with 191 additions and 29 deletions
+151 -26
View File
@@ -3132,7 +3132,66 @@ const AppLayout = () => {
);
if (!uniqueIds.length) return;
const uniqueIdSet = new Set(uniqueIds);
const target = targetFolderId === 'root' ? null : targetFolderId;
const targetLabel =
target === null
? DEFAULT_FOLDER_NAME
: folderLabelMap.get(targetFolderId) || 'target folder';
const movedDocs = uniqueIds
.map((id) => {
const doc = documentLookup.get(id);
if (!doc) {
return null;
}
return {
id,
sourceFolderId: doc.folder_id ?? null,
document: doc,
};
})
.filter(Boolean);
const updatedDocsMap = new Map();
const resolveTargetName = () => {
if (!targetLabel) {
return null;
}
const segments = String(targetLabel).split('/');
return segments[segments.length - 1] || targetLabel;
};
const targetName = resolveTargetName();
movedDocs.forEach(({ id, document }) => {
if (!document) {
return;
}
const updated = {
...document,
folder_id: target,
};
if (targetLabel) {
updated.folder_path = targetLabel;
if (targetName) {
updated.folder_name = targetName;
}
} else if (target === null) {
updated.folder_path = DEFAULT_FOLDER_NAME;
updated.folder_name = DEFAULT_FOLDER_NAME;
}
updatedDocsMap.set(id, updated);
});
const pruneRowCollection = (collection) =>
collection.filter((key) => {
if (!isDocumentRowKey(key)) {
return true;
}
const id = getRowId(key);
return id ? !uniqueIdSet.has(id) : true;
});
setLoading(true);
try {
if (uniqueIds.length === 1) {
@@ -3146,34 +3205,100 @@ const AppLayout = () => {
const count = uniqueIds.length;
const suffix = count === 1 ? '' : 's';
const targetLabel =
target === null
? DEFAULT_FOLDER_NAME
: folderLabelMap.get(targetFolderId) || 'target folder';
setStatusMessage(
`Moved ${count} document${suffix} to ${targetLabel}.`,
'success',
);
setStatusMessage(`Moved ${count} document${suffix} to ${targetLabel}.`, 'success');
await refreshCurrentFolder();
if (targetFolderId && targetFolderId !== selectedFolder) {
await ensureFolderData(targetFolderId, { force: true, prefetchDepth: 1 });
if (updatedDocsMap.size) {
mapDocumentCaches((doc) => {
if (!doc || !uniqueIdSet.has(doc.id)) {
return doc;
}
const updated = updatedDocsMap.get(doc.id);
if (updated) {
return updated;
}
return { ...doc, folder_id: target };
});
} else {
mapDocumentCaches((doc) => {
if (!doc || !uniqueIdSet.has(doc.id)) {
return doc;
}
return { ...doc, folder_id: target };
});
}
if (uniqueIdSet.size) {
setDocuments((prev) => prev.filter((doc) => !uniqueIdSet.has(doc.id)));
setFolderContents((prev) => {
if (!prev.size) {
return prev;
}
let changed = false;
const next = new Map(prev);
movedDocs.forEach(({ id, sourceFolderId }) => {
const sourceKey = sourceFolderId || 'root';
const entry = next.get(sourceKey);
if (!entry?.documents?.length) {
return;
}
const filteredDocs = entry.documents.filter((doc) => doc.id !== id);
if (filteredDocs.length !== entry.documents.length) {
changed = true;
next.set(sourceKey, { ...entry, documents: filteredDocs });
}
});
return changed ? next : prev;
});
setSelectedRowKeys((prev) => pruneRowCollection(prev));
setSelectionOrder((prev) => pruneRowCollection(prev));
selectionOrderRef.current = pruneRowCollection(selectionOrderRef.current);
if (
selectionAnchorRef.current &&
isDocumentRowKey(selectionAnchorRef.current) &&
uniqueIdSet.has(getRowId(selectionAnchorRef.current))
) {
selectionAnchorRef.current = null;
}
if (focusedDocumentId && uniqueIdSet.has(focusedDocumentId)) {
setFocusedDocumentId(null);
}
if (
focusedRowKey &&
isDocumentRowKey(focusedRowKey) &&
uniqueIdSet.has(getRowId(focusedRowKey))
) {
setFocusedRowKey(null);
}
}
if (targetFolderId && targetFolderId !== selectedFolder) {
await ensureFolderData(targetFolderId, { force: true, prefetchDepth: 1 });
}
} catch (error) {
const message = error.response?.data?.error || 'Failed to move documents.';
notifyApiError(error, message);
} finally {
setLoading(false);
}
} catch (error) {
const message = error.response?.data?.error || 'Failed to move documents.';
notifyApiError(error, message);
} finally {
setLoading(false);
}
},
[
ensureFolderData,
refreshCurrentFolder,
selectedFolder,
folderLabelMap,
notifyApiError,
setStatusMessage,
],
},
[
documentLookup,
ensureFolderData,
folderLabelMap,
focusedDocumentId,
mapDocumentCaches,
notifyApiError,
selectedFolder,
setDocuments,
setFolderContents,
setFocusedDocumentId,
focusedRowKey,
setFocusedRowKey,
setSelectionOrder,
setSelectedRowKeys,
setStatusMessage,
],
);
const handleThumbnailRegeneration = useCallback(
+3 -3
View File
@@ -6,7 +6,7 @@ import {
EditIcon,
ViewListIcon,
ViewGridIcon,
DesktopIcon,
IconFileStack,
FolderIcon,
TrashIcon,
RefreshIcon,
@@ -539,7 +539,7 @@ const DocumentsTable = ({
aria-pressed={isDeskView}
title="Desk view"
>
<DesktopIcon className="view-toggle__icon" size={18} />
<IconFileStack className="view-toggle__icon" size={18} />
</button>
</div>
<button className="secondary" onClick={onRefresh}>
@@ -1113,7 +1113,7 @@ export const createDocumentsTableHeaderActions = ({
aria-pressed={isDeskView}
title="Desk view"
>
<DesktopIcon className="view-toggle__icon" size={18} />
<IconFileStack className="view-toggle__icon" size={18} />
</button>
</div>
<span className="main-content__actions-divider" aria-hidden="true">
+37
View File
@@ -1,3 +1,4 @@
import { useId } from 'react';
import {
IconChevronRight as TablerChevronRight,
IconDownload as TablerDownload,
@@ -202,6 +203,42 @@ export const MinusVerticalIcon = ({ className, size = '1em', stroke = 1.6, ...re
/>
);
export const IconFileStack = ({ className, size = 24, stroke = 160, ...rest }) => {
return (
<svg
className={composeClassName('icon', className)}
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={stroke}
strokeLinecap="round"
strokeLinejoin="round"
{...rest}
>
<defs>
<path
id="icon-file-stack-front"
d="M 5 5.173 l 0 -1.673 c 0 -1.1 0.9 -2 2 -2 l 10 0 c 1.1 0 2 0.9 2 2 l 0 14 c 0 0.7611 -0.4309 1.4265 -1.0608 1.7642 c 0.0548 -0.2682 0.0567 -0.5513 -0.0036 -0.835 l -2.8267 -13.2989 c -0.2356 -1.1082 -1.3351 -1.8223 -2.4433 -1.5867 l -7.6656 1.6294 Z"
/>
<path
id="icon-file-stack-left"
d="M 16.349 20.8725 l -10.075 2.1415 c -1.1082 0.2355 -2.2077 -0.4785 -2.4432 -1.5867 l -2.8268 -13.2989 c -0.2356 -1.1083 0.4784 -2.2077 1.5867 -2.4433 l 10.0749 -2.1415 c 1.1082 -0.2356 2.2077 0.4785 2.4433 1.5867 l 2.8267 13.2989 c 0.2356 1.1082 -0.4784 2.2077 -1.5866 2.4433 Z"
/>
<path
id="icon-file-stack-right"
d="M 17.9392 19.2642 c 0.6299 -0.3377 1.0608 -1.0031 1.0608 -1.7642 l 0 -11.4909 l 2.502 0.5318 c 1.0329 0.2195 1.6984 1.2443 1.4788 2.2772 l -2.6346 12.3951 c -0.2196 1.0329 -1.2443 1.6984 -2.2772 1.4789 l -5.1403 -1.0926 l 3.4203 -0.727 c 0.8245 -0.1753 1.4308 -0.8288 1.5902 -1.6083 Z"
/>
</defs>
<use href="#icon-file-stack-right" fill="none" />
<use href="#icon-file-stack-front" fill="none" />
<use href="#icon-file-stack-left" fill="none" />
</svg>
);
};
export const CloseIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
<IconX
className={composeClassName('icon', className)}