전략편집기 수정

This commit is contained in:
Macbook
2026-06-15 01:38:52 +09:00
parent 3150dc8af6
commit 12c556a95e
5 changed files with 92 additions and 19 deletions
@@ -3,9 +3,11 @@ import {
handlePaletteMouseDown, handlePaletteMouseDown,
handlePalettePointerDown, handlePalettePointerDown,
handlePaletteTouchStart, handlePaletteTouchStart,
endHtmlDragMouseTracking,
markPaletteHtmlDragEnd, markPaletteHtmlDragEnd,
needsPointerPaletteDrag, needsPointerPaletteDrag,
shouldSuppressPaletteClick, shouldSuppressPaletteClick,
startHtmlDragMouseTracking,
writePaletteHtmlDragData, writePaletteHtmlDragData,
type PaletteDragPayload, type PaletteDragPayload,
} from '../../utils/paletteDragSession'; } from '../../utils/paletteDragSession';
@@ -55,11 +57,13 @@ export default function PaletteChip({
e.preventDefault(); e.preventDefault();
return; return;
} }
writePaletteHtmlDragData(e, buildPayload()); const payload = buildPayload();
writePaletteHtmlDragData(e, payload);
startHtmlDragMouseTracking(e.clientX, e.clientY, payload);
}; };
const onDragEnd = () => { const onDragEnd = () => {
markPaletteHtmlDragEnd(); endHtmlDragMouseTracking();
}; };
const onPointerDown = (e: React.PointerEvent) => { const onPointerDown = (e: React.PointerEvent) => {
@@ -4,8 +4,9 @@ import type { DefType } from '../../utils/strategyEditorShared';
import { import {
handlePalettePointerDown, handlePalettePointerDown,
handlePaletteTouchStart, handlePaletteTouchStart,
markPaletteHtmlDragEnd, endHtmlDragMouseTracking,
needsPointerPaletteDrag, needsPointerPaletteDrag,
startHtmlDragMouseTracking,
writePaletteHtmlDragData, writePaletteHtmlDragData,
type PaletteDragPayload, type PaletteDragPayload,
} from '../../utils/paletteDragSession'; } from '../../utils/paletteDragSession';
@@ -88,11 +89,12 @@ function SidewaysFilterChip({
label: item.label, label: item.label,
}; };
writePaletteHtmlDragData(e, payload as PaletteDragPayload); writePaletteHtmlDragData(e, payload as PaletteDragPayload);
startHtmlDragMouseTracking(e.clientX, e.clientY, payload as PaletteDragPayload);
}; };
const onDragEnd = () => { const onDragEnd = () => {
hideHint(); hideHint();
markPaletteHtmlDragEnd(); endHtmlDragMouseTracking();
}; };
const onPointerDown = (e: React.PointerEvent) => { const onPointerDown = (e: React.PointerEvent) => {
@@ -1308,32 +1308,26 @@ function StrategyEditorCanvasInner({
if (anchorId) { if (anchorId) {
updatePreviewRef.current(anchorId, flowPos); updatePreviewRef.current(anchorId, flowPos);
lastPreviewAnchorRef.current = anchorId; lastPreviewAnchorRef.current = anchorId;
return;
}
if (lastPreviewAnchorRef.current != null) {
lastPreviewAnchorRef.current = null;
lastPreviewKeyRef.current = null;
clearPreviewRef.current();
} }
}; };
document.addEventListener('dragover', onDocDragOver, { passive: false });
return () => document.removeEventListener('dragover', onDocDragOver);
}, []);
useEffect(() => {
const onDocDragEnd = () => { const onDocDragEnd = () => {
lastPreviewAnchorRef.current = null; lastPreviewAnchorRef.current = null;
lastPreviewKeyRef.current = null; lastPreviewKeyRef.current = null;
clearPreviewRef.current(); clearPreviewRef.current();
markPaletteHtmlDragEnd(); markPaletteHtmlDragEnd();
}; };
document.addEventListener('dragover', onDocDragOver, { passive: false });
document.addEventListener('drag', onDocDragOver);
document.addEventListener('dragend', onDocDragEnd); document.addEventListener('dragend', onDocDragEnd);
return () => { return () => {
document.removeEventListener('dragover', onDocDragOver);
document.removeEventListener('drag', onDocDragOver);
document.removeEventListener('dragend', onDocDragEnd); document.removeEventListener('dragend', onDocDragEnd);
}; };
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!needsPointerPaletteDrag()) return;
return subscribePaletteDrag((ev: PaletteDragEvent) => { return subscribePaletteDrag((ev: PaletteDragEvent) => {
if (ev.phase === 'move') { if (ev.phase === 'move') {
const { flowPos, anchorId } = resolveDropFromClientRef.current(ev.x, ev.y); const { flowPos, anchorId } = resolveDropFromClientRef.current(ev.x, ev.y);
@@ -493,7 +493,6 @@ export default function StrategyListEditor({
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!needsPointerPaletteDrag()) return;
return subscribePaletteDrag(ev => { return subscribePaletteDrag(ev => {
if (ev.phase === 'move') { if (ev.phase === 'move') {
if (!isOverListEditor(ev.x, ev.y)) { if (!isOverListEditor(ev.x, ev.y)) {
+77 -3
View File
@@ -7,7 +7,7 @@ import {
bindWindowDrag, bindWindowDrag,
isPrimaryPointerButton, isPrimaryPointerButton,
} from './pointerDrag'; } from './pointerDrag';
import { isDesktop } from './platform'; import { isDesktop, isMacDesktop } from './platform';
export type PaletteDragPayload = Record<string, unknown> & { export type PaletteDragPayload = Record<string, unknown> & {
type: string; type: string;
@@ -54,6 +54,7 @@ let moveRafId: number | null = null;
let pendingMove: { x: number; y: number } | null = null; let pendingMove: { x: number; y: number } | null = null;
let unbindGesture: (() => void) | null = null; let unbindGesture: (() => void) | null = null;
let lastDragClientXY: { x: number; y: number } | null = null; let lastDragClientXY: { x: number; y: number } | null = null;
let htmlDragMouseTrackUnbind: (() => void) | null = null;
let htmlPaletteDragActive = false; let htmlPaletteDragActive = false;
let lastHtmlDragClientXY: { x: number; y: number } | null = null; let lastHtmlDragClientXY: { x: number; y: number } | null = null;
@@ -80,6 +81,7 @@ function installHtmlPaletteDragGlobals() {
}); });
window.addEventListener('dragend', () => { window.addEventListener('dragend', () => {
stopHtmlDragMouseTracking();
htmlPaletteDragActive = false; htmlPaletteDragActive = false;
lastHtmlDragClientXY = null; lastHtmlDragClientXY = null;
}); });
@@ -96,8 +98,9 @@ function hasCoarsePointer(): boolean {
} }
} }
/** HTML5 DnD 대신 pointer/touch overlay 드래그 (Tauri Desktop 전체 — macOS WKWebView HTML5 DnD 불안정) */ /** HTML5 DnD 대신 pointer overlay (Windows Desktop 등). macOS WKWebView HTML5 + mousemove preview */
export function needsPointerPaletteDrag(): boolean { export function needsPointerPaletteDrag(): boolean {
if (isMacDesktop()) return false;
return isDesktop() || hasCoarsePointer(); return isDesktop() || hasCoarsePointer();
} }
@@ -277,8 +280,46 @@ function startPaletteDragGesture(
finishDrag('end', xy); finishDrag('end', xy);
} }
}, },
{ preventTouchScroll: true, useCapture: isDesktop() }, { preventTouchScroll: true },
); );
if (gestureCaptureEl) {
const el = gestureCaptureEl;
const pid = pointerId;
const onElMove = (ev: PointerEvent) => {
if (ev.pointerId !== pid) return;
const xy = { x: ev.clientX, y: ev.clientY };
if (!dragging) {
const dx = xy.x - startX;
const dy = xy.y - startY;
if (Math.hypot(dx, dy) < DRAG_THRESHOLD_PX) return;
dragging = true;
mountDragOverlay(payload, xy, pointerId);
return;
}
notifyPaletteDragMove(xy.x, xy.y);
};
const onElEnd = (ev: PointerEvent) => {
if (ev.pointerId !== pid) return;
el.removeEventListener('pointermove', onElMove);
el.removeEventListener('pointerup', onElEnd);
el.removeEventListener('pointercancel', onElEnd);
unbindGestureListeners();
if (dragging && activePayload) {
finishDrag('end', { x: ev.clientX, y: ev.clientY });
}
};
el.addEventListener('pointermove', onElMove);
el.addEventListener('pointerup', onElEnd);
el.addEventListener('pointercancel', onElEnd);
const windowUnbind = unbindGesture;
unbindGesture = () => {
el.removeEventListener('pointermove', onElMove);
el.removeEventListener('pointerup', onElEnd);
el.removeEventListener('pointercancel', onElEnd);
windowUnbind?.();
};
}
} }
export function handlePalettePointerDown( export function handlePalettePointerDown(
@@ -336,12 +377,45 @@ export function handlePaletteTouchStart(
startPaletteDragGesture(touch.clientX, touch.clientY, touch.identifier, payload); startPaletteDragGesture(touch.clientX, touch.clientY, touch.identifier, payload);
} }
function stopHtmlDragMouseTracking(): void {
htmlDragMouseTrackUnbind?.();
htmlDragMouseTrackUnbind = null;
}
/** HTML5 drag 중 mousemove 로 preview 좌표 추적 (macOS WKWebView — dragover 좌표/hit-test 불안정) */
export function startHtmlDragMouseTracking(
clientX: number,
clientY: number,
payload: PaletteDragPayload,
): void {
stopHtmlDragMouseTracking();
if (clientX !== 0 || clientY !== 0) {
lastHtmlDragClientXY = { x: clientX, y: clientY };
}
htmlDragMouseTrackUnbind = bindWindowDrag(
(xy) => {
lastHtmlDragClientXY = xy;
emit({ phase: 'move', x: xy.x, y: xy.y, payload });
},
() => {
/* native HTML5 drag 중 mouseup 은 dragend 전에 올 수 있음 — dragend 에서 정리 */
},
{ preventTouchScroll: false },
);
}
export function endHtmlDragMouseTracking(): void {
stopHtmlDragMouseTracking();
markPaletteHtmlDragEnd();
}
export function markPaletteHtmlDragStart(): void { export function markPaletteHtmlDragStart(): void {
htmlPaletteDragActive = true; htmlPaletteDragActive = true;
lastHtmlDragClientXY = null; lastHtmlDragClientXY = null;
} }
export function markPaletteHtmlDragEnd(): void { export function markPaletteHtmlDragEnd(): void {
stopHtmlDragMouseTracking();
htmlPaletteDragActive = false; htmlPaletteDragActive = false;
lastHtmlDragClientXY = null; lastHtmlDragClientXY = null;
} }