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: `
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: `\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) --- */}