import { initEditors, switchMode, getEditorValues, setEditorValues, setOnChange, getCurrentMode, getCssType, setCssType, relayoutEditors, MODE_TABS, MODE_TO_JS_TYPE, JS_TYPE_TO_MODE, } from './editors.js'; import { renderPreview } from './preview.js'; import { initConsole, clearConsole } from './console-panel.js'; import { compileCss } from './preprocessors.js'; import { compileJs } from './js-preprocessors.js'; import { createFiddle, loadFiddle, updateFiddle } from './api.js'; import { getPref, setPref } from './preferences.js'; import { initEmmet } from './emmet.js'; import { initKeybindings } from './keybindings.js'; import { initResizer, clearInlineSizes } from './resizer.js'; let currentId = null; let debounceTimer = null; const $ = (sel) => document.querySelector(sel); const STARTER_TEMPLATES = { 'html-css-js': { html: '', css: '', js: '' }, 'typescript': { html: '
', css: '', js: `interface Greeting {\n name: string;\n message: string;\n}\n\nconst greet = (g: Greeting): string => \`\${g.message}, \${g.name}!\`;\n\nconst result = greet({ name: "World", message: "Hello" });\ndocument.getElementById("app")!.innerHTML = \`

\${result}

\`;\nconsole.log(result);`, }, 'react': { js: `const App = () => {\n const [count, setCount] = React.useState(0);\n return (\n
\n

Hello React

\n \n
\n );\n};\n\nReactDOM.createRoot(document.getElementById('root')).render();`, css: '', html: '', }, 'react-ts': { js: `const App: React.FC = () => {\n const [count, setCount] = React.useState(0);\n return (\n
\n

Hello React + TypeScript

\n \n
\n );\n};\n\nReactDOM.createRoot(document.getElementById('root')!).render();`, css: '', html: '', }, 'vue': { js: `\n\n`, css: '', }, 'svelte': { js: `\n\n

Hello Svelte

\n\n\n`, css: '', }, }; async function run() { const mode = getCurrentMode(); const { html, css, js } = getEditorValues(); const cssType = getCssType(); try { const compiledCss = await compileCss(css, cssType); const result = await compileJs(js, mode); clearConsole(); // Show warnings from compilation (e.g., Svelte) if (result.warnings && result.warnings.length) { result.warnings.forEach((w) => { window.postMessage({ type: 'console', method: 'warn', args: [w] }, '*'); }); } renderPreview(html, compiledCss, result.js, mode, result.extraCss || ''); } catch (e) { clearConsole(); renderPreview(html, '', '', mode); window.postMessage({ type: 'console', method: 'error', args: [`Compile error: ${e.message}`] }, '*'); } } function scheduleRun() { if (!getPref('autoRun')) return; clearTimeout(debounceTimer); debounceTimer = setTimeout(run, 500); } function showToast(msg) { const toast = $('#share-toast'); toast.textContent = msg; toast.classList.remove('hidden'); setTimeout(() => toast.classList.add('hidden'), 3000); } async function save() { const { html, css, js } = getEditorValues(); const title = $('#title-input').value || 'Untitled'; const css_type = getCssType(); const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript'; try { if (currentId) { await updateFiddle(currentId, { title, html, css, css_type, js, js_type }); showToast(`Saved! Share: ${location.origin}/f/${currentId}`); } else { const result = await createFiddle({ title, html, css, css_type, js, js_type }); currentId = result.id; history.pushState(null, '', `/f/${currentId}`); showToast(`Saved! Share: ${location.origin}/f/${currentId}`); } } catch (e) { showToast(`Save failed: ${e.message}`); } } async function fork() { const { html, css, js } = getEditorValues(); const title = ($('#title-input').value || 'Untitled') + ' (fork)'; const css_type = getCssType(); const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript'; try { const result = await createFiddle({ title, html, css, css_type, js, js_type }); currentId = result.id; $('#title-input').value = title; history.pushState(null, '', `/f/${currentId}`); showToast(`Forked! New URL: ${location.origin}/f/${currentId}`); } catch (e) { showToast(`Fork failed: ${e.message}`); } } async function loadFromUrl() { const match = location.pathname.match(/^\/f\/([a-zA-Z0-9_-]+)$/); if (!match) return; try { const fiddle = await loadFiddle(match[1]); currentId = fiddle.id; $('#title-input').value = fiddle.title; // Restore mode from js_type const mode = JS_TYPE_TO_MODE[fiddle.js_type] || 'html-css-js'; $('#framework-mode').value = mode; switchMode(mode); // Restore CSS type setCssType(fiddle.css_type || 'css'); setEditorValues(fiddle); setTimeout(run, 100); } catch (e) { showToast(`Failed to load fiddle: ${e.message}`); } } function handleModeChange(newMode) { const oldMode = getCurrentMode(); if (newMode === oldMode) return; switchMode(newMode); // Insert starter template if editors are empty const { html, css, js } = getEditorValues(); if (!html && !css && !js) { const template = STARTER_TEMPLATES[newMode]; if (template) { setEditorValues(template); } } scheduleRun(); } function applyLayout(layout) { const grid = $('.grid'); // Remove all layout classes grid.classList.remove('layout-top-bottom', 'layout-editor-only', 'layout-preview-only'); // Clear resizer inline styles when switching layouts clearInlineSizes(); if (layout === 'top-bottom') grid.classList.add('layout-top-bottom'); else if (layout === 'editor-only') grid.classList.add('layout-editor-only'); else if (layout === 'preview-only') grid.classList.add('layout-preview-only'); setPref('layout', layout); // Give DOM time to reflow then relayout editors requestAnimationFrame(() => relayoutEditors()); } async function init() { // Load Emmet before editors so completion providers are registered await initEmmet(); initEditors('html-css-js'); setOnChange(scheduleRun); initConsole(); initResizer(); initKeybindings(); // Auto-run checkbox const autoRunCb = $('#auto-run-checkbox'); autoRunCb.checked = getPref('autoRun'); autoRunCb.addEventListener('change', (e) => setPref('autoRun', e.target.checked)); // Layout selector const layoutSel = $('#layout-mode'); const savedLayout = getPref('layout') || 'default'; layoutSel.value = savedLayout; if (savedLayout !== 'default') applyLayout(savedLayout); layoutSel.addEventListener('change', (e) => applyLayout(e.target.value)); // Mode selector $('#framework-mode').addEventListener('change', (e) => { handleModeChange(e.target.value); }); // Toolbar buttons $('#btn-run').addEventListener('click', run); $('#btn-save').addEventListener('click', save); $('#btn-fork').addEventListener('click', fork); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); save(); } if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); run(); } }); // Load fiddle from URL if present loadFromUrl(); // Handle browser back/forward window.addEventListener('popstate', () => { currentId = null; loadFromUrl(); }); } init();