rename skeuo to desktop
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useDesktopContext } from './context';
|
||||
import { preventAll } from './events';
|
||||
import { clamp, formatTransform } from './math';
|
||||
|
||||
const useDocumentDrag = () => {
|
||||
const {
|
||||
layoutRef,
|
||||
itemRefs,
|
||||
documentLookup,
|
||||
ensureDocumentSize,
|
||||
resolveBaseMetrics,
|
||||
bringToFront,
|
||||
setDraggingId,
|
||||
syncLayoutSnapshot,
|
||||
canvasSize,
|
||||
openOverlayForDoc,
|
||||
recalcVisibleDocIds,
|
||||
settings,
|
||||
} = useDesktopContext();
|
||||
|
||||
const dragStateRef = useRef(null);
|
||||
const { canvasPadding, defaultCanvasWidth, defaultCanvasHeight, debugDrag } = settings;
|
||||
|
||||
const finishDrag = useCallback(
|
||||
(pointerId) => {
|
||||
const state = dragStateRef.current;
|
||||
if (!state || state.pointerId !== pointerId) {
|
||||
return;
|
||||
}
|
||||
const capturedTarget = state.capturedTarget;
|
||||
if (capturedTarget && typeof capturedTarget.releasePointerCapture === 'function') {
|
||||
try {
|
||||
capturedTarget.releasePointerCapture(pointerId);
|
||||
} catch (
|
||||
// eslint-disable-next-line no-empty
|
||||
error
|
||||
) {}
|
||||
}
|
||||
dragStateRef.current = null;
|
||||
setDraggingId((current) => (current === state.docId ? null : current));
|
||||
syncLayoutSnapshot();
|
||||
},
|
||||
[setDraggingId, syncLayoutSnapshot],
|
||||
);
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(event, docId) => {
|
||||
if (debugDrag) {
|
||||
console.log(
|
||||
'[skeuo] handlePointerDown fired for doc',
|
||||
docId,
|
||||
'button',
|
||||
event.button,
|
||||
'pointerType',
|
||||
event.pointerType,
|
||||
'pointerId',
|
||||
event.pointerId,
|
||||
);
|
||||
}
|
||||
preventAll(event);
|
||||
const entry = layoutRef.current.get(docId) || null;
|
||||
const docKey = docId != null ? String(docId) : null;
|
||||
const doc = docKey ? documentLookup.get(docKey) : null;
|
||||
const { width: docWidth, height: docHeight } = ensureDocumentSize(doc);
|
||||
const { baseScale } = resolveBaseMetrics(doc, docWidth, docHeight);
|
||||
const normalizedBaseScale =
|
||||
Number.isFinite(baseScale) && baseScale > 0 ? baseScale : 1;
|
||||
const defaultCenterX = canvasPadding + docWidth / 2;
|
||||
const defaultCenterY = canvasPadding + docHeight / 2;
|
||||
const centerX = typeof entry?.centerX === 'number' ? entry.centerX : defaultCenterX;
|
||||
const centerY = typeof entry?.centerY === 'number' ? entry.centerY : defaultCenterY;
|
||||
|
||||
if (entry && (entry.centerX !== centerX || entry.centerY !== centerY)) {
|
||||
layoutRef.current.set(docId, { ...entry, centerX, centerY });
|
||||
}
|
||||
|
||||
bringToFront(docId);
|
||||
const capturedTarget = event.currentTarget instanceof HTMLElement ? event.currentTarget : null;
|
||||
if (capturedTarget && typeof capturedTarget.setPointerCapture === 'function') {
|
||||
try {
|
||||
capturedTarget.setPointerCapture(event.pointerId);
|
||||
} catch (
|
||||
// eslint-disable-next-line no-empty
|
||||
error
|
||||
) {}
|
||||
}
|
||||
dragStateRef.current = {
|
||||
docId,
|
||||
pointerId: event.pointerId,
|
||||
originCenterX: centerX,
|
||||
originCenterY: centerY,
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
rotation: entry?.rotation ?? 0,
|
||||
moved: false,
|
||||
locked: false,
|
||||
width: docWidth,
|
||||
height: docHeight,
|
||||
dragScale: 1,
|
||||
baseScale: normalizedBaseScale,
|
||||
capturedTarget,
|
||||
};
|
||||
setDraggingId(docId);
|
||||
},
|
||||
[
|
||||
bringToFront,
|
||||
canvasPadding,
|
||||
documentLookup,
|
||||
ensureDocumentSize,
|
||||
layoutRef,
|
||||
resolveBaseMetrics,
|
||||
setDraggingId,
|
||||
debugDrag,
|
||||
],
|
||||
);
|
||||
|
||||
const handlePointerMove = useCallback(
|
||||
(event) => {
|
||||
const state = dragStateRef.current;
|
||||
if (!state) {
|
||||
if (debugDrag) {
|
||||
console.log('[skeuo] handlePointerMove: no drag state for pointer', event.pointerId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state.pointerId !== event.pointerId) {
|
||||
if (debugDrag) {
|
||||
console.log(
|
||||
'[skeuo] handlePointerMove: pointer mismatch expected',
|
||||
state.pointerId,
|
||||
'got',
|
||||
event.pointerId,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
preventAll(event);
|
||||
if (state.locked) {
|
||||
if (debugDrag) {
|
||||
console.log('[skeuo] handlePointerMove: locked drag for doc', state.docId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = layoutRef.current.get(state.docId);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaX = event.clientX - state.startX;
|
||||
const deltaY = event.clientY - state.startY;
|
||||
const nextCenterX = state.originCenterX + deltaX;
|
||||
const nextCenterY = state.originCenterY + deltaY;
|
||||
|
||||
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 clampedCenterX = clamp(nextCenterX, minCenterX, maxCenterX);
|
||||
const clampedCenterY = clamp(nextCenterY, minCenterY, maxCenterY);
|
||||
|
||||
const prevCenterX = typeof entry.centerX === 'number' ? entry.centerX : state.originCenterX;
|
||||
const prevCenterY = typeof entry.centerY === 'number' ? entry.centerY : state.originCenterY;
|
||||
if (Math.abs(clampedCenterX - prevCenterX) < 0.5 && Math.abs(clampedCenterY - prevCenterY) < 0.5) {
|
||||
if (debugDrag) {
|
||||
console.log('[skeuo] handlePointerMove: movement under threshold for doc', state.docId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = { ...entry, centerX: clampedCenterX, centerY: clampedCenterY };
|
||||
layoutRef.current.set(state.docId, updated);
|
||||
|
||||
const node = itemRefs.current.get(state.docId);
|
||||
if (node) {
|
||||
node.style.transform = formatTransform(
|
||||
clampedCenterX - state.width / 2,
|
||||
clampedCenterY - state.height / 2,
|
||||
state.rotation,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
}
|
||||
state.moved = true;
|
||||
if (debugDrag) {
|
||||
console.log('[skeuo] handlePointerMove: moved doc', state.docId, 'to', clampedCenterX, clampedCenterY);
|
||||
}
|
||||
recalcVisibleDocIds();
|
||||
},
|
||||
[
|
||||
canvasPadding,
|
||||
canvasSize.height,
|
||||
canvasSize.width,
|
||||
defaultCanvasHeight,
|
||||
defaultCanvasWidth,
|
||||
itemRefs,
|
||||
layoutRef,
|
||||
recalcVisibleDocIds,
|
||||
debugDrag,
|
||||
],
|
||||
);
|
||||
|
||||
const handlePointerUp = useCallback(
|
||||
(event) => {
|
||||
const state = dragStateRef.current;
|
||||
if (state && state.pointerId === event.pointerId) {
|
||||
const moved = Boolean(state.moved);
|
||||
const docId = state.docId;
|
||||
const shouldOpen = !moved && event.detail >= 2;
|
||||
finishDrag(event.pointerId);
|
||||
if (shouldOpen) {
|
||||
const originInfo = {
|
||||
rotation: state.rotation || 0,
|
||||
scale: state.baseScale || 1,
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
};
|
||||
openOverlayForDoc(docId, originInfo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
finishDrag(event.pointerId);
|
||||
},
|
||||
[finishDrag, openOverlayForDoc],
|
||||
);
|
||||
|
||||
const handlePointerCancel = useCallback(
|
||||
(event) => {
|
||||
finishDrag(event.pointerId);
|
||||
},
|
||||
[finishDrag],
|
||||
);
|
||||
|
||||
return {
|
||||
handlePointerDown,
|
||||
handlePointerMove,
|
||||
handlePointerUp,
|
||||
handlePointerCancel,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDocumentDrag;
|
||||
Reference in New Issue
Block a user