"use client"; import { useUpdateEffect } from "react-use"; import { useMemo, useState } from "react"; import classNames from "classnames"; import { toast } from "sonner"; import { useThrottleFn } from "react-use"; import { cn } from "@/lib/utils"; import { GridPattern } from "@/components/magic-ui/grid-pattern"; import { htmlTagToText } from "@/lib/html-tag-to-text"; import { Page } from "@/types"; export const Preview = ({ html, isResizing, isAiWorking, ref, device, currentTab, iframeRef, pages, setCurrentPage, isEditableModeEnabled, onClickElement, }: { html: string; isResizing: boolean; isAiWorking: boolean; pages: Page[]; setCurrentPage: React.Dispatch>; ref: React.RefObject; iframeRef?: React.RefObject; device: "desktop" | "mobile"; currentTab: string; isEditableModeEnabled?: boolean; onClickElement?: (element: HTMLElement) => void; }) => { const [hoveredElement, setHoveredElement] = useState( null ); const handleMouseOver = (event: MouseEvent) => { if (iframeRef?.current) { const iframeDocument = iframeRef.current.contentDocument; if (iframeDocument) { const targetElement = event.target as HTMLElement; if ( hoveredElement !== targetElement && targetElement !== iframeDocument.body ) { setHoveredElement(targetElement); targetElement.classList.add("hovered-element"); } else { return setHoveredElement(null); } } } }; const handleMouseOut = () => { setHoveredElement(null); }; const handleClick = (event: MouseEvent) => { if (iframeRef?.current) { const iframeDocument = iframeRef.current.contentDocument; if (iframeDocument) { const targetElement = event.target as HTMLElement; if (targetElement !== iframeDocument.body) { onClickElement?.(targetElement); } } } }; const handleCustomNavigation = (event: MouseEvent) => { if (iframeRef?.current) { const iframeDocument = iframeRef.current.contentDocument; if (iframeDocument) { const findClosestAnchor = ( element: HTMLElement ): HTMLAnchorElement | null => { let current = element; while (current && current !== iframeDocument.body) { if (current.tagName === "A") { return current as HTMLAnchorElement; } current = current.parentElement as HTMLElement; } return null; }; const anchorElement = findClosestAnchor(event.target as HTMLElement); if (anchorElement) { let href = anchorElement.getAttribute("href"); if (href) { event.stopPropagation(); event.preventDefault(); if (href.includes("#") && !href.includes(".html")) { const targetElement = iframeDocument.querySelector(href); if (targetElement) { targetElement.scrollIntoView({ behavior: "smooth" }); } return; } href = href.split(".html")[0] + ".html"; const isPageExist = pages.some((page) => page.path === href); if (isPageExist) { setCurrentPage(href); } } } } } }; useUpdateEffect(() => { const cleanupListeners = () => { if (iframeRef?.current?.contentDocument) { const iframeDocument = iframeRef.current.contentDocument; iframeDocument.removeEventListener("mouseover", handleMouseOver); iframeDocument.removeEventListener("mouseout", handleMouseOut); iframeDocument.removeEventListener("click", handleClick); } }; if (iframeRef?.current) { const iframeDocument = iframeRef.current.contentDocument; if (iframeDocument) { cleanupListeners(); if (isEditableModeEnabled) { iframeDocument.addEventListener("mouseover", handleMouseOver); iframeDocument.addEventListener("mouseout", handleMouseOut); iframeDocument.addEventListener("click", handleClick); } } } return cleanupListeners; }, [iframeRef, isEditableModeEnabled]); const selectedElement = useMemo(() => { if (!isEditableModeEnabled) return null; if (!hoveredElement) return null; return hoveredElement; }, [hoveredElement, isEditableModeEnabled]); const throttledHtml = useThrottleFn((html) => html, 1000, [html]); return (
{ if (isAiWorking) { e.preventDefault(); e.stopPropagation(); toast.warning("Please wait for the AI to finish working."); } }} > {!isAiWorking && hoveredElement && selectedElement && (
{htmlTagToText(selectedElement.tagName.toLowerCase())}
)}