Merge remote-tracking branch 'ui/ui' into dev

This commit is contained in:
2025-11-11 15:29:07 +01:00
44 changed files with 1998 additions and 2148 deletions
@@ -87,7 +87,7 @@ const useDocumentCorrespondentActions = ({
if (!target) {
try {
target = await handleCorrespondentCreate({ name: trimmed });
} catch (error) {
} catch {
return;
}
}
@@ -127,4 +127,3 @@ const useDocumentCorrespondentActions = ({
};
export default useDocumentCorrespondentActions;
@@ -29,6 +29,10 @@ const useDocumentDragHandlers = ({
({ documents = [], folders = [] } = {}) => {
destroyDragPreview();
if (typeof document === 'undefined') {
return null;
}
const docEntries = (documents || []).filter(Boolean);
const folderEntries = (folders || []).filter(Boolean);
const totalCount = docEntries.length + folderEntries.length;
@@ -53,24 +57,100 @@ const useDocumentDragHandlers = ({
const wrapper = document.createElement('div');
wrapper.className = 'document-drag-preview';
wrapper.style.setProperty('--drag-preview-size', `${canvasSize}px`);
wrapper.style.width = `${canvasSize}px`;
wrapper.style.height = `${canvasSize}px`;
visibleItems.forEach((item, index) => {
const slot = document.createElement('div');
slot.className = 'document-drag-preview__item';
slot.style.setProperty('--index', String(index));
slot.style.width = `${size}px`;
slot.style.height = `${size}px`;
const layer = document.createElement('div');
layer.className = 'document-drag-preview__item';
layer.style.setProperty('--index', String(index));
const rotationMagnitude = Math.random() * 8 + 2;
const rotation = (index % 2 === 0 ? 1 : -1) * rotationMagnitude;
layer.style.setProperty('--rotation-deg', `${rotation}deg`);
if (item.type === 'document') {
slot.textContent = item.payload?.title || 'Document';
const doc = item.payload;
const rowEl = doc?.id
? document.getElementById(`document-row-${doc.id}`)
|| document.getElementById(`document-card-${doc.id}`)
: null;
const wrapperEl = rowEl?.querySelector('.document-thumbnail-wrapper');
const thumbnailEl = rowEl?.querySelector('.document-thumbnail');
const placeholderEl = rowEl?.querySelector('.thumb-placeholder');
const aspectAttr = wrapperEl?.dataset?.thumbnailAspect;
const aspectRatio = aspectAttr ? parseFloat(aspectAttr) : null;
let thumbWidth = size;
let thumbHeight = size;
if (Number.isFinite(aspectRatio) && aspectRatio > 0) {
if (aspectRatio >= 1) {
thumbWidth = size;
thumbHeight = Math.max(size / aspectRatio, size * 0.5);
} else {
thumbHeight = size;
thumbWidth = Math.max(size * aspectRatio, size * 0.5);
}
}
layer.style.width = `${Math.round(thumbWidth)}px`;
layer.style.height = `${Math.round(thumbHeight)}px`;
const thumbSrc = thumbnailEl?.currentSrc || thumbnailEl?.src || null;
if (thumbSrc) {
layer.classList.add('document-drag-preview__item--image');
layer.style.backgroundImage = `url("${thumbSrc}")`;
} else if (placeholderEl instanceof HTMLElement) {
const clone = placeholderEl.cloneNode(true);
clone.style.pointerEvents = 'none';
layer.appendChild(clone);
} else {
layer.textContent = doc?.title || 'Document';
}
} else {
slot.textContent = 'Folder';
const payload = item.payload;
const folderId = typeof payload === 'string' ? payload : payload?.id;
const rowEl = folderId
? document.getElementById(`folder-row-${folderId}`)
|| document.getElementById(`folder-card-${folderId}`)
: null;
const iconEl = rowEl?.querySelector('.thumb-icon, .folder-card__icon');
layer.style.width = `${size}px`;
layer.style.height = `${size}px`;
layer.classList.add('document-drag-preview__item--folder');
let content = null;
if (iconEl instanceof HTMLElement) {
const cloneSource = iconEl.classList.contains('folder-card__icon')
? iconEl.querySelector('svg') || iconEl
: iconEl;
content = cloneSource.cloneNode(true);
content.classList.add('document-drag-preview__folder-thumb');
const svg = content.querySelector('svg');
if (svg) {
svg.setAttribute('width', '48');
svg.setAttribute('height', '48');
}
}
if (!content) {
content = document.createElement('div');
content.className = 'document-drag-preview__folder-placeholder';
content.textContent = 'Folder';
}
layer.appendChild(content);
}
wrapper.appendChild(slot);
wrapper.appendChild(layer);
});
if (totalCount > 1) {
const badge = document.createElement('div');
badge.className = 'document-drag-preview__count';
badge.textContent = `${totalCount}`;
wrapper.appendChild(badge);
}
document.body.appendChild(wrapper);
dragPreviewRef.current = wrapper;
return wrapper;
@@ -97,7 +177,7 @@ const useDocumentDragHandlers = ({
: isGridView
? [...selectedDocumentIds, documentId]
: [documentId];
const folderSelection = selectedFolderIds.length ? [...selectedFolderIds] : [];
const folderSelection = [];
if (!isAlreadySelected && !isGridView) {
applySelection([documentKey], {
@@ -143,7 +223,6 @@ const useDocumentDragHandlers = ({
},
[
selectedDocumentIds,
selectedFolderIds,
applySelection,
documentLookup,
createDragPreview,
@@ -210,11 +289,19 @@ const useDocumentDragHandlers = ({
console.warn('[documents] Failed to populate folder drag payload', error);
}
const previewDocs = effectiveDocumentSelection
.map((id) => documentLookup.get(id) || null)
.filter(Boolean);
createDragPreview({ documents: previewDocs, folders: uniqueFolders });
const previewNode = createDragPreview({
documents: effectiveDocumentSelection
.map((id) => documentLookup.get(id) || null)
.filter(Boolean),
folders: uniqueFolders,
});
event.currentTarget.classList.add('dragging');
if (previewNode) {
const width = previewNode.offsetWidth || 96;
const height = previewNode.offsetHeight || 96;
event.dataTransfer.setDragImage(previewNode, width / 2, height / 2);
}
},
[
selectedFolderIds,
@@ -175,7 +175,6 @@ const useDocumentUploads = ({
const entries = [];
let batch = [];
do {
// eslint-disable-next-line no-await-in-loop
batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject));
if (batch.length) {
entries.push(...batch);
@@ -203,7 +202,6 @@ const useDocumentUploads = ({
const reader = entry.createReader();
const entries = await readAllEntries(reader);
for (const child of entries) {
// eslint-disable-next-line no-await-in-loop
await walkEntry(child, nextAncestors);
}
}
@@ -306,7 +304,6 @@ const useDocumentUploads = ({
updateQueueItem(queueItem.id, patch);
Object.assign(queueItem, patch);
}
// eslint-disable-next-line no-await-in-loop
const destinationId = segments.length
? await ensureFolderPathOnServer(baseFolderId, segments)
: baseFolderId;
@@ -316,7 +313,6 @@ const useDocumentUploads = ({
(targetFolderId && targetFolderId !== 'root' ? targetFolderId : 'root');
try {
// eslint-disable-next-line no-await-in-loop
const { duplicate, statusCode, document, conflictDocumentId } = await uploadFile(
file,
uploadTarget,
@@ -50,6 +50,11 @@ import useDocumentDragHandlers from './useDocumentDragHandlers';
import useDocumentMutations from './useDocumentMutations';
import useDetailWorkspace from '../../detail/useDetailWorkspace';
const EntryType = Object.freeze({
document: 'document',
folder: 'folder',
});
const noop = () => {};
const useDocumentsWorkspace = ({
@@ -1185,19 +1190,15 @@ const useDocumentsWorkspace = ({
const handleEntryPointerCore = useEntryPointerCore({
resolveDocumentRowKey,
resolveFolderRowKey,
onSelectDocument: (documentId, event, { rowKey }) => {
const key = rowKey || resolveDocumentRowKey(documentId);
onSelectEntry: (entry, event, { rowKey, modifierClick, primaryClick }) => {
const { type, id } = entry;
const key = rowKey
|| (type === EntryType.document ? resolveDocumentRowKey(id) : resolveFolderRowKey(id));
if (key) {
handleEntrySelection(key, event);
}
},
onSelectFolder: (folderId, event, { modifierClick, primaryClick, rowKey }) => {
const key = rowKey || resolveFolderRowKey(folderId);
if (key) {
handleEntrySelection(key, event);
}
if (!modifierClick && primaryClick) {
selectFolder(folderId);
if (type === EntryType.folder && !modifierClick && primaryClick) {
selectFolder(id);
}
},
});
@@ -85,9 +85,8 @@ const useFolderTree = ({
})();
setFocusedDocumentId(nextFocus);
selectionAnchorRef.current = nextDocKeys.length
? nextDocKeys[nextDocKeys.length - 1]
: null;
const nextAnchor = mergedSelection.length ? mergedSelection[mergedSelection.length - 1] : null;
selectionAnchorRef.current = nextAnchor;
selectionOrderRef.current = mergedSelection;
setSelectionOrder(mergedSelection);
},
@@ -96,7 +96,6 @@ const useFolderTreeActions = ({
const refreshTargets = new Set([previousParentKey, targetKey]);
for (const key of refreshTargets) {
// eslint-disable-next-line no-await-in-loop
await ensureFolderData(key === 'root' ? 'root' : key, { force: true, prefetchDepth: 1 });
}
@@ -112,7 +111,6 @@ const useFolderTreeActions = ({
const refreshTargets = new Set([previousParentKey, targetKey]);
for (const key of refreshTargets) {
// eslint-disable-next-line no-await-in-loop
await ensureFolderData(key === 'root' ? 'root' : key, { force: true, prefetchDepth: 1 });
}
}
@@ -516,7 +514,6 @@ const useFolderTreeActions = ({
}
for (const sourceId of folderIds) {
// eslint-disable-next-line no-await-in-loop
await moveFolder(sourceId, folderId);
}
}