|
| 1 | +//에디터 모드전용 노드 렌더러 래퍼 컴포넌트 |
| 2 | +import clsx from "clsx"; |
| 3 | +import { Rnd } from "react-rnd"; |
| 4 | +import { BaseNode } from "types"; |
| 5 | +import { CanvasState, Layer } from "types/rnd"; |
| 6 | + |
| 7 | +interface WrapperProps { |
| 8 | + children: React.ReactNode; |
| 9 | + node: BaseNode; |
| 10 | + selectedId: string; |
| 11 | + updateNode: (id: string, updates: Partial<Layer>) => void; //노드의 레이아웃 업데이트 함수 from editor의 스토어 액션 |
| 12 | + selectNode: (id: string) => void; |
| 13 | + canvas: CanvasState; |
| 14 | +} |
| 15 | + |
| 16 | +//에디터 전용 노드 렌더러 래퍼 |
| 17 | +//래퍼 컴포넌트에서 rnd작업 발생할때 현재 액션이 일어나는 노드 id는 몰라도 될듯? -> 항상 스토어의 selectedId를 기준으로 데이터를 수정하면 된다. |
| 18 | +export default function EditorNodeWrapper({ |
| 19 | + children, |
| 20 | + node, |
| 21 | + selectedId, |
| 22 | + updateNode, |
| 23 | + selectNode, |
| 24 | + canvas, |
| 25 | +}: WrapperProps) { |
| 26 | + const isSelected = selectedId === node.id; |
| 27 | + const wrapperStyle: React.CSSProperties = { |
| 28 | + // 선택되었을 때 시각적 피드백 (테두리 등) |
| 29 | + outline: isSelected ? "outline outline-gray-500 outline-2" : "none", |
| 30 | + cursor: "move", |
| 31 | + }; |
| 32 | + |
| 33 | + const { id } = node; |
| 34 | + const { width, height, x, y } = node.layout; |
| 35 | + const selectedNodeGuideClasses = { |
| 36 | + handle: "bg-white border rounded-full border-gray-500 !w-3 !h-3", |
| 37 | + outline: "ring ring-2 ring-gray-500", |
| 38 | + }; |
| 39 | + |
| 40 | + //TODO- 노드 선택 로직 구현, 선택 ID 공유하는 zustand 스토어 구현 필요 |
| 41 | + |
| 42 | + return ( |
| 43 | + <Rnd |
| 44 | + size={{ width, height }} |
| 45 | + position={{ x, y }} |
| 46 | + scale={canvas.scale} |
| 47 | + onDragStart={(e) => e.stopPropagation()} |
| 48 | + //TODO-일단 이동중에 스토어 업데이트는 미루기 -> 성능 이슈 |
| 49 | + // onDrag={(e, d) => updateNode(id, { x: d.x, y: d.y })} |
| 50 | + onDragStop={(e, d) => updateNode(id, { x: d.x, y: d.y })} |
| 51 | + onResizeStart={(e) => e.stopPropagation()} |
| 52 | + //TODO-일단 리사이징중에 스토어 업데이트는 미루기 -> 성능 이슈 |
| 53 | + /* |
| 54 | + onResize={(e, dir, ref, delta, pos) => |
| 55 | + updateNode(id, { |
| 56 | + width: parseInt(ref.style.width), |
| 57 | + height: parseInt(ref.style.height), |
| 58 | + ...pos, |
| 59 | + }) |
| 60 | + } |
| 61 | + */ |
| 62 | + onResizeStop={(e, dir, ref, delta, pos) => |
| 63 | + updateNode(id, { |
| 64 | + width: parseInt(ref.style.width), |
| 65 | + height: parseInt(ref.style.height), |
| 66 | + ...pos, |
| 67 | + }) |
| 68 | + } |
| 69 | + enableResizing={isSelected ? undefined : false} |
| 70 | + disableDragging={!isSelected} |
| 71 | + className={clsx("group cursor-pointer", isSelected && "z-50")} |
| 72 | + resizeHandleClasses={{ |
| 73 | + bottomLeft: isSelected |
| 74 | + ? clsx(selectedNodeGuideClasses.handle, "-left-1.5 -bottom-1.5") |
| 75 | + : undefined, |
| 76 | + bottomRight: isSelected |
| 77 | + ? clsx(selectedNodeGuideClasses.handle, "-right-1.5 -bottom-1.5") |
| 78 | + : undefined, |
| 79 | + topLeft: isSelected |
| 80 | + ? clsx(selectedNodeGuideClasses.handle, "-left-1.5 -top-1.5") |
| 81 | + : undefined, |
| 82 | + topRight: isSelected |
| 83 | + ? clsx(selectedNodeGuideClasses.handle, "-right-1.5 -top-1.5") |
| 84 | + : undefined, |
| 85 | + }} |
| 86 | + > |
| 87 | + <div |
| 88 | + onClick={(e) => { |
| 89 | + e.stopPropagation(); |
| 90 | + selectNode(id); |
| 91 | + }} |
| 92 | + style={wrapperStyle} |
| 93 | + className={clsx( |
| 94 | + "transition-shadow duration-200", |
| 95 | + isSelected && selectedNodeGuideClasses.outline, |
| 96 | + )} |
| 97 | + > |
| 98 | + {/* 실제 컴포넌트(Hero 등)는 이 안에 렌더링됨 */} |
| 99 | + {children} |
| 100 | + </div> |
| 101 | + </Rnd> |
| 102 | + ); |
| 103 | +} |
0 commit comments