fix
This commit is contained in:
@@ -1930,7 +1930,15 @@ const DesktopWorkspaceView = () => {
|
||||
}
|
||||
}}
|
||||
onPointerDown={(event) => {
|
||||
if (typeof onDocumentPointerSelect === 'function') {
|
||||
const alreadySelected = selectedDocumentIds.includes(doc.id);
|
||||
if (
|
||||
typeof onDocumentPointerSelect === 'function'
|
||||
&& (!alreadySelected
|
||||
|| event.metaKey
|
||||
|| event.ctrlKey
|
||||
|| event.shiftKey
|
||||
|| event.altKey)
|
||||
) {
|
||||
onDocumentPointerSelect(doc.id, event);
|
||||
}
|
||||
handlePointerDown(event, doc.id);
|
||||
|
||||
@@ -14,13 +14,6 @@ const ANGULAR_DAMPING = 11;
|
||||
const TORQUE_TO_ACCELERATION = 0.006;
|
||||
const SETTLE_ANGULAR_VELOCITY = 1.2;
|
||||
const EDGE_COLLISION_THRESHOLD = 0.5;
|
||||
const EDGE_ALIGNMENT_STIFFNESS = 30;
|
||||
const BASE_TORQUE_FACTOR = 0.4;
|
||||
const EDGE_COLLISION_TORQUE_FACTOR = 0.08;
|
||||
const EDGE_ALIGNMENT_TORQUE_MULTIPLIER = 35;
|
||||
const EDGE_COLLISION_EXTRA_DAMPING = 4;
|
||||
const EDGE_REST_REALIGN_RATE = 10;
|
||||
const EDGE_ALIGNMENT_EPSILON = 0.15;
|
||||
|
||||
const useDocumentDrag = () => {
|
||||
const {
|
||||
@@ -39,8 +32,62 @@ const useDocumentDrag = () => {
|
||||
containerRef,
|
||||
onDocumentOpen,
|
||||
onInspectDocument,
|
||||
selectedDocumentIds,
|
||||
} = useDesktopContext();
|
||||
|
||||
const applyTransform = useCallback(
|
||||
(docId, centerX, centerY, width, height, rotation, scale = 1) => {
|
||||
const node = itemRefs.current.get(docId);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
node.style.transform = formatTransform(
|
||||
centerX - width / 2,
|
||||
centerY - height / 2,
|
||||
rotation,
|
||||
scale,
|
||||
);
|
||||
},
|
||||
[itemRefs],
|
||||
);
|
||||
|
||||
const finalizeGroupDrag = useCallback(
|
||||
(dragState) => {
|
||||
if (!dragState?.groupItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragState.groupItems.forEach((item) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entryItem = layoutRef.current.get(item.docId) || {};
|
||||
const centerX = item.currentCenterX ?? entryItem.centerX ?? dragState.originCenterX;
|
||||
const centerY = item.currentCenterY ?? entryItem.centerY ?? dragState.originCenterY;
|
||||
const rotation = item.displayRotation ?? entryItem.rotation ?? 0;
|
||||
|
||||
layoutRef.current.set(item.docId, {
|
||||
...entryItem,
|
||||
centerX,
|
||||
centerY,
|
||||
rotation,
|
||||
});
|
||||
|
||||
applyTransform(
|
||||
item.docId,
|
||||
centerX,
|
||||
centerY,
|
||||
item.width,
|
||||
item.height,
|
||||
rotation,
|
||||
item.docId === dragState.docKey ? dragState.dragScale || 1 : 1,
|
||||
);
|
||||
});
|
||||
},
|
||||
[applyTransform, layoutRef],
|
||||
);
|
||||
|
||||
const tapHandler = usePointerTap({
|
||||
delay: 220,
|
||||
onSingle: ({ data, event }) => {
|
||||
@@ -200,11 +247,11 @@ const useDocumentDrag = () => {
|
||||
);
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(event, docId) => {
|
||||
(event, docIdInput) => {
|
||||
if (debugDrag) {
|
||||
console.log(
|
||||
'[desk] handlePointerDown fired for doc',
|
||||
docId,
|
||||
docIdInput,
|
||||
'button',
|
||||
event.button,
|
||||
'pointerType',
|
||||
@@ -214,25 +261,74 @@ const useDocumentDrag = () => {
|
||||
);
|
||||
}
|
||||
preventAll(event);
|
||||
cancelInertiaAnimation(docId);
|
||||
|
||||
const docId = docIdInput != null ? docIdInput : null;
|
||||
const docKey = docId != null ? String(docId) : null;
|
||||
const doc = docKey ? documentLookup.get(docKey) : null;
|
||||
const { width: docWidth, height: docHeight } = ensureDocumentSize(doc);
|
||||
if (!docKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelInertiaAnimation(docId);
|
||||
|
||||
const doc = documentLookup.get(docKey);
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectionIds = Array.isArray(selectedDocumentIds)
|
||||
? selectedDocumentIds.map((id) => String(id))
|
||||
: [];
|
||||
const metaOrCtrl = event.metaKey || event.ctrlKey;
|
||||
if (metaOrCtrl && !selectionIds.includes(docKey)) {
|
||||
selectionIds = [...selectionIds, docKey];
|
||||
}
|
||||
let groupDocIds = [];
|
||||
if (selectionIds.includes(docKey) && selectionIds.length > 1) {
|
||||
groupDocIds = selectionIds
|
||||
.map((id) => String(id))
|
||||
.filter((id, index, array) => array.indexOf(id) === index && documentLookup.has(id));
|
||||
}
|
||||
if (!groupDocIds.includes(docKey)) {
|
||||
groupDocIds.unshift(docKey);
|
||||
}
|
||||
groupDocIds = groupDocIds.filter((id, index, array) => array.indexOf(id) === index);
|
||||
if (!groupDocIds.length) {
|
||||
groupDocIds = [docKey];
|
||||
}
|
||||
const isGroupDrag = groupDocIds.length > 1;
|
||||
|
||||
if (isGroupDrag) {
|
||||
groupDocIds.forEach((id) => {
|
||||
if (id !== docKey) {
|
||||
cancelInertiaAnimation(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const sizeInfo = ensureDocumentSize(doc) || { width: 0, height: 0 };
|
||||
const docWidth = sizeInfo.width || 320;
|
||||
const docHeight = sizeInfo.height || 240;
|
||||
const { baseScale } = resolveBaseMetrics(doc, docWidth, docHeight);
|
||||
const normalizedBaseScale =
|
||||
Number.isFinite(baseScale) && baseScale > 0 ? baseScale : 1;
|
||||
|
||||
const entry = layoutRef.current.get(docKey) || null;
|
||||
const defaultCenterX = canvasPadding + docWidth / 2;
|
||||
const defaultCenterY = canvasPadding + docHeight / 2;
|
||||
const metaPressed = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
||||
if (!metaPressed) {
|
||||
bringToFront(docId);
|
||||
}
|
||||
const entry = layoutRef.current.get(docId) || null;
|
||||
const centerX = typeof entry?.centerX === 'number' ? entry.centerX : defaultCenterX;
|
||||
const centerY = typeof entry?.centerY === 'number' ? entry.centerY : defaultCenterY;
|
||||
|
||||
const modifierPressed = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
||||
if (!modifierPressed) {
|
||||
if (isGroupDrag) {
|
||||
groupDocIds.forEach((id) => bringToFront(id));
|
||||
} else {
|
||||
bringToFront(docId);
|
||||
}
|
||||
}
|
||||
|
||||
if (entry && (entry.centerX !== centerX || entry.centerY !== centerY)) {
|
||||
layoutRef.current.set(docId, { ...entry, centerX, centerY });
|
||||
layoutRef.current.set(docKey, { ...entry, centerX, centerY });
|
||||
}
|
||||
|
||||
const capturedTarget = event.currentTarget instanceof HTMLElement ? event.currentTarget : null;
|
||||
@@ -245,6 +341,7 @@ const useDocumentDrag = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const containerRect = containerRef?.current?.getBoundingClientRect?.() || null;
|
||||
const containerLeft = containerRect?.left || 0;
|
||||
const containerTop = containerRect?.top || 0;
|
||||
@@ -258,14 +355,48 @@ const useDocumentDrag = () => {
|
||||
const sinInitial = Math.sin(-initialRotationRad);
|
||||
const localPointerOffsetX = pointerOffsetX * cosInitial - pointerOffsetY * sinInitial;
|
||||
const localPointerOffsetY = pointerOffsetX * sinInitial + pointerOffsetY * cosInitial;
|
||||
|
||||
const stackRandom = () => Math.random();
|
||||
const groupItems = groupDocIds.map((id, index) => {
|
||||
const itemDoc = documentLookup.get(id);
|
||||
const itemSize = ensureDocumentSize(itemDoc) || sizeInfo;
|
||||
const itemWidth = itemSize.width || docWidth;
|
||||
const itemHeight = itemSize.height || docHeight;
|
||||
const itemEntry = layoutRef.current.get(id) || null;
|
||||
const itemCenterX =
|
||||
typeof itemEntry?.centerX === 'number' ? itemEntry.centerX : canvasPadding + itemWidth / 2;
|
||||
const itemCenterY =
|
||||
typeof itemEntry?.centerY === 'number' ? itemEntry.centerY : canvasPadding + itemHeight / 2;
|
||||
const radius = index === 0 ? 0 : 24 + index * 8;
|
||||
const offsetAngle = (index * 1.618 + stackRandom() * 0.5) * Math.PI;
|
||||
const offsetX = Math.cos(offsetAngle) * radius;
|
||||
const offsetY = Math.sin(offsetAngle) * radius;
|
||||
const initialRotation = itemEntry?.rotation ?? 0;
|
||||
const targetRotation = initialRotation;
|
||||
return {
|
||||
docId: id,
|
||||
width: itemWidth,
|
||||
height: itemHeight,
|
||||
currentCenterX: itemCenterX,
|
||||
currentCenterY: itemCenterY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
initialRotation,
|
||||
displayRotation: initialRotation,
|
||||
targetRotation,
|
||||
};
|
||||
});
|
||||
|
||||
const eventTimestamp =
|
||||
typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp)
|
||||
? event.timeStamp
|
||||
: typeof performance !== 'undefined' && typeof performance.now === 'function'
|
||||
? performance.now()
|
||||
: Date.now();
|
||||
|
||||
dragStateRef.current = {
|
||||
docId,
|
||||
docKey,
|
||||
pointerId: event.pointerId,
|
||||
originCenterX: centerX,
|
||||
originCenterY: centerY,
|
||||
@@ -289,44 +420,198 @@ const useDocumentDrag = () => {
|
||||
localPointerOffsetY,
|
||||
containerRectLeft: containerLeft,
|
||||
containerRectTop: containerTop,
|
||||
};
|
||||
setDraggingId(docId);
|
||||
},
|
||||
[
|
||||
bringToFront,
|
||||
isGroup: isGroupDrag,
|
||||
groupDocIds,
|
||||
groupItems,
|
||||
groupElevated: !isGroupDrag,
|
||||
};
|
||||
|
||||
setDraggingId(docId);
|
||||
|
||||
if (isGroupDrag) {
|
||||
groupItems.forEach((item) => {
|
||||
if (item.docId === docKey) {
|
||||
return;
|
||||
}
|
||||
const node = itemRefs.current.get(item.docId);
|
||||
if (node) {
|
||||
item.displayRotation = item.initialRotation;
|
||||
node.style.transform = formatTransform(
|
||||
item.currentCenterX - item.width / 2,
|
||||
item.currentCenterY - item.height / 2,
|
||||
item.displayRotation,
|
||||
1,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
bringToFront,
|
||||
canvasPadding,
|
||||
cancelInertiaAnimation,
|
||||
documentLookup,
|
||||
ensureDocumentSize,
|
||||
layoutRef,
|
||||
resolveBaseMetrics,
|
||||
setDraggingId,
|
||||
debugDrag,
|
||||
containerRef,
|
||||
],
|
||||
);
|
||||
documentLookup,
|
||||
ensureDocumentSize,
|
||||
layoutRef,
|
||||
resolveBaseMetrics,
|
||||
selectedDocumentIds,
|
||||
setDraggingId,
|
||||
debugDrag,
|
||||
itemRefs,
|
||||
],
|
||||
);
|
||||
|
||||
const handlePointerMove = useCallback(
|
||||
(event) => {
|
||||
const state = dragStateRef.current;
|
||||
if (!state) {
|
||||
if (debugDrag) {
|
||||
console.log('[desk] handlePointerMove: no drag state for pointer', event.pointerId);
|
||||
}
|
||||
return;
|
||||
if (debugDrag) {
|
||||
console.log('[desk] handlePointerMove: no drag state for pointer', event.pointerId);
|
||||
}
|
||||
if (state.pointerId !== event.pointerId) {
|
||||
if (debugDrag) {
|
||||
console.log(
|
||||
'[desk] handlePointerMove: pointer mismatch expected',
|
||||
state.pointerId,
|
||||
'got',
|
||||
event.pointerId,
|
||||
);
|
||||
}
|
||||
return;
|
||||
return;
|
||||
}
|
||||
if (state.pointerId !== event.pointerId) {
|
||||
if (debugDrag) {
|
||||
console.log(
|
||||
'[desk] handlePointerMove: pointer mismatch expected',
|
||||
state.pointerId,
|
||||
'got',
|
||||
event.pointerId,
|
||||
);
|
||||
}
|
||||
preventAll(event);
|
||||
return;
|
||||
}
|
||||
preventAll(event);
|
||||
|
||||
if (state.isGroup) {
|
||||
const containerRect = containerRef?.current?.getBoundingClientRect?.();
|
||||
if (containerRect) {
|
||||
state.containerRectLeft = containerRect.left;
|
||||
state.containerRectTop = containerRect.top;
|
||||
}
|
||||
|
||||
const pointerCanvasX = event.clientX - state.containerRectLeft;
|
||||
const pointerCanvasY = event.clientY - state.containerRectTop;
|
||||
const deltaX = event.clientX - state.startX;
|
||||
const deltaY = event.clientY - state.startY;
|
||||
|
||||
if (!state.moved) {
|
||||
const distanceSquared = deltaX * deltaX + deltaY * deltaY;
|
||||
if (distanceSquared < DRAG_HYSTERESIS_SQUARED) {
|
||||
return;
|
||||
}
|
||||
state.moved = true;
|
||||
if (!state.groupElevated) {
|
||||
state.groupDocIds.forEach((id) => {
|
||||
if (id === state.docKey) {
|
||||
bringToFront(state.docId);
|
||||
} else {
|
||||
bringToFront(id);
|
||||
}
|
||||
});
|
||||
state.groupElevated = true;
|
||||
}
|
||||
}
|
||||
|
||||
const docWidth = state.width;
|
||||
const docHeight = state.height;
|
||||
const halfWidth = docWidth / 2;
|
||||
const halfHeight = docHeight / 2;
|
||||
const canvasWidth = canvasSize.width || defaultCanvasWidth;
|
||||
const canvasHeight = canvasSize.height || defaultCanvasHeight;
|
||||
const minCenterX = canvasPadding + halfWidth;
|
||||
const maxCenterX = Math.max(minCenterX, canvasWidth - canvasPadding - halfWidth);
|
||||
const minCenterY = canvasPadding + halfHeight;
|
||||
const maxCenterY = Math.max(minCenterY, canvasHeight - canvasPadding - halfHeight);
|
||||
|
||||
const desiredCenterX = pointerCanvasX - state.localPointerOffsetX;
|
||||
const desiredCenterY = pointerCanvasY - state.localPointerOffsetY;
|
||||
const centerX = clamp(desiredCenterX, minCenterX, maxCenterX);
|
||||
const centerY = clamp(desiredCenterY, minCenterY, maxCenterY);
|
||||
|
||||
const primaryEntry = layoutRef.current.get(state.docKey) || {};
|
||||
const primaryRotation = state.rotation ?? primaryEntry.rotation ?? 0;
|
||||
layoutRef.current.set(state.docKey, {
|
||||
...primaryEntry,
|
||||
centerX,
|
||||
centerY,
|
||||
});
|
||||
const primaryNode = itemRefs.current.get(state.docId);
|
||||
if (primaryNode) {
|
||||
primaryNode.style.transform = formatTransform(
|
||||
centerX - docWidth / 2,
|
||||
centerY - docHeight / 2,
|
||||
primaryRotation,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
}
|
||||
|
||||
state.groupItems.forEach((item) => {
|
||||
const isPrimary = item.docId === state.docKey;
|
||||
|
||||
if (isPrimary) {
|
||||
item.currentCenterX = centerX;
|
||||
item.currentCenterY = centerY;
|
||||
item.offsetX *= 0.92;
|
||||
item.offsetY *= 0.92;
|
||||
item.displayRotation = state.rotation ?? item.displayRotation ?? 0;
|
||||
} else {
|
||||
item.offsetX *= 0.92;
|
||||
item.offsetY *= 0.92;
|
||||
if (Math.abs(item.offsetX) < 1) item.offsetX = 0;
|
||||
if (Math.abs(item.offsetY) < 1) item.offsetY = 0;
|
||||
|
||||
const targetX = centerX + item.offsetX;
|
||||
const targetY = centerY + item.offsetY;
|
||||
const smoothing = 0.18;
|
||||
item.currentCenterX += (targetX - item.currentCenterX) * smoothing;
|
||||
item.currentCenterY += (targetY - item.currentCenterY) * smoothing;
|
||||
|
||||
const halfW = item.width / 2;
|
||||
const halfH = item.height / 2;
|
||||
const minX = canvasPadding + halfW;
|
||||
const maxX = Math.max(minX, canvasWidth - canvasPadding - halfW);
|
||||
const minY = canvasPadding + halfH;
|
||||
const maxY = Math.max(minY, canvasHeight - canvasPadding - halfH);
|
||||
item.currentCenterX = clamp(item.currentCenterX, minX, maxX);
|
||||
item.currentCenterY = clamp(item.currentCenterY, minY, maxY);
|
||||
|
||||
const rotationBlend = 0.16;
|
||||
item.displayRotation += (item.targetRotation - item.displayRotation) * rotationBlend;
|
||||
}
|
||||
|
||||
const entryItem = layoutRef.current.get(item.docId) || {};
|
||||
layoutRef.current.set(item.docId, {
|
||||
...entryItem,
|
||||
centerX: item.currentCenterX,
|
||||
centerY: item.currentCenterY,
|
||||
rotation: item.displayRotation ?? entryItem.rotation ?? 0,
|
||||
});
|
||||
|
||||
applyTransform(
|
||||
item.docId,
|
||||
item.currentCenterX,
|
||||
item.currentCenterY,
|
||||
item.width,
|
||||
item.height,
|
||||
item.displayRotation ?? entryItem.rotation ?? 0,
|
||||
isPrimary ? state.dragScale || 1 : 1,
|
||||
);
|
||||
});
|
||||
|
||||
state.lastClientX = event.clientX;
|
||||
state.lastClientY = event.clientY;
|
||||
state.lastTimestamp =
|
||||
typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp)
|
||||
? event.timeStamp
|
||||
: typeof performance !== 'undefined' && typeof performance.now === 'function'
|
||||
? performance.now()
|
||||
: Date.now();
|
||||
|
||||
recalcVisibleDocIds();
|
||||
return;
|
||||
}
|
||||
if (state.locked) {
|
||||
if (debugDrag) {
|
||||
console.log('[desk] handlePointerMove: locked drag for doc', state.docId);
|
||||
@@ -358,7 +643,7 @@ const useDocumentDrag = () => {
|
||||
const pointerCanvasX = event.clientX - containerLeft;
|
||||
const pointerCanvasY = event.clientY - containerTop;
|
||||
|
||||
const rotationDeg = state.rotation || 0;
|
||||
const rotationDeg = entry?.rotation ?? 0;
|
||||
const rotationRad = (rotationDeg * Math.PI) / 180;
|
||||
const cosRot = Math.cos(rotationRad);
|
||||
const sinRot = Math.sin(rotationRad);
|
||||
@@ -420,6 +705,26 @@ const useDocumentDrag = () => {
|
||||
const updated = { ...entry, centerX: currentCenterX, centerY: currentCenterY };
|
||||
layoutRef.current.set(state.docId, updated);
|
||||
|
||||
applyTransform(
|
||||
state.docId,
|
||||
currentCenterX,
|
||||
currentCenterY,
|
||||
state.width,
|
||||
state.height,
|
||||
rotationDeg,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
|
||||
const primaryNode = itemRefs.current.get(state.docId);
|
||||
if (primaryNode) {
|
||||
primaryNode.style.transform = formatTransform(
|
||||
currentCenterX - state.width / 2,
|
||||
currentCenterY - state.height / 2,
|
||||
rotationDeg,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
}
|
||||
|
||||
const offsetX = pointerCanvasX - currentCenterX;
|
||||
const offsetY = pointerCanvasY - currentCenterY;
|
||||
const cosInverseCurrent = Math.cos(-rotationRad);
|
||||
@@ -442,147 +747,10 @@ const useDocumentDrag = () => {
|
||||
}
|
||||
dt = clamp(dt, MIN_TIMESTEP, MAX_TIMESTEP);
|
||||
|
||||
const previousClientX = state.lastClientX;
|
||||
const previousClientY = state.lastClientY;
|
||||
const velocityX = (event.clientX - previousClientX) / dt;
|
||||
const velocityY = (event.clientY - previousClientY) / dt;
|
||||
const rawTorque = offsetX * velocityY - offsetY * velocityX;
|
||||
|
||||
const rotationBeforeIntegration = state.rotation;
|
||||
|
||||
let torque = rawTorque * BASE_TORQUE_FACTOR;
|
||||
let dampingOverride = null;
|
||||
if (collidedWithEdge) {
|
||||
const currentRotation =
|
||||
typeof state.rotation === 'number'
|
||||
? state.rotation
|
||||
: state.restRotation + state.dynamicRotation;
|
||||
torque =
|
||||
(rawTorque * EDGE_COLLISION_TORQUE_FACTOR + currentRotation * EDGE_ALIGNMENT_STIFFNESS) *
|
||||
EDGE_ALIGNMENT_TORQUE_MULTIPLIER;
|
||||
const restBlend = 1 - Math.exp(-EDGE_REST_REALIGN_RATE * dt);
|
||||
if (restBlend > 0) {
|
||||
const previousRest = state.restRotation;
|
||||
const nextRest = previousRest + (0 - previousRest) * restBlend;
|
||||
state.restRotation = nextRest;
|
||||
}
|
||||
dampingOverride = ANGULAR_DAMPING + EDGE_COLLISION_EXTRA_DAMPING;
|
||||
if (!pointerInsideCard) {
|
||||
torque = 0;
|
||||
currentCenterX = entry?.centerX ?? currentCenterX;
|
||||
currentCenterY = entry?.centerY ?? currentCenterY;
|
||||
if (updated.centerX !== currentCenterX || updated.centerY !== currentCenterY) {
|
||||
layoutRef.current.set(state.docId, { ...updated, centerX: currentCenterX, centerY: currentCenterY });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.lastClientX = event.clientX;
|
||||
state.lastClientY = event.clientY;
|
||||
state.lastTimestamp = currentTimestamp;
|
||||
|
||||
integrateRotation(state, dt, torque, dampingOverride);
|
||||
|
||||
if (!collidedWithEdge) {
|
||||
const rotationAfter = state.rotation || 0;
|
||||
if (rotationAfter !== rotationBeforeIntegration) {
|
||||
const rotationAfterRad = (rotationAfter * Math.PI) / 180;
|
||||
const cosAfter = Math.cos(rotationAfterRad);
|
||||
const sinAfter = Math.sin(rotationAfterRad);
|
||||
const rotatedOffsetXAfter =
|
||||
state.localPointerOffsetX * cosAfter - state.localPointerOffsetY * sinAfter;
|
||||
const rotatedOffsetYAfter =
|
||||
state.localPointerOffsetX * sinAfter + state.localPointerOffsetY * cosAfter;
|
||||
|
||||
const desiredCenterXAfter = pointerCanvasX - rotatedOffsetXAfter;
|
||||
const desiredCenterYAfter = pointerCanvasY - rotatedOffsetYAfter;
|
||||
|
||||
const absCosAfter = Math.abs(cosAfter);
|
||||
const absSinAfter = Math.abs(sinAfter);
|
||||
const rotatedHalfWidthAfter = absCosAfter * halfWidth + absSinAfter * halfHeight;
|
||||
const rotatedHalfHeightAfter = absSinAfter * halfWidth + absCosAfter * halfHeight;
|
||||
|
||||
const minCenterXAfter = canvasPadding + rotatedHalfWidthAfter;
|
||||
const maxCenterXAfter = Math.max(
|
||||
minCenterXAfter,
|
||||
canvasWidth - canvasPadding - rotatedHalfWidthAfter,
|
||||
);
|
||||
const minCenterYAfter = canvasPadding + rotatedHalfHeightAfter;
|
||||
const maxCenterYAfter = Math.max(
|
||||
minCenterYAfter,
|
||||
canvasHeight - canvasPadding - rotatedHalfHeightAfter,
|
||||
);
|
||||
|
||||
const correctedCenterX = clamp(
|
||||
desiredCenterXAfter,
|
||||
minCenterXAfter,
|
||||
maxCenterXAfter,
|
||||
);
|
||||
const correctedCenterY = clamp(
|
||||
desiredCenterYAfter,
|
||||
minCenterYAfter,
|
||||
maxCenterYAfter,
|
||||
);
|
||||
|
||||
if (
|
||||
Math.abs(correctedCenterX - currentCenterX) > 0.01 ||
|
||||
Math.abs(correctedCenterY - currentCenterY) > 0.01
|
||||
) {
|
||||
const entryAfter = layoutRef.current.get(state.docId);
|
||||
if (entryAfter) {
|
||||
const adjustedEntry = {
|
||||
...entryAfter,
|
||||
centerX: correctedCenterX,
|
||||
centerY: correctedCenterY,
|
||||
};
|
||||
layoutRef.current.set(state.docId, adjustedEntry);
|
||||
const nodeAfter = itemRefs.current.get(state.docId);
|
||||
if (nodeAfter) {
|
||||
nodeAfter.style.transform = formatTransform(
|
||||
correctedCenterX - state.width / 2,
|
||||
correctedCenterY - state.height / 2,
|
||||
state.rotation,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
}
|
||||
currentCenterX = correctedCenterX;
|
||||
currentCenterY = correctedCenterY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collidedWithEdge) {
|
||||
const rotationAfter = state.rotation;
|
||||
const crossedAlignment =
|
||||
rotationBeforeIntegration > EDGE_ALIGNMENT_EPSILON && rotationAfter < 0
|
||||
? rotationBeforeIntegration - rotationAfter > EDGE_ALIGNMENT_EPSILON
|
||||
: rotationBeforeIntegration < -EDGE_ALIGNMENT_EPSILON && rotationAfter > 0
|
||||
? rotationAfter - rotationBeforeIntegration > EDGE_ALIGNMENT_EPSILON
|
||||
: false;
|
||||
const nearAlignment = Math.abs(rotationAfter) <= EDGE_ALIGNMENT_EPSILON;
|
||||
if (crossedAlignment || nearAlignment) {
|
||||
state.restRotation = 0;
|
||||
state.dynamicRotation = 0;
|
||||
state.angularVelocity = 0;
|
||||
state.rotation = 0;
|
||||
const updatedEntry = layoutRef.current.get(state.docId);
|
||||
if (updatedEntry) {
|
||||
const alignedEntry = { ...updatedEntry, rotation: 0 };
|
||||
layoutRef.current.set(state.docId, alignedEntry);
|
||||
const node = itemRefs.current.get(state.docId);
|
||||
if (node) {
|
||||
node.style.transform = formatTransform(
|
||||
alignedEntry.centerX - state.width / 2,
|
||||
alignedEntry.centerY - state.height / 2,
|
||||
0,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rotationForOffsetDeg = state.rotation || 0;
|
||||
const rotationForOffsetRad = (rotationForOffsetDeg * Math.PI) / 180;
|
||||
const cosInverse = Math.cos(-rotationForOffsetRad);
|
||||
@@ -609,8 +777,9 @@ const useDocumentDrag = () => {
|
||||
defaultCanvasHeight,
|
||||
defaultCanvasWidth,
|
||||
containerRef,
|
||||
integrateRotation,
|
||||
layoutRef,
|
||||
itemRefs,
|
||||
applyTransform,
|
||||
recalcVisibleDocIds,
|
||||
debugDrag,
|
||||
],
|
||||
@@ -619,51 +788,61 @@ const useDocumentDrag = () => {
|
||||
const handlePointerUp = useCallback(
|
||||
(event) => {
|
||||
const state = dragStateRef.current;
|
||||
if (state && state.pointerId === event.pointerId) {
|
||||
if (state.moved) {
|
||||
const inertiaState = {
|
||||
restRotation: state.restRotation,
|
||||
dynamicRotation: state.dynamicRotation,
|
||||
angularVelocity: state.angularVelocity,
|
||||
rotation: state.rotation,
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
dragScale: state.dragScale || 1,
|
||||
};
|
||||
const docId = state.docId;
|
||||
finishDrag(event.pointerId);
|
||||
startInertiaAnimation(docId, inertiaState);
|
||||
return;
|
||||
}
|
||||
|
||||
const docId = state.docId;
|
||||
const metaPressed = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
||||
if (!metaPressed) {
|
||||
bringToFront(docId);
|
||||
}
|
||||
const originInfo = {
|
||||
rotation: state.rotation || 0,
|
||||
scale: state.baseScale || 1,
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
};
|
||||
const docKey = docId != null ? String(docId) : null;
|
||||
const doc = docKey ? documentLookup.get(docKey) : null;
|
||||
tapHandler(event, {
|
||||
docId,
|
||||
originInfo,
|
||||
docTitle: doc?.title || 'document',
|
||||
});
|
||||
if (!state || state.pointerId !== event.pointerId) {
|
||||
finishDrag(event.pointerId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.isGroup) {
|
||||
finalizeGroupDrag(state);
|
||||
finishDrag(event.pointerId);
|
||||
recalcVisibleDocIds();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.moved) {
|
||||
const inertiaState = {
|
||||
restRotation: state.restRotation,
|
||||
dynamicRotation: state.dynamicRotation,
|
||||
angularVelocity: state.angularVelocity,
|
||||
rotation: state.rotation,
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
dragScale: state.dragScale || 1,
|
||||
};
|
||||
const docId = state.docId;
|
||||
finishDrag(event.pointerId);
|
||||
startInertiaAnimation(docId, inertiaState);
|
||||
return;
|
||||
}
|
||||
|
||||
const docId = state.docId;
|
||||
const metaPressed = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
||||
if (!metaPressed) {
|
||||
bringToFront(docId);
|
||||
}
|
||||
const originInfo = {
|
||||
rotation: state.rotation || 0,
|
||||
scale: state.baseScale || 1,
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
};
|
||||
const docKey = docId != null ? String(docId) : null;
|
||||
const doc = docKey ? documentLookup.get(docKey) : null;
|
||||
tapHandler(event, {
|
||||
docId,
|
||||
originInfo,
|
||||
docTitle: doc?.title || 'document',
|
||||
});
|
||||
finishDrag(event.pointerId);
|
||||
},
|
||||
[
|
||||
bringToFront,
|
||||
documentLookup,
|
||||
finishDrag,
|
||||
finalizeGroupDrag,
|
||||
startInertiaAnimation,
|
||||
recalcVisibleDocIds,
|
||||
tapHandler,
|
||||
],
|
||||
);
|
||||
@@ -672,6 +851,13 @@ const useDocumentDrag = () => {
|
||||
(event) => {
|
||||
const state = dragStateRef.current;
|
||||
if (state && state.pointerId === event.pointerId && state.moved) {
|
||||
if (state.isGroup) {
|
||||
finalizeGroupDrag(state);
|
||||
finishDrag(event.pointerId);
|
||||
recalcVisibleDocIds();
|
||||
return;
|
||||
}
|
||||
|
||||
const inertiaState = {
|
||||
restRotation: state.restRotation,
|
||||
dynamicRotation: state.dynamicRotation,
|
||||
@@ -688,7 +874,7 @@ const useDocumentDrag = () => {
|
||||
}
|
||||
finishDrag(event.pointerId);
|
||||
},
|
||||
[finishDrag, startInertiaAnimation],
|
||||
[finalizeGroupDrag, finishDrag, recalcVisibleDocIds, startInertiaAnimation],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user