"use client";
import { useRef, useState, useEffect, useCallback, useMemo } 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 { HistoryNotification } from "../history-notification";
import { api } from "@/lib/api";
import { toast } from "sonner";
import { RefreshCcw, TriangleAlert } from "lucide-react";
export const Preview = ({ isNew }: { isNew: boolean }) => {
const {
project,
device,
isLoadingProject,
currentTab,
currentCommit,
setCurrentCommit,
currentPageData,
pages,
setPages,
setCurrentPage,
previewPage,
setPreviewPage,
setLastSavedPages,
hasUnsavedChanges,
} = useEditor();
const {
isEditableModeEnabled,
setSelectedElement,
isAiWorking,
globalAiLoading,
} = useAi();
const iframeRef = useRef(null);
const [hoveredElement, setHoveredElement] = useState<{
tagName: string;
rect: { top: number; left: number; width: number; height: number };
} | null>(null);
const [isPromotingVersion, setIsPromotingVersion] = useState(false);
const [stableHtml, setStableHtml] = useState("");
const [throttledHtml, setThrottledHtml] = useState("");
const lastUpdateTimeRef = useRef(0);
const [iframeKey, setIframeKey] = useState(0);
useEffect(() => {
if (!previewPage && pages.length > 0) {
const indexPage = pages.find(
(p) => p.path === "index.html" || p.path === "index" || p.path === "/"
);
const firstHtmlPage = pages.find((p) => p.path.endsWith(".html"));
setPreviewPage(indexPage?.path || firstHtmlPage?.path || "index.html");
}
}, [pages, previewPage]);
const previewPageData = useMemo(() => {
const found = pages.find((p) => {
const normalizedPagePath = p.path.replace(/^\.?\//, "");
const normalizedPreviewPage = previewPage.replace(/^\.?\//, "");
return normalizedPagePath === normalizedPreviewPage;
});
return found || currentPageData;
}, [pages, previewPage, currentPageData]);
const injectAssetsIntoHtml = useCallback(
(html: string): string => {
if (!html) return html;
const cssFiles = pages.filter(
(p) => p.path.endsWith(".css") && p.path !== previewPageData?.path
);
const jsFiles = pages.filter(
(p) => p.path.endsWith(".js") && p.path !== previewPageData?.path
);
const jsonFiles = pages.filter(
(p) => p.path.endsWith(".json") && p.path !== previewPageData?.path
);
let modifiedHtml = html;
// Inject all CSS files
if (cssFiles.length > 0) {
const allCssContent = cssFiles
.map(
(file) =>
``
)
.join("\n");
if (modifiedHtml.includes("")) {
modifiedHtml = modifiedHtml.replace(
"",
`${allCssContent}\n`
);
} else if (modifiedHtml.includes("")) {
modifiedHtml = modifiedHtml.replace(
"",
`\n${allCssContent}`
);
} else {
modifiedHtml = allCssContent + "\n" + modifiedHtml;
}
cssFiles.forEach((file) => {
const escapedPath = file.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
modifiedHtml = modifiedHtml.replace(
new RegExp(
`]*href=["'][\\.\/]*${escapedPath}["'][^>]*>`,
"gi"
),
""
);
});
}
if (jsFiles.length > 0) {
const allJsContent = jsFiles
.map(
(file) =>
``
)
.join("\n");
if (modifiedHtml.includes("")) {
modifiedHtml = modifiedHtml.replace(
"",
`${allJsContent}\n`
);
} else if (modifiedHtml.includes("")) {
modifiedHtml = modifiedHtml + allJsContent;
} else {
modifiedHtml = modifiedHtml + "\n" + allJsContent;
}
jsFiles.forEach((file) => {
const escapedPath = file.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
modifiedHtml = modifiedHtml.replace(
new RegExp(
``
)
.join("\n");
if (modifiedHtml.includes("")) {
modifiedHtml = modifiedHtml.replace(
"
")) {
modifiedHtml = modifiedHtml + allJsonContent;
} else {
modifiedHtml = modifiedHtml + "\n" + allJsonContent;
}
jsonFiles.forEach((file) => {
const escapedPath = file.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
modifiedHtml = modifiedHtml.replace(
new RegExp(
`