|  | import { useRef, useState } from "react"; | 
					
						
						|  | import Editor from "@monaco-editor/react"; | 
					
						
						|  | import classNames from "classnames"; | 
					
						
						|  | import { editor } from "monaco-editor"; | 
					
						
						|  | import { | 
					
						
						|  | useMount, | 
					
						
						|  | useUnmount, | 
					
						
						|  | useEvent, | 
					
						
						|  | useLocalStorage, | 
					
						
						|  | } from "react-use"; | 
					
						
						|  | import { toast } from "react-toastify"; | 
					
						
						|  |  | 
					
						
						|  | import Header from "./header/header"; | 
					
						
						|  | import DeployButton from "./deploy-button/deploy-button"; | 
					
						
						|  | import { defaultHTML } from "../utils/consts"; | 
					
						
						|  | import Tabs from "./tabs/tabs"; | 
					
						
						|  | import AskAI from "./ask-ai/ask-ai"; | 
					
						
						|  | import { Auth } from "../utils/types"; | 
					
						
						|  | import Preview from "./preview/preview"; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | function App() { | 
					
						
						|  | const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content"); | 
					
						
						|  |  | 
					
						
						|  | const preview = useRef<HTMLDivElement>(null); | 
					
						
						|  | const editor = useRef<HTMLDivElement>(null); | 
					
						
						|  | const resizer = useRef<HTMLDivElement>(null); | 
					
						
						|  | const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null); | 
					
						
						|  |  | 
					
						
						|  | const [isResizing, setIsResizing] = useState(false); | 
					
						
						|  | const [error, setError] = useState(false); | 
					
						
						|  | const [html, setHtml] = useState((htmlStorage as string) ?? defaultHTML); | 
					
						
						|  | const [isAiWorking, setisAiWorking] = useState(false); | 
					
						
						|  | const [auth, setAuth] = useState<Auth | undefined>(undefined); | 
					
						
						|  |  | 
					
						
						|  | const fetchMe = async () => { | 
					
						
						|  | const res = await fetch("/api/@me"); | 
					
						
						|  | if (res.ok) { | 
					
						
						|  | const data = await res.json(); | 
					
						
						|  | setAuth(data); | 
					
						
						|  | } else { | 
					
						
						|  | setAuth(undefined); | 
					
						
						|  | } | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const resetLayout = () => { | 
					
						
						|  | if (!editor.current || !preview.current) return; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (window.innerWidth >= 1024) { | 
					
						
						|  |  | 
					
						
						|  | const resizerWidth = resizer.current?.offsetWidth ?? 8; | 
					
						
						|  | const availableWidth = window.innerWidth - resizerWidth; | 
					
						
						|  | const initialEditorWidth = availableWidth / 3; | 
					
						
						|  | const initialPreviewWidth = availableWidth - initialEditorWidth; | 
					
						
						|  | editor.current.style.width = `${initialEditorWidth}px`; | 
					
						
						|  | preview.current.style.width = `${initialPreviewWidth}px`; | 
					
						
						|  | } else { | 
					
						
						|  |  | 
					
						
						|  | editor.current.style.width = ''; | 
					
						
						|  | preview.current.style.width = ''; | 
					
						
						|  | } | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const handleResize = (e: MouseEvent) => { | 
					
						
						|  | if (!editor.current || !preview.current || !resizer.current) return; | 
					
						
						|  |  | 
					
						
						|  | const resizerWidth = resizer.current.offsetWidth; | 
					
						
						|  | const minWidth = 100; | 
					
						
						|  | const maxWidth = window.innerWidth - resizerWidth - minWidth; | 
					
						
						|  |  | 
					
						
						|  | const editorWidth = e.clientX; | 
					
						
						|  | const clampedEditorWidth = Math.max(minWidth, Math.min(editorWidth, maxWidth)); | 
					
						
						|  | const calculatedPreviewWidth = window.innerWidth - clampedEditorWidth - resizerWidth; | 
					
						
						|  |  | 
					
						
						|  | editor.current.style.width = `${clampedEditorWidth}px`; | 
					
						
						|  | preview.current.style.width = `${calculatedPreviewWidth}px`; | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  | const handleMouseDown = () => { | 
					
						
						|  | setIsResizing(true); | 
					
						
						|  | document.addEventListener("mousemove", handleResize); | 
					
						
						|  | document.addEventListener("mouseup", handleMouseUp); | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  | const handleMouseUp = () => { | 
					
						
						|  | setIsResizing(false); | 
					
						
						|  | document.removeEventListener("mousemove", handleResize); | 
					
						
						|  | document.removeEventListener("mouseup", handleMouseUp); | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | useEvent("beforeunload", (e) => { | 
					
						
						|  | if (isAiWorking || html !== defaultHTML) { | 
					
						
						|  | e.preventDefault(); | 
					
						
						|  | return ""; | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | useMount(() => { | 
					
						
						|  |  | 
					
						
						|  | fetchMe(); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (htmlStorage) { | 
					
						
						|  | removeHtmlStorage(); | 
					
						
						|  | toast.warn("Previous HTML content restored from local storage."); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | resetLayout(); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!resizer.current) return; | 
					
						
						|  | resizer.current.addEventListener("mousedown", handleMouseDown); | 
					
						
						|  | window.addEventListener("resize", resetLayout); | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | useUnmount(() => { | 
					
						
						|  | document.removeEventListener("mousemove", handleResize); | 
					
						
						|  | document.removeEventListener("mouseup", handleMouseUp); | 
					
						
						|  | if (resizer.current) { | 
					
						
						|  | resizer.current.removeEventListener("mousedown", handleMouseDown); | 
					
						
						|  | } | 
					
						
						|  | window.removeEventListener("resize", resetLayout); | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | return ( | 
					
						
						|  | <div className="h-screen bg-gray-950 font-sans overflow-hidden"> | 
					
						
						|  | <Header> | 
					
						
						|  | <DeployButton html={html} error={error} auth={auth} /> | 
					
						
						|  | </Header> | 
					
						
						|  | <main className="max-lg:flex-col flex w-full"> | 
					
						
						|  | <div | 
					
						
						|  | ref={editor} | 
					
						
						|  | className="w-full h-[30dvh] lg:h-full relative" | 
					
						
						|  | onClick={(e) => { | 
					
						
						|  | if (isAiWorking) { | 
					
						
						|  | e.preventDefault(); | 
					
						
						|  | e.stopPropagation(); | 
					
						
						|  | toast.warn("Please wait for the AI to finish working."); | 
					
						
						|  | } | 
					
						
						|  | }} | 
					
						
						|  | > | 
					
						
						|  | <Tabs /> | 
					
						
						|  | <Editor | 
					
						
						|  | language="html" | 
					
						
						|  | theme="vs-dark" | 
					
						
						|  | className={classNames( | 
					
						
						|  | "h-[calc(30dvh-41px)] lg:h-[calc(100dvh-96px)]", | 
					
						
						|  | { | 
					
						
						|  | "pointer-events-none": isAiWorking, | 
					
						
						|  | } | 
					
						
						|  | )} | 
					
						
						|  | value={html} | 
					
						
						|  | onValidate={(markers) => { | 
					
						
						|  | if (markers?.length > 0) { | 
					
						
						|  | setError(true); | 
					
						
						|  | } | 
					
						
						|  | }} | 
					
						
						|  | onChange={(value) => { | 
					
						
						|  | const newValue = value ?? ""; | 
					
						
						|  | setHtml(newValue); | 
					
						
						|  | setError(false); | 
					
						
						|  | }} | 
					
						
						|  | onMount={(editor) => (editorRef.current = editor)} | 
					
						
						|  | /> | 
					
						
						|  | <AskAI | 
					
						
						|  | html={html} | 
					
						
						|  | setHtml={setHtml} | 
					
						
						|  | isAiWorking={isAiWorking} | 
					
						
						|  | setisAiWorking={setisAiWorking} | 
					
						
						|  | onScrollToBottom={() => { | 
					
						
						|  | editorRef.current?.revealLine( | 
					
						
						|  | editorRef.current?.getModel()?.getLineCount() ?? 0 | 
					
						
						|  | ); | 
					
						
						|  | }} | 
					
						
						|  | /> | 
					
						
						|  | </div> | 
					
						
						|  | <div | 
					
						
						|  | ref={resizer} | 
					
						
						|  | className="bg-gray-700 hover:bg-blue-500 w-2 cursor-col-resize h-[calc(100dvh-54px)] max-lg:hidden" | 
					
						
						|  | /> | 
					
						
						|  | <Preview | 
					
						
						|  | html={html} | 
					
						
						|  | isResizing={isResizing} | 
					
						
						|  | isAiWorking={isAiWorking} | 
					
						
						|  | ref={preview} | 
					
						
						|  | /> | 
					
						
						|  | </main> | 
					
						
						|  | </div> | 
					
						
						|  | ); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | export default App; | 
					
						
						|  |  |