import type { Frame, WObject } from '@/lib/wysiwyg/schema' export type Exported = { html: string; css: string; js: string } function esc(s: string) { return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') } function isImage(o: WObject): o is Extract { return o.type === 'image' } function isText(o: WObject): o is Extract { return o.type === 'text' } function isButton(o: WObject): o is Extract { return o.type === 'button' } function isInput(o: WObject): o is Extract { return o.type === 'input' } function isSelect(o: WObject): o is Extract { return o.type === 'select' } function isTextarea(o: WObject): o is Extract { return o.type === 'textarea' } function isRadio(o: WObject): o is Extract { return o.type === 'radio' } function isCheckbox(o: WObject): o is Extract { return o.type === 'checkbox' } function renderObject(o: WObject): { html: string; css: string } { const baseCss = `#${o.id}{position:absolute;left:${o.x}px;top:${o.y}px;width:${o.width}px;height:${o.height}px;transform:rotate(${o.rotate}deg)}` if (isImage(o)) { const html = `${esc(o.props.alt || '')}` return { html, css: baseCss } } if (isText(o)) { const html = `

${esc(o.props.text)}

` return { html, css: baseCss + `;font-size:${o.props.fontSize}px;color:${o.props.color}` } } if (isInput(o)) { const cid = `${o.id}-control` const req = o.props.required ? ' required' : '' const ph = o.props.placeholder ? ` placeholder="${esc(o.props.placeholder)}"` : '' const helpId = o.props.help ? `${cid}-help` : '' const described = helpId ? ` aria-describedby="${helpId}"` : '' const pat = o.props.pattern ? ` pattern="${esc(o.props.pattern)}"` : '' const min = typeof o.props.minLength === 'number' ? ` minlength="${o.props.minLength}"` : '' const max = typeof o.props.maxLength === 'number' ? ` maxlength="${o.props.maxLength}"` : '' const html = [ `
`, ``, ``, o.props.help ? `${esc(o.props.help)}` : '', `
`, ].join('') return { html, css: baseCss } } if (isSelect(o)) { const cid = `${o.id}-control` const opts = (o.props.options || []) .map((opt) => ``) .join('') const helpId = o.props.help ? `${cid}-help` : '' const described = helpId ? ` aria-describedby="${helpId}"` : '' const html = [ `
`, ``, ``, o.props.help ? `${esc(o.props.help)}` : '', `
`, ].join('') return { html, css: baseCss } } if (isTextarea(o)) { const cid = `${o.id}-control` const req = o.props.required ? ' required' : '' const ph = o.props.placeholder ? ` placeholder="${esc(o.props.placeholder)}"` : '' const rows = Number(o.props.rows ?? 3) const helpId = o.props.help ? `${cid}-help` : '' const described = helpId ? ` aria-describedby="${helpId}"` : '' const min = typeof o.props.minLength === 'number' ? ` minlength="${o.props.minLength}"` : '' const max = typeof o.props.maxLength === 'number' ? ` maxlength="${o.props.maxLength}"` : '' const html = [ `
`, ``, ``, o.props.help ? `${esc(o.props.help)}` : '', `
`, ].join('') return { html, css: baseCss } } if (isRadio(o)) { const req = o.props.required ? ' required' : '' const items = (o.props.options || []).map((opt, idx) => { const id = `${o.id}-${idx}` const checked = o.props.defaultValue === opt ? ' checked' : '' return `
` }).join('') const help = o.props.help ? `${esc(o.props.help)}` : '' const html = [`
`, `${esc(o.props.legend)}`, items, help, `
`].join('') return { html, css: baseCss } } if (isCheckbox(o)) { const req = o.props.required ? ' required' : '' const def = new Set(o.props.defaultValues || []) const items = (o.props.options || []).map((opt, idx) => { const id = `${o.id}-${idx}` const checked = def.has(opt) ? ' checked' : '' return `
` }).join('') const help = o.props.help ? `${esc(o.props.help)}` : '' const html = [`
`, `${esc(o.props.legend)}`, items, help, `
`].join('') return { html, css: baseCss } } // button minimal (fallback) const label = isButton(o) ? o.props.label : 'Button' const href = isButton(o) ? (o.props.href || '#') : '#' const html = `${esc(label)}` return { html, css: baseCss } } export function exportFrame(frame: Frame): Exported { const piecesHtml: string[] = [] const piecesCss: string[] = [] for (const layer of frame.layers) { if (!layer.visible) continue for (const o of layer.objects) { const r = renderObject(o) piecesHtml.push(r.html) piecesCss.push(r.css) } } const inner = `
${piecesHtml.join('')}
` const css = [ `:root{--bg:${frame.background};--text:#f9fafb;--border:#94a3b8;--primary:#0ea5e9;--danger:#ef4444;--ring:#22c55e;--ring-offset:2px;--disabled-bg:#e5e7eb;--disabled-fg:#9ca3af}`, `[data-theme="light"]{--bg:#ffffff;--text:#0f172a;--border:#cbd5e1;--primary:#0ea5e9;--danger:#ef4444;--ring:#22c55e;--ring-offset:2px;--disabled-bg:#e5e7eb;--disabled-fg:#6b7280}`, `[data-theme="dark"]{--bg:#111111;--text:#f9fafb;--border:#94a3b8;--primary:#0ea5e9;--danger:#ef4444;--ring:#22c55e;--ring-offset:2px;--disabled-bg:#1f2937;--disabled-fg:#9ca3af}`, `.frame{position:relative;width:${frame.width}px;height:${frame.height}px;background:var(--bg)}`, `.frame-scale{transform-origin:0 0}`, `a{color:var(--primary);text-decoration:none}`, `input,select,textarea{color:var(--text);background:transparent;border:1px solid var(--border)}`, `:focus-visible{outline:2px solid var(--ring);outline-offset:var(--ring-offset)}`, `input:focus,select:focus,textarea:focus{outline:2px solid var(--primary);outline-offset:2px}`, `input:invalid,select:invalid,textarea:invalid{border-color:var(--danger)}`, `input:disabled,select:disabled,textarea:disabled{background:var(--disabled-bg);color:var(--disabled-fg)}`, `input[readonly],select[readonly],textarea[readonly]{color:var(--disabled-fg)}`, `fieldset{border:1px solid var(--border)}`, `legend{color:var(--text)}`, ...piecesCss, ].join('\n') const html = [ '', '', '', '', '', 'WYSIWYG Frame', '', '', '', inner, '', '', '', ].join('') return { html, css, js: '' } }