Spaces:
Running
Running
Merge pull request #2 from novitalabs/refine-render
Browse files- src/components/preview.tsx +30 -19
- src/lib/constants.ts +0 -22
src/components/preview.tsx
CHANGED
@@ -1,10 +1,11 @@
|
|
1 |
"use client"
|
2 |
|
3 |
import { useState, forwardRef, useImperativeHandle, useEffect, useRef } from "react"
|
4 |
-
import { DEFAULT_HTML
|
5 |
import { PreviewRef, Version } from "@/lib/types"
|
6 |
import { MinimizeIcon, MaximizeIcon, DownloadIcon } from "./ui/fullscreen-icons"
|
7 |
import { useModel } from "@/lib/contexts/model-context"
|
|
|
8 |
import { cn } from "@/lib/utils"
|
9 |
|
10 |
interface PreviewProps {
|
@@ -27,6 +28,8 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
|
|
27 |
const [error, setError] = useState<string | null>(null);
|
28 |
const [showAuthError, setShowAuthError] = useState(false);
|
29 |
const { selectedModelId } = useModel();
|
|
|
|
|
30 |
|
31 |
// Update html when initialHtml changes
|
32 |
useEffect(() => {
|
@@ -55,26 +58,22 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
|
|
55 |
const partialUpdate = (htmlStr: string) => {
|
56 |
const parser = new DOMParser();
|
57 |
const partialDoc = parser.parseFromString(htmlStr, 'text/html');
|
58 |
-
|
59 |
const iframe = document.querySelector('iframe');
|
60 |
if (!iframe || !iframe.contentDocument) return;
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
const initDoc = parser.parseFromString(PARTIAL_GENERATING_HTML_TEMPLATE, 'text/html');
|
66 |
-
if (iframe.contentDocument.documentElement) {
|
67 |
-
iframe.contentDocument.documentElement.innerHTML = initDoc.documentElement.innerHTML;
|
68 |
-
}
|
69 |
-
setHtml(PARTIAL_GENERATING_HTML_TEMPLATE);
|
70 |
-
return;
|
71 |
}
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
|
|
|
|
77 |
}
|
|
|
78 |
}
|
79 |
|
80 |
const downloadHtml = () => {
|
@@ -106,6 +105,8 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
|
|
106 |
|
107 |
const generateCode = async (prompt: string, colors: string[] = [], previousPrompt?: string) => {
|
108 |
setLoading(true);
|
|
|
|
|
109 |
if (onLoadingChange) {
|
110 |
onLoadingChange(true);
|
111 |
}
|
@@ -191,8 +192,8 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
|
|
191 |
}, 50);
|
192 |
}
|
193 |
}
|
194 |
-
break;
|
195 |
setIsPartialGenerating(false);
|
|
|
196 |
} else {
|
197 |
setIsPartialGenerating(true);
|
198 |
}
|
@@ -252,7 +253,17 @@ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
|
|
252 |
|
253 |
return (
|
254 |
<div className={`${isFullscreen ? 'fixed inset-0 z-10 bg-novita-dark' : 'h-full'} p-4`}>
|
255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
<div className="absolute top-3 right-3 flex gap-2 z-[100]">
|
257 |
<button
|
258 |
onClick={downloadHtml}
|
|
|
1 |
"use client"
|
2 |
|
3 |
import { useState, forwardRef, useImperativeHandle, useEffect, useRef } from "react"
|
4 |
+
import { DEFAULT_HTML } from "@/lib/constants"
|
5 |
import { PreviewRef, Version } from "@/lib/types"
|
6 |
import { MinimizeIcon, MaximizeIcon, DownloadIcon } from "./ui/fullscreen-icons"
|
7 |
import { useModel } from "@/lib/contexts/model-context"
|
8 |
+
import { Loader2 } from "lucide-react"
|
9 |
import { cn } from "@/lib/utils"
|
10 |
|
11 |
interface PreviewProps {
|
|
|
28 |
const [error, setError] = useState<string | null>(null);
|
29 |
const [showAuthError, setShowAuthError] = useState(false);
|
30 |
const { selectedModelId } = useModel();
|
31 |
+
const renderCount = useRef(0);
|
32 |
+
const headUpdated = useRef(false);
|
33 |
|
34 |
// Update html when initialHtml changes
|
35 |
useEffect(() => {
|
|
|
58 |
const partialUpdate = (htmlStr: string) => {
|
59 |
const parser = new DOMParser();
|
60 |
const partialDoc = parser.parseFromString(htmlStr, 'text/html');
|
|
|
61 |
const iframe = document.querySelector('iframe');
|
62 |
if (!iframe || !iframe.contentDocument) return;
|
63 |
+
|
64 |
+
const iframeContainer = iframe.contentDocument;
|
65 |
+
if (iframeContainer?.body && iframeContainer) {
|
66 |
+
iframeContainer.body.innerHTML = partialDoc.body?.innerHTML;
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
}
|
68 |
+
if (renderCount.current % 10 === 0 && !headUpdated.current) {
|
69 |
+
setHtml(htmlStr);
|
70 |
+
if (htmlStr.includes('</head>')) {
|
71 |
+
setTimeout(() => {
|
72 |
+
headUpdated.current = true;
|
73 |
+
}, 1000);
|
74 |
+
}
|
75 |
}
|
76 |
+
renderCount.current++;
|
77 |
}
|
78 |
|
79 |
const downloadHtml = () => {
|
|
|
105 |
|
106 |
const generateCode = async (prompt: string, colors: string[] = [], previousPrompt?: string) => {
|
107 |
setLoading(true);
|
108 |
+
renderCount.current = 0;
|
109 |
+
headUpdated.current = false;
|
110 |
if (onLoadingChange) {
|
111 |
onLoadingChange(true);
|
112 |
}
|
|
|
192 |
}, 50);
|
193 |
}
|
194 |
}
|
|
|
195 |
setIsPartialGenerating(false);
|
196 |
+
break;
|
197 |
} else {
|
198 |
setIsPartialGenerating(true);
|
199 |
}
|
|
|
253 |
|
254 |
return (
|
255 |
<div className={`${isFullscreen ? 'fixed inset-0 z-10 bg-novita-dark' : 'h-full'} p-4`}>
|
256 |
+
{ isPartialGenerating && (
|
257 |
+
<div className="w-full bg-slate-50 border-b border-slate-200 py-2 px-4">
|
258 |
+
<div className="container mx-auto flex items-center justify-center">
|
259 |
+
<div className="flex items-center space-x-2 text-slate-700">
|
260 |
+
<Loader2 className="h-4 w-4 animate-spin" />
|
261 |
+
<span className="text-sm font-medium">building...</span>
|
262 |
+
</div>
|
263 |
+
</div>
|
264 |
+
</div>
|
265 |
+
)}
|
266 |
+
<div className="bg-white text-black h-full overflow-hidden relative isolation-auto">
|
267 |
<div className="absolute top-3 right-3 flex gap-2 z-[100]">
|
268 |
<button
|
269 |
onClick={downloadHtml}
|
src/lib/constants.ts
CHANGED
@@ -88,25 +88,3 @@ export const DEFAULT_HTML = `<!DOCTYPE html>
|
|
88 |
</div>
|
89 |
</body>
|
90 |
</html>`;
|
91 |
-
|
92 |
-
export const PARTIAL_GENERATING_HTML_TEMPLATE = `
|
93 |
-
<!DOCTYPE html>
|
94 |
-
<html lang="en">
|
95 |
-
<head>
|
96 |
-
<meta charset="UTF-8">
|
97 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
98 |
-
<title>Novita AnySite</title>
|
99 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
100 |
-
</head>
|
101 |
-
<body>
|
102 |
-
<div id="container" class="py-10">
|
103 |
-
</div>
|
104 |
-
<div class="fixed inset-0 bg-gray-300 bg-opacity-25 flex items-center justify-center z-50">
|
105 |
-
<div class="flex flex-col items-center">
|
106 |
-
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-600"></div>
|
107 |
-
<p class="mt-3 text-gray-600 text-sm">Loading...</p>
|
108 |
-
</div>
|
109 |
-
</div>
|
110 |
-
</body>
|
111 |
-
</html>
|
112 |
-
`;
|
|
|
88 |
</div>
|
89 |
</body>
|
90 |
</html>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|