200 lines
6.9 KiB
TypeScript
200 lines
6.9 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { useState, useEffect } from "react";
|
||
|
|
import { cn } from "@/lib/utils";
|
||
|
|
import { Smartphone, Tablet, Loader2, ExternalLink, RefreshCw } from "lucide-react";
|
||
|
|
import { Button } from "@/components/ui/button";
|
||
|
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||
|
|
import { ScreenDefinition } from "@/types/screen";
|
||
|
|
import { screenApi } from "@/lib/api/screen";
|
||
|
|
|
||
|
|
// ============================================================
|
||
|
|
// 타입 정의
|
||
|
|
// ============================================================
|
||
|
|
|
||
|
|
type DeviceType = "mobile" | "tablet";
|
||
|
|
|
||
|
|
interface PopScreenPreviewProps {
|
||
|
|
screen: ScreenDefinition | null;
|
||
|
|
className?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 디바이스 프레임 크기
|
||
|
|
const DEVICE_SIZES = {
|
||
|
|
mobile: { width: 375, height: 667 }, // iPhone SE 기준
|
||
|
|
tablet: { width: 768, height: 1024 }, // iPad 기준
|
||
|
|
};
|
||
|
|
|
||
|
|
// ============================================================
|
||
|
|
// 메인 컴포넌트
|
||
|
|
// ============================================================
|
||
|
|
|
||
|
|
export function PopScreenPreview({ screen, className }: PopScreenPreviewProps) {
|
||
|
|
const [deviceType, setDeviceType] = useState<DeviceType>("tablet");
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
const [hasLayout, setHasLayout] = useState(false);
|
||
|
|
const [key, setKey] = useState(0); // iframe 새로고침용
|
||
|
|
|
||
|
|
// 레이아웃 존재 여부 확인
|
||
|
|
useEffect(() => {
|
||
|
|
if (!screen) {
|
||
|
|
setHasLayout(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const checkLayout = async () => {
|
||
|
|
try {
|
||
|
|
setLoading(true);
|
||
|
|
const layout = await screenApi.getLayoutPop(screen.screenId);
|
||
|
|
setHasLayout(layout && layout.sections && layout.sections.length > 0);
|
||
|
|
} catch {
|
||
|
|
setHasLayout(false);
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
checkLayout();
|
||
|
|
}, [screen]);
|
||
|
|
|
||
|
|
// 미리보기 URL
|
||
|
|
const previewUrl = screen ? `/pop/screens/${screen.screenId}?preview=true&device=${deviceType}` : null;
|
||
|
|
|
||
|
|
// 새 탭에서 열기
|
||
|
|
const openInNewTab = () => {
|
||
|
|
if (previewUrl) {
|
||
|
|
const size = DEVICE_SIZES[deviceType];
|
||
|
|
window.open(previewUrl, "_blank", `width=${size.width + 40},height=${size.height + 80}`);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// iframe 새로고침
|
||
|
|
const refreshPreview = () => {
|
||
|
|
setKey((prev) => prev + 1);
|
||
|
|
};
|
||
|
|
|
||
|
|
const deviceSize = DEVICE_SIZES[deviceType];
|
||
|
|
// 미리보기 컨테이너에 맞게 스케일 조정
|
||
|
|
const scale = deviceType === "tablet" ? 0.5 : 0.6;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={cn("flex flex-col h-full bg-muted/30", className)}>
|
||
|
|
{/* 헤더 */}
|
||
|
|
<div className="shrink-0 p-3 border-b bg-background flex items-center justify-between">
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<h3 className="text-sm font-medium">미리보기</h3>
|
||
|
|
{screen && (
|
||
|
|
<span className="text-xs text-muted-foreground truncate max-w-[150px]">
|
||
|
|
{screen.screenName}
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
{/* 디바이스 선택 */}
|
||
|
|
<Tabs value={deviceType} onValueChange={(v) => setDeviceType(v as DeviceType)}>
|
||
|
|
<TabsList className="h-8">
|
||
|
|
<TabsTrigger value="mobile" className="h-7 px-2">
|
||
|
|
<Smartphone className="h-3.5 w-3.5" />
|
||
|
|
</TabsTrigger>
|
||
|
|
<TabsTrigger value="tablet" className="h-7 px-2">
|
||
|
|
<Tablet className="h-3.5 w-3.5" />
|
||
|
|
</TabsTrigger>
|
||
|
|
</TabsList>
|
||
|
|
</Tabs>
|
||
|
|
|
||
|
|
{screen && hasLayout && (
|
||
|
|
<>
|
||
|
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={refreshPreview}>
|
||
|
|
<RefreshCw className="h-3.5 w-3.5" />
|
||
|
|
</Button>
|
||
|
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={openInNewTab}>
|
||
|
|
<ExternalLink className="h-3.5 w-3.5" />
|
||
|
|
</Button>
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 미리보기 영역 */}
|
||
|
|
<div className="flex-1 flex items-center justify-center p-4 overflow-auto">
|
||
|
|
{!screen ? (
|
||
|
|
// 화면 미선택
|
||
|
|
<div className="text-center text-muted-foreground">
|
||
|
|
<div className="w-16 h-16 rounded-full bg-muted flex items-center justify-center mx-auto mb-3">
|
||
|
|
{deviceType === "mobile" ? (
|
||
|
|
<Smartphone className="h-8 w-8" />
|
||
|
|
) : (
|
||
|
|
<Tablet className="h-8 w-8" />
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<p className="text-sm">화면을 선택하면 미리보기가 표시됩니다.</p>
|
||
|
|
</div>
|
||
|
|
) : loading ? (
|
||
|
|
// 로딩 중
|
||
|
|
<div className="text-center text-muted-foreground">
|
||
|
|
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-3" />
|
||
|
|
<p className="text-sm">레이아웃 확인 중...</p>
|
||
|
|
</div>
|
||
|
|
) : !hasLayout ? (
|
||
|
|
// 레이아웃 없음
|
||
|
|
<div className="text-center text-muted-foreground">
|
||
|
|
<div className="w-16 h-16 rounded-full bg-muted flex items-center justify-center mx-auto mb-3">
|
||
|
|
{deviceType === "mobile" ? (
|
||
|
|
<Smartphone className="h-8 w-8" />
|
||
|
|
) : (
|
||
|
|
<Tablet className="h-8 w-8" />
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<p className="text-sm mb-2">POP 레이아웃이 없습니다.</p>
|
||
|
|
<p className="text-xs text-muted-foreground">
|
||
|
|
화면을 더블클릭하여 설계 모드로 이동하세요.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
// 디바이스 프레임 + iframe
|
||
|
|
<div
|
||
|
|
className="relative bg-gray-900 rounded-[2rem] p-2 shadow-xl"
|
||
|
|
style={{
|
||
|
|
width: deviceSize.width * scale + 16,
|
||
|
|
height: deviceSize.height * scale + 16,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{/* 디바이스 노치 (모바일) */}
|
||
|
|
{deviceType === "mobile" && (
|
||
|
|
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-24 h-6 bg-gray-900 rounded-b-xl z-10" />
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 디바이스 홈 버튼 (태블릿) */}
|
||
|
|
{deviceType === "tablet" && (
|
||
|
|
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 w-8 h-8 bg-gray-800 rounded-full" />
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* iframe 컨테이너 */}
|
||
|
|
<div
|
||
|
|
className="bg-white rounded-[1.5rem] overflow-hidden"
|
||
|
|
style={{
|
||
|
|
width: deviceSize.width * scale,
|
||
|
|
height: deviceSize.height * scale,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<iframe
|
||
|
|
key={key}
|
||
|
|
src={previewUrl || ""}
|
||
|
|
className="w-full h-full border-0"
|
||
|
|
style={{
|
||
|
|
width: deviceSize.width,
|
||
|
|
height: deviceSize.height,
|
||
|
|
transform: `scale(${scale})`,
|
||
|
|
transformOrigin: "top left",
|
||
|
|
}}
|
||
|
|
title="POP Screen Preview"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|