Add responsive preview, editor themes, template gallery, devtools, and autocomplete

- Device breakpoint toggles (mobile 375px / tablet 768px / desktop 100%)
- Editor theme selector with 6 themes (VS Dark/Light, High Contrast, Monokai, Dracula, GitHub Dark)
- Starter template gallery with 8 pre-built templates (Todo, API Fetch, CSS Animation, etc.)
- Code autocomplete with DOM/React type definitions and snippet completions
- Devtools panels: console, network, elements, performance
- Code formatter (Prettier), diff view, and linter integration
This commit is contained in:
root
2026-02-27 01:22:01 -06:00
parent b18c9c1dc8
commit 6ca8519250
19 changed files with 1755 additions and 29 deletions

View File

@@ -1,6 +1,7 @@
import {
initEditors, switchMode, getEditorValues, setEditorValues,
setOnChange, getCurrentMode, getCssType, setCssType,
setOnChange, setOnTabSwitch, getCurrentMode, getCssType, setCssType,
setOnFormat, setOnDiff, setEditorTheme,
relayoutEditors,
MODE_TABS, MODE_TO_JS_TYPE, JS_TYPE_TO_MODE,
} from './editors.js';
@@ -15,6 +16,16 @@ import { initKeybindings } from './keybindings.js';
import { initResizer, clearInlineSizes } from './resizer.js';
import { exportHtml } from './export.js';
import { showQrModal } from './qr.js';
import { initDevtools } from './devtools.js';
import { initNetwork, clearNetwork } from './network-panel.js';
import { initElements, clearElements } from './elements-panel.js';
import { initPerformance, clearPerformance } from './performance-panel.js';
import { formatActiveEditor } from './formatter.js';
import { initLinter, lintOnChange } from './linter.js';
import { toggleDiff, snapshotValues, onTabSwitch as diffOnTabSwitch } from './diff-view.js';
import { registerCustomThemes, THEMES } from './editor-themes.js';
import { GALLERY_TEMPLATES } from './templates.js';
import { configureTypeDefaults, registerSnippetProviders } from './autocomplete.js';
let currentId = null;
let debounceTimer = null;
@@ -73,6 +84,9 @@ async function run() {
const compiledCss = await compileCss(css, cssType);
const result = await compileJs(js, mode);
clearConsole();
clearNetwork();
clearElements();
clearPerformance();
// Show warnings from compilation (e.g., Svelte)
if (result.warnings && result.warnings.length) {
@@ -121,10 +135,12 @@ async function save() {
try {
if (currentId) {
await updateFiddle(currentId, { title, html, css, css_type, js, js_type, listed, options, tags });
snapshotValues();
showToast(`Saved! Share: ${location.origin}/f/${currentId}`);
} else {
const result = await createFiddle({ title, html, css, css_type, js, js_type, listed, options, tags });
currentId = result.id;
snapshotValues();
history.pushState(null, '', `/f/${currentId}`);
showToast(`Saved! Share: ${location.origin}/f/${currentId}`);
}
@@ -181,6 +197,7 @@ async function loadFromUrl() {
renderResourceList();
setEditorValues(fiddle);
snapshotValues();
setTimeout(run, 100);
} catch (e) {
showToast(`Failed to load fiddle: ${e.message}`);
@@ -225,11 +242,26 @@ async function init() {
// Load Emmet before editors so completion providers are registered
await initEmmet();
// Register custom Monaco themes before creating editors
registerCustomThemes();
// Configure autocomplete: type defaults + snippet providers
configureTypeDefaults();
registerSnippetProviders();
initEditors('html-css-js');
setOnChange(scheduleRun);
setOnChange(() => { scheduleRun(); lintOnChange(); });
setOnFormat(() => formatActiveEditor());
setOnDiff(() => toggleDiff());
setOnTabSwitch(diffOnTabSwitch);
initConsole();
initDevtools();
initNetwork();
initElements();
initPerformance();
initResizer();
initKeybindings();
initLinter();
// Auto-run checkbox
const autoRunCb = $('#auto-run-checkbox');
@@ -249,6 +281,65 @@ async function init() {
if (savedLayout !== 'default') applyLayout(savedLayout);
layoutSel.addEventListener('change', (e) => applyLayout(e.target.value));
// Editor theme selector
const themeSel2 = $('#editor-theme');
const savedEditorTheme = getPref('editorTheme') || 'vs-dark';
themeSel2.value = savedEditorTheme;
themeSel2.addEventListener('change', (e) => {
setEditorTheme(e.target.value);
setPref('editorTheme', e.target.value);
});
// Device preview toggles
const viewport = $('#preview-viewport');
const savedDevice = getPref('previewDevice') || 'desktop';
if (savedDevice !== 'desktop') {
viewport.classList.add(`device-${savedDevice}`);
document.querySelectorAll('.device-btn').forEach(b => {
b.classList.toggle('active', b.dataset.device === savedDevice);
});
}
document.querySelectorAll('.device-btn').forEach(btn => {
btn.addEventListener('click', () => {
const device = btn.dataset.device;
viewport.classList.remove('device-tablet', 'device-mobile');
if (device !== 'desktop') viewport.classList.add(`device-${device}`);
document.querySelectorAll('.device-btn').forEach(b => b.classList.toggle('active', b === btn));
setPref('previewDevice', device);
});
});
// Templates gallery
const tplModal = $('#templates-modal');
const tplGrid = $('#templates-grid');
$('#btn-templates').addEventListener('click', () => tplModal.classList.remove('hidden'));
$('#templates-modal-close').addEventListener('click', () => tplModal.classList.add('hidden'));
tplModal.addEventListener('click', (e) => { if (e.target === tplModal) tplModal.classList.add('hidden'); });
// Render template cards
tplGrid.innerHTML = '';
for (const tpl of GALLERY_TEMPLATES) {
const card = document.createElement('div');
card.className = 'template-card';
card.innerHTML = `
<div class="template-card-icon">${tpl.icon}</div>
<div class="template-card-title">${tpl.title}</div>
<div class="template-card-desc">${tpl.description}</div>
<div class="template-card-mode">${tpl.mode}</div>
`;
card.addEventListener('click', () => {
// Switch mode
$('#framework-mode').value = tpl.mode;
handleModeChange(tpl.mode);
// Set editor values
setEditorValues({ html: tpl.html || '', css: tpl.css || '', js: tpl.js || '' });
// Close modal and run
tplModal.classList.add('hidden');
run();
});
tplGrid.appendChild(card);
}
// Mode selector
$('#framework-mode').addEventListener('change', (e) => {
handleModeChange(e.target.value);
@@ -273,6 +364,16 @@ async function init() {
if (e.key === 'Escape') {
document.querySelectorAll('.modal-overlay:not(.hidden)').forEach(m => m.classList.add('hidden'));
}
// Shift+Alt+F — format code
if (e.key === 'F' && e.shiftKey && e.altKey) {
e.preventDefault();
formatActiveEditor();
}
// Ctrl/Cmd+D — toggle diff
if ((e.ctrlKey || e.metaKey) && e.key === 'd') {
e.preventDefault();
toggleDiff();
}
// ? key opens shortcuts (only when not typing in an input/editor)
if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
const tag = document.activeElement?.tagName;