This commit is contained in:
2025-11-02 21:38:11 +01:00
parent 4ec19dbd70
commit 3c24b890a4
2 changed files with 64 additions and 123 deletions
+50 -109
View File
@@ -35,9 +35,6 @@ const CARD_MIN = 240;
const CARD_MAX = 340; const CARD_MAX = 340;
const TAG_REMOVE_DISTANCE = 160; const TAG_REMOVE_DISTANCE = 160;
const STACK_HIT_EPSILON = 4; const STACK_HIT_EPSILON = 4;
const STACK_CENTER_TOLERANCE = 0.35;
const STACK_CENTER_MIN = 32;
const STACK_ROTATION_TOLERANCE = 15;
const DEBUG_DRAG = false; const DEBUG_DRAG = false;
const DEBUG_FOCUS = true; const DEBUG_FOCUS = true;
@@ -1953,7 +1950,6 @@ const DesktopWorkspaceView = () => {
onClearSelection, onClearSelection,
detailPanelOpen, detailPanelOpen,
onCloseDetailPanel, onCloseDetailPanel,
documentLookup,
} = useDesktopContext(); } = useDesktopContext();
const { handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel } = const { handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel } =
@@ -1974,7 +1970,8 @@ const DesktopWorkspaceView = () => {
return []; return [];
} }
const hits = []; const candidates = [];
items.forEach((doc) => { items.forEach((doc) => {
if (!doc?.id) { if (!doc?.id) {
return; return;
@@ -1983,6 +1980,7 @@ const DesktopWorkspaceView = () => {
if (!layout) { if (!layout) {
return; return;
} }
const sizeInfo = ensureDocumentSize(doc); const sizeInfo = ensureDocumentSize(doc);
if (!sizeInfo) { if (!sizeInfo) {
return; return;
@@ -2018,122 +2016,66 @@ const DesktopWorkspaceView = () => {
const halfWidth = width / 2; const halfWidth = width / 2;
const halfHeight = height / 2; const halfHeight = height / 2;
if ( const containsPointer =
Math.abs(localX) <= halfWidth + STACK_HIT_EPSILON Math.abs(localX) <= halfWidth + STACK_HIT_EPSILON
&& Math.abs(localY) <= halfHeight + STACK_HIT_EPSILON && Math.abs(localY) <= halfHeight + STACK_HIT_EPSILON;
) {
const docKey = String(doc.id); candidates.push({
if (!hits.some((entry) => entry.id === docKey)) { id: String(doc.id),
hits.push({ z: Number.isFinite(layout.z) ? layout.z : 0,
id: docKey, centerX,
z: Number.isFinite(layout.z) ? layout.z : 0, centerY,
}); rotationDeg,
} width,
} height,
halfWidth,
halfHeight,
localX,
localY,
marginX: halfWidth - Math.abs(localX),
marginY: halfHeight - Math.abs(localY),
containsPointer,
});
}); });
if (!hits.length) { const pointerCandidates = candidates.filter((candidate) => candidate.containsPointer);
if (!pointerCandidates.length) {
return []; return [];
} }
hits.sort((a, b) => (b.z ?? 0) - (a.z ?? 0)); const sortedByZ = [...pointerCandidates].sort((a, b) => (b.z ?? 0) - (a.z ?? 0));
const targetKey = targetDocId != null ? String(targetDocId) : sortedByZ[0].id;
const targetKey = targetDocId != null ? String(targetDocId) : hits[0].id; const primary = sortedByZ.find((candidate) => candidate.id === targetKey) || sortedByZ[0];
const orderedIds = hits.map((entry) => entry.id); if (!primary) {
return [];
}
const radius = Math.max(Math.min(primary.halfWidth, primary.halfHeight) * 1.2, 6);
const radiusSquared = radius * radius;
const selected = candidates
.filter((candidate) => {
if (!candidate?.id) {
return false;
}
const dx = candidate.centerX - primary.centerX;
const dy = candidate.centerY - primary.centerY;
return dx * dx + dy * dy <= radiusSquared + 1e-4;
})
.sort((a, b) => (b.z ?? 0) - (a.z ?? 0));
if (targetKey) { if (targetKey) {
const targetIndex = orderedIds.indexOf(targetKey); const targetIndex = selected.findIndex((entry) => entry.id === targetKey);
if (targetIndex > 0) { if (targetIndex > 0) {
const [targetEntry] = orderedIds.splice(targetIndex, 1); const [targetEntry] = selected.splice(targetIndex, 1);
orderedIds.unshift(targetEntry); selected.unshift(targetEntry);
} }
} }
const primaryKey = orderedIds[0]; return selected
if (!primaryKey) { .map((candidate) => candidate.id)
return orderedIds; .filter((id, index, array) => array.indexOf(id) === index);
}
const primaryDoc = documentLookup.get(primaryKey) || null;
const primaryLayout = primaryDoc
? layoutSnapshot.get(primaryDoc.id) ?? layoutRef.current.get(primaryKey)
: null;
const primarySize = primaryDoc ? ensureDocumentSize(primaryDoc) : null;
if (!primaryLayout || !primarySize) {
return orderedIds;
}
const primaryCenterX = Number(primaryLayout.centerX);
const primaryCenterY = Number(primaryLayout.centerY);
const primaryRotation = Number(primaryLayout.rotation) || 0;
if (!Number.isFinite(primaryCenterX) || !Number.isFinite(primaryCenterY)) {
return orderedIds;
}
const centerTolX = Math.max(primarySize.width * STACK_CENTER_TOLERANCE, STACK_CENTER_MIN);
const centerTolY = Math.max(primarySize.height * STACK_CENTER_TOLERANCE, STACK_CENTER_MIN);
const filteredIds = [];
orderedIds.forEach((docKey, index) => {
if (!docKey) {
return;
}
if (index === 0 || docKey === targetKey) {
filteredIds.push(docKey);
return;
}
const candidateDoc = documentLookup.get(docKey) || null;
if (!candidateDoc) {
return;
}
const candidateLayout = layoutSnapshot.get(candidateDoc.id) ?? layoutRef.current.get(docKey);
if (!candidateLayout) {
return;
}
const candidateSize = ensureDocumentSize(candidateDoc);
if (!candidateSize) {
return;
}
const candidateCenterX = Number(candidateLayout.centerX);
const candidateCenterY = Number(candidateLayout.centerY);
if (!Number.isFinite(candidateCenterX) || !Number.isFinite(candidateCenterY)) {
return;
}
const dx = Math.abs(candidateCenterX - primaryCenterX);
const dy = Math.abs(candidateCenterY - primaryCenterY);
if (dx > centerTolX || dy > centerTolY) {
return;
}
const candidateRotation = Number(candidateLayout.rotation) || 0;
const rotationDiffRaw = Math.abs(candidateRotation - primaryRotation) % 360;
const rotationDiff = rotationDiffRaw > 180 ? 360 - rotationDiffRaw : rotationDiffRaw;
if (rotationDiff > STACK_ROTATION_TOLERANCE) {
return;
}
const sizeRatio = candidateSize.width && primarySize.width
? Math.min(candidateSize.width, primarySize.width) / Math.max(candidateSize.width, primarySize.width)
: 1;
const heightRatio = candidateSize.height && primarySize.height
? Math.min(candidateSize.height, primarySize.height) / Math.max(candidateSize.height, primarySize.height)
: 1;
if (sizeRatio < 0.55 || heightRatio < 0.55) {
return;
}
filteredIds.push(docKey);
});
return filteredIds;
}, },
[ [
activeTagSet, activeTagSet,
@@ -2142,7 +2084,6 @@ const DesktopWorkspaceView = () => {
layoutRef, layoutRef,
layoutSnapshot, layoutSnapshot,
containerRef, containerRef,
documentLookup,
], ],
); );
+14 -14
View File
@@ -380,8 +380,7 @@ const useDocumentDrag = () => {
const localPointerOffsetX = pointerOffsetX * cosInitial - pointerOffsetY * sinInitial; const localPointerOffsetX = pointerOffsetX * cosInitial - pointerOffsetY * sinInitial;
const localPointerOffsetY = pointerOffsetX * sinInitial + pointerOffsetY * cosInitial; const localPointerOffsetY = pointerOffsetX * sinInitial + pointerOffsetY * cosInitial;
const stackRandom = () => Math.random(); const groupItems = groupDocIds.map((id) => {
const groupItems = groupDocIds.map((id, index) => {
const itemDoc = documentLookup.get(id); const itemDoc = documentLookup.get(id);
const itemSize = ensureDocumentSize(itemDoc) || sizeInfo; const itemSize = ensureDocumentSize(itemDoc) || sizeInfo;
const itemWidth = itemSize.width || docWidth; const itemWidth = itemSize.width || docWidth;
@@ -391,10 +390,8 @@ const useDocumentDrag = () => {
typeof itemEntry?.centerX === 'number' ? itemEntry.centerX : canvasPadding + itemWidth / 2; typeof itemEntry?.centerX === 'number' ? itemEntry.centerX : canvasPadding + itemWidth / 2;
const itemCenterY = const itemCenterY =
typeof itemEntry?.centerY === 'number' ? itemEntry.centerY : canvasPadding + itemHeight / 2; typeof itemEntry?.centerY === 'number' ? itemEntry.centerY : canvasPadding + itemHeight / 2;
const radius = index === 0 ? 0 : 24 + index * 8; const baseOffsetX = itemCenterX - centerX;
const offsetAngle = (index * 1.618 + stackRandom() * 0.5) * Math.PI; const baseOffsetY = itemCenterY - centerY;
const offsetX = Math.cos(offsetAngle) * radius;
const offsetY = Math.sin(offsetAngle) * radius;
const initialRotation = itemEntry?.rotation ?? 0; const initialRotation = itemEntry?.rotation ?? 0;
const targetRotation = initialRotation; const targetRotation = initialRotation;
return { return {
@@ -403,8 +400,10 @@ const useDocumentDrag = () => {
height: itemHeight, height: itemHeight,
currentCenterX: itemCenterX, currentCenterX: itemCenterX,
currentCenterY: itemCenterY, currentCenterY: itemCenterY,
offsetX, baseOffsetX,
offsetY, baseOffsetY,
offsetX: baseOffsetX,
offsetY: baseOffsetY,
initialRotation, initialRotation,
displayRotation: initialRotation, displayRotation: initialRotation,
targetRotation, targetRotation,
@@ -599,14 +598,15 @@ const useDocumentDrag = () => {
if (isPrimary) { if (isPrimary) {
item.currentCenterX = centerX; item.currentCenterX = centerX;
item.currentCenterY = centerY; item.currentCenterY = centerY;
item.offsetX *= 0.92; item.offsetX = item.baseOffsetX ?? 0;
item.offsetY *= 0.92; item.offsetY = item.baseOffsetY ?? 0;
item.displayRotation = state.rotation ?? item.displayRotation ?? 0; item.displayRotation = state.rotation ?? item.displayRotation ?? 0;
} else { } else {
item.offsetX *= 0.92; const decay = 0.82;
item.offsetY *= 0.92; const currentOffsetX = (item.offsetX ?? item.baseOffsetX ?? 0) * decay;
if (Math.abs(item.offsetX) < 1) item.offsetX = 0; const currentOffsetY = (item.offsetY ?? item.baseOffsetY ?? 0) * decay;
if (Math.abs(item.offsetY) < 1) item.offsetY = 0; item.offsetX = Math.abs(currentOffsetX) < 0.5 ? 0 : currentOffsetX;
item.offsetY = Math.abs(currentOffsetY) < 0.5 ? 0 : currentOffsetY;
const targetX = centerX + item.offsetX; const targetX = centerX + item.offsetX;
const targetY = centerY + item.offsetY; const targetY = centerY + item.offsetY;