"use client"; import { useRef, useState, useEffect } from "react"; import { useUpdateEffect } from "react-use"; import classNames from "classnames"; import { cn } from "@/lib/utils"; import { GridPattern } from "@/components/magic-ui/grid-pattern"; import { useEditor } from "@/hooks/useEditor"; import { useAi } from "@/hooks/useAi"; import { htmlTagToText } from "@/lib/html-tag-to-text"; import { AnimatedBlobs } from "@/components/animated-blobs"; import { AiLoading } from "../ask-ai/loading"; import { defaultHTML } from "@/lib/consts"; import { Button } from "@/components/ui/button"; import { LivePreview } from "../live-preview"; import { MousePointerClick, History, AlertCircle, ChevronDown, ChevronUp, } from "lucide-react"; import { api } from "@/lib/api"; import { toast } from "sonner"; import Loading from "@/components/loading"; export const Preview = ({ isNew }: { isNew: boolean }) => { const { project, device, isLoadingProject, currentTab, currentCommit, setCurrentCommit, currentPageData, } = useEditor(); const { isEditableModeEnabled, setSelectedElement, isAiWorking, setIsEditableModeEnabled, } = useAi(); const iframeSrc = project?.space_id ? `/api/proxy/?spaceId=${encodeURIComponent(project.space_id)}${ currentCommit ? `&commitId=${currentCommit}` : "" }` : ""; const iframeRef = useRef(null); const [hoveredElement, setHoveredElement] = useState<{ tagName: string; rect: { top: number; left: number; width: number; height: number }; } | null>(null); const [isHistoryNotificationCollapsed, setIsHistoryNotificationCollapsed] = useState(false); const [isPromotingVersion, setIsPromotingVersion] = useState(false); // Handle PostMessage communication with iframe useEffect(() => { const handleMessage = (event: MessageEvent) => { // Verify origin for security if (!event.origin.includes(window.location.origin)) { return; } const { type, data } = event.data; switch (type) { case "PROXY_SCRIPT_READY": if (iframeRef.current?.contentWindow) { iframeRef.current.contentWindow.postMessage( { type: isEditableModeEnabled ? "ENABLE_EDIT_MODE" : "DISABLE_EDIT_MODE", }, "*" ); } break; case "ELEMENT_HOVERED": if (isEditableModeEnabled) { setHoveredElement(data); } break; case "ELEMENT_MOUSE_OUT": if (isEditableModeEnabled) { setHoveredElement(null); } break; case "ELEMENT_CLICKED": if (isEditableModeEnabled) { const mockElement = { tagName: data.tagName, getBoundingClientRect: () => data.rect, outerHTML: data.element, }; setSelectedElement(mockElement as any); setIsEditableModeEnabled(false); } break; case "NAVIGATE_TO_PROXY": // Handle navigation within the iframe while maintaining proxy context if (iframeRef.current && data.proxyUrl) { iframeRef.current.src = data.proxyUrl; } break; } }; window.addEventListener("message", handleMessage); return () => window.removeEventListener("message", handleMessage); }, [setSelectedElement, isEditableModeEnabled]); // Send edit mode state to iframe and clear hover state when disabled useUpdateEffect(() => { if (iframeRef.current?.contentWindow) { iframeRef.current.contentWindow.postMessage( { type: isEditableModeEnabled ? "ENABLE_EDIT_MODE" : "DISABLE_EDIT_MODE", }, "*" ); } // Clear hover state when edit mode is disabled if (!isEditableModeEnabled) { setHoveredElement(null); } }, [isEditableModeEnabled, project?.space_id]); const promoteVersion = async () => { setIsPromotingVersion(true); await api .post( `/me/projects/${project?.space_id}/commits/${currentCommit}/promote` ) .then((res) => { if (res.data.ok) { setCurrentCommit(null); toast.success("Version promoted successfully"); } }) .catch((err) => { toast.error(err.response.data.error); }); setIsPromotingVersion(false); }; return (
{!isAiWorking && hoveredElement && isEditableModeEnabled && (
{htmlTagToText(hoveredElement.tagName.toLowerCase())}
)} {isNew && !isAiWorking ? (