import React, { useState, useEffect, useRef, useCallback } from 'react'; import { FileCode2, Save, Undo2, Redo2, Download, Plus, Trash2, FileText, Settings, Play, CheckCircle2, AlertCircle, Loader2, Menu, X, MonitorPlay, Smartphone, Tablet, Monitor, Terminal, LayoutTemplate, Code, Maximize2, Minimize2, Palette, AlignLeft, FileJson, Trash, Copy, Scissors } from 'lucide-react'; import { initializeApp } from 'firebase/app'; import { getAuth, onAuthStateChanged, signInAnonymously, signInWithCustomToken } from 'firebase/auth'; import { getFirestore, collection, doc, setDoc, deleteDoc, onSnapshot } from 'firebase/firestore'; // ========================================== // 1. FIREBASE INITIALIZATION & SETUP // ========================================== // Firebase setup bahar kiya gaya hai taki re-render pe baar-baar initialize na ho const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // ========================================== // 2. TEMPLATES & SNIPPETS LIBRARY (Feature Rich Data) // ========================================== // Ye pre-built templates hain naye projects ke liye const STARTER_TEMPLATES = { basic: { name: "Basic HTML5", code: ` Mera Naya Project

Namaste Dunia!

Ye ek basic HTML5 template hai.

` }, tailwind: { name: "Tailwind CSS Starter", code: ` Tailwind Project

Tailwind Ready!

Aapka Tailwind CSS project setup ho gaya hai. Ab styling shuru karein!

` }, game: { name: "Canvas Game Starter", code: ` Simple Canvas Game
Use Arrow Keys to move! | Score: 0
` } }; // Ye HTML snippets hain jo user ek click me insert kar sakta hai const HTML_SNIPPETS = [ { name: "Navigation Bar", code: `\n` }, { name: "Modern Card", code: `
Card Image

Beautiful Card

Ye ek bahut hi sundar modern card ka snippet hai jo aap apne project me use kar sakte hain.

\n` }, { name: "Contact Form", code: `

Contact Us

