전략편집기 수정1
This commit is contained in:
@@ -33,9 +33,11 @@ import { buildSidewaysFilterNode } from '../../utils/sidewaysFilterPaletteStorag
|
|||||||
import {
|
import {
|
||||||
isPaletteHtmlDrag,
|
isPaletteHtmlDrag,
|
||||||
isPaletteHtmlDragActive,
|
isPaletteHtmlDragActive,
|
||||||
|
getLastHtmlPaletteDragClientXY,
|
||||||
markPaletteHtmlDragEnd,
|
markPaletteHtmlDragEnd,
|
||||||
needsPointerPaletteDrag,
|
needsPointerPaletteDrag,
|
||||||
readPaletteHtmlDragData,
|
readPaletteHtmlDragData,
|
||||||
|
setHtmlPaletteDropRouter,
|
||||||
setPaletteDragDropHandler,
|
setPaletteDragDropHandler,
|
||||||
subscribePaletteDrag,
|
subscribePaletteDrag,
|
||||||
type PaletteDragEvent,
|
type PaletteDragEvent,
|
||||||
@@ -1281,6 +1283,15 @@ function StrategyEditorCanvasInner({
|
|||||||
const clearPreviewRef = useRef(clearDropPreview);
|
const clearPreviewRef = useRef(clearDropPreview);
|
||||||
clearPreviewRef.current = clearDropPreview;
|
clearPreviewRef.current = clearDropPreview;
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (needsPointerPaletteDrag()) return undefined;
|
||||||
|
setHtmlPaletteDropRouter((x, y, payload) => {
|
||||||
|
clearPreviewRef.current();
|
||||||
|
applyDropRef.current(payload, x, y);
|
||||||
|
});
|
||||||
|
return () => setHtmlPaletteDropRouter(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!needsPointerPaletteDrag()) return undefined;
|
if (!needsPointerPaletteDrag()) return undefined;
|
||||||
setPaletteDragDropHandler((ev) => {
|
setPaletteDragDropHandler((ev) => {
|
||||||
@@ -1299,6 +1310,37 @@ function StrategyEditorCanvasInner({
|
|||||||
const lastPreviewAnchorRef = useRef<string | null>(null);
|
const lastPreviewAnchorRef = useRef<string | null>(null);
|
||||||
const lastPreviewKeyRef = useRef<string | null>(null);
|
const lastPreviewKeyRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (needsPointerPaletteDrag()) return undefined;
|
||||||
|
let rafId = 0;
|
||||||
|
const tick = () => {
|
||||||
|
if (!isPaletteHtmlDragActive()) {
|
||||||
|
rafId = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const xy = getLastHtmlPaletteDragClientXY();
|
||||||
|
if (xy) {
|
||||||
|
const { flowPos, anchorId } = resolveDropFromClientRef.current(xy.x, xy.y);
|
||||||
|
if (anchorId) {
|
||||||
|
updatePreviewRef.current(anchorId, flowPos);
|
||||||
|
lastPreviewAnchorRef.current = anchorId;
|
||||||
|
} else if (lastPreviewAnchorRef.current != null) {
|
||||||
|
lastPreviewAnchorRef.current = null;
|
||||||
|
clearPreviewRef.current();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rafId = requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
const onDragStart = () => {
|
||||||
|
if (!rafId) rafId = requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
document.addEventListener('dragstart', onDragStart, true);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('dragstart', onDragStart, true);
|
||||||
|
if (rafId) cancelAnimationFrame(rafId);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (needsPointerPaletteDrag()) return;
|
if (needsPointerPaletteDrag()) return;
|
||||||
const onDocDragOver = (e: DragEvent) => {
|
const onDocDragOver = (e: DragEvent) => {
|
||||||
|
|||||||
@@ -32,10 +32,11 @@ import StartTimeframeCheckboxes from './StartTimeframeCheckboxes';
|
|||||||
import LogicGateOpToggle from './LogicGateOpToggle';
|
import LogicGateOpToggle from './LogicGateOpToggle';
|
||||||
import { formatStartCandleTypesLabel } from '../../utils/strategyStartNodes';
|
import { formatStartCandleTypesLabel } from '../../utils/strategyStartNodes';
|
||||||
import { buildSidewaysFilterNode } from '../../utils/sidewaysFilterPaletteStorage';
|
import { buildSidewaysFilterNode } from '../../utils/sidewaysFilterPaletteStorage';
|
||||||
import { isOverListEditor } from '../../utils/flowScreenProjection';
|
|
||||||
import {
|
import {
|
||||||
findPaletteDropKeyAtPoint,
|
findPaletteDropKeyAtPoint,
|
||||||
needsPointerPaletteDrag,
|
needsPointerPaletteDrag,
|
||||||
|
readPaletteHtmlDragData,
|
||||||
|
setHtmlPaletteDropRouter,
|
||||||
setPaletteDragDropHandler,
|
setPaletteDragDropHandler,
|
||||||
subscribePaletteDrag,
|
subscribePaletteDrag,
|
||||||
type PaletteDragPayload,
|
type PaletteDragPayload,
|
||||||
@@ -303,12 +304,9 @@ function StartSectionBlock({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setDragOverKey(null);
|
setDragOverKey(null);
|
||||||
try {
|
const data = readPaletteHtmlDragData(e);
|
||||||
const data = JSON.parse(e.dataTransfer.getData('application/json'));
|
if (!data) return;
|
||||||
applyDrop(null, data);
|
applyDrop(null, data);
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDropOnNode = (targetId: string, data: { type: string; value: string; label: string; composite?: boolean }) => {
|
const handleDropOnNode = (targetId: string, data: { type: string; value: string; label: string; composite?: boolean }) => {
|
||||||
@@ -480,13 +478,22 @@ export default function StrategyListEditor({
|
|||||||
const applyDropRef = useRef(applyPaletteDropAtKey);
|
const applyDropRef = useRef(applyPaletteDropAtKey);
|
||||||
applyDropRef.current = applyPaletteDropAtKey;
|
applyDropRef.current = applyPaletteDropAtKey;
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (needsPointerPaletteDrag()) return undefined;
|
||||||
|
setHtmlPaletteDropRouter((x, y, payload) => {
|
||||||
|
dragOverKeyRef.current = null;
|
||||||
|
setDragOverKey(null);
|
||||||
|
applyDropRef.current(findPaletteDropKeyAtPoint(x, y), payload);
|
||||||
|
});
|
||||||
|
return () => setHtmlPaletteDropRouter(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!needsPointerPaletteDrag()) return undefined;
|
if (!needsPointerPaletteDrag()) return undefined;
|
||||||
setPaletteDragDropHandler(ev => {
|
setPaletteDragDropHandler(ev => {
|
||||||
if (ev.phase !== 'end') return;
|
if (ev.phase !== 'end') return;
|
||||||
dragOverKeyRef.current = null;
|
dragOverKeyRef.current = null;
|
||||||
setDragOverKey(null);
|
setDragOverKey(null);
|
||||||
if (!isOverListEditor(ev.x, ev.y)) return;
|
|
||||||
applyDropRef.current(findPaletteDropKeyAtPoint(ev.x, ev.y), ev.payload);
|
applyDropRef.current(findPaletteDropKeyAtPoint(ev.x, ev.y), ev.payload);
|
||||||
});
|
});
|
||||||
return () => setPaletteDragDropHandler(null);
|
return () => setPaletteDragDropHandler(null);
|
||||||
@@ -495,10 +502,6 @@ export default function StrategyListEditor({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return subscribePaletteDrag(ev => {
|
return subscribePaletteDrag(ev => {
|
||||||
if (ev.phase === 'move') {
|
if (ev.phase === 'move') {
|
||||||
if (!isOverListEditor(ev.x, ev.y)) {
|
|
||||||
setDragOverKeyThrottled(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setDragOverKeyThrottled(findPaletteDropKeyAtPoint(ev.x, ev.y));
|
setDragOverKeyThrottled(findPaletteDropKeyAtPoint(ev.x, ev.y));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -519,14 +522,9 @@ export default function StrategyListEditor({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setDragOverKey(null);
|
setDragOverKey(null);
|
||||||
try {
|
const data = readPaletteHtmlDragData(e);
|
||||||
const data = JSON.parse(e.dataTransfer.getData('application/json'));
|
if (!data) return;
|
||||||
if (data.type === 'start') {
|
applyDropRef.current(findPaletteDropKeyAtPoint(e.clientX, e.clientY) ?? LIST_DROP_KEY, data);
|
||||||
handleAddStart();
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -77,16 +77,33 @@ export function projectScreenToFlowPosition(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 목록 방식 편집기 위인지 */
|
/** 목록 방식 편집기 위인지 (rect hit-test) */
|
||||||
export function isOverListEditor(clientX: number, clientY: number): boolean {
|
export function isOverListEditor(clientX: number, clientY: number): boolean {
|
||||||
for (const el of elementsUnderDragPoint(clientX, clientY)) {
|
const editors = document.querySelectorAll<HTMLElement>('.se-list-editor');
|
||||||
if (el.closest('.se-list-editor')) return true;
|
for (const el of editors) {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
if (
|
||||||
|
clientX >= rect.left && clientX <= rect.right
|
||||||
|
&& clientY >= rect.top && clientY <= rect.bottom
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 팔레트 드롭 — 전략 빌더 캔버스 위인지 */
|
/** 팔레트 드롭 — 전략 빌더 캔버스 위인지 */
|
||||||
export function isOverStrategyBuilder(clientX: number, clientY: number): boolean {
|
export function isOverStrategyBuilder(clientX: number, clientY: number): boolean {
|
||||||
|
const canvas = document.querySelector<HTMLElement>('.se-canvas-wrap');
|
||||||
|
if (canvas) {
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
if (
|
||||||
|
clientX >= rect.left && clientX <= rect.right
|
||||||
|
&& clientY >= rect.top && clientY <= rect.bottom
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const el of elementsUnderDragPoint(clientX, clientY)) {
|
for (const el of elementsUnderDragPoint(clientX, clientY)) {
|
||||||
if (el.closest('.se-palette-panel, .se-side-wrap--right')) continue;
|
if (el.closest('.se-palette-panel, .se-side-wrap--right')) continue;
|
||||||
if (el.closest('.se-canvas-wrap, .react-flow__pane, .react-flow__renderer')) return true;
|
if (el.closest('.se-canvas-wrap, .react-flow__pane, .react-flow__renderer')) return true;
|
||||||
@@ -171,7 +188,7 @@ export function resolvePaletteDropClientPoint(
|
|||||||
return { flowPos, anchorId: domId };
|
return { flowPos, anchorId: domId };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getActivePaletteDrag()) {
|
if (getActivePaletteDrag() || isPaletteHtmlDragActive()) {
|
||||||
const flowHit = findAtFlow(flowPos, nodes);
|
const flowHit = findAtFlow(flowPos, nodes);
|
||||||
if (flowHit && isConnectTarget(flowHit.id)) {
|
if (flowHit && isConnectTarget(flowHit.id)) {
|
||||||
return { flowPos, anchorId: flowHit.id };
|
return { flowPos, anchorId: flowHit.id };
|
||||||
|
|||||||
@@ -57,6 +57,23 @@ let lastDragClientXY: { x: number; y: number } | null = null;
|
|||||||
let htmlDragMouseTrackUnbind: (() => void) | 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;
|
||||||
|
let activeHtmlDragPayload: PaletteDragPayload | null = null;
|
||||||
|
|
||||||
|
type HtmlPaletteDropRouter = (
|
||||||
|
clientX: number,
|
||||||
|
clientY: number,
|
||||||
|
payload: PaletteDragPayload,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
let htmlDropRouter: HtmlPaletteDropRouter | null = null;
|
||||||
|
|
||||||
|
export function setHtmlPaletteDropRouter(router: HtmlPaletteDropRouter | null): void {
|
||||||
|
htmlDropRouter = router;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveHtmlDragPayload(): PaletteDragPayload | null {
|
||||||
|
return activeHtmlDragPayload;
|
||||||
|
}
|
||||||
|
|
||||||
function installHtmlPaletteDragGlobals() {
|
function installHtmlPaletteDragGlobals() {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
@@ -84,7 +101,19 @@ function installHtmlPaletteDragGlobals() {
|
|||||||
stopHtmlDragMouseTracking();
|
stopHtmlDragMouseTracking();
|
||||||
htmlPaletteDragActive = false;
|
htmlPaletteDragActive = false;
|
||||||
lastHtmlDragClientXY = null;
|
lastHtmlDragClientXY = null;
|
||||||
|
activeHtmlDragPayload = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener('drop', (e) => {
|
||||||
|
if (!htmlPaletteDragActive) return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const payload = readPaletteHtmlDragDataFromEvent(e) ?? activeHtmlDragPayload;
|
||||||
|
if (!payload || !htmlDropRouter) return;
|
||||||
|
const x = e.clientX !== 0 || e.clientY !== 0 ? e.clientX : (lastHtmlDragClientXY?.x ?? 0);
|
||||||
|
const y = e.clientX !== 0 || e.clientY !== 0 ? e.clientY : (lastHtmlDragClientXY?.y ?? 0);
|
||||||
|
htmlDropRouter(x, y, payload);
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
installHtmlPaletteDragGlobals();
|
installHtmlPaletteDragGlobals();
|
||||||
@@ -388,6 +417,7 @@ export function startHtmlDragMouseTracking(
|
|||||||
clientY: number,
|
clientY: number,
|
||||||
payload: PaletteDragPayload,
|
payload: PaletteDragPayload,
|
||||||
): void {
|
): void {
|
||||||
|
activeHtmlDragPayload = payload;
|
||||||
stopHtmlDragMouseTracking();
|
stopHtmlDragMouseTracking();
|
||||||
if (clientX !== 0 || clientY !== 0) {
|
if (clientX !== 0 || clientY !== 0) {
|
||||||
lastHtmlDragClientXY = { x: clientX, y: clientY };
|
lastHtmlDragClientXY = { x: clientX, y: clientY };
|
||||||
@@ -418,6 +448,18 @@ export function markPaletteHtmlDragEnd(): void {
|
|||||||
stopHtmlDragMouseTracking();
|
stopHtmlDragMouseTracking();
|
||||||
htmlPaletteDragActive = false;
|
htmlPaletteDragActive = false;
|
||||||
lastHtmlDragClientXY = null;
|
lastHtmlDragClientXY = null;
|
||||||
|
activeHtmlDragPayload = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPaletteHtmlDragDataFromEvent(e: DragEvent): PaletteDragPayload | null {
|
||||||
|
const raw = e.dataTransfer?.getData('text/plain')
|
||||||
|
|| e.dataTransfer?.getData('application/json');
|
||||||
|
if (!raw) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw) as PaletteDragPayload;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLastHtmlPaletteDragClientXY(): { x: number; y: number } | null {
|
export function getLastHtmlPaletteDragClientXY(): { x: number; y: number } | null {
|
||||||
@@ -429,6 +471,7 @@ export function isPaletteHtmlDragActive(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function writePaletteHtmlDragData(e: React.DragEvent, payload: PaletteDragPayload): void {
|
export function writePaletteHtmlDragData(e: React.DragEvent, payload: PaletteDragPayload): void {
|
||||||
|
activeHtmlDragPayload = payload;
|
||||||
markPaletteHtmlDragStart();
|
markPaletteHtmlDragStart();
|
||||||
const json = JSON.stringify(payload);
|
const json = JSON.stringify(payload);
|
||||||
e.dataTransfer.setData('application/json', json);
|
e.dataTransfer.setData('application/json', json);
|
||||||
@@ -445,17 +488,20 @@ export function isPaletteHtmlDrag(e: React.DragEvent): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function readPaletteHtmlDragData(e: React.DragEvent): PaletteDragPayload | null {
|
export function readPaletteHtmlDragData(e: React.DragEvent): PaletteDragPayload | null {
|
||||||
const raw = e.dataTransfer.getData('text/plain')
|
return readPaletteHtmlDragDataFromEvent(e.nativeEvent) ?? activeHtmlDragPayload;
|
||||||
|| e.dataTransfer.getData('application/json');
|
|
||||||
if (!raw) return null;
|
|
||||||
try {
|
|
||||||
return JSON.parse(raw) as PaletteDragPayload;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findPaletteDropKeyAtPoint(x: number, y: number): string | null {
|
export function findPaletteDropKeyAtPoint(x: number, y: number): string | null {
|
||||||
|
const hosts = Array.from(document.querySelectorAll<HTMLElement>('[data-palette-drop-key]'));
|
||||||
|
for (let i = hosts.length - 1; i >= 0; i--) {
|
||||||
|
const host = hosts[i];
|
||||||
|
const key = host.dataset.paletteDropKey;
|
||||||
|
if (!key) continue;
|
||||||
|
const rect = host.getBoundingClientRect();
|
||||||
|
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const el of elementsUnderDragPoint(x, y)) {
|
for (const el of elementsUnderDragPoint(x, y)) {
|
||||||
const host = el.closest('[data-palette-drop-key]') as HTMLElement | null;
|
const host = el.closest('[data-palette-drop-key]') as HTMLElement | null;
|
||||||
if (host?.dataset.paletteDropKey) return host.dataset.paletteDropKey;
|
if (host?.dataset.paletteDropKey) return host.dataset.paletteDropKey;
|
||||||
|
|||||||
Reference in New Issue
Block a user