Files
probably-rendering-one-more…/apps/studio/src/components/TemplateTrial.tsx
T
codex-bot dba9785379
studio CI and deployment / verify-and-deploy (pull_request) Successful in 5m33s
waterfall-wiki CI and deployment / verify-and-deploy (pull_request) Successful in 16m52s
Build Studio visual layout library with recoverable design CRUD
2026-10-28 14:45:37 +08:00

31 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {referencePreview} from '../lib/layout-images-client';
import {useState} from 'react';
import Head from 'next/head';
import {tutorialPost} from '../content/tool-links';
import {ideaMarkdown,type Idea,type Slide} from '../content/ideas';
import {Ref17Cover} from './Ref17Cover';
export function TemplateTrial({idea}:{idea:Idea}){
const template=idea.template!;
const [slides,setSlides]=useState<Slide[]>(idea.slides),[active,setActive]=useState(0),[masthead,setMasthead]=useState(template.masthead),[guides,setGuides]=useState(false),[notice,setNotice]=useState('');
const selected=slides[active],current={...idea,slides,template:{...template,masthead}};
const update=(patch:Partial<Slide>)=>setSlides(old=>old.map((slide,i)=>i===active?{...slide,...patch}:slide));
const download=(text:string,type:string,name:string)=>{const url=URL.createObjectURL(new Blob([text],{type})),a=document.createElement('a');a.href=url;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(url),500)};
async function exportSvg(){await document.fonts.ready;const svg=document.querySelector(`[data-proof="${selected.key}"] svg`);if(!svg)return;download(new XMLSerializer().serializeToString(svg),'image/svg+xml',`ref-17-${selected.key}.svg`);setNotice('已下载当前封面 SVG,可继续编辑文字与图形。')}
const reset=()=>{setSlides(idea.slides);setMasthead(template.masthead);setGuides(false);setNotice('已恢复初始试版。')};
return <main className="template-trial"><Head><title>{idea.title} · Studio</title><link rel="alternate" type="text/markdown" href={'/studio/api/export/'+idea.slug+'?format=markdown'}/></Head>
<header className="trial-nav"><a href={tutorialPost}>← PROMPT 使用教程</a><span>STUDIO / TEMPLATE PROOF</span><a href={template.tutorialUrl} target="_blank" rel="noreferrer">排版教程 ↗</a></header>
<section className="trial-intro"><div><small>REF-17 · 同一母版 / 四张试版</small><h1>先试封面,再填内容。</h1><p>英文刊头、圆形叠标、左下主图与右侧窄栏保持同一套坐标。三组合集先用主题占位,暂不放入具体 Skills。</p></div><div className="trial-source"><b>01 母版</b><span>02 笔记复习</span><span>03 教学资料</span><span>04 英语学习</span></div></section>
<div className="trial-workspace"><aside className="trial-controls"><h2>试一试</h2><label>当前封面<select value={active} onChange={e=>{setActive(Number(e.target.value));setNotice('')}}>{slides.map((s,i)=><option value={i} key={s.key}>{s.label}</option>)}</select></label>
<fieldset><legend>共享英文刊头</legend><label>第一行<input value={masthead[0]} maxLength={8} onChange={e=>setMasthead([e.target.value.toUpperCase().replace(/[^A-Z ]/g,''),masthead[1]])}/></label><label>第二行<input value={masthead[1]} maxLength={8} onChange={e=>setMasthead([masthead[0],e.target.value.replace(/[^A-Za-z ]/g,'')])}/></label></fieldset>
<label>本张标题 <small>建议 4–6 字</small><input maxLength={6} value={selected.heading} onChange={e=>update({heading:e.target.value})}/></label>
<label>右侧简介 <small>最多三行</small><textarea rows={3} value={selected.body} onChange={e=>update({body:e.target.value.split('\n').slice(0,3).join('\n')})}/></label>
<div className="trial-colors"><label>主图色块<input type="color" value={selected.color} onChange={e=>update({color:e.target.value})}/></label><label>圆形与贴条<input type="color" value={selected.accent} onChange={e=>update({accent:e.target.value})}/></label></div>
<label className="trial-switch"><input type="checkbox" checked={guides} onChange={e=>setGuides(e.target.checked)}/>显示构件边界</label>
<div className="trial-actions"><button className="trial-primary" onClick={exportSvg}>下载当前 SVG</button><button onClick={()=>download(JSON.stringify({version:2,...current},null,2),'application/json',idea.slug+'.json')}>导出当前试版 JSON</button><button onClick={()=>download(ideaMarkdown(current),'text/markdown',idea.slug+'.md')}>导出说明 Markdown</button><button onClick={reset}>恢复初始试版</button></div>
<p className="trial-help">修改仅保留在当前页面,刷新会恢复;可下载 JSON 保存试版。主视觉仍是色块占位。</p><p role="status" className="trial-notice">{notice}</p>
</aside><div><div className="trial-grid">{slides.map((slide,i)=><figure className={'proof-card '+(i===active?'proof-active':'')} key={slide.key} data-proof={slide.key}><button className="proof-select" onClick={()=>setActive(i)} aria-label={'编辑'+slide.label} aria-pressed={i===active}><Ref17Cover template={template} slide={slide} masthead={masthead} guides={guides}/></button><figcaption><b>{slide.label}</b><span>共用 REF-17</span><a href={'/studio/api/export/'+idea.slug+'?format=svg&slide='+i}>初始 SVG ↗</a></figcaption></figure>)}</div>
<details className="trial-reference"><summary>查看原图与构件对应</summary><div className="trial-reference-body"><img src={referencePreview(template.referenceUrl)} alt="ref-17 原始参考裁片"/><div><p>保留 {template.width}×{template.height} 的画板比例与九个构件位置;本试版重新绘制可编辑文字与色块,替换了文案和色调。</p><table><thead><tr><th>构件</th><th>用途</th><th>原图坐标</th></tr></thead><tbody>{template.slots.map(s=><tr key={s.id}><td>{s.id}</td><td>{s.role}</td><td>{s.box.join(', ')}</td></tr>)}</tbody></table><a href={template.tutorialUrl+'?agent_mode=True'}>读取教程纯文本 ↗</a></div></div></details>
</div></div>
</main>;
}