linting
This commit is contained in:
@@ -34,6 +34,84 @@ const DEBUG_DRAG = false;
|
|||||||
const DEBUG_FOCUS = true;
|
const DEBUG_FOCUS = true;
|
||||||
const DEBUG_DROP = true;
|
const DEBUG_DROP = true;
|
||||||
|
|
||||||
|
const signedDistance = (ax, ay, bx, by, px, py) => (bx - ax) * (py - ay) - (by - ay) * (px - ax);
|
||||||
|
|
||||||
|
const clipPolygonWithEdge = (subject, edgeStart, edgeEnd) => {
|
||||||
|
if (!subject.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result = [];
|
||||||
|
let prev = subject[subject.length - 1];
|
||||||
|
let prevInside = signedDistance(edgeStart.x, edgeStart.y, edgeEnd.x, edgeEnd.y, prev.x, prev.y) >= 0;
|
||||||
|
subject.forEach((curr) => {
|
||||||
|
const currInside = signedDistance(edgeStart.x, edgeStart.y, edgeEnd.x, edgeEnd.y, curr.x, curr.y) >= 0;
|
||||||
|
if (currInside !== prevInside) {
|
||||||
|
const dx = curr.x - prev.x;
|
||||||
|
const dy = curr.y - prev.y;
|
||||||
|
const denom = (edgeEnd.x - edgeStart.x) * dy - (edgeEnd.y - edgeStart.y) * dx;
|
||||||
|
if (Math.abs(denom) > 1e-9) {
|
||||||
|
const t = ((edgeStart.x - prev.x) * dy - (edgeStart.y - prev.y) * dx) / denom;
|
||||||
|
result.push({
|
||||||
|
x: edgeStart.x + t * (edgeEnd.x - edgeStart.x),
|
||||||
|
y: edgeStart.y + t * (edgeEnd.y - edgeStart.y),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currInside) {
|
||||||
|
result.push(curr);
|
||||||
|
}
|
||||||
|
prev = curr;
|
||||||
|
prevInside = currInside;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clipPolygon = (subject, clipShape) => {
|
||||||
|
if (!subject.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let output = subject;
|
||||||
|
let prev = clipShape[clipShape.length - 1];
|
||||||
|
for (let index = 0; index < clipShape.length; index += 1) {
|
||||||
|
const curr = clipShape[index];
|
||||||
|
output = clipPolygonWithEdge(output, prev, curr);
|
||||||
|
if (!output.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPointInsideConvex = (point, polygon) => {
|
||||||
|
if (!polygon.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let prev = polygon[polygon.length - 1];
|
||||||
|
for (let index = 0; index < polygon.length; index += 1) {
|
||||||
|
const curr = polygon[index];
|
||||||
|
if (signedDistance(prev.x, prev.y, curr.x, curr.y, point.x, point.y) < -1e-6) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const polygonCentroid = (polygon) => {
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
polygon.forEach((point) => {
|
||||||
|
x += point.x;
|
||||||
|
y += point.y;
|
||||||
|
});
|
||||||
|
const count = polygon.length || 1;
|
||||||
|
return {
|
||||||
|
x: x / count,
|
||||||
|
y: y / count,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const readTransferData = (dataTransfer, mimeTypes) => {
|
const readTransferData = (dataTransfer, mimeTypes) => {
|
||||||
if (!dataTransfer) {
|
if (!dataTransfer) {
|
||||||
return null;
|
return null;
|
||||||
@@ -866,84 +944,6 @@ const DesktopWorkspace = ({
|
|||||||
},
|
},
|
||||||
[resolvePreviewDimensions],
|
[resolvePreviewDimensions],
|
||||||
);
|
);
|
||||||
const signedDistance = (ax, ay, bx, by, px, py) => (bx - ax) * (py - ay) - (by - ay) * (px - ax);
|
|
||||||
|
|
||||||
const clipPolygonWithEdge = (subject, edgeStart, edgeEnd) => {
|
|
||||||
if (!subject.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const result = [];
|
|
||||||
let prev = subject[subject.length - 1];
|
|
||||||
let prevInside = signedDistance(edgeStart.x, edgeStart.y, edgeEnd.x, edgeEnd.y, prev.x, prev.y) >= 0;
|
|
||||||
subject.forEach((curr) => {
|
|
||||||
const currInside = signedDistance(edgeStart.x, edgeStart.y, edgeEnd.x, edgeEnd.y, curr.x, curr.y) >= 0;
|
|
||||||
if (currInside !== prevInside) {
|
|
||||||
const dx = curr.x - prev.x;
|
|
||||||
const dy = curr.y - prev.y;
|
|
||||||
const denom = (edgeEnd.x - edgeStart.x) * dy - (edgeEnd.y - edgeStart.y) * dx;
|
|
||||||
if (Math.abs(denom) > 1e-9) {
|
|
||||||
const t = ((edgeStart.x - prev.x) * dy - (edgeStart.y - prev.y) * dx) / denom;
|
|
||||||
result.push({
|
|
||||||
x: edgeStart.x + t * (edgeEnd.x - edgeStart.x),
|
|
||||||
y: edgeStart.y + t * (edgeEnd.y - edgeStart.y),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (currInside) {
|
|
||||||
result.push(curr);
|
|
||||||
}
|
|
||||||
prev = curr;
|
|
||||||
prevInside = currInside;
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const clipPolygon = (subject, clipShape) => {
|
|
||||||
if (!subject.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
let output = subject;
|
|
||||||
let prev = clipShape[clipShape.length - 1];
|
|
||||||
for (let index = 0; index < clipShape.length; index += 1) {
|
|
||||||
const curr = clipShape[index];
|
|
||||||
output = clipPolygonWithEdge(output, prev, curr);
|
|
||||||
if (!output.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
prev = curr;
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isPointInsideConvex = (point, polygon) => {
|
|
||||||
if (!polygon.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let prev = polygon[polygon.length - 1];
|
|
||||||
for (let index = 0; index < polygon.length; index += 1) {
|
|
||||||
const curr = polygon[index];
|
|
||||||
if (signedDistance(prev.x, prev.y, curr.x, curr.y, point.x, point.y) < -1e-6) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
prev = curr;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const polygonCentroid = (polygon) => {
|
|
||||||
let x = 0;
|
|
||||||
let y = 0;
|
|
||||||
polygon.forEach((point) => {
|
|
||||||
x += point.x;
|
|
||||||
y += point.y;
|
|
||||||
});
|
|
||||||
const count = polygon.length || 1;
|
|
||||||
return {
|
|
||||||
x: x / count,
|
|
||||||
y: y / count,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const recalcVisibleDocIds = useCallback(() => {
|
const recalcVisibleDocIds = useCallback(() => {
|
||||||
const layoutMap = layoutRef.current;
|
const layoutMap = layoutRef.current;
|
||||||
const canvasWidth = canvasSize.width || DEFAULT_CANVAS_WIDTH;
|
const canvasWidth = canvasSize.width || DEFAULT_CANVAS_WIDTH;
|
||||||
@@ -1069,7 +1069,6 @@ const recalcVisibleDocIds = useCallback(() => {
|
|||||||
canvasSize.height,
|
canvasSize.height,
|
||||||
ensureDocumentSize,
|
ensureDocumentSize,
|
||||||
documentLookup,
|
documentLookup,
|
||||||
docSizeVersion,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const syncLayoutSnapshot = useCallback(() => {
|
const syncLayoutSnapshot = useCallback(() => {
|
||||||
@@ -1080,7 +1079,8 @@ const syncLayoutSnapshot = useCallback(() => {
|
|||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container) return () => {};
|
if (!container) return () => {};
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
const nodeEnv = typeof globalThis !== 'undefined' ? globalThis.process?.env?.NODE_ENV : undefined;
|
||||||
|
if (nodeEnv !== 'production') {
|
||||||
console.log('[skeuo] canvas element', container);
|
console.log('[skeuo] canvas element', container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1207,7 +1207,7 @@ const syncLayoutSnapshot = useCallback(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
recalcVisibleDocIds();
|
recalcVisibleDocIds();
|
||||||
}, [recalcVisibleDocIds, items.length, canvasSize.width, canvasSize.height]);
|
}, [recalcVisibleDocIds, items.length, canvasSize.width, canvasSize.height, docSizeVersion]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (draggingId && !items.some((doc) => doc.id === draggingId)) {
|
if (draggingId && !items.some((doc) => doc.id === draggingId)) {
|
||||||
@@ -1849,16 +1849,12 @@ const DesktopWorkspaceView = () => {
|
|||||||
closeOverlay,
|
closeOverlay,
|
||||||
overlayOriginRect,
|
overlayOriginRect,
|
||||||
overlayOriginTransform,
|
overlayOriginTransform,
|
||||||
docSizeVersion,
|
|
||||||
} = useDesktopContext();
|
} = useDesktopContext();
|
||||||
|
|
||||||
const { handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel } =
|
const { handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel } =
|
||||||
useDocumentDrag();
|
useDocumentDrag();
|
||||||
|
|
||||||
const allSizesReady = useMemo(
|
const allSizesReady = items.every((doc) => ensureDocumentSize(doc));
|
||||||
() => items.every((doc) => ensureDocumentSize(doc)),
|
|
||||||
[items, ensureDocumentSize, docSizeVersion],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ const useDocumentDrag = () => {
|
|||||||
recalcVisibleDocIds();
|
recalcVisibleDocIds();
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
bringToFront,
|
||||||
canvasPadding,
|
canvasPadding,
|
||||||
canvasSize.height,
|
canvasSize.height,
|
||||||
canvasSize.width,
|
canvasSize.width,
|
||||||
|
|||||||
@@ -828,11 +828,13 @@ const DetailPanel = ({
|
|||||||
warmNavigator(stackPreviewNavigator);
|
warmNavigator(stackPreviewNavigator);
|
||||||
}, [
|
}, [
|
||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
|
singlePreviewNavigator,
|
||||||
singlePreviewNavigator.documentId,
|
singlePreviewNavigator.documentId,
|
||||||
singlePreviewNavigator.asset,
|
singlePreviewNavigator.asset,
|
||||||
singlePreviewNavigator.ordinal,
|
singlePreviewNavigator.ordinal,
|
||||||
singlePreviewNavigator.canGoPrev,
|
singlePreviewNavigator.canGoPrev,
|
||||||
singlePreviewNavigator.canGoNext,
|
singlePreviewNavigator.canGoNext,
|
||||||
|
stackPreviewNavigator,
|
||||||
stackPreviewNavigator.documentId,
|
stackPreviewNavigator.documentId,
|
||||||
stackPreviewNavigator.asset,
|
stackPreviewNavigator.asset,
|
||||||
stackPreviewNavigator.ordinal,
|
stackPreviewNavigator.ordinal,
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ const DocumentThumbnailImage = ({
|
|||||||
} else {
|
} else {
|
||||||
delete node.dataset.thumbnailAspect;
|
delete node.dataset.thumbnailAspect;
|
||||||
}
|
}
|
||||||
}, [aspectRatio]);
|
}, [aspectRatio, visibilityRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="document-thumbnail-wrapper" ref={visibilityRef}>
|
<div className="document-thumbnail-wrapper" ref={visibilityRef}>
|
||||||
|
|||||||
+408
-132
@@ -291,6 +291,90 @@ const resolveFolderRowKey = (folderId) =>
|
|||||||
const hasFiles = (event) =>
|
const hasFiles = (event) =>
|
||||||
Array.from(event.dataTransfer?.types || []).includes('Files');
|
Array.from(event.dataTransfer?.types || []).includes('Files');
|
||||||
|
|
||||||
|
const isAssetEquivalent = (lhs, rhs) => {
|
||||||
|
if (!lhs || !rhs) return false;
|
||||||
|
const lhsView = createAssetView(lhs);
|
||||||
|
const rhsView = createAssetView(rhs);
|
||||||
|
const lhsPrimaryMetadata = lhsView.getPrimaryMetadata() || lhs?.metadata;
|
||||||
|
const rhsPrimaryMetadata = rhsView.getPrimaryMetadata() || rhs?.metadata;
|
||||||
|
const lhsCardinality = lhsView.getCardinality() || lhs?.cardinality || null;
|
||||||
|
const rhsCardinality = rhsView.getCardinality() || rhs?.cardinality || null;
|
||||||
|
const lhsObjects = lhsView.getObjects();
|
||||||
|
const rhsObjects = rhsView.getObjects();
|
||||||
|
const objectsComparable =
|
||||||
|
lhsObjects.length === rhsObjects.length
|
||||||
|
&& lhsObjects.every((entry, index) => {
|
||||||
|
const other = rhsObjects[index];
|
||||||
|
if (!other) return false;
|
||||||
|
if (entry.ordinal !== other.ordinal) return false;
|
||||||
|
if (entry.url && other.url && entry.url === other.url) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!entry.url && !other.url) {
|
||||||
|
return JSON.stringify(entry.metadata || null) === JSON.stringify(other.metadata || null);
|
||||||
|
}
|
||||||
|
return entry.url === other.url;
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
lhs.id === rhs.id
|
||||||
|
&& lhs.url === rhs.url
|
||||||
|
&& lhsPrimaryMetadata?.width === rhsPrimaryMetadata?.width
|
||||||
|
&& lhsPrimaryMetadata?.height === rhsPrimaryMetadata?.height
|
||||||
|
&& lhs.mime_type === rhs.mime_type
|
||||||
|
&& lhs.asset_type === rhs.asset_type
|
||||||
|
&& lhs.created_at === rhs.created_at
|
||||||
|
&& lhsCardinality === rhsCardinality
|
||||||
|
&& objectsComparable
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeAssetIntoGroup = (group, assetData) => {
|
||||||
|
if (!assetData || !assetData.asset_type) {
|
||||||
|
if (Array.isArray(group)) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
return group || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(group) || !group) {
|
||||||
|
const list = Array.isArray(group) ? group : [];
|
||||||
|
const index = list.findIndex((item) => item?.id === assetData.id);
|
||||||
|
if (index >= 0) {
|
||||||
|
const existing = list[index];
|
||||||
|
if (isAssetEquivalent(existing, assetData)) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
const next = list.slice();
|
||||||
|
next[index] = { ...existing, ...assetData };
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
return list.concat({ ...assetData });
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = assetData.asset_type;
|
||||||
|
const previous = group?.[key];
|
||||||
|
if (previous && isAssetEquivalent(previous, assetData)) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = { ...(group || {}) };
|
||||||
|
next[key] = { ...(previous || {}), ...assetData };
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeAssetIntoDocument = (doc, assetData) => {
|
||||||
|
if (!doc) return doc;
|
||||||
|
const existingGroup = doc.current_version?.assets || null;
|
||||||
|
const nextGroup = mergeAssetIntoGroup(existingGroup, assetData);
|
||||||
|
if (nextGroup === existingGroup) {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
const updatedCurrentVersion = doc.current_version
|
||||||
|
? { ...doc.current_version, assets: nextGroup }
|
||||||
|
: { assets: nextGroup };
|
||||||
|
return { ...doc, current_version: updatedCurrentVersion };
|
||||||
|
};
|
||||||
|
|
||||||
const createRootNode = () => ({
|
const createRootNode = () => ({
|
||||||
id: 'root',
|
id: 'root',
|
||||||
name: DEFAULT_FOLDER_NAME,
|
name: DEFAULT_FOLDER_NAME,
|
||||||
@@ -591,89 +675,6 @@ const AppLayout = () => {
|
|||||||
return getAssetFromVersion(doc.current_version || null, type);
|
return getAssetFromVersion(doc.current_version || null, type);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const isAssetEquivalent = (lhs, rhs) => {
|
|
||||||
if (!lhs || !rhs) return false;
|
|
||||||
const lhsView = createAssetView(lhs);
|
|
||||||
const rhsView = createAssetView(rhs);
|
|
||||||
const lhsPrimaryMetadata = lhsView.getPrimaryMetadata() || lhs?.metadata;
|
|
||||||
const rhsPrimaryMetadata = rhsView.getPrimaryMetadata() || rhs?.metadata;
|
|
||||||
const lhsCardinality = lhsView.getCardinality() || lhs?.cardinality || null;
|
|
||||||
const rhsCardinality = rhsView.getCardinality() || rhs?.cardinality || null;
|
|
||||||
const lhsObjects = lhsView.getObjects();
|
|
||||||
const rhsObjects = rhsView.getObjects();
|
|
||||||
const objectsComparable = lhsObjects.length === rhsObjects.length
|
|
||||||
&& lhsObjects.every((entry, index) => {
|
|
||||||
const other = rhsObjects[index];
|
|
||||||
if (!other) return false;
|
|
||||||
if (entry.ordinal !== other.ordinal) return false;
|
|
||||||
if (entry.url && other.url && entry.url === other.url) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!entry.url && !other.url) {
|
|
||||||
return JSON.stringify(entry.metadata || null) === JSON.stringify(other.metadata || null);
|
|
||||||
}
|
|
||||||
return entry.url === other.url;
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
lhs.id === rhs.id &&
|
|
||||||
lhs.url === rhs.url &&
|
|
||||||
lhsPrimaryMetadata?.width === rhsPrimaryMetadata?.width &&
|
|
||||||
lhsPrimaryMetadata?.height === rhsPrimaryMetadata?.height &&
|
|
||||||
lhs.mime_type === rhs.mime_type &&
|
|
||||||
lhs.asset_type === rhs.asset_type &&
|
|
||||||
lhs.created_at === rhs.created_at &&
|
|
||||||
lhsCardinality === rhsCardinality &&
|
|
||||||
objectsComparable
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mergeAssetIntoGroup = (group, assetData) => {
|
|
||||||
if (!assetData || !assetData.asset_type) {
|
|
||||||
if (Array.isArray(group)) {
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
return group || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(group) || !group) {
|
|
||||||
const list = Array.isArray(group) ? group : [];
|
|
||||||
const index = list.findIndex((item) => item?.id === assetData.id);
|
|
||||||
if (index >= 0) {
|
|
||||||
const existing = list[index];
|
|
||||||
if (isAssetEquivalent(existing, assetData)) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
const next = list.slice();
|
|
||||||
next[index] = { ...existing, ...assetData };
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
return list.concat({ ...assetData });
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = assetData.asset_type;
|
|
||||||
const previous = group?.[key];
|
|
||||||
if (previous && isAssetEquivalent(previous, assetData)) {
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = { ...(group || {}) };
|
|
||||||
next[key] = { ...(previous || {}), ...assetData };
|
|
||||||
return next;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mergeAssetIntoDocument = (doc, assetData) => {
|
|
||||||
if (!doc) return doc;
|
|
||||||
const existingGroup = doc.current_version?.assets || null;
|
|
||||||
const nextGroup = mergeAssetIntoGroup(existingGroup, assetData);
|
|
||||||
if (nextGroup === existingGroup) {
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
const updatedCurrentVersion = doc.current_version
|
|
||||||
? { ...doc.current_version, assets: nextGroup }
|
|
||||||
: { assets: nextGroup };
|
|
||||||
return { ...doc, current_version: updatedCurrentVersion };
|
|
||||||
};
|
|
||||||
|
|
||||||
const bootstrapInitializedRef = useRef(false);
|
const bootstrapInitializedRef = useRef(false);
|
||||||
const selectionInitializedRef = useRef(false);
|
const selectionInitializedRef = useRef(false);
|
||||||
const dragCounterRef = useRef(0);
|
const dragCounterRef = useRef(0);
|
||||||
@@ -1557,7 +1558,7 @@ const AppLayout = () => {
|
|||||||
|
|
||||||
return enriched;
|
return enriched;
|
||||||
},
|
},
|
||||||
[api, assetManager, folderContents],
|
[assetManager, folderContents],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isInvalidFolderDrop = useCallback(
|
const isInvalidFolderDrop = useCallback(
|
||||||
@@ -1681,7 +1682,15 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, folderNodes, ensureFolderData, selectedFolder, setSelectedFolder, setFolderNodes, notifyApiError],
|
[
|
||||||
|
folderNodes,
|
||||||
|
ensureFolderData,
|
||||||
|
selectedFolder,
|
||||||
|
setSelectedFolder,
|
||||||
|
setFolderNodes,
|
||||||
|
notifyApiError,
|
||||||
|
setStatusMessage,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const refreshTags = useCallback(async () => {
|
const refreshTags = useCallback(async () => {
|
||||||
@@ -1698,7 +1707,7 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
notifyApiError(error, 'Unable to load tags.');
|
notifyApiError(error, 'Unable to load tags.');
|
||||||
}
|
}
|
||||||
}, [api, notifyApiError]);
|
}, [notifyApiError]);
|
||||||
|
|
||||||
const refreshCorrespondents = useCallback(async () => {
|
const refreshCorrespondents = useCallback(async () => {
|
||||||
const requestTenantId = tenantIdRef.current;
|
const requestTenantId = tenantIdRef.current;
|
||||||
@@ -1714,7 +1723,7 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
notifyApiError(error, 'Unable to load correspondents.');
|
notifyApiError(error, 'Unable to load correspondents.');
|
||||||
}
|
}
|
||||||
}, [api, notifyApiError]);
|
}, [notifyApiError]);
|
||||||
|
|
||||||
const refreshWebdavTokens = useCallback(async () => {
|
const refreshWebdavTokens = useCallback(async () => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -1729,7 +1738,7 @@ const AppLayout = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setWebdavTokensLoading(false);
|
setWebdavTokensLoading(false);
|
||||||
}
|
}
|
||||||
}, [api, notifyApiError, token]);
|
}, [notifyApiError, token]);
|
||||||
|
|
||||||
const createWebdavToken = useCallback(
|
const createWebdavToken = useCallback(
|
||||||
async ({ label, expires_at } = {}) => {
|
async ({ label, expires_at } = {}) => {
|
||||||
@@ -1766,7 +1775,7 @@ const AppLayout = () => {
|
|||||||
setCreatingWebdavToken(false);
|
setCreatingWebdavToken(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, creatingWebdavToken, notifyApiError, refreshWebdavTokens, setStatusMessage],
|
[creatingWebdavToken, notifyApiError, refreshWebdavTokens, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteWebdavToken = useCallback(
|
const deleteWebdavToken = useCallback(
|
||||||
@@ -1787,7 +1796,7 @@ const AppLayout = () => {
|
|||||||
setDeletingWebdavTokenId(null);
|
setDeletingWebdavTokenId(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, refreshWebdavTokens, notifyApiError, setStatusMessage],
|
[refreshWebdavTokens, notifyApiError, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const dismissCreatedWebdavToken = useCallback(() => {
|
const dismissCreatedWebdavToken = useCallback(() => {
|
||||||
@@ -1836,7 +1845,7 @@ const AppLayout = () => {
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, refreshTags, notifyApiError, setStatusMessage],
|
[refreshTags, notifyApiError, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTagCreate = useCallback(
|
const handleTagCreate = useCallback(
|
||||||
@@ -1852,7 +1861,7 @@ const AppLayout = () => {
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, refreshTags, notifyApiError, setStatusMessage, tagManager],
|
[refreshTags, notifyApiError, setStatusMessage, tagManager],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCorrespondentUpdate = useCallback(
|
const handleCorrespondentUpdate = useCallback(
|
||||||
@@ -1885,7 +1894,7 @@ const AppLayout = () => {
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, refreshCorrespondents, notifyApiError, setStatusMessage],
|
[refreshCorrespondents, notifyApiError, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCorrespondentCreate = useCallback(
|
const handleCorrespondentCreate = useCallback(
|
||||||
@@ -1905,7 +1914,7 @@ const AppLayout = () => {
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, refreshCorrespondents, notifyApiError, setStatusMessage],
|
[refreshCorrespondents, notifyApiError, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCorrespondentDelete = useCallback(
|
const handleCorrespondentDelete = useCallback(
|
||||||
@@ -1939,13 +1948,11 @@ const AppLayout = () => {
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, refreshCorrespondents, notifyApiError, setStatusMessage, mapDocumentCaches],
|
[refreshCorrespondents, notifyApiError, setStatusMessage, mapDocumentCaches],
|
||||||
);
|
);
|
||||||
|
|
||||||
async function handleDocumentCorrespondentAttach(
|
const handleDocumentCorrespondentAttach = useCallback(
|
||||||
{ documentId, correspondentId },
|
async ({ documentId, correspondentId }, { notify = true, refresh = true } = {}) => {
|
||||||
{ notify = true, refresh = true } = {},
|
|
||||||
) {
|
|
||||||
if (!documentId || !correspondentId) {
|
if (!documentId || !correspondentId) {
|
||||||
throw new Error('Missing document or correspondent.');
|
throw new Error('Missing document or correspondent.');
|
||||||
}
|
}
|
||||||
@@ -1966,12 +1973,12 @@ const AppLayout = () => {
|
|||||||
notifyApiError(error, message);
|
notifyApiError(error, message);
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[notifyApiError, refreshCurrentFolder, setStatusMessage],
|
||||||
|
);
|
||||||
|
|
||||||
async function handleCorrespondentRemove(
|
const handleCorrespondentRemove = useCallback(
|
||||||
{ documentId, correspondentId },
|
async ({ documentId, correspondentId }, { notify = true, refresh = true } = {}) => {
|
||||||
{ notify = true, refresh = true } = {},
|
|
||||||
) {
|
|
||||||
if (!documentId || !correspondentId) {
|
if (!documentId || !correspondentId) {
|
||||||
throw new Error('Missing document or correspondent.');
|
throw new Error('Missing document or correspondent.');
|
||||||
}
|
}
|
||||||
@@ -1989,7 +1996,9 @@ const AppLayout = () => {
|
|||||||
notifyApiError(error, message);
|
notifyApiError(error, message);
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[notifyApiError, refreshCurrentFolder, setStatusMessage],
|
||||||
|
);
|
||||||
|
|
||||||
const handleCorrespondentAdd = useCallback(
|
const handleCorrespondentAdd = useCallback(
|
||||||
async ({ document, name, input }) => {
|
async ({ document, name, input }) => {
|
||||||
@@ -2070,7 +2079,6 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
api,
|
|
||||||
refreshTags,
|
refreshTags,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
@@ -2367,7 +2375,6 @@ const AppLayout = () => {
|
|||||||
[
|
[
|
||||||
correspondentLookupByName,
|
correspondentLookupByName,
|
||||||
handleCorrespondentCreate,
|
handleCorrespondentCreate,
|
||||||
api,
|
|
||||||
refreshCurrentFolder,
|
refreshCurrentFolder,
|
||||||
resolveTargetDocumentIds,
|
resolveTargetDocumentIds,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
@@ -2414,7 +2421,7 @@ const AppLayout = () => {
|
|||||||
setStatusMessage('No correspondents changed.', 'info');
|
setStatusMessage('No correspondents changed.', 'info');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, refreshCurrentFolder, resolveTargetDocumentIds, setStatusMessage],
|
[refreshCurrentFolder, resolveTargetDocumentIds, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -2528,7 +2535,6 @@ const AppLayout = () => {
|
|||||||
[
|
[
|
||||||
resolveTargetDocumentIds,
|
resolveTargetDocumentIds,
|
||||||
tags,
|
tags,
|
||||||
api,
|
|
||||||
refreshTags,
|
refreshTags,
|
||||||
refreshCurrentFolder,
|
refreshCurrentFolder,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
@@ -2629,7 +2635,7 @@ const AppLayout = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[resolveTargetDocumentIds, api, notifyApiError, setStatusMessage],
|
[resolveTargetDocumentIds, notifyApiError, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const uploadFile = useCallback(
|
const uploadFile = useCallback(
|
||||||
@@ -2727,12 +2733,7 @@ const AppLayout = () => {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[assetManager, setDocuments, setSearchResults, notifyApiError],
|
||||||
assetManager,
|
|
||||||
setDocuments,
|
|
||||||
setSearchResults,
|
|
||||||
notifyApiError,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const dragPreviewRef = useRef(null);
|
const dragPreviewRef = useRef(null);
|
||||||
@@ -3082,7 +3083,7 @@ const AppLayout = () => {
|
|||||||
previewInflightRef.current.set(documentId, request);
|
previewInflightRef.current.set(documentId, request);
|
||||||
return request;
|
return request;
|
||||||
},
|
},
|
||||||
[previewEntries, notifyApiError, resolveApiPath],
|
[previewEntries, notifyApiError],
|
||||||
);
|
);
|
||||||
|
|
||||||
const extractFilesFromDataTransfer = useCallback(async (dataTransfer) => {
|
const extractFilesFromDataTransfer = useCallback(async (dataTransfer) => {
|
||||||
@@ -3327,7 +3328,6 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
api,
|
|
||||||
ensureFolderData,
|
ensureFolderData,
|
||||||
refreshCurrentFolder,
|
refreshCurrentFolder,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
@@ -3402,7 +3402,6 @@ const AppLayout = () => {
|
|||||||
[
|
[
|
||||||
searchResults,
|
searchResults,
|
||||||
documents,
|
documents,
|
||||||
api,
|
|
||||||
assetManager,
|
assetManager,
|
||||||
setDocuments,
|
setDocuments,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
@@ -3665,7 +3664,7 @@ const AppLayout = () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, refreshTags, notifyApiError, setStatusMessage, applyTagRemovalToCaches],
|
[refreshTags, notifyApiError, setStatusMessage, applyTagRemovalToCaches],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTagAdd = useCallback(
|
const handleTagAdd = useCallback(
|
||||||
@@ -3734,7 +3733,6 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
api,
|
|
||||||
refreshCurrentFolder,
|
refreshCurrentFolder,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
@@ -3899,7 +3897,7 @@ const AppLayout = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, token, notifyApiError, setStatusMessage],
|
[token, notifyApiError, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFolderCreate = useCallback(
|
const handleFolderCreate = useCallback(
|
||||||
@@ -4379,7 +4377,8 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
}, [appDispatch, setStatusMessage]);
|
}, [appDispatch, setStatusMessage]);
|
||||||
|
|
||||||
const folderClickHandlers = {
|
const folderClickHandlers = useMemo(
|
||||||
|
() => ({
|
||||||
onToggle: async (folderId) => {
|
onToggle: async (folderId) => {
|
||||||
const node = folderNodes.get(folderId);
|
const node = folderNodes.get(folderId);
|
||||||
const nextExpanded = !(node?.expanded ?? false);
|
const nextExpanded = !(node?.expanded ?? false);
|
||||||
@@ -4533,6 +4532,177 @@ const AppLayout = () => {
|
|||||||
onDragLeave: (event) => {
|
onDragLeave: (event) => {
|
||||||
event.currentTarget.classList.remove('is-drop-target');
|
event.currentTarget.classList.remove('is-drop-target');
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
draggedDocumentIds,
|
||||||
|
draggedFolderId,
|
||||||
|
ensureFolderData,
|
||||||
|
folderNodes,
|
||||||
|
handleFileDrop,
|
||||||
|
isInvalidFolderDrop,
|
||||||
|
moveDocumentsToFolder,
|
||||||
|
moveFolder,
|
||||||
|
notifyApiError,
|
||||||
|
selectFolder,
|
||||||
|
selectedFolder,
|
||||||
|
setDraggedDocumentIds,
|
||||||
|
setDraggedFolderId,
|
||||||
|
setFolderNodes,
|
||||||
|
setStatusMessage,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const node = folderNodes.get(folderId);
|
||||||
|
const nextExpanded = !(node?.expanded ?? false);
|
||||||
|
if (nextExpanded) {
|
||||||
|
try {
|
||||||
|
await ensureFolderData(folderId, {
|
||||||
|
includeDocuments: false,
|
||||||
|
prefetchDepth: 1,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
notifyApiError(error, 'Failed to load folder.');
|
||||||
|
}
|
||||||
|
} else if (node && !node.loaded) {
|
||||||
|
try {
|
||||||
|
await ensureFolderData(folderId, {
|
||||||
|
includeDocuments: false,
|
||||||
|
prefetchDepth: 1,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
notifyApiError(error, 'Failed to load folder.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFolderNodes((prev) => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
const current = next.get(folderId);
|
||||||
|
if (!current) return prev;
|
||||||
|
next.set(folderId, { ...current, expanded: nextExpanded });
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSelect: selectFolder,
|
||||||
|
onDrop: async (event, folderId) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.currentTarget.classList.remove('is-drop-target');
|
||||||
|
|
||||||
|
let folderIds = [];
|
||||||
|
try {
|
||||||
|
const rawFolderList = event.dataTransfer.getData('application/x-papercrate-folder-list');
|
||||||
|
if (rawFolderList) {
|
||||||
|
const parsed = JSON.parse(rawFolderList);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
folderIds = parsed.filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
error
|
||||||
|
) {}
|
||||||
|
|
||||||
|
if (!folderIds.length) {
|
||||||
|
let folderSourceId = draggedFolderId;
|
||||||
|
if (!folderSourceId) {
|
||||||
|
try {
|
||||||
|
if (event.dataTransfer.types?.includes('application/x-papercrate-folder')) {
|
||||||
|
folderSourceId = event.dataTransfer.getData('application/x-papercrate-folder');
|
||||||
|
}
|
||||||
|
} catch (
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
error
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folderSourceId) {
|
||||||
|
folderIds = [folderSourceId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
folderIds = Array.from(new Set(folderIds.filter(Boolean)));
|
||||||
|
|
||||||
|
if (folderIds.length) {
|
||||||
|
setDraggedFolderId(null);
|
||||||
|
const invalidMove = folderIds.some((sourceId) => isInvalidFolderDrop(sourceId, folderId));
|
||||||
|
if (invalidMove) {
|
||||||
|
setStatusMessage(
|
||||||
|
'Cannot move a folder into itself or one of its descendants.',
|
||||||
|
'error',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const sourceId of folderIds) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await moveFolder(sourceId, folderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFiles(event)) {
|
||||||
|
await handleFileDrop(event.dataTransfer, folderId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let docIds = [];
|
||||||
|
try {
|
||||||
|
const raw = event.dataTransfer.getData('application/x-papercrate-doc-list');
|
||||||
|
if (raw) {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
docIds = parsed.filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
error
|
||||||
|
) {}
|
||||||
|
|
||||||
|
if (!docIds.length) {
|
||||||
|
try {
|
||||||
|
const single = event.dataTransfer.getData('application/x-papercrate-doc');
|
||||||
|
if (single) {
|
||||||
|
docIds = [single];
|
||||||
|
}
|
||||||
|
} catch (
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
error
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!docIds.length && draggedDocumentIds.length) {
|
||||||
|
docIds = draggedDocumentIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
docIds = Array.from(new Set(docIds));
|
||||||
|
|
||||||
|
if (!docIds.length || folderId === selectedFolder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDraggedDocumentIds([]);
|
||||||
|
await moveDocumentsToFolder(docIds, folderId);
|
||||||
|
},
|
||||||
|
onDragOver: (event, folderId) => {
|
||||||
|
const folderDragActive = Boolean(draggedFolderId);
|
||||||
|
if (folderDragActive && isInvalidFolderDrop(draggedFolderId, folderId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFiles(event)) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.dataTransfer.dropEffect = 'copy';
|
||||||
|
event.currentTarget.classList.add('is-drop-target');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draggedDocumentIds.length || folderDragActive) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.dataTransfer.dropEffect = 'move';
|
||||||
|
event.currentTarget.classList.add('is-drop-target');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragLeave: (event) => {
|
||||||
|
event.currentTarget.classList.remove('is-drop-target');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedDocument = useMemo(() => {
|
const selectedDocument = useMemo(() => {
|
||||||
@@ -4600,7 +4770,6 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
api,
|
|
||||||
token,
|
token,
|
||||||
documentLookup,
|
documentLookup,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
@@ -5016,7 +5185,8 @@ const AppLayout = () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const documentsTableProps = {
|
const documentsTableProps = useMemo(
|
||||||
|
() => ({
|
||||||
currentFolderName,
|
currentFolderName,
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
onRefresh: refreshCurrentFolder,
|
onRefresh: refreshCurrentFolder,
|
||||||
@@ -5066,9 +5236,56 @@ const AppLayout = () => {
|
|||||||
viewMode: documentsViewMode,
|
viewMode: documentsViewMode,
|
||||||
onViewModeChange: handleDocumentsViewModeChange,
|
onViewModeChange: handleDocumentsViewModeChange,
|
||||||
onClearSelection: clearDocumentSelection,
|
onClearSelection: clearDocumentSelection,
|
||||||
};
|
}),
|
||||||
|
[
|
||||||
|
activeCorrespondentFilters,
|
||||||
|
breadcrumbs,
|
||||||
|
clearDocumentSelection,
|
||||||
|
currentFolderName,
|
||||||
|
currentSubfolders,
|
||||||
|
documents,
|
||||||
|
documentsViewMode,
|
||||||
|
draggedDocumentIds,
|
||||||
|
draggedFolderId,
|
||||||
|
focusedDocumentId,
|
||||||
|
focusedRowKey,
|
||||||
|
folderClickHandlers,
|
||||||
|
handleDocumentDelete,
|
||||||
|
handleDocumentDragEnd,
|
||||||
|
handleDocumentDragStart,
|
||||||
|
handleDocumentListFocus,
|
||||||
|
handleDocumentListKeyDown,
|
||||||
|
handleDocumentRowClick,
|
||||||
|
handleDocumentTagDrop,
|
||||||
|
handleDocumentTitleUpdate,
|
||||||
|
handleDocumentsViewModeChange,
|
||||||
|
handleFolderDelete,
|
||||||
|
handleFolderDragEnd,
|
||||||
|
handleFolderDragStart,
|
||||||
|
handleFolderRename,
|
||||||
|
handleFolderRowClick,
|
||||||
|
isFilterActive,
|
||||||
|
creatingFolder,
|
||||||
|
openCreateFolderModal,
|
||||||
|
openDocumentPreview,
|
||||||
|
refreshCurrentFolder,
|
||||||
|
searchLoading,
|
||||||
|
searchResults,
|
||||||
|
selectFolder,
|
||||||
|
selectedDocumentIds,
|
||||||
|
selectedFolderIds,
|
||||||
|
setFocusedRowKey,
|
||||||
|
showSkeuoWorkspace,
|
||||||
|
tagLookupById,
|
||||||
|
toggleCorrespondentFilter,
|
||||||
|
toggleTagFilter,
|
||||||
|
ensureAssetUrl,
|
||||||
|
getDocumentAsset,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const sidebarProps = {
|
const sidebarProps = useMemo(
|
||||||
|
() => ({
|
||||||
folderNodes,
|
folderNodes,
|
||||||
onToggle: folderClickHandlers.onToggle,
|
onToggle: folderClickHandlers.onToggle,
|
||||||
onSelect: folderClickHandlers.onSelect,
|
onSelect: folderClickHandlers.onSelect,
|
||||||
@@ -5102,7 +5319,39 @@ const AppLayout = () => {
|
|||||||
activeTenantId: currentTenantId,
|
activeTenantId: currentTenantId,
|
||||||
onSelectTenant: handleTenantSelect,
|
onSelectTenant: handleTenantSelect,
|
||||||
onOpenSettings: openSettingsModal,
|
onOpenSettings: openSettingsModal,
|
||||||
};
|
}),
|
||||||
|
[
|
||||||
|
activeCorrespondentFilters,
|
||||||
|
activeTagFilters,
|
||||||
|
appStatus,
|
||||||
|
clearFilters,
|
||||||
|
correspondents,
|
||||||
|
currentTenantId,
|
||||||
|
folderClickHandlers,
|
||||||
|
folderNodes,
|
||||||
|
handleFolderDelete,
|
||||||
|
handleFolderDragEnd,
|
||||||
|
handleFolderDragStart,
|
||||||
|
handleFolderRename,
|
||||||
|
handleLogout,
|
||||||
|
handleSearchChange,
|
||||||
|
handleSearchSubmit,
|
||||||
|
handleTenantSelect,
|
||||||
|
loading,
|
||||||
|
openSettingsModal,
|
||||||
|
previewActive,
|
||||||
|
searchQuery,
|
||||||
|
draggedFolderId,
|
||||||
|
isFilterActive,
|
||||||
|
selectedFolder,
|
||||||
|
status,
|
||||||
|
tags,
|
||||||
|
tenantOptions,
|
||||||
|
tenantSlug,
|
||||||
|
toggleCorrespondentFilter,
|
||||||
|
toggleTagFilter,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const handleDetailPanelClose = useCallback(() => {
|
const handleDetailPanelClose = useCallback(() => {
|
||||||
setDetailPanelOpen(false);
|
setDetailPanelOpen(false);
|
||||||
@@ -5111,7 +5360,8 @@ const AppLayout = () => {
|
|||||||
setDetailPanelDocs([]);
|
setDetailPanelDocs([]);
|
||||||
}, [clearDocumentSelection]);
|
}, [clearDocumentSelection]);
|
||||||
|
|
||||||
const detailPanelProps = {
|
const detailPanelProps = useMemo(
|
||||||
|
() => ({
|
||||||
selectedDocuments: detailPanelSelectedDocuments,
|
selectedDocuments: detailPanelSelectedDocuments,
|
||||||
tags,
|
tags,
|
||||||
tagLookupById,
|
tagLookupById,
|
||||||
@@ -5138,7 +5388,35 @@ const AppLayout = () => {
|
|||||||
onFolderNavigate: selectFolder,
|
onFolderNavigate: selectFolder,
|
||||||
onClose: handleDetailPanelClose,
|
onClose: handleDetailPanelClose,
|
||||||
resolveFolderPath,
|
resolveFolderPath,
|
||||||
};
|
}),
|
||||||
|
[
|
||||||
|
activePreviewId,
|
||||||
|
correspondents,
|
||||||
|
detailPanelSelectedDocuments,
|
||||||
|
ensureAssetUrl,
|
||||||
|
ensurePreviewData,
|
||||||
|
getDocumentAsset,
|
||||||
|
handleBulkCorrespondentAdd,
|
||||||
|
handleBulkCorrespondentRemove,
|
||||||
|
handleBulkSelectionReanalyze,
|
||||||
|
handleBulkTagAddFromDetail,
|
||||||
|
handleBulkTagRemoveFromDetail,
|
||||||
|
handleCorrespondentAdd,
|
||||||
|
handleCorrespondentRemove,
|
||||||
|
handleDetailPanelClose,
|
||||||
|
handleDocumentTitleUpdate,
|
||||||
|
handleTagAdd,
|
||||||
|
handleTagRemove,
|
||||||
|
handleThumbnailRegeneration,
|
||||||
|
openDocumentPreview,
|
||||||
|
promoteSelectionOrder,
|
||||||
|
resolveFolderPath,
|
||||||
|
selectFolder,
|
||||||
|
selectedPreviewEntry,
|
||||||
|
tags,
|
||||||
|
tagLookupById,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const skeuoWorkspaceProps = useMemo(
|
const skeuoWorkspaceProps = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -5210,7 +5488,6 @@ const AppLayout = () => {
|
|||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
getDocumentAsset,
|
getDocumentAsset,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
resolveApiPath,
|
|
||||||
openTagsModal,
|
openTagsModal,
|
||||||
openCorrespondentsModal,
|
openCorrespondentsModal,
|
||||||
openSettingsModal,
|
openSettingsModal,
|
||||||
@@ -5253,7 +5530,6 @@ const AppLayout = () => {
|
|||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
getDocumentAsset,
|
getDocumentAsset,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
resolveApiPath,
|
|
||||||
openTagsModal,
|
openTagsModal,
|
||||||
openCorrespondentsModal,
|
openCorrespondentsModal,
|
||||||
openSettingsModal,
|
openSettingsModal,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { formatFileSize } from '../utils/format';
|
import { formatFileSize } from '../utils/format';
|
||||||
import { DownloadIcon, TextScanIcon, AnalyzeIcon, CloseIcon } from '../ui/icons';
|
import { DownloadIcon, TextScanIcon, AnalyzeIcon, CloseIcon } from '../ui/icons';
|
||||||
import { openOcrTextInNewTab } from '../utils/ocr';
|
import { openOcrTextInNewTab } from '../utils/ocr';
|
||||||
@@ -24,7 +24,7 @@ const PreviewWorkspace = ({
|
|||||||
const tags = Array.isArray(document.tags) ? document.tags : [];
|
const tags = Array.isArray(document.tags) ? document.tags : [];
|
||||||
const correspondents = Array.isArray(document.correspondents) ? document.correspondents : [];
|
const correspondents = Array.isArray(document.correspondents) ? document.correspondents : [];
|
||||||
|
|
||||||
const metadataSummary = useMemo(() => {
|
const metadataSummary = (() => {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
if (mime) rows.push(['Type', mime]);
|
if (mime) rows.push(['Type', mime]);
|
||||||
if (sizeLabel) rows.push(['Size', sizeLabel]);
|
if (sizeLabel) rows.push(['Size', sizeLabel]);
|
||||||
@@ -45,7 +45,7 @@ const PreviewWorkspace = ({
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
}, [mime, sizeLabel, issuedAt, createdAt, updatedAt, folderName, tags, correspondents]);
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="preview-workspace">
|
<section className="preview-workspace">
|
||||||
|
|||||||
Reference in New Issue
Block a user