Spaces:
Build error
Build error
resize (#1)
Browse files- feat: Add resize functionality and logging for breakpoint (2c6c947770edd4b2f62b971f917d4b49a9b14464)
- toto (974e9f3b3d2fdde002506d66b3a43051b638f9cd)
- aider (dd0993f748a36b51efee9048a24a2d13d14c34cd)
Co-authored-by: Victor Mustar <[email protected]>
- .gitignore +2 -1
- server.js +0 -2
- src/components/App.tsx +50 -22
- src/components/login/login.tsx +1 -3
.gitignore
CHANGED
|
@@ -22,4 +22,5 @@ dist-ssr
|
|
| 22 |
*.njsproj
|
| 23 |
*.sln
|
| 24 |
*.sw?
|
| 25 |
-
.env
|
|
|
|
|
|
| 22 |
*.njsproj
|
| 23 |
*.sln
|
| 24 |
*.sw?
|
| 25 |
+
.env
|
| 26 |
+
.aider*
|
server.js
CHANGED
|
@@ -218,10 +218,8 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
| 218 |
// Stream chunk to client
|
| 219 |
let newChunk = chunk;
|
| 220 |
if (chunk.includes("</html>")) {
|
| 221 |
-
console.log("Chunk before replacement:", newChunk);
|
| 222 |
// Replace everything after the last </html> tag with an empty string
|
| 223 |
newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
|
| 224 |
-
console.log("Chunk after replacement:", newChunk);
|
| 225 |
}
|
| 226 |
completeResponse += newChunk;
|
| 227 |
res.write(newChunk);
|
|
|
|
| 218 |
// Stream chunk to client
|
| 219 |
let newChunk = chunk;
|
| 220 |
if (chunk.includes("</html>")) {
|
|
|
|
| 221 |
// Replace everything after the last </html> tag with an empty string
|
| 222 |
newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
|
|
|
|
| 223 |
}
|
| 224 |
completeResponse += newChunk;
|
| 225 |
res.write(newChunk);
|
src/components/App.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import { useRef, useState } from "react";
|
| 3 |
import Editor from "@monaco-editor/react";
|
| 4 |
import classNames from "classnames";
|
|
@@ -7,7 +6,6 @@ import {
|
|
| 7 |
useMount,
|
| 8 |
useUnmount,
|
| 9 |
useEvent,
|
| 10 |
-
createBreakpoint,
|
| 11 |
useLocalStorage,
|
| 12 |
} from "react-use";
|
| 13 |
import { toast } from "react-toastify";
|
|
@@ -20,15 +18,9 @@ import AskAI from "./ask-ai/ask-ai";
|
|
| 20 |
import { Auth } from "../utils/types";
|
| 21 |
import Preview from "./preview/preview";
|
| 22 |
|
| 23 |
-
const useBreakpoint = createBreakpoint({
|
| 24 |
-
md: 768,
|
| 25 |
-
lg: 1024,
|
| 26 |
-
});
|
| 27 |
|
| 28 |
function App() {
|
| 29 |
-
const
|
| 30 |
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
| 31 |
-
const [htmlStorage, _, removeHtmlStorage] = useLocalStorage("html_content");
|
| 32 |
|
| 33 |
const preview = useRef<HTMLDivElement>(null);
|
| 34 |
const editor = useRef<HTMLDivElement>(null);
|
|
@@ -51,12 +43,47 @@ function App() {
|
|
| 51 |
}
|
| 52 |
};
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
const handleResize = (e: MouseEvent) => {
|
| 55 |
if (!editor.current || !preview.current || !resizer.current) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
const editorWidth = e.clientX;
|
| 57 |
-
const
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
| 60 |
};
|
| 61 |
|
| 62 |
const handleMouseDown = () => {
|
|
@@ -71,6 +98,7 @@ function App() {
|
|
| 71 |
document.removeEventListener("mouseup", handleMouseUp);
|
| 72 |
};
|
| 73 |
|
|
|
|
| 74 |
useEvent("beforeunload", (e) => {
|
| 75 |
if (isAiWorking || html !== defaultHTML) {
|
| 76 |
e.preventDefault();
|
|
@@ -78,34 +106,34 @@ function App() {
|
|
| 78 |
}
|
| 79 |
});
|
| 80 |
|
|
|
|
| 81 |
useMount(() => {
|
|
|
|
| 82 |
fetchMe();
|
|
|
|
|
|
|
| 83 |
if (htmlStorage) {
|
| 84 |
removeHtmlStorage();
|
| 85 |
toast.warn("Previous HTML content restored from local storage.");
|
| 86 |
}
|
| 87 |
-
if (!editor.current || !preview.current) return;
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
const initialEditorWidth = window.innerWidth / 2;
|
| 92 |
-
const initialPreviewWidth = window.innerWidth - initialEditorWidth - 4;
|
| 93 |
-
editor.current.style.width = `${initialEditorWidth}px`;
|
| 94 |
-
preview.current.style.width = `${initialPreviewWidth}px`;
|
| 95 |
-
}
|
| 96 |
|
|
|
|
| 97 |
if (!resizer.current) return;
|
| 98 |
resizer.current.addEventListener("mousedown", handleMouseDown);
|
| 99 |
-
window.addEventListener("resize",
|
| 100 |
});
|
| 101 |
|
|
|
|
| 102 |
useUnmount(() => {
|
| 103 |
document.removeEventListener("mousemove", handleResize);
|
| 104 |
document.removeEventListener("mouseup", handleMouseUp);
|
| 105 |
if (resizer.current) {
|
| 106 |
resizer.current.removeEventListener("mousedown", handleMouseDown);
|
| 107 |
}
|
| 108 |
-
window.removeEventListener("resize",
|
| 109 |
});
|
| 110 |
|
| 111 |
return (
|
|
|
|
|
|
|
| 1 |
import { useRef, useState } from "react";
|
| 2 |
import Editor from "@monaco-editor/react";
|
| 3 |
import classNames from "classnames";
|
|
|
|
| 6 |
useMount,
|
| 7 |
useUnmount,
|
| 8 |
useEvent,
|
|
|
|
| 9 |
useLocalStorage,
|
| 10 |
} from "react-use";
|
| 11 |
import { toast } from "react-toastify";
|
|
|
|
| 18 |
import { Auth } from "../utils/types";
|
| 19 |
import Preview from "./preview/preview";
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
function App() {
|
| 23 |
+
const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
|
|
|
|
|
|
|
| 24 |
|
| 25 |
const preview = useRef<HTMLDivElement>(null);
|
| 26 |
const editor = useRef<HTMLDivElement>(null);
|
|
|
|
| 43 |
}
|
| 44 |
};
|
| 45 |
|
| 46 |
+
/**
|
| 47 |
+
* Resets the layout based on screen size
|
| 48 |
+
* - For desktop: Sets editor to 1/3 width and preview to 2/3
|
| 49 |
+
* - For mobile: Removes inline styles to let CSS handle it
|
| 50 |
+
*/
|
| 51 |
+
const resetLayout = () => {
|
| 52 |
+
if (!editor.current || !preview.current) return;
|
| 53 |
+
|
| 54 |
+
// lg breakpoint is 1024px based on useBreakpoint definition and Tailwind defaults
|
| 55 |
+
if (window.innerWidth >= 1024) {
|
| 56 |
+
// Set initial 1/3 - 2/3 sizes for large screens, accounting for resizer width
|
| 57 |
+
const resizerWidth = resizer.current?.offsetWidth ?? 8; // w-2 = 0.5rem = 8px
|
| 58 |
+
const availableWidth = window.innerWidth - resizerWidth;
|
| 59 |
+
const initialEditorWidth = availableWidth / 3; // Editor takes 1/3 of space
|
| 60 |
+
const initialPreviewWidth = availableWidth - initialEditorWidth; // Preview takes 2/3
|
| 61 |
+
editor.current.style.width = `${initialEditorWidth}px`;
|
| 62 |
+
preview.current.style.width = `${initialPreviewWidth}px`;
|
| 63 |
+
} else {
|
| 64 |
+
// Remove inline styles for smaller screens, let CSS flex-col handle it
|
| 65 |
+
editor.current.style.width = '';
|
| 66 |
+
preview.current.style.width = '';
|
| 67 |
+
}
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
/**
|
| 71 |
+
* Handles resizing when the user drags the resizer
|
| 72 |
+
* Ensures minimum widths are maintained for both panels
|
| 73 |
+
*/
|
| 74 |
const handleResize = (e: MouseEvent) => {
|
| 75 |
if (!editor.current || !preview.current || !resizer.current) return;
|
| 76 |
+
|
| 77 |
+
const resizerWidth = resizer.current.offsetWidth;
|
| 78 |
+
const minWidth = 100; // Minimum width for editor/preview
|
| 79 |
+
const maxWidth = window.innerWidth - resizerWidth - minWidth;
|
| 80 |
+
|
| 81 |
const editorWidth = e.clientX;
|
| 82 |
+
const clampedEditorWidth = Math.max(minWidth, Math.min(editorWidth, maxWidth));
|
| 83 |
+
const calculatedPreviewWidth = window.innerWidth - clampedEditorWidth - resizerWidth;
|
| 84 |
+
|
| 85 |
+
editor.current.style.width = `${clampedEditorWidth}px`;
|
| 86 |
+
preview.current.style.width = `${calculatedPreviewWidth}px`;
|
| 87 |
};
|
| 88 |
|
| 89 |
const handleMouseDown = () => {
|
|
|
|
| 98 |
document.removeEventListener("mouseup", handleMouseUp);
|
| 99 |
};
|
| 100 |
|
| 101 |
+
// Prevent accidental navigation away when AI is working or content has changed
|
| 102 |
useEvent("beforeunload", (e) => {
|
| 103 |
if (isAiWorking || html !== defaultHTML) {
|
| 104 |
e.preventDefault();
|
|
|
|
| 106 |
}
|
| 107 |
});
|
| 108 |
|
| 109 |
+
// Initialize component on mount
|
| 110 |
useMount(() => {
|
| 111 |
+
// Fetch user data
|
| 112 |
fetchMe();
|
| 113 |
+
|
| 114 |
+
// Restore content from storage if available
|
| 115 |
if (htmlStorage) {
|
| 116 |
removeHtmlStorage();
|
| 117 |
toast.warn("Previous HTML content restored from local storage.");
|
| 118 |
}
|
|
|
|
| 119 |
|
| 120 |
+
// Set initial layout based on window size
|
| 121 |
+
resetLayout();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
+
// Attach event listeners
|
| 124 |
if (!resizer.current) return;
|
| 125 |
resizer.current.addEventListener("mousedown", handleMouseDown);
|
| 126 |
+
window.addEventListener("resize", resetLayout);
|
| 127 |
});
|
| 128 |
|
| 129 |
+
// Clean up event listeners on unmount
|
| 130 |
useUnmount(() => {
|
| 131 |
document.removeEventListener("mousemove", handleResize);
|
| 132 |
document.removeEventListener("mouseup", handleMouseUp);
|
| 133 |
if (resizer.current) {
|
| 134 |
resizer.current.removeEventListener("mousedown", handleMouseDown);
|
| 135 |
}
|
| 136 |
+
window.removeEventListener("resize", resetLayout);
|
| 137 |
});
|
| 138 |
|
| 139 |
return (
|
src/components/login/login.tsx
CHANGED
|
@@ -8,14 +8,12 @@ function Login({
|
|
| 8 |
html?: string;
|
| 9 |
children?: React.ReactNode;
|
| 10 |
}) {
|
| 11 |
-
|
| 12 |
-
const [_, setStorage] = useLocalStorage("html_content");
|
| 13 |
|
| 14 |
const handleClick = () => {
|
| 15 |
if (html !== defaultHTML) {
|
| 16 |
setStorage(html);
|
| 17 |
}
|
| 18 |
-
console.log("store current HTML in local storage");
|
| 19 |
};
|
| 20 |
|
| 21 |
return (
|
|
|
|
| 8 |
html?: string;
|
| 9 |
children?: React.ReactNode;
|
| 10 |
}) {
|
| 11 |
+
const [, setStorage] = useLocalStorage("html_content");
|
|
|
|
| 12 |
|
| 13 |
const handleClick = () => {
|
| 14 |
if (html !== defaultHTML) {
|
| 15 |
setStorage(html);
|
| 16 |
}
|
|
|
|
| 17 |
};
|
| 18 |
|
| 19 |
return (
|