전략편집기 전략컨트롤 드래그
This commit is contained in:
@@ -51,6 +51,10 @@ export default function PaletteChip({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onDragStart = (e: React.DragEvent) => {
|
const onDragStart = (e: React.DragEvent) => {
|
||||||
|
if (needsPointerPaletteDrag()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
writePaletteHtmlDragData(e, buildPayload());
|
writePaletteHtmlDragData(e, buildPayload());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,7 +99,7 @@ export default function PaletteChip({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
draggable={!needsPointerPaletteDrag()}
|
draggable={!needsPointerPaletteDrag()}
|
||||||
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}`}
|
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}${needsPointerPaletteDrag() ? ' se-palette-card--pointer' : ''}`}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ function SidewaysFilterChip({
|
|||||||
|
|
||||||
const onDragStart = (e: React.DragEvent) => {
|
const onDragStart = (e: React.DragEvent) => {
|
||||||
hideHint();
|
hideHint();
|
||||||
|
if (needsPointerPaletteDrag()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const payload: SidewaysFilterDragPayload = {
|
const payload: SidewaysFilterDragPayload = {
|
||||||
type: 'sidewaysFilter',
|
type: 'sidewaysFilter',
|
||||||
value: item.id,
|
value: item.id,
|
||||||
@@ -127,7 +131,7 @@ function SidewaysFilterChip({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
draggable={!needsPointerPaletteDrag()}
|
draggable={!needsPointerPaletteDrag()}
|
||||||
className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}`}
|
className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}${needsPointerPaletteDrag() ? ' se-palette-card--pointer' : ''}`}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
|
|||||||
@@ -1336,20 +1336,10 @@ function StrategyEditorCanvasInner({
|
|||||||
if (!needsPointerPaletteDrag()) return;
|
if (!needsPointerPaletteDrag()) return;
|
||||||
return subscribePaletteDrag((ev: PaletteDragEvent) => {
|
return subscribePaletteDrag((ev: PaletteDragEvent) => {
|
||||||
if (ev.phase === 'move') {
|
if (ev.phase === 'move') {
|
||||||
if (!isOverStrategyBuilder(ev.x, ev.y)) {
|
|
||||||
if (lastPreviewAnchorRef.current != null) {
|
|
||||||
lastPreviewAnchorRef.current = null;
|
|
||||||
lastPreviewKeyRef.current = null;
|
|
||||||
clearPreviewRef.current();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { flowPos, anchorId } = resolveDropFromClientRef.current(ev.x, ev.y);
|
const { flowPos, anchorId } = resolveDropFromClientRef.current(ev.x, ev.y);
|
||||||
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;
|
lastPreviewAnchorRef.current = anchorId;
|
||||||
|
lastPreviewKeyRef.current = `${anchorId}:${Math.round(flowPos.x / 8)}:${Math.round(flowPos.y / 8)}`;
|
||||||
updatePreviewRef.current(anchorId, flowPos);
|
updatePreviewRef.current(anchorId, flowPos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2033,6 +2033,11 @@
|
|||||||
.desktop-client .se-palette-card:active {
|
.desktop-client .se-palette-card:active {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
|
.se-palette-card--pointer {
|
||||||
|
touch-action: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
.se-palette-card:hover {
|
.se-palette-card:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import type { ReactFlowInstance } from '@xyflow/react';
|
import type { ReactFlowInstance } from '@xyflow/react';
|
||||||
import { getLastHtmlPaletteDragClientXY, isPaletteHtmlDragActive } from './paletteDragSession';
|
import {
|
||||||
|
getActivePaletteDrag,
|
||||||
|
getLastHtmlPaletteDragClientXY,
|
||||||
|
isPaletteHtmlDragActive,
|
||||||
|
} from './paletteDragSession';
|
||||||
|
|
||||||
type FlowProjector = Pick<ReactFlowInstance, 'screenToFlowPosition' | 'getViewport'>;
|
type FlowProjector = Pick<ReactFlowInstance, 'screenToFlowPosition' | 'getViewport'>;
|
||||||
|
|
||||||
@@ -93,8 +97,9 @@ export function isOverStrategyBuilder(clientX: number, clientY: number): boolean
|
|||||||
/** DOM hit-test — react-flow 노드 id (Safari HTML5 drag: elementsFromPoint 불가 → rect 우선) */
|
/** DOM hit-test — react-flow 노드 id (Safari HTML5 drag: elementsFromPoint 불가 → rect 우선) */
|
||||||
export function findFlowNodeIdAtClientPoint(clientX: number, clientY: number): string | null {
|
export function findFlowNodeIdAtClientPoint(clientX: number, clientY: number): string | null {
|
||||||
const pad = 10;
|
const pad = 10;
|
||||||
const nodeEls = document.querySelectorAll<HTMLElement>('.react-flow__node');
|
const nodeEls = Array.from(document.querySelectorAll<HTMLElement>('.react-flow__node'));
|
||||||
for (const nodeEl of nodeEls) {
|
for (let i = nodeEls.length - 1; i >= 0; i--) {
|
||||||
|
const nodeEl = nodeEls[i];
|
||||||
const rect = nodeEl.getBoundingClientRect();
|
const rect = nodeEl.getBoundingClientRect();
|
||||||
if (
|
if (
|
||||||
clientX >= rect.left - pad && clientX <= rect.right + pad
|
clientX >= rect.left - pad && clientX <= rect.right + pad
|
||||||
@@ -166,6 +171,13 @@ export function resolvePaletteDropClientPoint(
|
|||||||
return { flowPos, anchorId: domId };
|
return { flowPos, anchorId: domId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getActivePaletteDrag()) {
|
||||||
|
const flowHit = findAtFlow(flowPos, nodes);
|
||||||
|
if (flowHit && isConnectTarget(flowHit.id)) {
|
||||||
|
return { flowPos, anchorId: flowHit.id };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (!isConnectTarget(node.id)) continue;
|
if (!isConnectTarget(node.id)) continue;
|
||||||
const escaped = typeof CSS !== 'undefined' && CSS.escape
|
const escaped = typeof CSS !== 'undefined' && CSS.escape
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
bindWindowDrag,
|
bindWindowDrag,
|
||||||
isPrimaryPointerButton,
|
isPrimaryPointerButton,
|
||||||
} from './pointerDrag';
|
} from './pointerDrag';
|
||||||
import { isDesktop, isMacDesktop } from './platform';
|
import { isDesktop } from './platform';
|
||||||
|
|
||||||
export type PaletteDragPayload = Record<string, unknown> & {
|
export type PaletteDragPayload = Record<string, unknown> & {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -96,10 +96,8 @@ function hasCoarsePointer(): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** HTML5 DnD 대신 pointer/touch overlay 드래그 사용 (Windows Desktop 등) */
|
/** HTML5 DnD 대신 pointer/touch overlay 드래그 (Tauri Desktop 전체 — macOS WKWebView HTML5 DnD 불안정) */
|
||||||
export function needsPointerPaletteDrag(): boolean {
|
export function needsPointerPaletteDrag(): boolean {
|
||||||
// macOS Tauri WKWebView — Safari 웹과 동일하게 HTML5 DnD 사용
|
|
||||||
if (isMacDesktop()) return false;
|
|
||||||
return isDesktop() || hasCoarsePointer();
|
return isDesktop() || hasCoarsePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +277,7 @@ function startPaletteDragGesture(
|
|||||||
finishDrag('end', xy);
|
finishDrag('end', xy);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ preventTouchScroll: true },
|
{ preventTouchScroll: true, useCapture: isDesktop() },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ export type EdgeHandleBinding = {
|
|||||||
export const FLOW_NODE_W = 200;
|
export const FLOW_NODE_W = 200;
|
||||||
export const FLOW_NODE_H = 72;
|
export const FLOW_NODE_H = 72;
|
||||||
export const FLOW_START_W = 200;
|
export const FLOW_START_W = 200;
|
||||||
export const FLOW_START_H = 78;
|
export const FLOW_START_H = 168;
|
||||||
|
|
||||||
function nodeCenter(
|
function nodeCenter(
|
||||||
id: string,
|
id: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user