| import { useRef, useState } from "react"; |
| import Editor from "@monaco-editor/react"; |
| import classNames from "classnames"; |
| 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"; |
| import RedirectModal from "./redirect-modal/redirect-modal"; |
|
|
| function App() { |
| const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content"); |
|
|
| const preview = useRef<HTMLDivElement>(null); |
| const editor = useRef<HTMLDivElement>(null); |
| const resizer = useRef<HTMLDivElement>(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 [showRedirectModal, setShowRedirectModal] = useState(true); |
|
|
| 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"> |
| {showRedirectModal && ( |
| <RedirectModal onDismiss={() => setShowRedirectModal(false)} /> |
| )} |
| <Header |
| onReset={() => { |
| if (isAiWorking) { |
| toast.warn("Please wait for the AI to finish working."); |
| return; |
| } |
| if ( |
| window.confirm("You're about to reset the editor. Are you sure?") |
| ) { |
| setHtml(defaultHTML); |
| setError(false); |
| removeHtmlStorage(); |
| // Removed editorRef scroll logic |
| } |
| }} |
| > |
| <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); |
| }} |
| // Removed onMount for editorRef |
| /> |
| <AskAI |
| html={html} |
| setHtml={setHtml} // Used for both full and diff updates now |
| // Removed editorRef prop |
| isAiWorking={isAiWorking} |
| setisAiWorking={setisAiWorking} |
| onScrollToBottom={() => { |
| // Consider if scrolling is still needed here, maybe based on html length change? |
| // For now, removing the direct editor scroll. |
| }} |
| /> |
| </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; |
|
|