전략 편집기 수정
This commit is contained in:
@@ -1295,15 +1295,17 @@ function StrategyEditorCanvasInner({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { flowPos, anchorId } = resolveDropFromClientRef.current(ev.x, ev.y);
|
const { flowPos, anchorId } = resolveDropFromClientRef.current(ev.x, ev.y);
|
||||||
const previewKey = anchorId
|
|
||||||
? `${anchorId}:${Math.round(flowPos.x)}:${Math.round(flowPos.y)}`
|
|
||||||
: '';
|
|
||||||
if (previewKey === lastPreviewKeyRef.current) return;
|
|
||||||
lastPreviewAnchorRef.current = anchorId;
|
|
||||||
lastPreviewKeyRef.current = previewKey;
|
|
||||||
if (anchorId) {
|
if (anchorId) {
|
||||||
|
const sideKey = `${anchorId}:${Math.round(flowPos.x / 8)}:${Math.round(flowPos.y / 8)}`;
|
||||||
|
if (sideKey === lastPreviewKeyRef.current) return;
|
||||||
|
lastPreviewKeyRef.current = sideKey;
|
||||||
|
lastPreviewAnchorRef.current = anchorId;
|
||||||
updatePreviewRef.current(anchorId, flowPos);
|
updatePreviewRef.current(anchorId, flowPos);
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
if (lastPreviewAnchorRef.current != null) {
|
||||||
|
lastPreviewAnchorRef.current = null;
|
||||||
|
lastPreviewKeyRef.current = null;
|
||||||
clearPreviewRef.current();
|
clearPreviewRef.current();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { elementsUnderDragPoint } from './flowScreenProjection';
|
import { elementsUnderDragPoint } from './flowScreenProjection';
|
||||||
import {
|
import {
|
||||||
bindWindowDrag,
|
bindWindowDrag,
|
||||||
|
clientXYFromNative,
|
||||||
isPrimaryPointerButton,
|
isPrimaryPointerButton,
|
||||||
} from './pointerDrag';
|
} from './pointerDrag';
|
||||||
import { isDesktop } from './platform';
|
import { isDesktop } from './platform';
|
||||||
@@ -47,13 +48,16 @@ let activePointerId: number | null = null;
|
|||||||
let activeTouchId: number | null = null;
|
let activeTouchId: number | null = null;
|
||||||
let overlayController: OverlayController | null = null;
|
let overlayController: OverlayController | null = null;
|
||||||
let overlayElement: HTMLElement | null = null;
|
let overlayElement: HTMLElement | null = null;
|
||||||
|
let gestureCaptureEl: HTMLElement | null = null;
|
||||||
let listeners = new Set<PaletteDragListener>();
|
let listeners = new Set<PaletteDragListener>();
|
||||||
let dropHandler: PaletteDropHandler | null = null;
|
let dropHandler: PaletteDropHandler | null = null;
|
||||||
let moveRafId: number | null = null;
|
let moveRafId: number | null = null;
|
||||||
let pendingMove: { x: number; y: number } | null = null;
|
let pendingMove: { x: number; y: number } | null = null;
|
||||||
let unbindActiveDrag: (() => void) | null = null;
|
let unbindActiveMove: (() => void) | null = null;
|
||||||
|
let unbindActiveEnd: (() => void) | null = null;
|
||||||
let pendingPointerListeners: (() => void) | null = null;
|
let pendingPointerListeners: (() => void) | null = null;
|
||||||
let lastDragClientXY: { x: number; y: number } | null = null;
|
let lastDragClientXY: { x: number; y: number } | null = null;
|
||||||
|
let endListenersArmed = false;
|
||||||
|
|
||||||
function hasCoarsePointer(): boolean {
|
function hasCoarsePointer(): boolean {
|
||||||
if (typeof window === 'undefined') return false;
|
if (typeof window === 'undefined') return false;
|
||||||
@@ -108,6 +112,12 @@ function dispatchEnd(xy: { x: number; y: number }, payload: PaletteDragPayload)
|
|||||||
|
|
||||||
function releasePointerCapture(pointerId: number | null) {
|
function releasePointerCapture(pointerId: number | null) {
|
||||||
if (pointerId == null) return;
|
if (pointerId == null) return;
|
||||||
|
try {
|
||||||
|
gestureCaptureEl?.releasePointerCapture(pointerId);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
gestureCaptureEl = null;
|
||||||
try {
|
try {
|
||||||
overlayElement?.releasePointerCapture(pointerId);
|
overlayElement?.releasePointerCapture(pointerId);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -127,10 +137,17 @@ function clearPendingPointerListeners() {
|
|||||||
pendingPointerListeners = null;
|
pendingPointerListeners = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unbindActiveDragListeners() {
|
||||||
|
unbindActiveMove?.();
|
||||||
|
unbindActiveMove = null;
|
||||||
|
unbindActiveEnd?.();
|
||||||
|
unbindActiveEnd = null;
|
||||||
|
endListenersArmed = false;
|
||||||
|
}
|
||||||
|
|
||||||
function cleanupDrag() {
|
function cleanupDrag() {
|
||||||
const pointerId = activePointerId;
|
const pointerId = activePointerId;
|
||||||
unbindActiveDrag?.();
|
unbindActiveDragListeners();
|
||||||
unbindActiveDrag = null;
|
|
||||||
clearPendingPointerListeners();
|
clearPendingPointerListeners();
|
||||||
releasePointerCapture(pointerId);
|
releasePointerCapture(pointerId);
|
||||||
activePayload = null;
|
activePayload = null;
|
||||||
@@ -177,10 +194,12 @@ export function notifyPaletteDragMove(clientX: number, clientY: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function notifyPaletteDragEnd(clientX: number, clientY: number) {
|
export function notifyPaletteDragEnd(clientX: number, clientY: number) {
|
||||||
|
if (!endListenersArmed) return;
|
||||||
finishDrag('end', { x: clientX, y: clientY });
|
finishDrag('end', { x: clientX, y: clientY });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function notifyPaletteDragCancel(clientX: number, clientY: number) {
|
export function notifyPaletteDragCancel(clientX: number, clientY: number) {
|
||||||
|
if (!endListenersArmed) return;
|
||||||
finishDrag('cancel', { x: clientX, y: clientY });
|
finishDrag('cancel', { x: clientX, y: clientY });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,24 +222,43 @@ function eventMatchesActiveInput(ev: Event): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindActiveDragWindowListeners(pointerId: number) {
|
function bindActiveDragMoveListeners(pointerId: number) {
|
||||||
unbindActiveDrag?.();
|
unbindActiveMove?.();
|
||||||
|
|
||||||
unbindActiveDrag = bindWindowDrag(
|
unbindActiveMove = bindWindowDrag(
|
||||||
(xy, ev) => {
|
(xy, ev) => {
|
||||||
if (!activePayload || activePointerId !== pointerId) return;
|
if (!activePayload || activePointerId !== pointerId) return;
|
||||||
if (!eventMatchesActiveInput(ev)) return;
|
if (!eventMatchesActiveInput(ev)) return;
|
||||||
notifyPaletteDragMove(xy.x, xy.y);
|
notifyPaletteDragMove(xy.x, xy.y);
|
||||||
},
|
},
|
||||||
(xy, ev) => {
|
() => { /* end 는 별도 등록 */ },
|
||||||
if (!activePayload || activePointerId !== pointerId) return;
|
|
||||||
if (!eventMatchesActiveInput(ev)) return;
|
|
||||||
finishDrag('end', xy);
|
|
||||||
},
|
|
||||||
{ preventTouchScroll: true, useCapture: true },
|
{ preventTouchScroll: true, useCapture: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bindActiveDragEndListeners(pointerId: number) {
|
||||||
|
unbindActiveEnd?.();
|
||||||
|
|
||||||
|
const onEnd = (ev: Event) => {
|
||||||
|
if (!endListenersArmed) return;
|
||||||
|
if (!activePayload || activePointerId !== pointerId) return;
|
||||||
|
if (!eventMatchesActiveInput(ev)) return;
|
||||||
|
const xy = clientXYFromNative(ev) ?? lastDragClientXY;
|
||||||
|
if (!xy) return;
|
||||||
|
finishDrag('end', xy);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('pointerup', onEnd, true);
|
||||||
|
window.addEventListener('mouseup', onEnd, true);
|
||||||
|
window.addEventListener('touchend', onEnd, true);
|
||||||
|
|
||||||
|
unbindActiveEnd = () => {
|
||||||
|
window.removeEventListener('pointerup', onEnd, true);
|
||||||
|
window.removeEventListener('mouseup', onEnd, true);
|
||||||
|
window.removeEventListener('touchend', onEnd, true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function captureOnOverlay(pointerId: number) {
|
function captureOnOverlay(pointerId: number) {
|
||||||
try {
|
try {
|
||||||
overlayElement?.setPointerCapture(pointerId);
|
overlayElement?.setPointerCapture(pointerId);
|
||||||
@@ -236,6 +274,11 @@ function captureOnOverlay(pointerId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function armDragEndListeners(pointerId: number) {
|
||||||
|
endListenersArmed = true;
|
||||||
|
bindActiveDragEndListeners(pointerId);
|
||||||
|
}
|
||||||
|
|
||||||
function beginDrag(
|
function beginDrag(
|
||||||
payload: PaletteDragPayload,
|
payload: PaletteDragPayload,
|
||||||
xy: { x: number; y: number },
|
xy: { x: number; y: number },
|
||||||
@@ -250,14 +293,23 @@ function beginDrag(
|
|||||||
activePointerId = pointerId;
|
activePointerId = pointerId;
|
||||||
activeTouchId = touchId;
|
activeTouchId = touchId;
|
||||||
lastDragClientXY = { x: xy.x, y: xy.y };
|
lastDragClientXY = { x: xy.x, y: xy.y };
|
||||||
|
endListenersArmed = false;
|
||||||
document.body.classList.add('se-palette-drag-active');
|
document.body.classList.add('se-palette-drag-active');
|
||||||
|
|
||||||
bindActiveDragWindowListeners(pointerId);
|
bindActiveDragMoveListeners(pointerId);
|
||||||
overlayController?.mount({ payload, pointerId, x: xy.x, y: xy.y });
|
overlayController?.mount({ payload, pointerId, x: xy.x, y: xy.y });
|
||||||
|
|
||||||
requestAnimationFrame(() => captureOnOverlay(pointerId));
|
|
||||||
|
|
||||||
emit({ phase: 'start', x: xy.x, y: xy.y, payload });
|
emit({ phase: 'start', x: xy.x, y: xy.y, payload });
|
||||||
|
notifyPaletteDragMove(xy.x, xy.y);
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (activePointerId !== pointerId) return;
|
||||||
|
captureOnOverlay(pointerId);
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (activePointerId !== pointerId) return;
|
||||||
|
armDragEndListeners(pointerId);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bindPaletteDragOverlayElement(el: HTMLElement | null) {
|
export function bindPaletteDragOverlayElement(el: HTMLElement | null) {
|
||||||
@@ -315,6 +367,14 @@ export function handlePalettePointerDown(
|
|||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
const sourceEl = e.currentTarget as HTMLElement;
|
||||||
|
try {
|
||||||
|
sourceEl.setPointerCapture(e.pointerId);
|
||||||
|
gestureCaptureEl = sourceEl;
|
||||||
|
} catch {
|
||||||
|
/* overlay/body capture later */
|
||||||
|
}
|
||||||
|
|
||||||
startPaletteDragGesture(
|
startPaletteDragGesture(
|
||||||
e.clientX,
|
e.clientX,
|
||||||
e.clientY,
|
e.clientY,
|
||||||
|
|||||||
Reference in New Issue
Block a user