2026-02-02 15:15:01 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2026-02-03 19:11:03 +09:00
|
|
|
import { useState, useCallback, useEffect } from "react";
|
2026-02-02 15:15:01 +09:00
|
|
|
import { DndProvider } from "react-dnd";
|
|
|
|
|
import { HTML5Backend } from "react-dnd-html5-backend";
|
2026-02-04 14:14:48 +09:00
|
|
|
import { ArrowLeft, Save, Smartphone, Tablet, Undo2, Redo2 } from "lucide-react";
|
2026-02-02 15:15:01 +09:00
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
|
|
|
import {
|
|
|
|
|
ResizableHandle,
|
|
|
|
|
ResizablePanel,
|
|
|
|
|
ResizablePanelGroup,
|
|
|
|
|
} from "@/components/ui/resizable";
|
|
|
|
|
import { toast } from "sonner";
|
|
|
|
|
|
|
|
|
|
import { PopCanvas } from "./PopCanvas";
|
2026-02-04 14:14:48 +09:00
|
|
|
import { PopCanvasV4 } from "./PopCanvasV4";
|
2026-02-02 15:15:01 +09:00
|
|
|
import { PopPanel } from "./panels/PopPanel";
|
2026-02-04 14:14:48 +09:00
|
|
|
import { ComponentPaletteV4 } from "./panels/ComponentPaletteV4";
|
|
|
|
|
import { ComponentEditorPanelV4 } from "./panels/ComponentEditorPanelV4";
|
2026-02-02 15:15:01 +09:00
|
|
|
import {
|
2026-02-03 19:11:03 +09:00
|
|
|
PopLayoutDataV3,
|
2026-02-04 14:14:48 +09:00
|
|
|
PopLayoutDataV4,
|
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
|
|
|
PopLayoutModeKey,
|
2026-02-02 15:15:01 +09:00
|
|
|
PopComponentType,
|
|
|
|
|
GridPosition,
|
2026-02-03 19:11:03 +09:00
|
|
|
PopComponentDefinition,
|
2026-02-04 14:14:48 +09:00
|
|
|
PopComponentDefinitionV4,
|
|
|
|
|
PopContainerV4,
|
|
|
|
|
PopSizeConstraintV4,
|
2026-02-03 19:11:03 +09:00
|
|
|
createEmptyPopLayoutV3,
|
2026-02-04 14:14:48 +09:00
|
|
|
createEmptyPopLayoutV4,
|
2026-02-03 19:11:03 +09:00
|
|
|
ensureV3Layout,
|
|
|
|
|
addComponentToV3Layout,
|
|
|
|
|
removeComponentFromV3Layout,
|
|
|
|
|
updateComponentPositionInModeV3,
|
2026-02-04 14:14:48 +09:00
|
|
|
addComponentToV4Layout,
|
|
|
|
|
removeComponentFromV4Layout,
|
|
|
|
|
updateComponentInV4Layout,
|
|
|
|
|
updateContainerV4,
|
|
|
|
|
findContainerV4,
|
2026-02-03 19:11:03 +09:00
|
|
|
isV3Layout,
|
2026-02-04 14:14:48 +09:00
|
|
|
isV4Layout,
|
2026-02-02 15:15:01 +09:00
|
|
|
} from "./types/pop-layout";
|
|
|
|
|
import { screenApi } from "@/lib/api/screen";
|
|
|
|
|
import { ScreenDefinition } from "@/types/screen";
|
|
|
|
|
|
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-04 14:14:48 +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-04 14:14:48 +09:00
|
|
|
type LayoutMode = "v3" | "v4";
|
2026-02-02 15:15:01 +09:00
|
|
|
type DeviceType = "mobile" | "tablet";
|
|
|
|
|
|
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 PopDesignerProps {
|
|
|
|
|
selectedScreen: ScreenDefinition;
|
|
|
|
|
onBackToList: () => void;
|
|
|
|
|
onScreenUpdate?: (updatedScreen: Partial<ScreenDefinition>) => void;
|
|
|
|
|
}
|
|
|
|
|
|
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-04 14:14:48 +09:00
|
|
|
// 메인 컴포넌트 (v3/v4 통합)
|
|
|
|
|
// - 새 화면: v4로 시작
|
|
|
|
|
// - 기존 v3 화면: v3로 로드 (하위 호환)
|
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
|
|
|
export default function PopDesigner({
|
|
|
|
|
selectedScreen,
|
|
|
|
|
onBackToList,
|
|
|
|
|
onScreenUpdate,
|
|
|
|
|
}: PopDesignerProps) {
|
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-04 14:14:48 +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-04 14:14:48 +09:00
|
|
|
const [layoutMode, setLayoutMode] = useState<LayoutMode>("v4");
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
// 레이아웃 상태 (데스크탑 모드와 동일한 방식)
|
|
|
|
|
// ========================================
|
|
|
|
|
const [layoutV4, setLayoutV4] = useState<PopLayoutDataV4>(createEmptyPopLayoutV4());
|
|
|
|
|
const [layoutV3, setLayoutV3] = useState<PopLayoutDataV3>(createEmptyPopLayoutV3());
|
|
|
|
|
|
|
|
|
|
// 히스토리 (v4용)
|
|
|
|
|
const [historyV4, setHistoryV4] = useState<PopLayoutDataV4[]>([]);
|
|
|
|
|
const [historyIndexV4, setHistoryIndexV4] = useState(-1);
|
|
|
|
|
|
|
|
|
|
// 히스토리 (v3용)
|
|
|
|
|
const [historyV3, setHistoryV3] = useState<PopLayoutDataV3[]>([]);
|
|
|
|
|
const [historyIndexV3, setHistoryIndexV3] = useState(-1);
|
|
|
|
|
|
|
|
|
|
const [idCounter, setIdCounter] = useState(1);
|
|
|
|
|
|
2026-02-02 15:15:01 +09:00
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
|
const [hasChanges, setHasChanges] = 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
|
|
|
// ========================================
|
2026-02-04 14:14:48 +09:00
|
|
|
// 히스토리 저장 함수
|
|
|
|
|
// ========================================
|
|
|
|
|
const saveToHistoryV4 = useCallback((newLayout: PopLayoutDataV4) => {
|
|
|
|
|
setHistoryV4((prev) => {
|
|
|
|
|
const newHistory = prev.slice(0, historyIndexV4 + 1);
|
|
|
|
|
newHistory.push(JSON.parse(JSON.stringify(newLayout))); // 깊은 복사
|
|
|
|
|
return newHistory.slice(-50); // 최대 50개
|
|
|
|
|
});
|
|
|
|
|
setHistoryIndexV4((prev) => Math.min(prev + 1, 49));
|
|
|
|
|
}, [historyIndexV4]);
|
|
|
|
|
|
|
|
|
|
const saveToHistoryV3 = useCallback((newLayout: PopLayoutDataV3) => {
|
|
|
|
|
setHistoryV3((prev) => {
|
|
|
|
|
const newHistory = prev.slice(0, historyIndexV3 + 1);
|
|
|
|
|
newHistory.push(JSON.parse(JSON.stringify(newLayout)));
|
|
|
|
|
return newHistory.slice(-50);
|
|
|
|
|
});
|
|
|
|
|
setHistoryIndexV3((prev) => Math.min(prev + 1, 49));
|
|
|
|
|
}, [historyIndexV3]);
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
// Undo/Redo 함수
|
|
|
|
|
// ========================================
|
|
|
|
|
const undoV4 = useCallback(() => {
|
|
|
|
|
if (historyIndexV4 > 0) {
|
|
|
|
|
const newIndex = historyIndexV4 - 1;
|
|
|
|
|
const previousLayout = historyV4[newIndex];
|
|
|
|
|
if (previousLayout) {
|
|
|
|
|
setLayoutV4(JSON.parse(JSON.stringify(previousLayout)));
|
|
|
|
|
setHistoryIndexV4(newIndex);
|
|
|
|
|
console.log("[Undo V4] 복원됨, index:", newIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [historyIndexV4, historyV4]);
|
|
|
|
|
|
|
|
|
|
const redoV4 = useCallback(() => {
|
|
|
|
|
if (historyIndexV4 < historyV4.length - 1) {
|
|
|
|
|
const newIndex = historyIndexV4 + 1;
|
|
|
|
|
const nextLayout = historyV4[newIndex];
|
|
|
|
|
if (nextLayout) {
|
|
|
|
|
setLayoutV4(JSON.parse(JSON.stringify(nextLayout)));
|
|
|
|
|
setHistoryIndexV4(newIndex);
|
|
|
|
|
console.log("[Redo V4] 복원됨, index:", newIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [historyIndexV4, historyV4]);
|
|
|
|
|
|
|
|
|
|
const undoV3 = useCallback(() => {
|
|
|
|
|
if (historyIndexV3 > 0) {
|
|
|
|
|
const newIndex = historyIndexV3 - 1;
|
|
|
|
|
const previousLayout = historyV3[newIndex];
|
|
|
|
|
if (previousLayout) {
|
|
|
|
|
setLayoutV3(JSON.parse(JSON.stringify(previousLayout)));
|
|
|
|
|
setHistoryIndexV3(newIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [historyIndexV3, historyV3]);
|
|
|
|
|
|
|
|
|
|
const redoV3 = useCallback(() => {
|
|
|
|
|
if (historyIndexV3 < historyV3.length - 1) {
|
|
|
|
|
const newIndex = historyIndexV3 + 1;
|
|
|
|
|
const nextLayout = historyV3[newIndex];
|
|
|
|
|
if (nextLayout) {
|
|
|
|
|
setLayoutV3(JSON.parse(JSON.stringify(nextLayout)));
|
|
|
|
|
setHistoryIndexV3(newIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [historyIndexV3, historyV3]);
|
|
|
|
|
|
|
|
|
|
// 현재 모드의 Undo/Redo
|
|
|
|
|
const canUndo = layoutMode === "v4" ? historyIndexV4 > 0 : historyIndexV3 > 0;
|
|
|
|
|
const canRedo = layoutMode === "v4"
|
|
|
|
|
? historyIndexV4 < historyV4.length - 1
|
|
|
|
|
: historyIndexV3 < historyV3.length - 1;
|
|
|
|
|
const handleUndo = layoutMode === "v4" ? undoV4 : undoV3;
|
|
|
|
|
const handleRedo = layoutMode === "v4" ? redoV4 : redoV3;
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
// v3용 디바이스/모드 상태
|
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
|
|
|
const [activeDevice, setActiveDevice] = useState<DeviceType>("tablet");
|
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 [activeModeKey, setActiveModeKey] = useState<PopLayoutModeKey>("tablet_landscape");
|
|
|
|
|
|
2026-02-04 18:23:59 +09:00
|
|
|
// ========================================
|
|
|
|
|
// v4용 뷰포트 모드 상태
|
|
|
|
|
// ========================================
|
|
|
|
|
type ViewportMode = "mobile_portrait" | "mobile_landscape" | "tablet_portrait" | "tablet_landscape";
|
|
|
|
|
const [currentViewportMode, setCurrentViewportMode] = useState<ViewportMode>("tablet_landscape");
|
|
|
|
|
|
|
|
|
|
// v4: 임시 레이아웃 (고정 전 배치) - 다른 모드에서만 사용
|
|
|
|
|
const [tempLayout, setTempLayout] = useState<PopContainerV4 | null>(null);
|
|
|
|
|
|
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-04 14:14:48 +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-02 15:15:01 +09:00
|
|
|
const [selectedComponentId, setSelectedComponentId] = useState<string | null>(null);
|
2026-02-04 14:14:48 +09:00
|
|
|
const [selectedContainerId, setSelectedContainerId] = useState<string | null>(null);
|
2026-02-02 15:15:01 +09:00
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
// 선택된 컴포넌트/컨테이너
|
|
|
|
|
const selectedComponentV3: PopComponentDefinition | null = selectedComponentId
|
|
|
|
|
? layoutV3.components[selectedComponentId] || null
|
|
|
|
|
: null;
|
|
|
|
|
const selectedComponentV4: PopComponentDefinitionV4 | null = selectedComponentId
|
|
|
|
|
? layoutV4.components[selectedComponentId] || null
|
|
|
|
|
: null;
|
|
|
|
|
const selectedContainer: PopContainerV4 | null = selectedContainerId
|
|
|
|
|
? findContainerV4(layoutV4.root, selectedContainerId)
|
2026-02-03 19:11:03 +09:00
|
|
|
: null;
|
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
|
|
|
// 레이아웃 로드
|
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
|
|
|
useEffect(() => {
|
|
|
|
|
const loadLayout = async () => {
|
|
|
|
|
if (!selectedScreen?.screenId) return;
|
|
|
|
|
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
try {
|
2026-02-02 18:01:05 +09:00
|
|
|
const loadedLayout = await screenApi.getLayoutPop(selectedScreen.screenId);
|
2026-02-04 14:14:48 +09:00
|
|
|
|
|
|
|
|
// 유효한 레이아웃인지 확인:
|
|
|
|
|
// 1. version 필드 필수
|
|
|
|
|
// 2. 컴포넌트가 있어야 함 (빈 레이아웃은 새 화면 취급)
|
|
|
|
|
const hasValidLayout = loadedLayout && loadedLayout.version;
|
|
|
|
|
const hasComponents = loadedLayout?.components && Object.keys(loadedLayout.components).length > 0;
|
2026-02-02 15:15:01 +09:00
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
if (hasValidLayout && hasComponents) {
|
|
|
|
|
if (isV4Layout(loadedLayout)) {
|
|
|
|
|
// v4 레이아웃
|
|
|
|
|
setLayoutV4(loadedLayout);
|
|
|
|
|
setHistoryV4([loadedLayout]);
|
|
|
|
|
setHistoryIndexV4(0);
|
|
|
|
|
setLayoutMode("v4");
|
|
|
|
|
console.log(`POP v4 레이아웃 로드: ${Object.keys(loadedLayout.components).length}개 컴포넌트`);
|
|
|
|
|
} else {
|
|
|
|
|
// v1/v2/v3 → v3로 변환
|
|
|
|
|
const v3Layout = ensureV3Layout(loadedLayout);
|
|
|
|
|
setLayoutV3(v3Layout);
|
|
|
|
|
setHistoryV3([v3Layout]);
|
|
|
|
|
setHistoryIndexV3(0);
|
|
|
|
|
setLayoutMode("v3");
|
|
|
|
|
console.log(`POP v3 레이아웃 로드: ${Object.keys(v3Layout.components).length}개 컴포넌트`);
|
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
|
|
|
} else {
|
2026-02-04 14:14:48 +09:00
|
|
|
// 새 화면 또는 빈 레이아웃 → v4로 시작
|
|
|
|
|
const emptyLayout = createEmptyPopLayoutV4();
|
|
|
|
|
setLayoutV4(emptyLayout);
|
|
|
|
|
setHistoryV4([emptyLayout]);
|
|
|
|
|
setHistoryIndexV4(0);
|
|
|
|
|
setLayoutMode("v4");
|
2026-02-02 15:15:01 +09:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("레이아웃 로드 실패:", error);
|
|
|
|
|
toast.error("레이아웃을 불러오는데 실패했습니다");
|
2026-02-04 14:14:48 +09:00
|
|
|
const emptyLayout = createEmptyPopLayoutV4();
|
|
|
|
|
setLayoutV4(emptyLayout);
|
|
|
|
|
setHistoryV4([emptyLayout]);
|
|
|
|
|
setHistoryIndexV4(0);
|
|
|
|
|
setLayoutMode("v4");
|
2026-02-02 15:15:01 +09:00
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loadLayout();
|
|
|
|
|
}, [selectedScreen?.screenId]);
|
|
|
|
|
|
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
|
|
|
// 저장
|
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
|
|
|
const handleSave = useCallback(async () => {
|
|
|
|
|
if (!selectedScreen?.screenId) return;
|
|
|
|
|
|
|
|
|
|
setIsSaving(true);
|
|
|
|
|
try {
|
2026-02-04 14:14:48 +09:00
|
|
|
const layoutToSave = layoutMode === "v3" ? layoutV3 : layoutV4;
|
|
|
|
|
await screenApi.saveLayoutPop(selectedScreen.screenId, layoutToSave);
|
2026-02-02 15:15:01 +09:00
|
|
|
toast.success("저장되었습니다");
|
|
|
|
|
setHasChanges(false);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("저장 실패:", error);
|
|
|
|
|
toast.error("저장에 실패했습니다");
|
|
|
|
|
} finally {
|
|
|
|
|
setIsSaving(false);
|
|
|
|
|
}
|
2026-02-04 14:14:48 +09:00
|
|
|
}, [selectedScreen?.screenId, layoutMode, layoutV3, layoutV4]);
|
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-04 14:14:48 +09:00
|
|
|
// v3: 컴포넌트 핸들러
|
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-04 14:14:48 +09:00
|
|
|
const handleDropComponentV3 = useCallback(
|
2026-02-03 19:11:03 +09:00
|
|
|
(type: PopComponentType, gridPosition: GridPosition) => {
|
2026-02-02 15:15:01 +09:00
|
|
|
const newId = `${type}-${Date.now()}`;
|
2026-02-04 14:14:48 +09:00
|
|
|
const newLayout = addComponentToV3Layout(layoutV3, newId, type, gridPosition);
|
|
|
|
|
setLayoutV3(newLayout);
|
|
|
|
|
saveToHistoryV3(newLayout);
|
2026-02-02 15:15:01 +09:00
|
|
|
setSelectedComponentId(newId);
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
},
|
2026-02-04 14:14:48 +09:00
|
|
|
[layoutV3, saveToHistoryV3]
|
2026-02-02 15:15:01 +09:00
|
|
|
);
|
|
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
const handleUpdateComponentDefinitionV3 = useCallback(
|
2026-02-03 19:11:03 +09:00
|
|
|
(componentId: string, updates: Partial<PopComponentDefinition>) => {
|
2026-02-04 14:14:48 +09:00
|
|
|
const newLayout = {
|
|
|
|
|
...layoutV3,
|
2026-02-03 19:11:03 +09:00
|
|
|
components: {
|
2026-02-04 14:14:48 +09:00
|
|
|
...layoutV3.components,
|
|
|
|
|
[componentId]: { ...layoutV3.components[componentId], ...updates },
|
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-04 14:14:48 +09:00
|
|
|
};
|
|
|
|
|
setLayoutV3(newLayout);
|
|
|
|
|
saveToHistoryV3(newLayout);
|
2026-02-02 15:15:01 +09:00
|
|
|
setHasChanges(true);
|
|
|
|
|
},
|
2026-02-04 14:14:48 +09:00
|
|
|
[layoutV3, saveToHistoryV3]
|
2026-02-02 15:15:01 +09:00
|
|
|
);
|
|
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
const handleUpdateComponentPositionV3 = useCallback(
|
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
|
|
|
(componentId: string, position: GridPosition, modeKey?: PopLayoutModeKey) => {
|
|
|
|
|
const targetMode = modeKey || activeModeKey;
|
2026-02-04 14:14:48 +09:00
|
|
|
const newLayout = updateComponentPositionInModeV3(layoutV3, targetMode, componentId, position);
|
|
|
|
|
setLayoutV3(newLayout);
|
|
|
|
|
saveToHistoryV3(newLayout);
|
2026-02-02 15:15:01 +09:00
|
|
|
setHasChanges(true);
|
|
|
|
|
},
|
2026-02-04 14:14:48 +09:00
|
|
|
[layoutV3, activeModeKey, saveToHistoryV3]
|
2026-02-02 15:15:01 +09:00
|
|
|
);
|
|
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
const handleDeleteComponentV3 = useCallback((componentId: string) => {
|
|
|
|
|
const newLayout = removeComponentFromV3Layout(layoutV3, componentId);
|
|
|
|
|
setLayoutV3(newLayout);
|
|
|
|
|
saveToHistoryV3(newLayout);
|
|
|
|
|
setSelectedComponentId(null);
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
}, [layoutV3, saveToHistoryV3]);
|
|
|
|
|
|
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-04 14:14:48 +09:00
|
|
|
// v4: 컴포넌트 핸들러
|
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-04 14:14:48 +09:00
|
|
|
const handleDropComponentV4 = useCallback(
|
|
|
|
|
(type: PopComponentType, containerId: string) => {
|
|
|
|
|
const componentId = `comp_${idCounter}`;
|
|
|
|
|
setIdCounter((prev) => prev + 1);
|
|
|
|
|
const newLayout = addComponentToV4Layout(layoutV4, componentId, type, containerId, `${type} ${idCounter}`);
|
|
|
|
|
setLayoutV4(newLayout);
|
|
|
|
|
saveToHistoryV4(newLayout);
|
|
|
|
|
setSelectedComponentId(componentId);
|
|
|
|
|
setSelectedContainerId(null);
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
console.log("[V4] 컴포넌트 추가, 히스토리 저장됨");
|
|
|
|
|
},
|
|
|
|
|
[idCounter, layoutV4, saveToHistoryV4]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleUpdateComponentV4 = useCallback(
|
|
|
|
|
(componentId: string, updates: Partial<PopComponentDefinitionV4>) => {
|
|
|
|
|
const newLayout = updateComponentInV4Layout(layoutV4, componentId, updates);
|
|
|
|
|
setLayoutV4(newLayout);
|
|
|
|
|
saveToHistoryV4(newLayout);
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
},
|
|
|
|
|
[layoutV4, saveToHistoryV4]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleUpdateContainerV4 = useCallback(
|
|
|
|
|
(containerId: string, updates: Partial<PopContainerV4>) => {
|
2026-02-04 18:23:59 +09:00
|
|
|
if (currentViewportMode === "tablet_landscape") {
|
|
|
|
|
// 기본 모드 (태블릿 가로) → root 직접 수정 ✅
|
|
|
|
|
const newLayout = {
|
|
|
|
|
...layoutV4,
|
|
|
|
|
root: updateContainerV4(layoutV4.root, containerId, updates),
|
|
|
|
|
};
|
|
|
|
|
setLayoutV4(newLayout);
|
|
|
|
|
saveToHistoryV4(newLayout);
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
console.log("[기본 모드] root 컨테이너 수정");
|
|
|
|
|
} else {
|
|
|
|
|
// 다른 모드 → 속성 패널에서 수정 차단됨 (UI에서 비활성화)
|
|
|
|
|
toast.warning("기본 모드(태블릿 가로)에서만 속성을 변경할 수 있습니다");
|
|
|
|
|
console.log("[다른 모드] 속성 수정 차단");
|
|
|
|
|
}
|
2026-02-04 14:14:48 +09:00
|
|
|
},
|
2026-02-04 18:23:59 +09:00
|
|
|
[layoutV4, currentViewportMode, saveToHistoryV4]
|
2026-02-04 14:14:48 +09:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleDeleteComponentV4 = useCallback((componentId: string) => {
|
|
|
|
|
const newLayout = removeComponentFromV4Layout(layoutV4, componentId);
|
|
|
|
|
setLayoutV4(newLayout);
|
|
|
|
|
saveToHistoryV4(newLayout);
|
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
|
|
|
setSelectedComponentId(null);
|
|
|
|
|
setHasChanges(true);
|
2026-02-04 14:14:48 +09:00
|
|
|
console.log("[V4] 컴포넌트 삭제, 히스토리 저장됨");
|
|
|
|
|
}, [layoutV4, saveToHistoryV4]);
|
|
|
|
|
|
2026-02-04 18:23:59 +09:00
|
|
|
// v4: 현재 모드 배치 고정 (오버라이드 저장) 🔥
|
|
|
|
|
const handleLockLayoutV4 = useCallback(() => {
|
|
|
|
|
if (currentViewportMode === "tablet_landscape") {
|
|
|
|
|
toast.info("기본 모드는 고정할 필요가 없습니다");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!tempLayout) {
|
|
|
|
|
toast.info("변경사항이 없습니다");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 임시 레이아웃을 오버라이드에 저장 ✅
|
|
|
|
|
const newLayout = {
|
|
|
|
|
...layoutV4,
|
|
|
|
|
overrides: {
|
|
|
|
|
...layoutV4.overrides,
|
|
|
|
|
[currentViewportMode]: {
|
|
|
|
|
...layoutV4.overrides?.[currentViewportMode as keyof typeof layoutV4.overrides],
|
|
|
|
|
containers: {
|
|
|
|
|
root: {
|
|
|
|
|
direction: tempLayout.direction,
|
|
|
|
|
wrap: tempLayout.wrap,
|
|
|
|
|
gap: tempLayout.gap,
|
|
|
|
|
alignItems: tempLayout.alignItems,
|
|
|
|
|
justifyContent: tempLayout.justifyContent,
|
|
|
|
|
padding: tempLayout.padding,
|
|
|
|
|
children: tempLayout.children, // 순서 고정
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setLayoutV4(newLayout);
|
|
|
|
|
saveToHistoryV4(newLayout);
|
|
|
|
|
setTempLayout(null); // 임시 레이아웃 초기화
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
toast.success(`${currentViewportMode} 모드 배치가 고정되었습니다`);
|
|
|
|
|
console.log(`[V4] ${currentViewportMode} 배치 고정됨 (tempLayout → overrides)`);
|
|
|
|
|
}, [layoutV4, currentViewportMode, tempLayout, saveToHistoryV4]);
|
|
|
|
|
|
|
|
|
|
// v4: 오버라이드 초기화 (자동 계산으로 되돌리기)
|
|
|
|
|
const handleResetOverrideV4 = useCallback((mode: ViewportMode) => {
|
|
|
|
|
if (mode === "tablet_landscape") {
|
|
|
|
|
toast.info("기본 모드는 초기화할 수 없습니다");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newOverrides = { ...layoutV4.overrides };
|
|
|
|
|
delete newOverrides[mode as keyof typeof newOverrides];
|
|
|
|
|
|
|
|
|
|
const newLayout = {
|
|
|
|
|
...layoutV4,
|
|
|
|
|
overrides: Object.keys(newOverrides).length > 0 ? newOverrides : undefined
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setLayoutV4(newLayout);
|
|
|
|
|
saveToHistoryV4(newLayout);
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
toast.success(`${mode} 모드 오버라이드가 초기화되었습니다`);
|
|
|
|
|
console.log(`[V4] ${mode} 오버라이드 초기화됨`);
|
|
|
|
|
}, [layoutV4, saveToHistoryV4]);
|
|
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
// v4: 컴포넌트 크기 조정 (드래그) - 리사이즈 중에는 히스토리 저장 안 함
|
|
|
|
|
// 리사이즈 완료 시 별도로 저장해야 함 (TODO: 드래그 종료 시 저장)
|
|
|
|
|
const handleResizeComponentV4 = useCallback(
|
|
|
|
|
(componentId: string, sizeUpdates: Partial<PopSizeConstraintV4>) => {
|
|
|
|
|
const existingComponent = layoutV4.components[componentId];
|
|
|
|
|
if (!existingComponent) return;
|
|
|
|
|
|
|
|
|
|
const newLayout = {
|
|
|
|
|
...layoutV4,
|
|
|
|
|
components: {
|
|
|
|
|
...layoutV4.components,
|
|
|
|
|
[componentId]: {
|
|
|
|
|
...existingComponent,
|
|
|
|
|
size: {
|
|
|
|
|
...existingComponent.size,
|
|
|
|
|
...sizeUpdates,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
setLayoutV4(newLayout);
|
|
|
|
|
// 리사이즈 중에는 히스토리 저장 안 함 (너무 많아짐)
|
|
|
|
|
// saveToHistoryV4(newLayout);
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
},
|
|
|
|
|
[layoutV4]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// v4: 컴포넌트 순서 변경 (드래그 앤 드롭)
|
|
|
|
|
const handleReorderComponentV4 = useCallback(
|
|
|
|
|
(containerId: string, fromIndex: number, toIndex: number) => {
|
|
|
|
|
// 컨테이너 찾기 (재귀)
|
|
|
|
|
const reorderInContainer = (container: PopContainerV4): PopContainerV4 => {
|
|
|
|
|
if (container.id === containerId) {
|
|
|
|
|
const newChildren = [...container.children];
|
|
|
|
|
const [movedItem] = newChildren.splice(fromIndex, 1);
|
|
|
|
|
newChildren.splice(toIndex, 0, movedItem);
|
|
|
|
|
return { ...container, children: newChildren };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 자식 컨테이너에서도 찾기
|
|
|
|
|
return {
|
|
|
|
|
...container,
|
|
|
|
|
children: container.children.map(child => {
|
|
|
|
|
if (typeof child === "object") {
|
|
|
|
|
return reorderInContainer(child);
|
|
|
|
|
}
|
|
|
|
|
return child;
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-04 18:23:59 +09:00
|
|
|
if (currentViewportMode === "tablet_landscape") {
|
|
|
|
|
// 기본 모드 → root 직접 수정 ✅
|
|
|
|
|
const newLayout = {
|
|
|
|
|
...layoutV4,
|
|
|
|
|
root: reorderInContainer(layoutV4.root),
|
|
|
|
|
};
|
|
|
|
|
setLayoutV4(newLayout);
|
|
|
|
|
saveToHistoryV4(newLayout);
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
console.log("[기본 모드] 컴포넌트 순서 변경 (root 저장)");
|
|
|
|
|
} else {
|
|
|
|
|
// 다른 모드 → 임시 레이아웃에만 저장 (화면에만 표시, layoutV4는 안 건드림) 🔥
|
|
|
|
|
const reorderedRoot = reorderInContainer(layoutV4.root);
|
|
|
|
|
setTempLayout(reorderedRoot);
|
|
|
|
|
console.log(`[${currentViewportMode}] 컴포넌트 순서 변경 (임시, 고정 필요)`);
|
|
|
|
|
toast.info("배치 변경됨. '고정' 버튼을 클릭하여 저장하세요", { duration: 2000 });
|
|
|
|
|
}
|
2026-02-04 14:14:48 +09:00
|
|
|
},
|
2026-02-04 18:23:59 +09:00
|
|
|
[layoutV4, currentViewportMode, saveToHistoryV4]
|
2026-02-04 14:14:48 +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-04 14:14:48 +09:00
|
|
|
// v3: 디바이스/모드 전환
|
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 handleDeviceChange = useCallback((device: DeviceType) => {
|
|
|
|
|
setActiveDevice(device);
|
|
|
|
|
setActiveModeKey(device === "tablet" ? "tablet_landscape" : "mobile_landscape");
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleModeKeyChange = useCallback((modeKey: PopLayoutModeKey) => {
|
|
|
|
|
setActiveModeKey(modeKey);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// ========================================
|
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-02 15:15:01 +09:00
|
|
|
const handleBack = useCallback(() => {
|
|
|
|
|
if (hasChanges) {
|
|
|
|
|
if (confirm("저장하지 않은 변경사항이 있습니다. 나가시겠습니까?")) {
|
|
|
|
|
onBackToList();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
onBackToList();
|
|
|
|
|
}
|
|
|
|
|
}, [hasChanges, onBackToList]);
|
|
|
|
|
|
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-04 14:14:48 +09:00
|
|
|
// 단축키 처리 (Delete, Undo, Redo)
|
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
|
|
|
// ========================================
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
|
|
|
const target = e.target as HTMLElement;
|
2026-02-04 14:14:48 +09:00
|
|
|
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
|
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
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
const key = e.key.toLowerCase();
|
|
|
|
|
const isCtrlOrCmd = e.ctrlKey || e.metaKey;
|
|
|
|
|
|
|
|
|
|
// Delete / Backspace: 컴포넌트 삭제
|
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
|
|
|
if (e.key === "Delete" || e.key === "Backspace") {
|
2026-02-03 19:11:03 +09:00
|
|
|
e.preventDefault();
|
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
|
|
|
if (selectedComponentId) {
|
2026-02-04 14:14:48 +09:00
|
|
|
layoutMode === "v3" ? handleDeleteComponentV3(selectedComponentId) : handleDeleteComponentV4(selectedComponentId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ctrl+Z / Cmd+Z: Undo (Shift 안 눌림)
|
|
|
|
|
if (isCtrlOrCmd && key === "z" && !e.shiftKey) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
console.log("Undo 시도:", { canUndo, layoutMode });
|
|
|
|
|
if (canUndo) {
|
|
|
|
|
handleUndo();
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
toast.success("실행 취소됨");
|
|
|
|
|
} else {
|
|
|
|
|
toast.info("실행 취소할 내용이 없습니다");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ctrl+Shift+Z / Cmd+Shift+Z: Redo
|
|
|
|
|
if (isCtrlOrCmd && key === "z" && e.shiftKey) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
console.log("Redo 시도:", { canRedo, layoutMode });
|
|
|
|
|
if (canRedo) {
|
|
|
|
|
handleRedo();
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
toast.success("다시 실행됨");
|
|
|
|
|
} else {
|
|
|
|
|
toast.info("다시 실행할 내용이 없습니다");
|
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-04 14:14:48 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ctrl+Y / Cmd+Y: Redo (대체)
|
|
|
|
|
if (isCtrlOrCmd && key === "y") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (canRedo) {
|
|
|
|
|
handleRedo();
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
toast.success("다시 실행됨");
|
|
|
|
|
}
|
|
|
|
|
return;
|
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);
|
2026-02-03 19:11:03 +09:00
|
|
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
2026-02-04 14:14:48 +09:00
|
|
|
}, [selectedComponentId, layoutMode, handleDeleteComponentV3, handleDeleteComponentV4, canUndo, canRedo, handleUndo, handleRedo]);
|
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-04 14:14:48 +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-02 15:15:01 +09:00
|
|
|
if (isLoading) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex h-screen items-center justify-center">
|
|
|
|
|
<div className="text-muted-foreground">로딩 중...</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
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 (
|
|
|
|
|
<DndProvider backend={HTML5Backend}>
|
|
|
|
|
<div className="flex h-screen flex-col bg-background">
|
|
|
|
|
{/* 툴바 */}
|
|
|
|
|
<div className="flex h-12 items-center justify-between border-b px-4">
|
|
|
|
|
{/* 왼쪽: 뒤로가기 + 화면명 */}
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<Button variant="ghost" size="sm" onClick={handleBack}>
|
|
|
|
|
<ArrowLeft className="mr-1 h-4 w-4" />
|
|
|
|
|
목록
|
|
|
|
|
</Button>
|
|
|
|
|
<span className="text-sm font-medium">
|
|
|
|
|
{selectedScreen?.screenName || "POP 화면"}
|
|
|
|
|
</span>
|
2026-02-04 14:14:48 +09:00
|
|
|
{hasChanges && <span className="text-xs text-orange-500">*변경됨</span>}
|
2026-02-02 15:15:01 +09:00
|
|
|
</div>
|
|
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
{/* 중앙: 레이아웃 버전 + v3 디바이스 전환 */}
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<span className="text-muted-foreground text-xs">
|
|
|
|
|
{layoutMode === "v4" ? "자동 레이아웃 (v4)" : "4모드 레이아웃 (v3)"}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{layoutMode === "v3" && (
|
|
|
|
|
<Tabs value={activeDevice} onValueChange={(v) => handleDeviceChange(v as DeviceType)}>
|
|
|
|
|
<TabsList className="h-8">
|
|
|
|
|
<TabsTrigger value="tablet" className="h-7 px-3 text-xs">
|
|
|
|
|
<Tablet className="mr-1 h-3.5 w-3.5" />
|
|
|
|
|
태블릿
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
<TabsTrigger value="mobile" className="h-7 px-3 text-xs">
|
|
|
|
|
<Smartphone className="mr-1 h-3.5 w-3.5" />
|
|
|
|
|
모바일
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
</TabsList>
|
|
|
|
|
</Tabs>
|
|
|
|
|
)}
|
2026-02-02 15:15:01 +09:00
|
|
|
</div>
|
|
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
{/* 오른쪽: Undo/Redo + 저장 */}
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
{/* Undo/Redo 버튼 */}
|
|
|
|
|
<div className="flex items-center gap-1 border-r pr-2 mr-1">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
className="h-8 w-8"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleUndo();
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
toast.success("실행 취소됨");
|
|
|
|
|
}}
|
|
|
|
|
disabled={!canUndo}
|
|
|
|
|
title="실행 취소 (Ctrl+Z)"
|
|
|
|
|
>
|
|
|
|
|
<Undo2 className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
className="h-8 w-8"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handleRedo();
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
toast.success("다시 실행됨");
|
|
|
|
|
}}
|
|
|
|
|
disabled={!canRedo}
|
|
|
|
|
title="다시 실행 (Ctrl+Shift+Z)"
|
|
|
|
|
>
|
|
|
|
|
<Redo2 className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 저장 버튼 */}
|
|
|
|
|
<Button size="sm" onClick={handleSave} disabled={isSaving || !hasChanges}>
|
2026-02-02 15:15:01 +09:00
|
|
|
<Save className="mr-1 h-4 w-4" />
|
|
|
|
|
{isSaving ? "저장 중..." : "저장"}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-03 19:11:03 +09:00
|
|
|
{/* 메인 영역 */}
|
2026-02-02 15:15:01 +09:00
|
|
|
<ResizablePanelGroup direction="horizontal" className="flex-1">
|
2026-02-04 14:14:48 +09:00
|
|
|
{/* 왼쪽: 컴포넌트 패널 */}
|
|
|
|
|
<ResizablePanel defaultSize={20} minSize={15} maxSize={30} className="border-r">
|
|
|
|
|
{layoutMode === "v3" ? (
|
|
|
|
|
<PopPanel
|
|
|
|
|
layout={layoutV3}
|
|
|
|
|
activeModeKey={activeModeKey}
|
|
|
|
|
selectedComponentId={selectedComponentId}
|
|
|
|
|
selectedComponent={selectedComponentV3}
|
|
|
|
|
onUpdateComponentDefinition={handleUpdateComponentDefinitionV3}
|
|
|
|
|
onDeleteComponent={handleDeleteComponentV3}
|
|
|
|
|
activeDevice={activeDevice}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<ComponentPaletteV4 />
|
|
|
|
|
)}
|
2026-02-02 15:15:01 +09:00
|
|
|
</ResizablePanel>
|
|
|
|
|
|
|
|
|
|
<ResizableHandle withHandle />
|
|
|
|
|
|
2026-02-04 14:14:48 +09:00
|
|
|
{/* 중앙: 캔버스 */}
|
|
|
|
|
<ResizablePanel defaultSize={layoutMode === "v3" ? 80 : 60}>
|
|
|
|
|
{layoutMode === "v3" ? (
|
|
|
|
|
<PopCanvas
|
|
|
|
|
layout={layoutV3}
|
|
|
|
|
activeDevice={activeDevice}
|
|
|
|
|
activeModeKey={activeModeKey}
|
|
|
|
|
onModeKeyChange={handleModeKeyChange}
|
|
|
|
|
selectedComponentId={selectedComponentId}
|
|
|
|
|
onSelectComponent={setSelectedComponentId}
|
|
|
|
|
onUpdateComponentPosition={handleUpdateComponentPositionV3}
|
|
|
|
|
onDropComponent={handleDropComponentV3}
|
|
|
|
|
onDeleteComponent={handleDeleteComponentV3}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<PopCanvasV4
|
|
|
|
|
layout={layoutV4}
|
|
|
|
|
selectedComponentId={selectedComponentId}
|
|
|
|
|
selectedContainerId={selectedContainerId}
|
2026-02-04 18:23:59 +09:00
|
|
|
currentMode={currentViewportMode}
|
|
|
|
|
tempLayout={tempLayout}
|
|
|
|
|
onModeChange={setCurrentViewportMode}
|
2026-02-04 14:14:48 +09:00
|
|
|
onSelectComponent={setSelectedComponentId}
|
|
|
|
|
onSelectContainer={setSelectedContainerId}
|
|
|
|
|
onDropComponent={handleDropComponentV4}
|
|
|
|
|
onUpdateComponent={handleUpdateComponentV4}
|
|
|
|
|
onUpdateContainer={handleUpdateContainerV4}
|
|
|
|
|
onDeleteComponent={handleDeleteComponentV4}
|
|
|
|
|
onResizeComponent={handleResizeComponentV4}
|
|
|
|
|
onReorderComponent={handleReorderComponentV4}
|
2026-02-04 18:23:59 +09:00
|
|
|
onLockLayout={handleLockLayoutV4}
|
|
|
|
|
onResetOverride={handleResetOverrideV4}
|
2026-02-04 14:14:48 +09:00
|
|
|
/>
|
|
|
|
|
)}
|
2026-02-02 15:15:01 +09:00
|
|
|
</ResizablePanel>
|
2026-02-04 14:14:48 +09:00
|
|
|
|
|
|
|
|
{/* 오른쪽: 속성 패널 (v4만) */}
|
|
|
|
|
{layoutMode === "v4" && (
|
|
|
|
|
<>
|
|
|
|
|
<ResizableHandle withHandle />
|
|
|
|
|
<ResizablePanel defaultSize={20} minSize={15} maxSize={30}>
|
|
|
|
|
<ComponentEditorPanelV4
|
|
|
|
|
component={selectedComponentV4}
|
|
|
|
|
container={selectedContainer}
|
2026-02-04 18:23:59 +09:00
|
|
|
currentViewportMode={currentViewportMode}
|
2026-02-04 14:14:48 +09:00
|
|
|
onUpdateComponent={
|
|
|
|
|
selectedComponentId
|
|
|
|
|
? (updates) => handleUpdateComponentV4(selectedComponentId, updates)
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
onUpdateContainer={
|
|
|
|
|
selectedContainerId
|
|
|
|
|
? (updates) => handleUpdateContainerV4(selectedContainerId, updates)
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</ResizablePanel>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2026-02-02 15:15:01 +09:00
|
|
|
</ResizablePanelGroup>
|
|
|
|
|
</div>
|
|
|
|
|
</DndProvider>
|
|
|
|
|
);
|
|
|
|
|
}
|