2026-02-02 15:15:01 +09:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
import { useCallback, useRef, useState, useEffect, useMemo } from "react";
|
2026-02-02 15:15:01 +09:00
|
|
|
|
import { useDrop } from "react-dnd";
|
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
|
import {
|
2026-02-05 14:24:14 +09:00
|
|
|
|
PopLayoutDataV5,
|
|
|
|
|
|
PopComponentDefinitionV5,
|
2026-02-02 15:15:01 +09:00
|
|
|
|
PopComponentType,
|
2026-02-05 14:24:14 +09:00
|
|
|
|
PopGridPosition,
|
|
|
|
|
|
GridMode,
|
|
|
|
|
|
GRID_BREAKPOINTS,
|
|
|
|
|
|
DEFAULT_COMPONENT_GRID_SIZE,
|
2026-02-02 15:15:01 +09:00
|
|
|
|
} from "./types/pop-layout";
|
2026-02-05 14:24:14 +09:00
|
|
|
|
import { ZoomIn, ZoomOut, Maximize2, Smartphone, Tablet } from "lucide-react";
|
2026-02-02 15:15:01 +09:00
|
|
|
|
import { Button } from "@/components/ui/button";
|
2026-02-05 14:24:14 +09:00
|
|
|
|
import PopRenderer from "./renderers/PopRenderer";
|
|
|
|
|
|
import { mouseToGridPosition, findNextEmptyPosition } from "./utils/gridUtils";
|
|
|
|
|
|
|
|
|
|
|
|
// DnD 타입 상수 (인라인)
|
|
|
|
|
|
const DND_ITEM_TYPES = {
|
|
|
|
|
|
COMPONENT: "component",
|
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
|
|
interface DragItemComponent {
|
|
|
|
|
|
type: typeof DND_ITEM_TYPES.COMPONENT;
|
|
|
|
|
|
componentType: PopComponentType;
|
|
|
|
|
|
}
|
2026-02-02 15:15:01 +09:00
|
|
|
|
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
// ========================================
|
2026-02-05 14:24:14 +09:00
|
|
|
|
// 프리셋 해상도 (4개 모드)
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
// ========================================
|
2026-02-05 14:24:14 +09:00
|
|
|
|
const VIEWPORT_PRESETS = [
|
|
|
|
|
|
{ id: "mobile_portrait", label: "모바일 세로", shortLabel: "모바일↕ (4칸)", width: 375, height: 667, icon: Smartphone },
|
|
|
|
|
|
{ id: "mobile_landscape", label: "모바일 가로", shortLabel: "모바일↔ (6칸)", width: 667, height: 375, icon: Smartphone },
|
|
|
|
|
|
{ id: "tablet_portrait", label: "태블릿 세로", shortLabel: "태블릿↕ (8칸)", width: 768, height: 1024, icon: Tablet },
|
|
|
|
|
|
{ id: "tablet_landscape", label: "태블릿 가로", shortLabel: "태블릿↔ (12칸)", width: 1024, height: 768, icon: Tablet },
|
|
|
|
|
|
] as const;
|
2026-02-02 15:15:01 +09:00
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
type ViewportPreset = GridMode;
|
2026-02-02 15:15:01 +09:00
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
// 기본 프리셋 (태블릿 가로)
|
|
|
|
|
|
const DEFAULT_PRESET: ViewportPreset = "tablet_landscape";
|
2026-02-03 19:11:03 +09:00
|
|
|
|
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
// ========================================
|
|
|
|
|
|
// Props
|
|
|
|
|
|
// ========================================
|
2026-02-02 15:15:01 +09:00
|
|
|
|
interface PopCanvasProps {
|
2026-02-05 14:24:14 +09:00
|
|
|
|
layout: PopLayoutDataV5;
|
2026-02-02 15:15:01 +09:00
|
|
|
|
selectedComponentId: string | null;
|
2026-02-05 14:24:14 +09:00
|
|
|
|
currentMode: GridMode;
|
|
|
|
|
|
onModeChange: (mode: GridMode) => void;
|
2026-02-02 15:15:01 +09:00
|
|
|
|
onSelectComponent: (id: string | null) => void;
|
2026-02-05 14:24:14 +09:00
|
|
|
|
onDropComponent: (type: PopComponentType, position: PopGridPosition) => void;
|
|
|
|
|
|
onUpdateComponent: (componentId: string, updates: Partial<PopComponentDefinitionV5>) => void;
|
2026-02-03 19:11:03 +09:00
|
|
|
|
onDeleteComponent: (componentId: string) => void;
|
2026-02-05 14:24:14 +09:00
|
|
|
|
onMoveComponent?: (componentId: string, newPosition: PopGridPosition) => void;
|
|
|
|
|
|
onResizeComponent?: (componentId: string, newPosition: PopGridPosition) => void;
|
2026-02-02 15:15:01 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
// ========================================
|
2026-02-05 14:24:14 +09:00
|
|
|
|
// PopCanvas: 그리드 캔버스
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
// ========================================
|
2026-02-05 14:24:14 +09:00
|
|
|
|
|
|
|
|
|
|
export default function PopCanvas({
|
2026-02-02 15:15:01 +09:00
|
|
|
|
layout,
|
|
|
|
|
|
selectedComponentId,
|
2026-02-05 14:24:14 +09:00
|
|
|
|
currentMode,
|
|
|
|
|
|
onModeChange,
|
2026-02-02 15:15:01 +09:00
|
|
|
|
onSelectComponent,
|
|
|
|
|
|
onDropComponent,
|
2026-02-05 14:24:14 +09:00
|
|
|
|
onUpdateComponent,
|
2026-02-02 15:15:01 +09:00
|
|
|
|
onDeleteComponent,
|
2026-02-05 14:24:14 +09:00
|
|
|
|
onMoveComponent,
|
|
|
|
|
|
onResizeComponent,
|
2026-02-02 15:15:01 +09:00
|
|
|
|
}: PopCanvasProps) {
|
2026-02-05 14:24:14 +09:00
|
|
|
|
// 줌 상태
|
|
|
|
|
|
const [canvasScale, setCanvasScale] = useState(0.8);
|
|
|
|
|
|
|
|
|
|
|
|
// 커스텀 뷰포트 크기
|
|
|
|
|
|
const [customWidth, setCustomWidth] = useState(1024);
|
|
|
|
|
|
const [customHeight, setCustomHeight] = useState(768);
|
|
|
|
|
|
|
|
|
|
|
|
// 그리드 가이드 표시 여부
|
|
|
|
|
|
const [showGridGuide, setShowGridGuide] = useState(true);
|
|
|
|
|
|
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
// 패닝 상태
|
|
|
|
|
|
const [isPanning, setIsPanning] = useState(false);
|
|
|
|
|
|
const [panStart, setPanStart] = useState({ x: 0, y: 0 });
|
2026-02-03 19:11:03 +09:00
|
|
|
|
const [isSpacePressed, setIsSpacePressed] = useState(false);
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
2026-02-05 14:24:14 +09:00
|
|
|
|
const canvasRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
|
|
// 드래그 상태
|
|
|
|
|
|
const [isDraggingComponent, setIsDraggingComponent] = useState(false);
|
|
|
|
|
|
const [draggedComponentId, setDraggedComponentId] = useState<string | null>(null);
|
|
|
|
|
|
const [dragStartPos, setDragStartPos] = useState<{ x: number; y: number } | null>(null);
|
|
|
|
|
|
const [dragPreviewPos, setDragPreviewPos] = useState<PopGridPosition | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
// 현재 뷰포트 해상도
|
|
|
|
|
|
const currentPreset = VIEWPORT_PRESETS.find((p) => p.id === currentMode)!;
|
|
|
|
|
|
const breakpoint = GRID_BREAKPOINTS[currentMode];
|
|
|
|
|
|
|
|
|
|
|
|
// 그리드 라벨 계산
|
|
|
|
|
|
const gridLabels = useMemo(() => {
|
|
|
|
|
|
const columnLabels = Array.from({ length: breakpoint.columns }, (_, i) => i + 1);
|
|
|
|
|
|
const rowLabels = Array.from({ length: 20 }, (_, i) => i + 1);
|
|
|
|
|
|
return { columnLabels, rowLabels };
|
|
|
|
|
|
}, [breakpoint.columns]);
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
|
2026-02-03 19:11:03 +09:00
|
|
|
|
// 줌 컨트롤
|
|
|
|
|
|
const handleZoomIn = () => setCanvasScale((prev) => Math.min(1.5, prev + 0.1));
|
|
|
|
|
|
const handleZoomOut = () => setCanvasScale((prev) => Math.max(0.3, prev - 0.1));
|
|
|
|
|
|
const handleZoomFit = () => setCanvasScale(1.0);
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
// 모드 변경
|
|
|
|
|
|
const handleViewportChange = (mode: GridMode) => {
|
|
|
|
|
|
onModeChange(mode);
|
|
|
|
|
|
const presetData = VIEWPORT_PRESETS.find((p) => p.id === mode)!;
|
|
|
|
|
|
setCustomWidth(presetData.width);
|
|
|
|
|
|
setCustomHeight(presetData.height);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-03 19:11:03 +09:00
|
|
|
|
// 패닝
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
const handlePanStart = (e: React.MouseEvent) => {
|
|
|
|
|
|
const isMiddleButton = e.button === 1;
|
2026-02-05 14:24:14 +09:00
|
|
|
|
if (isMiddleButton || isSpacePressed) {
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
setIsPanning(true);
|
|
|
|
|
|
setPanStart({ x: e.clientX, y: e.clientY });
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handlePanMove = (e: React.MouseEvent) => {
|
|
|
|
|
|
if (!isPanning || !containerRef.current) return;
|
|
|
|
|
|
const deltaX = e.clientX - panStart.x;
|
|
|
|
|
|
const deltaY = e.clientY - panStart.y;
|
|
|
|
|
|
containerRef.current.scrollLeft -= deltaX;
|
|
|
|
|
|
containerRef.current.scrollTop -= deltaY;
|
|
|
|
|
|
setPanStart({ x: e.clientX, y: e.clientY });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-03 19:11:03 +09:00
|
|
|
|
const handlePanEnd = () => setIsPanning(false);
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
// Ctrl + 휠로 줌 조정
|
|
|
|
|
|
const handleWheel = (e: React.WheelEvent) => {
|
|
|
|
|
|
if (e.ctrlKey || e.metaKey) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
|
|
|
|
setCanvasScale((prev) => Math.max(0.3, Math.min(1.5, prev + delta)));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
|
|
|
|
|
|
// Space 키 감지
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
2026-02-03 19:11:03 +09:00
|
|
|
|
if (e.code === "Space" && !isSpacePressed) setIsSpacePressed(true);
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
};
|
|
|
|
|
|
const handleKeyUp = (e: KeyboardEvent) => {
|
2026-02-03 19:11:03 +09:00
|
|
|
|
if (e.code === "Space") setIsSpacePressed(false);
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
};
|
|
|
|
|
|
window.addEventListener("keydown", handleKeyDown);
|
|
|
|
|
|
window.addEventListener("keyup", handleKeyUp);
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
window.removeEventListener("keydown", handleKeyDown);
|
|
|
|
|
|
window.removeEventListener("keyup", handleKeyUp);
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [isSpacePressed]);
|
|
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
// 컴포넌트 드롭 (팔레트에서)
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
const [{ isOver, canDrop }, drop] = useDrop(
|
|
|
|
|
|
() => ({
|
2026-02-03 19:11:03 +09:00
|
|
|
|
accept: DND_ITEM_TYPES.COMPONENT,
|
|
|
|
|
|
drop: (item: DragItemComponent, monitor) => {
|
2026-02-05 14:24:14 +09:00
|
|
|
|
if (!canvasRef.current) return;
|
|
|
|
|
|
|
|
|
|
|
|
const offset = monitor.getClientOffset();
|
|
|
|
|
|
if (!offset) return;
|
|
|
|
|
|
|
|
|
|
|
|
const canvasRect = canvasRef.current.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
|
|
|
// 마우스 위치 → 그리드 좌표 변환
|
|
|
|
|
|
const gridPos = mouseToGridPosition(
|
|
|
|
|
|
offset.x,
|
|
|
|
|
|
offset.y,
|
|
|
|
|
|
canvasRect,
|
|
|
|
|
|
breakpoint.columns,
|
|
|
|
|
|
breakpoint.rowHeight,
|
|
|
|
|
|
breakpoint.gap,
|
|
|
|
|
|
breakpoint.padding
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 컴포넌트 기본 크기
|
|
|
|
|
|
const defaultSize = DEFAULT_COMPONENT_GRID_SIZE[item.componentType];
|
|
|
|
|
|
|
|
|
|
|
|
// 다음 빈 위치 찾기
|
|
|
|
|
|
const existingPositions = Object.values(layout.components).map(c => c.position);
|
|
|
|
|
|
const position = findNextEmptyPosition(
|
|
|
|
|
|
existingPositions,
|
|
|
|
|
|
defaultSize.colSpan,
|
|
|
|
|
|
defaultSize.rowSpan,
|
|
|
|
|
|
breakpoint.columns
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 컴포넌트 추가
|
|
|
|
|
|
onDropComponent(item.componentType, position);
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
},
|
|
|
|
|
|
collect: (monitor) => ({
|
|
|
|
|
|
isOver: monitor.isOver(),
|
|
|
|
|
|
canDrop: monitor.canDrop(),
|
|
|
|
|
|
}),
|
2026-02-02 15:15:01 +09:00
|
|
|
|
}),
|
2026-02-05 14:24:14 +09:00
|
|
|
|
[onDropComponent, breakpoint, layout.components]
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
);
|
2026-02-02 15:15:01 +09:00
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
drop(canvasRef);
|
2026-02-02 15:15:01 +09:00
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
// 빈 상태 체크
|
|
|
|
|
|
const isEmpty = Object.keys(layout.components).length === 0;
|
feat(pop-designer): POP 디자이너 v2.0 - 4가지 디바이스 모드 및 캔버스 UX 개선
- v2 레이아웃 데이터 구조 도입 (4모드별 별도 레이아웃 + 공유 컴포넌트 정의)
- tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
- sections/components를 Record<string, Definition> 객체로 관리
- v1 → v2 자동 마이그레이션 지원
- 캔버스 UX 개선
- 줌 기능 (30%~150%, 마우스 휠 + 버튼)
- 패닝 기능 (중앙 마우스, Space+드래그, 배경 드래그)
- 2개 캔버스 동시 표시 (가로/세로 모드)
- Delete 키로 섹션/컴포넌트 삭제 기능 추가
- layout.sections 순회하여 componentIds에서 부모 섹션 찾는 방식
- 미리보기 v2 레이아웃 호환성 수정
- Object.keys(layout.sections).length 체크로 변경
수정 파일: PopDesigner.tsx, PopCanvas.tsx, SectionGridV2.tsx(신규),
types/pop-layout.ts, PopPanel.tsx, PopScreenPreview.tsx,
PopCategoryTree.tsx, screenManagementService.ts
2026-02-03 11:25:00 +09:00
|
|
|
|
|
2026-02-02 15:15:01 +09:00
|
|
|
|
return (
|
2026-02-05 14:24:14 +09:00
|
|
|
|
<div className="flex h-full flex-col bg-gray-50">
|
|
|
|
|
|
{/* 상단 컨트롤 */}
|
|
|
|
|
|
<div className="flex items-center gap-2 border-b bg-white px-4 py-2">
|
|
|
|
|
|
{/* 모드 프리셋 버튼 */}
|
|
|
|
|
|
<div className="flex gap-1">
|
|
|
|
|
|
{VIEWPORT_PRESETS.map((preset) => {
|
|
|
|
|
|
const Icon = preset.icon;
|
|
|
|
|
|
const isActive = currentMode === preset.id;
|
|
|
|
|
|
const isDefault = preset.id === DEFAULT_PRESET;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
key={preset.id}
|
|
|
|
|
|
variant={isActive ? "default" : "outline"}
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
onClick={() => handleViewportChange(preset.id as GridMode)}
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"h-8 gap-1 text-xs",
|
|
|
|
|
|
isActive && "shadow-sm"
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icon className="h-3 w-3" />
|
|
|
|
|
|
{preset.shortLabel}
|
|
|
|
|
|
{isDefault && " (기본)"}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="h-4 w-px bg-gray-300" />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 해상도 표시 */}
|
|
|
|
|
|
<div className="text-xs text-muted-foreground">
|
|
|
|
|
|
{customWidth} × {customHeight}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex-1" />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 줌 컨트롤 */}
|
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
|
<span className="text-xs text-muted-foreground">
|
|
|
|
|
|
{Math.round(canvasScale * 100)}%
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
onClick={handleZoomOut}
|
|
|
|
|
|
disabled={canvasScale <= 0.3}
|
|
|
|
|
|
className="h-7 w-7"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ZoomOut className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
onClick={handleZoomFit}
|
|
|
|
|
|
className="h-7 w-7"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Maximize2 className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
onClick={handleZoomIn}
|
|
|
|
|
|
disabled={canvasScale >= 1.5}
|
|
|
|
|
|
className="h-7 w-7"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ZoomIn className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="h-4 w-px bg-gray-300" />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 그리드 가이드 토글 */}
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant={showGridGuide ? "default" : "outline"}
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
onClick={() => setShowGridGuide(!showGridGuide)}
|
|
|
|
|
|
className="h-8 text-xs"
|
|
|
|
|
|
>
|
|
|
|
|
|
그리드 {showGridGuide ? "ON" : "OFF"}
|
|
|
|
|
|
</Button>
|
2026-02-03 19:11:03 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-05 14:24:14 +09:00
|
|
|
|
{/* 캔버스 영역 */}
|
2026-02-03 19:11:03 +09:00
|
|
|
|
<div
|
2026-02-05 14:24:14 +09:00
|
|
|
|
ref={containerRef}
|
2026-02-03 19:11:03 +09:00
|
|
|
|
className={cn(
|
2026-02-05 14:24:14 +09:00
|
|
|
|
"canvas-scroll-area relative flex-1 overflow-auto bg-gray-100",
|
|
|
|
|
|
isSpacePressed && "cursor-grab",
|
|
|
|
|
|
isPanning && "cursor-grabbing"
|
2026-02-03 19:11:03 +09:00
|
|
|
|
)}
|
2026-02-05 14:24:14 +09:00
|
|
|
|
onMouseDown={handlePanStart}
|
|
|
|
|
|
onMouseMove={handlePanMove}
|
|
|
|
|
|
onMouseUp={handlePanEnd}
|
|
|
|
|
|
onMouseLeave={handlePanEnd}
|
|
|
|
|
|
onWheel={handleWheel}
|
2026-02-03 19:11:03 +09:00
|
|
|
|
>
|
|
|
|
|
|
<div
|
2026-02-05 14:24:14 +09:00
|
|
|
|
className="relative mx-auto my-8 origin-top"
|
2026-02-03 19:11:03 +09:00
|
|
|
|
style={{
|
2026-02-05 14:24:14 +09:00
|
|
|
|
width: `${customWidth + 32}px`, // 라벨 공간 추가
|
|
|
|
|
|
minHeight: `${customHeight + 32}px`,
|
|
|
|
|
|
transform: `scale(${canvasScale})`,
|
2026-02-03 19:11:03 +09:00
|
|
|
|
}}
|
2026-02-02 15:15:01 +09:00
|
|
|
|
>
|
2026-02-05 14:24:14 +09:00
|
|
|
|
{/* 그리드 라벨 영역 */}
|
|
|
|
|
|
{showGridGuide && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{/* 열 라벨 (상단) */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="flex absolute top-0 left-8"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
gap: `${breakpoint.gap}px`,
|
|
|
|
|
|
paddingLeft: `${breakpoint.padding}px`,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{gridLabels.columnLabels.map((num) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={`col-${num}`}
|
|
|
|
|
|
className="flex items-center justify-center text-xs font-semibold text-blue-500"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: `calc((${customWidth}px - ${breakpoint.padding * 2}px - ${breakpoint.gap * (breakpoint.columns - 1)}px) / ${breakpoint.columns})`,
|
|
|
|
|
|
height: "24px",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{num}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 행 라벨 (좌측) */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="flex flex-col absolute top-8 left-0"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
gap: `${breakpoint.gap}px`,
|
|
|
|
|
|
paddingTop: `${breakpoint.padding}px`,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{gridLabels.rowLabels.map((num) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={`row-${num}`}
|
|
|
|
|
|
className="flex items-center justify-center text-xs font-semibold text-blue-500"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: "24px",
|
|
|
|
|
|
height: `${breakpoint.rowHeight}px`,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{num}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
2026-02-02 15:15:01 +09:00
|
|
|
|
)}
|
2026-02-05 14:24:14 +09:00
|
|
|
|
|
|
|
|
|
|
{/* 디바이스 스크린 */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
ref={canvasRef}
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"relative rounded-lg border-2 bg-white shadow-xl overflow-hidden",
|
|
|
|
|
|
canDrop && isOver && "ring-4 ring-primary/20"
|
|
|
|
|
|
)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: `${customWidth}px`,
|
|
|
|
|
|
minHeight: `${customHeight}px`,
|
|
|
|
|
|
marginLeft: "32px",
|
|
|
|
|
|
marginTop: "32px",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{isEmpty ? (
|
|
|
|
|
|
// 빈 상태
|
|
|
|
|
|
<div className="flex h-full items-center justify-center p-8">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<div className="mb-2 text-sm font-medium text-gray-500">
|
|
|
|
|
|
컴포넌트를 드래그하여 배치하세요
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-gray-400">
|
|
|
|
|
|
{breakpoint.label} - {breakpoint.columns}칸 그리드
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
// 그리드 렌더러
|
|
|
|
|
|
<PopRenderer
|
|
|
|
|
|
layout={layout}
|
|
|
|
|
|
viewportWidth={customWidth}
|
|
|
|
|
|
currentMode={currentMode}
|
|
|
|
|
|
isDesignMode={true}
|
|
|
|
|
|
showGridGuide={showGridGuide}
|
|
|
|
|
|
selectedComponentId={selectedComponentId}
|
|
|
|
|
|
onComponentClick={onSelectComponent}
|
|
|
|
|
|
onBackgroundClick={() => onSelectComponent(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 하단 정보 */}
|
|
|
|
|
|
<div className="flex items-center justify-between border-t bg-white px-4 py-2">
|
|
|
|
|
|
<div className="text-xs text-muted-foreground">
|
|
|
|
|
|
{breakpoint.label} - {breakpoint.columns}칸 그리드 (행 높이: {breakpoint.rowHeight}px)
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-muted-foreground">
|
|
|
|
|
|
Space + 드래그: 패닝 | Ctrl + 휠: 줌
|
2026-02-02 15:15:01 +09:00
|
|
|
|
</div>
|
2026-02-03 19:11:03 +09:00
|
|
|
|
</div>
|
2026-02-02 15:15:01 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|