\n` }, { name: "Fetch API Script", code: `\n` } ]; // ========================================== // 3. CUSTOM HOOKS // ========================================== // Performance bachane ke liye Debounce (jab user type karta hai tab turant iframe reload na ho) function useDebounce(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => clearTimeout(handler); }, [value, delay]); return debouncedValue; } // ========================================== // 4. MONACO EDITOR COMPONENT (Advanced Wrapper) // ========================================== const MonacoEditor = ({ value, onChange, onInit, theme, fontSize, wordWrap, onCursorChange }) => { const editorContainerRef = useRef(null); const monacoInstanceRef = useRef(null); useEffect(() => { let isMounted = true; const initMonaco = () => { if (!editorContainerRef.current || monacoInstanceRef.current || !isMounted) return; monacoInstanceRef.current = window.monaco.editor.create(editorContainerRef.current, { value: value, language: 'html', theme: theme, automaticLayout: true, minimap: { enabled: false }, // Screen space bachane ke liye minimap band hai wordWrap: wordWrap ? 'on' : 'off', fontSize: fontSize, fontFamily: "'Fira Code', 'Consolas', monospace", scrollBeyondLastLine: false, roundedSelection: true, padding: { top: 16, bottom: 16 }, formatOnPaste: true, formatOnType: true }); // Code change event monacoInstanceRef.current.onDidChangeModelContent(() => { onChange(monacoInstanceRef.current.getValue()); }); // Cursor change event for Status Bar monacoInstanceRef.current.onDidChangeCursorPosition((e) => { if(onCursorChange) { onCursorChange(e.position.lineNumber, e.position.column); } }); if (onInit) { onInit(monacoInstanceRef.current); } }; // Monaco CDN se load karwana if (window.monaco) { initMonaco(); } else { const scriptId = 'monaco-loader-script'; let script = document.getElementById(scriptId); if (!script) { script = document.createElement('script'); script.id = scriptId; script.src = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs/loader.min.js'; script.onload = () => { if (!isMounted) return; window.require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs' }}); window.require(['vs/editor/editor.main'], () => { initMonaco(); }); }; document.body.appendChild(script); } else { const checkMonaco = setInterval(() => { if (window.monaco) { clearInterval(checkMonaco); initMonaco(); } }, 100); } } return () => { isMounted = false; if (monacoInstanceRef.current) { monacoInstanceRef.current.dispose(); monacoInstanceRef.current = null; } }; }, []); // Agar settings change hoti hain toh Monaco update karein useEffect(() => { if (monacoInstanceRef.current) { monacoInstanceRef.current.updateOptions({ theme: theme, fontSize: fontSize, wordWrap: wordWrap ? 'on' : 'off' }); } }, [theme, fontSize, wordWrap]); // Bahar se value update (jaise file switch karte time) useEffect(() => { if (monacoInstanceRef.current && value !== monacoInstanceRef.current.getValue()) { monacoInstanceRef.current.executeEdits('external', [{ range: monacoInstanceRef.current.getModel().getFullModelRange(), text: value, forceMoveMarkers: true }]); } }, [value]); return
; }; // ========================================== // 5. MAIN APP COMPONENT // ========================================== export default function App() { // Authentication State const [user, setUser] = useState(null); const [isAuthLoading, setIsAuthLoading] = useState(true); // Project & Editor Data State const [projects, setProjects] = useState([]); const [currentProject, setCurrentProject] = useState(null); const [editorValue, setEditorValue] = useState(STARTER_TEMPLATES.basic.code); const [downloadName, setDownloadName] = useState('mera_pro_project'); const debouncedEditorValue = useDebounce(editorValue, 1000); const [saveStatus, setSaveStatus] = useState('Saved'); // 'Saved', 'Saving...', 'Unsaved' // UI Layout State const [activeSidebarTab, setActiveSidebarTab] = useState('files'); // 'files', 'snippets', 'settings' const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [previewMode, setPreviewMode] = useState('desktop'); // 'mobile', 'tablet', 'desktop' const [isFullscreen, setIsFullscreen] = useState(false); // Editor Settings State const [editorSettings, setEditorSettings] = useState({ theme: 'vs-dark', // 'vs-dark', 'vs-light' fontSize: 14, wordWrap: true }); const [cursorPos, setCursorPos] = useState({ line: 1, col: 1 }); // Console Logging State const [consoleLogs, setConsoleLogs] = useState([]); const [isConsoleOpen, setIsConsoleOpen] = useState(false); const [toast, setToast] = useState({ show: false, message: '', type: 'success' }); const [monacoEditor, setMonacoEditor] = useState(null); // --- AUTHENTICATION --- useEffect(() => { const initAuth = async () => { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (error) { console.error("Auth Error:", error); showToast("Authentication fail ho gaya. Data save nahi hoga.", "error"); } }; initAuth(); const unsubscribe = onAuthStateChanged(auth, (currentUser) => { setUser(currentUser); setIsAuthLoading(false); }); return () => unsubscribe(); }, []); // --- DATA FETCHING (FIRESTORE) --- useEffect(() => { if (!user) return; const projectsRef = collection(db, 'artifacts', appId, 'users', user.uid, 'projects'); // Strict adherence to Rule 2: No complex queries const unsubscribe = onSnapshot(projectsRef, (snapshot) => { const fetchedProjects = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); // In-memory sorting descending fetchedProjects.sort((a, b) => b.updatedAt - a.updatedAt); setProjects(fetchedProjects); if (!currentProject && fetchedProjects.length > 0) { selectProject(fetchedProjects[0]); } else if (!currentProject && fetchedProjects.length === 0) { handleCreateNewProject(STARTER_TEMPLATES.basic.code); // Auto create first project } }, (error) => { console.error("Firestore Error:", error); showToast("Projects load karne me error aayi.", "error"); }); return () => unsubscribe(); }, [user]); // --- CONSOLE MESSAGE LISTENER --- // Ye listener Iframe se aane wale console messages ko catch karta hai useEffect(() => { const handleMessage = (event) => { if (event.data && event.data.source === 'html-editor-console') { setConsoleLogs(prev => [...prev, { type: event.data.type, // 'log', 'warn', 'error' message: event.data.message, time: new Date().toLocaleTimeString() }]); // Agar naya error aaye toh console automatically khol do if(event.data.type === 'error' && !isConsoleOpen) { setIsConsoleOpen(true); } } }; window.addEventListener('message', handleMessage); return () => window.removeEventListener('message', handleMessage); }, [isConsoleOpen]); // --- AUTO SAVE FUNCTIONALITY --- useEffect(() => { if (!user || !currentProject) return; if (editorValue !== currentProject.content) { setSaveStatus('Unsaved...'); } // Debounced value ke base par save karein if (debouncedEditorValue !== currentProject.content) { setSaveStatus('Saving...'); saveProjectToDB(currentProject.id, currentProject.name, debouncedEditorValue, false) .then(() => setSaveStatus('Saved')) .catch(() => setSaveStatus('Error!')); } }, [debouncedEditorValue, editorValue]); // --- CORE FUNCTIONS --- const showToast = (message, type = 'success') => { setToast({ show: true, message, type }); setTimeout(() => setToast({ show: false, message: '', type: 'success' }), 3000); }; const handleCreateNewProject = async (templateCode = STARTER_TEMPLATES.basic.code) => { if (!user) return; const newId = crypto.randomUUID(); const newProject = { id: newId, name: `Mera Project ${projects.length + 1}`, content: templateCode, updatedAt: Date.now() }; setEditorValue(templateCode); setCurrentProject(newProject); setDownloadName(`Project_${projects.length + 1}`); setConsoleLogs([]); // Console clear kar do naye project pe await saveProjectToDB(newId, newProject.name, templateCode, true); showToast("Naya Project Ban Gaya!"); }; const selectProject = (project) => { setCurrentProject(project); setEditorValue(project.content); setDownloadName(project.name.replace(/\s+/g, '_')); setConsoleLogs([]); // Console clear if (window.innerWidth < 768) setIsSidebarOpen(false); }; const saveProjectToDB = async (projectId, name, content, isManual = false) => { if (!user) return; try { const projectRef = doc(db, 'artifacts', appId, 'users', user.uid, 'projects', projectId); await setDoc(projectRef, { name, content, updatedAt: Date.now() }, { merge: true }); if (isManual) { showToast("Project Save Ho Gaya!"); setSaveStatus('Saved'); } setCurrentProject(prev => prev && prev.id === projectId ? { ...prev, name, content } : prev); } catch (error) { console.error("Save Error:", error); if (isManual) showToast("Save karne me problem aayi.", "error"); setSaveStatus('Error!'); } }; const handleDeleteProject = async (e, projectId) => { e.stopPropagation(); if (!user) return; // Custom Confirmation Modal ki jagah browser confirm use kar rahe for simplicity, // but iframe restrictions ki wajah se let's just delete it directly with toast warning // Or we build a custom UI alert (Better approach for iframes: No native alerts) // Here we use a safe approach, deleting with custom toast info. try { await deleteDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'projects', projectId)); showToast("Project Delete Kar Diya Gaya."); if (currentProject?.id === projectId) { setCurrentProject(null); setEditorValue(STARTER_TEMPLATES.basic.code); } } catch (error) { showToast("Delete karne me error aayi.", "error"); } }; const handleDownload = () => { const blob = new Blob([editorValue], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${downloadName || 'mera_code'}.html`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast("File Download Ho Gayi!"); }; const insertSnippet = (snippetCode) => { if (monacoEditor) { const position = monacoEditor.getPosition(); monacoEditor.executeEdits('snippet', [{ range: new window.monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), text: snippetCode, forceMoveMarkers: true }]); monacoEditor.focus(); showToast("Snippet Insert Ho Gaya!"); if (window.innerWidth < 768) setIsSidebarOpen(false); } }; // --- ADVANCED EDITOR COMMANDS --- const triggerCommand = (commandId) => { if (monacoEditor) { if(commandId === 'undo' || commandId === 'redo') { monacoEditor.trigger('keyboard', commandId, null); } else if (commandId === 'format') { monacoEditor.getAction('editor.action.formatDocument').run(); showToast("Code Format Ho Gaya!"); } else if (commandId === 'minify') { // Simple HTML minifier logic for demonstration let minified = editorValue .replace(/\s+/g, ' ') // remove extra spaces .replace(/> \s*<') // remove space between tags .replace(//g, ''); // remove comments setEditorValue(minified); showToast("Code Minify Ho Gaya!"); } monacoEditor.focus(); } }; const clearConsole = () => setConsoleLogs([]); // --- IFRAME INJECTION SCRIPT --- // Ye script humare user ke code se pehle judegi taki console.log pakad sake const buildIframeDoc = () => { const runnerScript = ` `; // We prepend the runnerScript to the user's debounced HTML code return runnerScript + debouncedEditorValue; }; // --- RENDER LOADING --- if (isAuthLoading) { return (

