CoolFace
Apppublic

eddyencode/deepsitev3

sourceHugging Facemitupdated 1y agoView on Hugging Face
1likes
App.tsx264 linesDownload Raw Back to components
1import { useRef, useState } from "react";2import Editor from "@monaco-editor/react";3import classNames from "classnames";4import { editor } from "monaco-editor";5import {6  useMount,7  useUnmount,8  useEvent,9  useLocalStorage,10  useSearchParam,11} from "react-use";12import { toast } from "react-toastify";13 14import Header from "./header/header";15import DeployButton from "./deploy-button/deploy-button";16import { defaultHTML } from "./../../utils/consts";17import Tabs from "./tabs/tabs";18import AskAI from "./ask-ai/ask-ai";19import { Auth } from "./../../utils/types";20import Preview from "./preview/preview";21import LoadButton from "./load-button/load-button";22 23function App() {24  const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");25  const remix = useSearchParam("remix");26 27  const preview = useRef<HTMLDivElement>(null);28  const editor = useRef<HTMLDivElement>(null);29  const resizer = useRef<HTMLDivElement>(null);30  const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);31 32  const [isResizing, setIsResizing] = useState(false);33  const [error, setError] = useState(false);34  const [html, setHtml] = useState((htmlStorage as string) ?? defaultHTML);35  const [isAiWorking, setisAiWorking] = useState(false);36  const [auth, setAuth] = useState<Auth | undefined>(undefined);37  const [currentView, setCurrentView] = useState<"editor" | "preview">(38    "editor"39  );40 41  const fetchMe = async () => {42    const res = await fetch("/api/@me");43    if (res.ok) {44      const data = await res.json();45      setAuth(data);46    } else {47      setAuth(undefined);48    }49  };50 51  const fetchRemix = async () => {52    if (!remix) return;53    const res = await fetch(`/api/remix/${remix}`);54    if (res.ok) {55      const data = await res.json();56      if (data.html) {57        setHtml(data.html);58        toast.success("Remix content loaded successfully.");59      }60    } else {61      toast.error("Failed to load remix content.");62    }63    const url = new URL(window.location.href);64    url.searchParams.delete("remix");65    window.history.replaceState({}, document.title, url.toString());66  };67 68  /**69   * Resets the layout based on screen size70   * - For desktop: Sets editor to 1/3 width and preview to 2/371   * - For mobile: Removes inline styles to let CSS handle it72   */73  const resetLayout = () => {74    if (!editor.current || !preview.current) return;75 76    // lg breakpoint is 1024px based on useBreakpoint definition and Tailwind defaults77    if (window.innerWidth >= 1024) {78      // Set initial 1/3 - 2/3 sizes for large screens, accounting for resizer width79      const resizerWidth = resizer.current?.offsetWidth ?? 8; // w-2 = 0.5rem = 8px80      const availableWidth = window.innerWidth - resizerWidth;81      const initialEditorWidth = availableWidth / 3; // Editor takes 1/3 of space82      const initialPreviewWidth = availableWidth - initialEditorWidth; // Preview takes 2/383      editor.current.style.width = `${initialEditorWidth}px`;84      preview.current.style.width = `${initialPreviewWidth}px`;85    } else {86      // Remove inline styles for smaller screens, let CSS flex-col handle it87      editor.current.style.width = "";88      preview.current.style.width = "";89    }90  };91 92  /**93   * Handles resizing when the user drags the resizer94   * Ensures minimum widths are maintained for both panels95   */96  const handleResize = (e: MouseEvent) => {97    if (!editor.current || !preview.current || !resizer.current) return;98 99    const resizerWidth = resizer.current.offsetWidth;100    const minWidth = 100; // Minimum width for editor/preview101    const maxWidth = window.innerWidth - resizerWidth - minWidth;102 103    const editorWidth = e.clientX;104    const clampedEditorWidth = Math.max(105      minWidth,106      Math.min(editorWidth, maxWidth)107    );108    const calculatedPreviewWidth =109      window.innerWidth - clampedEditorWidth - resizerWidth;110 111    editor.current.style.width = `${clampedEditorWidth}px`;112    preview.current.style.width = `${calculatedPreviewWidth}px`;113  };114 115  const handleMouseDown = () => {116    setIsResizing(true);117    document.addEventListener("mousemove", handleResize);118    document.addEventListener("mouseup", handleMouseUp);119  };120 121  const handleMouseUp = () => {122    setIsResizing(false);123    document.removeEventListener("mousemove", handleResize);124    document.removeEventListener("mouseup", handleMouseUp);125  };126 127  // Prevent accidental navigation away when AI is working or content has changed128  useEvent("beforeunload", (e) => {129    if (isAiWorking || html !== defaultHTML) {130      e.preventDefault();131      return "";132    }133  });134 135  // Initialize component on mount136  useMount(() => {137    // Fetch user data138    fetchMe();139    fetchRemix();140 141    // Restore content from storage if available142    if (htmlStorage) {143      removeHtmlStorage();144      toast.warn("Previous HTML content restored from local storage.");145    }146 147    // Set initial layout based on window size148    resetLayout();149 150    // Attach event listeners151    if (!resizer.current) return;152    resizer.current.addEventListener("mousedown", handleMouseDown);153    window.addEventListener("resize", resetLayout);154  });155 156  // Clean up event listeners on unmount157  useUnmount(() => {158    document.removeEventListener("mousemove", handleResize);159    document.removeEventListener("mouseup", handleMouseUp);160    if (resizer.current) {161      resizer.current.removeEventListener("mousedown", handleMouseDown);162    }163    window.removeEventListener("resize", resetLayout);164  });165 166  return (167    <div className="h-screen bg-gray-950 font-sans overflow-hidden">168      <Header169        onReset={() => {170          if (isAiWorking) {171            toast.warn("Please wait for the AI to finish working.");172            return;173          }174          if (175            window.confirm("You're about to reset the editor. Are you sure?")176          ) {177            setHtml(defaultHTML);178            setError(false);179            removeHtmlStorage();180            editorRef.current?.revealLine(181              editorRef.current?.getModel()?.getLineCount() ?? 0182            );183          }184        }}185      >186        <div className="flex items-center justify-end gap-5">187          <LoadButton auth={auth} setHtml={setHtml} />188          <DeployButton html={html} error={error} auth={auth} />189        </div>190      </Header>191      <main className="max-lg:flex-col flex w-full">192        <div193          ref={editor}194          className={classNames(195            "w-full h-[calc(100dvh-49px)] lg:h-[calc(100dvh-54px)] relative overflow-hidden max-lg:transition-all max-lg:duration-200 select-none",196            {197              "max-lg:h-0": currentView === "preview",198            }199          )}200        >201          <Tabs />202          <div203            onClick={(e) => {204              if (isAiWorking) {205                e.preventDefault();206                e.stopPropagation();207                toast.warn("Please wait for the AI to finish working.");208              }209            }}210          >211            <Editor212              language="html"213              theme="vs-dark"214              className={classNames(215                "h-[calc(100dvh-90px)] lg:h-[calc(100dvh-96px)]",216                {217                  "pointer-events-none": isAiWorking,218                }219              )}220              value={html}221              onValidate={(markers) => {222                if (markers?.length > 0) {223                  setError(true);224                }225              }}226              onChange={(value) => {227                const newValue = value ?? "";228                setHtml(newValue);229                setError(false);230              }}231              onMount={(editor) => (editorRef.current = editor)}232            />233          </div>234          <AskAI235            html={html}236            setHtml={setHtml}237            isAiWorking={isAiWorking}238            setisAiWorking={setisAiWorking}239            setView={setCurrentView}240            onScrollToBottom={() => {241              editorRef.current?.revealLine(242                editorRef.current?.getModel()?.getLineCount() ?? 0243              );244            }}245          />246        </div>247        <div248          ref={resizer}249          className="bg-gray-700 hover:bg-blue-500 w-2 cursor-col-resize h-[calc(100dvh-53px)] max-lg:hidden"250        />251        <Preview252          html={html}253          isResizing={isResizing}254          isAiWorking={isAiWorking}255          ref={preview}256          setView={setCurrentView}257        />258      </main>259    </div>260  );261}262 263export default App;264