foo
This commit is contained in:
+151
-26
@@ -3132,7 +3132,66 @@ const AppLayout = () => {
|
|||||||
);
|
);
|
||||||
if (!uniqueIds.length) return;
|
if (!uniqueIds.length) return;
|
||||||
|
|
||||||
|
const uniqueIdSet = new Set(uniqueIds);
|
||||||
const target = targetFolderId === 'root' ? null : targetFolderId;
|
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);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
if (uniqueIds.length === 1) {
|
if (uniqueIds.length === 1) {
|
||||||
@@ -3146,34 +3205,100 @@ const AppLayout = () => {
|
|||||||
|
|
||||||
const count = uniqueIds.length;
|
const count = uniqueIds.length;
|
||||||
const suffix = count === 1 ? '' : 's';
|
const suffix = count === 1 ? '' : 's';
|
||||||
const targetLabel =
|
setStatusMessage(`Moved ${count} document${suffix} to ${targetLabel}.`, 'success');
|
||||||
target === null
|
|
||||||
? DEFAULT_FOLDER_NAME
|
|
||||||
: folderLabelMap.get(targetFolderId) || 'target folder';
|
|
||||||
setStatusMessage(
|
|
||||||
`Moved ${count} document${suffix} to ${targetLabel}.`,
|
|
||||||
'success',
|
|
||||||
);
|
|
||||||
|
|
||||||
await refreshCurrentFolder();
|
if (updatedDocsMap.size) {
|
||||||
if (targetFolderId && targetFolderId !== selectedFolder) {
|
mapDocumentCaches((doc) => {
|
||||||
await ensureFolderData(targetFolderId, { force: true, prefetchDepth: 1 });
|
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);
|
documentLookup,
|
||||||
} finally {
|
ensureFolderData,
|
||||||
setLoading(false);
|
folderLabelMap,
|
||||||
}
|
focusedDocumentId,
|
||||||
},
|
mapDocumentCaches,
|
||||||
[
|
notifyApiError,
|
||||||
ensureFolderData,
|
selectedFolder,
|
||||||
refreshCurrentFolder,
|
setDocuments,
|
||||||
selectedFolder,
|
setFolderContents,
|
||||||
folderLabelMap,
|
setFocusedDocumentId,
|
||||||
notifyApiError,
|
focusedRowKey,
|
||||||
setStatusMessage,
|
setFocusedRowKey,
|
||||||
],
|
setSelectionOrder,
|
||||||
|
setSelectedRowKeys,
|
||||||
|
setStatusMessage,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleThumbnailRegeneration = useCallback(
|
const handleThumbnailRegeneration = useCallback(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
EditIcon,
|
EditIcon,
|
||||||
ViewListIcon,
|
ViewListIcon,
|
||||||
ViewGridIcon,
|
ViewGridIcon,
|
||||||
DesktopIcon,
|
IconFileStack,
|
||||||
FolderIcon,
|
FolderIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
RefreshIcon,
|
RefreshIcon,
|
||||||
@@ -539,7 +539,7 @@ const DocumentsTable = ({
|
|||||||
aria-pressed={isDeskView}
|
aria-pressed={isDeskView}
|
||||||
title="Desk view"
|
title="Desk view"
|
||||||
>
|
>
|
||||||
<DesktopIcon className="view-toggle__icon" size={18} />
|
<IconFileStack className="view-toggle__icon" size={18} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button className="secondary" onClick={onRefresh}>
|
<button className="secondary" onClick={onRefresh}>
|
||||||
@@ -1113,7 +1113,7 @@ export const createDocumentsTableHeaderActions = ({
|
|||||||
aria-pressed={isDeskView}
|
aria-pressed={isDeskView}
|
||||||
title="Desk view"
|
title="Desk view"
|
||||||
>
|
>
|
||||||
<DesktopIcon className="view-toggle__icon" size={18} />
|
<IconFileStack className="view-toggle__icon" size={18} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span className="main-content__actions-divider" aria-hidden="true">
|
<span className="main-content__actions-divider" aria-hidden="true">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useId } from 'react';
|
||||||
import {
|
import {
|
||||||
IconChevronRight as TablerChevronRight,
|
IconChevronRight as TablerChevronRight,
|
||||||
IconDownload as TablerDownload,
|
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 }) => (
|
export const CloseIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
<IconX
|
<IconX
|
||||||
className={composeClassName('icon', className)}
|
className={composeClassName('icon', className)}
|
||||||
|
|||||||
Reference in New Issue
Block a user