Spaces:
Running
Running
Update components/editor/index.tsx
Browse files- components/editor/index.tsx +47 -12
components/editor/index.tsx
CHANGED
|
@@ -27,11 +27,10 @@ import { SaveButton } from "./save-button";
|
|
| 27 |
import { LoadProject } from "../my-projects/load-project";
|
| 28 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
| 29 |
import { ListPages } from "./pages";
|
| 30 |
-
import { AiThinking } from "./ai-thinking"; // Import the new component
|
| 31 |
|
| 32 |
export const AppEditor = ({
|
| 33 |
project,
|
| 34 |
-
initialPages,
|
| 35 |
images,
|
| 36 |
isNew,
|
| 37 |
}: {
|
|
@@ -58,6 +57,7 @@ export const AppEditor = ({
|
|
| 58 |
const editor = useRef<HTMLDivElement>(null);
|
| 59 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
| 60 |
const resizer = useRef<HTMLDivElement>(null);
|
|
|
|
| 61 |
const monacoRef = useRef<any>(null);
|
| 62 |
|
| 63 |
const [currentTab, setCurrentTab] = useState("chat");
|
|
@@ -65,7 +65,6 @@ export const AppEditor = ({
|
|
| 65 |
const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
|
| 66 |
const [isResizing, setIsResizing] = useState(false);
|
| 67 |
const [isAiWorking, setIsAiWorking] = useState(false);
|
| 68 |
-
const [aiThinking, setAiThinking] = useState<string | null>(null); // State for AI thinking text
|
| 69 |
const [isEditableModeEnabled, setIsEditableModeEnabled] = useState(false);
|
| 70 |
const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
|
| 71 |
null
|
|
@@ -74,14 +73,18 @@ export const AppEditor = ({
|
|
| 74 |
|
| 75 |
const resetLayout = () => {
|
| 76 |
if (!editor.current || !preview.current) return;
|
|
|
|
|
|
|
| 77 |
if (window.innerWidth >= 1024) {
|
| 78 |
-
|
|
|
|
| 79 |
const availableWidth = window.innerWidth - resizerWidth;
|
| 80 |
-
const initialEditorWidth = availableWidth / 3;
|
| 81 |
-
const initialPreviewWidth = availableWidth - initialEditorWidth;
|
| 82 |
editor.current.style.width = `${initialEditorWidth}px`;
|
| 83 |
preview.current.style.width = `${initialPreviewWidth}px`;
|
| 84 |
} else {
|
|
|
|
| 85 |
editor.current.style.width = "";
|
| 86 |
preview.current.style.width = "";
|
| 87 |
}
|
|
@@ -89,9 +92,11 @@ export const AppEditor = ({
|
|
| 89 |
|
| 90 |
const handleResize = (e: MouseEvent) => {
|
| 91 |
if (!editor.current || !preview.current || !resizer.current) return;
|
|
|
|
| 92 |
const resizerWidth = resizer.current.offsetWidth;
|
| 93 |
-
const minWidth = 100;
|
| 94 |
const maxWidth = window.innerWidth - resizerWidth - minWidth;
|
|
|
|
| 95 |
const editorWidth = e.clientX;
|
| 96 |
const clampedEditorWidth = Math.max(
|
| 97 |
minWidth,
|
|
@@ -99,6 +104,7 @@ export const AppEditor = ({
|
|
| 99 |
);
|
| 100 |
const calculatedPreviewWidth =
|
| 101 |
window.innerWidth - clampedEditorWidth - resizerWidth;
|
|
|
|
| 102 |
editor.current.style.width = `${clampedEditorWidth}px`;
|
| 103 |
preview.current.style.width = `${calculatedPreviewWidth}px`;
|
| 104 |
};
|
|
@@ -134,12 +140,12 @@ export const AppEditor = ({
|
|
| 134 |
removeHtmlStorage();
|
| 135 |
toast.warning("Previous HTML content restored from local storage.");
|
| 136 |
}
|
|
|
|
| 137 |
resetLayout();
|
| 138 |
if (!resizer.current) return;
|
| 139 |
resizer.current.addEventListener("mousedown", handleMouseDown);
|
| 140 |
window.addEventListener("resize", resetLayout);
|
| 141 |
});
|
| 142 |
-
|
| 143 |
useUnmount(() => {
|
| 144 |
document.removeEventListener("mousemove", handleResize);
|
| 145 |
document.removeEventListener("mouseup", handleMouseUp);
|
|
@@ -149,6 +155,7 @@ export const AppEditor = ({
|
|
| 149 |
window.removeEventListener("resize", resetLayout);
|
| 150 |
});
|
| 151 |
|
|
|
|
| 152 |
useEvent("beforeunload", (e) => {
|
| 153 |
if (isAiWorking || !isTheSameHtml(currentPageData?.html)) {
|
| 154 |
e.preventDefault();
|
|
@@ -158,12 +165,15 @@ export const AppEditor = ({
|
|
| 158 |
|
| 159 |
useUpdateEffect(() => {
|
| 160 |
if (currentTab === "chat") {
|
|
|
|
| 161 |
resetLayout();
|
|
|
|
| 162 |
if (resizer.current) {
|
| 163 |
resizer.current.addEventListener("mousedown", handleMouseDown);
|
| 164 |
}
|
| 165 |
} else {
|
| 166 |
if (preview.current) {
|
|
|
|
| 167 |
preview.current.style.width = "100%";
|
| 168 |
}
|
| 169 |
}
|
|
@@ -190,6 +200,7 @@ export const AppEditor = ({
|
|
| 190 |
router.push(`/projects/${project.space_id}`);
|
| 191 |
}}
|
| 192 |
/>
|
|
|
|
| 193 |
{project?._id ? (
|
| 194 |
<SaveButton pages={pages} prompts={prompts} />
|
| 195 |
) : (
|
|
@@ -243,25 +254,29 @@ export const AppEditor = ({
|
|
| 243 |
toast.success("HTML copied to clipboard!");
|
| 244 |
}}
|
| 245 |
/>
|
| 246 |
-
{aiThinking && <AiThinking text={aiThinking} />}
|
| 247 |
<Editor
|
| 248 |
defaultLanguage="html"
|
| 249 |
theme="vs-dark"
|
| 250 |
className={classNames(
|
| 251 |
"h-full bg-neutral-900 transition-all duration-200 absolute left-0 top-0",
|
| 252 |
-
{
|
|
|
|
|
|
|
| 253 |
)}
|
| 254 |
options={{
|
| 255 |
colorDecorators: true,
|
| 256 |
fontLigatures: true,
|
| 257 |
theme: "vs-dark",
|
| 258 |
minimap: { enabled: false },
|
| 259 |
-
scrollbar: {
|
|
|
|
|
|
|
| 260 |
wordWrap: "on",
|
| 261 |
}}
|
| 262 |
value={currentPageData.html}
|
| 263 |
onChange={(value) => {
|
| 264 |
const newValue = value ?? "";
|
|
|
|
| 265 |
setPages((prev) =>
|
| 266 |
prev.map((page) =>
|
| 267 |
page.path === currentPageData.path
|
|
@@ -282,7 +297,6 @@ export const AppEditor = ({
|
|
| 282 |
currentPage={currentPageData}
|
| 283 |
htmlHistory={htmlHistory}
|
| 284 |
previousPrompts={prompts}
|
| 285 |
-
setAiThinking={setAiThinking} // Pass the setter function
|
| 286 |
onSuccess={(newPages, p: string) => {
|
| 287 |
const currentHistory = [...htmlHistory];
|
| 288 |
currentHistory.unshift({
|
|
@@ -293,9 +307,30 @@ export const AppEditor = ({
|
|
| 293 |
setHtmlHistory(currentHistory);
|
| 294 |
setSelectedElement(null);
|
| 295 |
setSelectedFiles([]);
|
|
|
|
| 296 |
if (window.innerWidth <= 1024) {
|
| 297 |
setCurrentTab("preview");
|
| 298 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
}}
|
| 300 |
setPages={setPages}
|
| 301 |
pages={pages}
|
|
|
|
| 27 |
import { LoadProject } from "../my-projects/load-project";
|
| 28 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
| 29 |
import { ListPages } from "./pages";
|
|
|
|
| 30 |
|
| 31 |
export const AppEditor = ({
|
| 32 |
project,
|
| 33 |
+
pages: initialPages,
|
| 34 |
images,
|
| 35 |
isNew,
|
| 36 |
}: {
|
|
|
|
| 57 |
const editor = useRef<HTMLDivElement>(null);
|
| 58 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
| 59 |
const resizer = useRef<HTMLDivElement>(null);
|
| 60 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 61 |
const monacoRef = useRef<any>(null);
|
| 62 |
|
| 63 |
const [currentTab, setCurrentTab] = useState("chat");
|
|
|
|
| 65 |
const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
|
| 66 |
const [isResizing, setIsResizing] = useState(false);
|
| 67 |
const [isAiWorking, setIsAiWorking] = useState(false);
|
|
|
|
| 68 |
const [isEditableModeEnabled, setIsEditableModeEnabled] = useState(false);
|
| 69 |
const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
|
| 70 |
null
|
|
|
|
| 73 |
|
| 74 |
const resetLayout = () => {
|
| 75 |
if (!editor.current || !preview.current) return;
|
| 76 |
+
|
| 77 |
+
// lg breakpoint is 1024px based on useBreakpoint definition and Tailwind defaults
|
| 78 |
if (window.innerWidth >= 1024) {
|
| 79 |
+
// Set initial 1/3 - 2/3 sizes for large screens, accounting for resizer width
|
| 80 |
+
const resizerWidth = resizer.current?.offsetWidth ?? 8; // w-2 = 0.5rem = 8px
|
| 81 |
const availableWidth = window.innerWidth - resizerWidth;
|
| 82 |
+
const initialEditorWidth = availableWidth / 3; // Editor takes 1/3 of space
|
| 83 |
+
const initialPreviewWidth = availableWidth - initialEditorWidth; // Preview takes 2/3
|
| 84 |
editor.current.style.width = `${initialEditorWidth}px`;
|
| 85 |
preview.current.style.width = `${initialPreviewWidth}px`;
|
| 86 |
} else {
|
| 87 |
+
// Remove inline styles for smaller screens, let CSS flex-col handle it
|
| 88 |
editor.current.style.width = "";
|
| 89 |
preview.current.style.width = "";
|
| 90 |
}
|
|
|
|
| 92 |
|
| 93 |
const handleResize = (e: MouseEvent) => {
|
| 94 |
if (!editor.current || !preview.current || !resizer.current) return;
|
| 95 |
+
|
| 96 |
const resizerWidth = resizer.current.offsetWidth;
|
| 97 |
+
const minWidth = 100; // Minimum width for editor/preview
|
| 98 |
const maxWidth = window.innerWidth - resizerWidth - minWidth;
|
| 99 |
+
|
| 100 |
const editorWidth = e.clientX;
|
| 101 |
const clampedEditorWidth = Math.max(
|
| 102 |
minWidth,
|
|
|
|
| 104 |
);
|
| 105 |
const calculatedPreviewWidth =
|
| 106 |
window.innerWidth - clampedEditorWidth - resizerWidth;
|
| 107 |
+
|
| 108 |
editor.current.style.width = `${clampedEditorWidth}px`;
|
| 109 |
preview.current.style.width = `${calculatedPreviewWidth}px`;
|
| 110 |
};
|
|
|
|
| 140 |
removeHtmlStorage();
|
| 141 |
toast.warning("Previous HTML content restored from local storage.");
|
| 142 |
}
|
| 143 |
+
|
| 144 |
resetLayout();
|
| 145 |
if (!resizer.current) return;
|
| 146 |
resizer.current.addEventListener("mousedown", handleMouseDown);
|
| 147 |
window.addEventListener("resize", resetLayout);
|
| 148 |
});
|
|
|
|
| 149 |
useUnmount(() => {
|
| 150 |
document.removeEventListener("mousemove", handleResize);
|
| 151 |
document.removeEventListener("mouseup", handleMouseUp);
|
|
|
|
| 155 |
window.removeEventListener("resize", resetLayout);
|
| 156 |
});
|
| 157 |
|
| 158 |
+
// Prevent accidental navigation away when AI is working or content has changed
|
| 159 |
useEvent("beforeunload", (e) => {
|
| 160 |
if (isAiWorking || !isTheSameHtml(currentPageData?.html)) {
|
| 161 |
e.preventDefault();
|
|
|
|
| 165 |
|
| 166 |
useUpdateEffect(() => {
|
| 167 |
if (currentTab === "chat") {
|
| 168 |
+
// Reset editor width when switching to reasoning tab
|
| 169 |
resetLayout();
|
| 170 |
+
// re-add the event listener for resizing
|
| 171 |
if (resizer.current) {
|
| 172 |
resizer.current.addEventListener("mousedown", handleMouseDown);
|
| 173 |
}
|
| 174 |
} else {
|
| 175 |
if (preview.current) {
|
| 176 |
+
// Reset preview width when switching to preview tab
|
| 177 |
preview.current.style.width = "100%";
|
| 178 |
}
|
| 179 |
}
|
|
|
|
| 200 |
router.push(`/projects/${project.space_id}`);
|
| 201 |
}}
|
| 202 |
/>
|
| 203 |
+
{/* for these buttons pass the whole pages */}
|
| 204 |
{project?._id ? (
|
| 205 |
<SaveButton pages={pages} prompts={prompts} />
|
| 206 |
) : (
|
|
|
|
| 254 |
toast.success("HTML copied to clipboard!");
|
| 255 |
}}
|
| 256 |
/>
|
|
|
|
| 257 |
<Editor
|
| 258 |
defaultLanguage="html"
|
| 259 |
theme="vs-dark"
|
| 260 |
className={classNames(
|
| 261 |
"h-full bg-neutral-900 transition-all duration-200 absolute left-0 top-0",
|
| 262 |
+
{
|
| 263 |
+
"pointer-events-none": isAiWorking,
|
| 264 |
+
}
|
| 265 |
)}
|
| 266 |
options={{
|
| 267 |
colorDecorators: true,
|
| 268 |
fontLigatures: true,
|
| 269 |
theme: "vs-dark",
|
| 270 |
minimap: { enabled: false },
|
| 271 |
+
scrollbar: {
|
| 272 |
+
horizontal: "hidden",
|
| 273 |
+
},
|
| 274 |
wordWrap: "on",
|
| 275 |
}}
|
| 276 |
value={currentPageData.html}
|
| 277 |
onChange={(value) => {
|
| 278 |
const newValue = value ?? "";
|
| 279 |
+
// setHtml(newValue);
|
| 280 |
setPages((prev) =>
|
| 281 |
prev.map((page) =>
|
| 282 |
page.path === currentPageData.path
|
|
|
|
| 297 |
currentPage={currentPageData}
|
| 298 |
htmlHistory={htmlHistory}
|
| 299 |
previousPrompts={prompts}
|
|
|
|
| 300 |
onSuccess={(newPages, p: string) => {
|
| 301 |
const currentHistory = [...htmlHistory];
|
| 302 |
currentHistory.unshift({
|
|
|
|
| 307 |
setHtmlHistory(currentHistory);
|
| 308 |
setSelectedElement(null);
|
| 309 |
setSelectedFiles([]);
|
| 310 |
+
// if xs or sm
|
| 311 |
if (window.innerWidth <= 1024) {
|
| 312 |
setCurrentTab("preview");
|
| 313 |
}
|
| 314 |
+
// if (updatedLines && updatedLines?.length > 0) {
|
| 315 |
+
// const decorations = updatedLines.map((line) => ({
|
| 316 |
+
// range: new monacoRef.current.Range(
|
| 317 |
+
// line[0],
|
| 318 |
+
// 1,
|
| 319 |
+
// line[1],
|
| 320 |
+
// 1
|
| 321 |
+
// ),
|
| 322 |
+
// options: {
|
| 323 |
+
// inlineClassName: "matched-line",
|
| 324 |
+
// },
|
| 325 |
+
// }));
|
| 326 |
+
// setTimeout(() => {
|
| 327 |
+
// editorRef?.current
|
| 328 |
+
// ?.getModel()
|
| 329 |
+
// ?.deltaDecorations([], decorations);
|
| 330 |
+
|
| 331 |
+
// editorRef.current?.revealLine(updatedLines[0][0]);
|
| 332 |
+
// }, 100);
|
| 333 |
+
// }
|
| 334 |
}}
|
| 335 |
setPages={setPages}
|
| 336 |
pages={pages}
|