Pro IDE Load Ho Raha Hai...

Apna Next-Level Environment taiyaar ho raha hai

); } // --- MAIN RENDER --- return (
{/* TOAST NOTIFICATION */} {toast.show && (
{toast.type === 'error' ? : } {toast.message}
)} {/* --- SIDEBAR AREA (Activity Bar + Panel) --- */}
{/* Activity Bar (Slim Left Column) */}
{/* Sidebar Panel Content */}

{activeSidebarTab === 'files' && <> Projects} {activeSidebarTab === 'snippets' && <> Components} {activeSidebarTab === 'settings' && <> Settings}

{/* TAB: FILES / PROJECTS */} {activeSidebarTab === 'files' && (

Starter Templates

{Object.entries(STARTER_TEMPLATES).map(([key, t]) => ( ))}

Aapke Projects {projects.length}

{projects.length === 0 ? (
Koi project nahi hai. Upar se naya banayein!
) : (
{projects.map(proj => (
selectProject(proj)} className={` group flex flex-col p-3 rounded-xl cursor-pointer transition-all border ${currentProject?.id === proj.id ? 'bg-[#cbf799] shadow-sm border-[#a6f059]' : 'bg-white hover:bg-[#f4fcf0] border-transparent shadow-sm'} `} >
{proj.name} {new Date(proj.updatedAt).toLocaleString()}
))}
)}
)} {/* TAB: SNIPPETS */} {activeSidebarTab === 'snippets' && (

Kisi bhi component par click karein aur wo aapke code me jahan cursor hai wahan insert ho jayega.

{HTML_SNIPPETS.map((snippet, idx) => (

{snippet.name}

{snippet.code}
))}
)} {/* TAB: SETTINGS */} {activeSidebarTab === 'settings' && (
setEditorSettings({...editorSettings, fontSize: parseInt(e.target.value)})} className="w-full accent-[#a6f059]" />
)}
{/* --- MAIN WORKSPACE --- */}
{/* TOP MAIN TOOLBAR */}
{(!isSidebarOpen || window.innerWidth < 1024) && ( )} {/* Project Name Editor */}
{ if (currentProject) { const newName = e.target.value; setCurrentProject({...currentProject, name: newName}); setDownloadName(newName.replace(/\s+/g, '_')); } }} disabled={!currentProject} className="bg-transparent border-none outline-none text-[#2d5214] font-bold text-sm sm:text-base w-32 sm:w-48 md:focus:w-64 transition-all" placeholder="Project Name..." />
{/* User Requested Left/Right Arrows for Undo/Redo */}
{/* Custom Download Action */}
setDownloadName(e.target.value)} placeholder="file_name" className="bg-transparent border-none outline-none text-sm px-2 w-24 focus:w-32 transition-all text-[#2d5214] font-medium" /> .html
{/* Mobile Download */} {/* Main Save Button */}
{/* EDITOR & PREVIEW PANELS WRAPPER */}
{/* ================================== */} {/* LEFT PANEL: ADVANCED CODE EDITOR */} {/* ================================== */}
{/* Editor Toolbar */}
HTML Editor
{/* Editor Action Buttons */}
{/* Monaco Container */}
setCursorPos({line, col})} />
{/* Editor Status Bar */}
Line {cursorPos.line}, Col {cursorPos.col} UTF-8 HTML
{saveStatus}
{/* ================================== */} {/* RIGHT PANEL: LIVE EXECUTABLE PREVIEW */} {/* ================================== */}
{/* Preview Toolbar */}
Live App Preview
{/* Device View Toggles */}
{/* Fullscreen Toggle */}
{/* Iframe Wrapper for Responsiveness */}
{/* Real Live Iframe with injected console script */}