{/* Header Bar */}
-
+
- {/* Main Content Area */}
-
- {activeTab === 'code' ? (
- <>
- {/* File Explorer Panel (collapsible) */}
+ {/* Main Content Area with Loading */}
+
+
+ {!isProjectFinished && (
-
+
+
+ Initializing project...
+
-
-
+
+
+ {activeTab === 'code' ? (
+ <>
+ {/* File Explorer Panel (collapsible) */}
+
+ transition={{ duration: 0.3, ease: 'easeInOut' }}
+ className="overflow-y-auto border-r"
+ >
+
+
+
+
+
+ >
+ ) : activeTab === 'preview' ? (
+
+
- >
- ) : activeTab === 'preview' ? (
-
- ) : activeTab === 'console' ? (
-
Console Content (Mock)
- ) : null}
-
+ ) : activeTab === 'console' ? (
+ Console Content (Mock)
+ ) : null}
+
- {/* Save Changes Bar */}
- {saving && (
-
- )}
+ {/* Save Changes Bar */}
+ {saving && (
+
+ )}
- {/* File Explorer Toggle Button */}
- {activeTab === 'code' && (
-
- )}
+ {/* File Explorer Toggle Button */}
+ {activeTab === 'code' && (
+
+ )}
+
);
}
+
// SaveChangesBar component for showing unsaved changes status
const SaveChangesBar = ({ saving, onSave, onReset }) => {
return (
diff --git a/frontend/src/components/code-engine/web-view.tsx b/frontend/src/components/code-engine/web-view.tsx
index b27d7eac..5f1140d9 100644
--- a/frontend/src/components/code-engine/web-view.tsx
+++ b/frontend/src/components/code-engine/web-view.tsx
@@ -1,14 +1,42 @@
import { useContext, useEffect, useRef, useState } from 'react';
import { ProjectContext } from './project-context';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import {
+ ChevronLeft,
+ ChevronRight,
+ Maximize,
+ ExternalLink,
+} from 'lucide-react';
export default function WebPreview() {
const { curProject } = useContext(ProjectContext);
- const [url, setUrl] = useState('');
+ const [baseUrl, setBaseUrl] = useState('');
+ const [displayPath, setDisplayPath] = useState('/');
+ const [history, setHistory] = useState
(['/']);
+ const [currentIndex, setCurrentIndex] = useState(0);
const iframeRef = useRef(null);
+ const containerRef = useRef<{ projectPath: string; domain: string } | null>(
+ null
+ );
+ const lastProjectPathRef = useRef(null);
useEffect(() => {
const getWebUrl = async () => {
+ if (!curProject) return;
const projectPath = curProject.projectPath;
+
+ if (lastProjectPathRef.current === projectPath) {
+ return;
+ }
+
+ lastProjectPathRef.current = projectPath;
+
+ if (containerRef.current?.projectPath === projectPath) {
+ setBaseUrl(`http://${containerRef.current.domain}`);
+ return;
+ }
+
try {
const response = await fetch(
`/api/runProject?projectPath=${encodeURIComponent(projectPath)}`,
@@ -20,9 +48,15 @@ export default function WebPreview() {
}
);
const json = await response.json();
- console.log(json);
- await new Promise((resolve) => setTimeout(resolve, 10000));
- setUrl(`http://${json.domain}/`);
+
+ await new Promise((resolve) => setTimeout(resolve, 200));
+
+ containerRef.current = {
+ projectPath,
+ domain: json.domain,
+ };
+ setBaseUrl(`http://${json.domain}`);
+ setDisplayPath('/');
} catch (error) {
console.error('fetching url error:', error);
}
@@ -32,52 +66,124 @@ export default function WebPreview() {
}, [curProject]);
useEffect(() => {
- if (iframeRef.current) {
- iframeRef.current.src = url;
+ if (iframeRef.current && baseUrl) {
+ const fullUrl = `${baseUrl}${displayPath}`;
+ iframeRef.current.src = fullUrl;
}
- }, [url]);
+ }, [baseUrl, displayPath]);
- const refreshIframe = () => {
+ const enterFullScreen = () => {
if (iframeRef.current) {
- iframeRef.current.src = url;
+ iframeRef.current.requestFullscreen();
}
};
- const enterFullScreen = () => {
- if (iframeRef.current) {
- iframeRef.current.requestFullscreen();
+ const openInNewTab = () => {
+ if (baseUrl) {
+ const fullUrl = `${baseUrl}${displayPath}`;
+ window.open(fullUrl, '_blank');
+ }
+ };
+
+ const handlePathChange = (newPath: string) => {
+ if (!newPath.startsWith('/')) {
+ newPath = '/' + newPath;
+ }
+ setDisplayPath(newPath);
+ // Add new path to history, removing any forward history
+ const newHistory = history.slice(0, currentIndex + 1);
+ setHistory([...newHistory, newPath]);
+ setCurrentIndex(newHistory.length);
+ };
+
+ const goBack = () => {
+ if (currentIndex > 0) {
+ setCurrentIndex(currentIndex - 1);
+ setDisplayPath(history[currentIndex - 1]);
+ }
+ };
+
+ const goForward = () => {
+ if (currentIndex < history.length - 1) {
+ setCurrentIndex(currentIndex + 1);
+ setDisplayPath(history[currentIndex + 1]);
}
};
return (
-
-
-
setUrl(e.target.value)}
- className="flex-1 p-2 border rounded"
- />
-
-
+
+ {/* URL Bar */}
+
+ {/* Navigation Controls */}
+
+
+
+
+
+ {/* URL Input */}
+
+ handlePathChange(e.target.value)}
+ className="h-8 bg-secondary"
+ placeholder="/"
+ disabled={!baseUrl}
+ />
+
+
+ {/* Actions */}
+
+
+
+
-
-
+ {/* Preview Container */}
+
+ {baseUrl ? (
+
+ ) : (
+
+ )}
);
diff --git a/frontend/src/components/project-modal.tsx b/frontend/src/components/project-modal.tsx
index 75dbcc68..3286af52 100644
--- a/frontend/src/components/project-modal.tsx
+++ b/frontend/src/components/project-modal.tsx
@@ -29,7 +29,7 @@ const ProjectModal = ({ isOpen, onClose, refetchProjects }) => {
return (