Render saved Studio results directly by ID without an editor
studio CI and deployment / verify-and-deploy (pull_request) Failing after 3m5s
waterfall-wiki CI and deployment / verify-and-deploy (pull_request) Has been cancelled

This commit is contained in:
2026-10-28 21:02:21 +08:00
parent efffcdb1fe
commit de30ee4ec8
14 changed files with 104 additions and 31 deletions
@@ -0,0 +1,28 @@
import Head from 'next/head';
import type {CSSProperties} from 'react';
import type {AddedSlot,Layout,Parameters} from '../lib/layout-parameters';
import {CurvedCopy} from './NativeCurvedCopy';
import {decorationKind,defaultText,emptyParameters,imageShape,paperColor,slotPosition,textColor,textSize,type Design} from '../lib/native-layout';
/** A saved result contains artwork only: no editing state, controls or event handlers. */
export function NativeResult({layout,design}:{layout:Layout;design:Design|null}){
const params:Parameters=design?.parameters||emptyParameters();
const elements=[...layout.elements,...(params.added||[])];
return <main className="native-layout-page">
<Head><title>{design?.name||layout.name} · Prompt</title><meta name="description" content={layout.name+' · 排版结果'}/></Head>
<article className={'native-layout '+layout.id} aria-label={design?.name||layout.name} data-layout-id={layout.id} data-design-id={design?.id} style={{aspectRatio:`${layout.width}/${layout.height}`,backgroundColor:params.paper||paperColor(layout.id)}}>
{['image','decoration','text'].flatMap(kind=>elements.filter(s=>s.kind===kind).map(s=>{
const t=params.texts[s.id],im=params.images[s.id],d=params.decorations[s.id],ls=params.layers?.[s.id]||{};
if(ls.hidden||(kind==='decoration'&&d?.hidden))return null;
const text=t?.value??defaultText(layout,s);
const position:CSSProperties={...slotPosition(layout,{...s,box:[ls.x??s.box[0],ls.y??s.box[1],ls.width??s.box[2],ls.height??s.box[3]]}),transform:`rotate(${ls.rotation||0}deg)`,opacity:ls.opacity??1,borderRadius:ls.radius===undefined?undefined:`${ls.radius/layout.width*100}cqw`,zIndex:ls.z??(kind==='text'?3:kind==='image'?1:2)};
const textStyle:CSSProperties={fontSize:`${(t?.fontSize??textSize(layout,s,text)*(t?.scale||1))/layout.width*100}cqw`,color:t?.color||textColor(layout.id,s),fontFamily:t?.fontFamily==='serif'?'Georgia,serif':t?.fontFamily==='mono'?'monospace':t?.fontFamily==='sans'?'Arial,sans-serif':undefined,fontWeight:t?.fontWeight,lineHeight:t?.lineHeight,letterSpacing:t?.letterSpacing===undefined?undefined:`${t.letterSpacing/layout.width*100}cqw`,textAlign:t?.align==='middle'?'center':t?.align==='end'?'right':'left'};
return <div key={s.id} className={'native-element native-'+kind} data-element={s.id} aria-label={s.role} style={position}>
{kind==='text'?(s.direction==='curved'?<CurvedCopy layout={layout} slot={s} value={text} spacing={t?.letterSpacing} style={textStyle}/>:<div className={'native-copy direction-'+s.direction} style={textStyle}>{text}</div>):kind==='image'?<div className="native-image-fill" style={{...imageShape(layout,s),...(im?.color?{backgroundColor:im.color}:{}),...(ls.radius!==undefined?{borderRadius:`${ls.radius/layout.width*100}cqw`}:{})}}>
{im?.assetId&&<img src={'/api/preview?'+new URLSearchParams({source:'/api/media/'+im.assetId,width:'960'})} srcSet={[240,480,960,1280].map(width=>'/api/preview?'+new URLSearchParams({source:'/api/media/'+im.assetId!,width:String(width)})+' '+width+'w').join(',')} sizes={`(max-width: 860px) ${Math.min(100,(ls.width??s.box[2])/layout.width*100)}vw, ${Math.min(860,(ls.width??s.box[2])/layout.width*860)}px`} alt={s.role} style={{objectFit:im.fit||'cover',objectPosition:`${im.cropX??50}% ${im.cropY??50}%`,transform:`scale(${im.zoom??1})`}}/>}
</div>:<div className={'native-decoration decoration-'+((s as AddedSlot).shape?'solid':decorationKind(s))} style={{borderRadius:ls.radius!==undefined?`${ls.radius/layout.width*100}cqw`:(s as AddedSlot).shape==='ellipse'?'50%':undefined,color:d?.color||'#83908a',backgroundColor:(s as AddedSlot).shape?d?.color||'#83908a':decorationKind(s)==='band'?d?.color||'#dce1d8':undefined}}>{decorationKind(s)==='lines'&&Array.from({length:s.box[3]>4?3:1},(_,i)=><i key={i}/>)}{decorationKind(s)==='vertical-lines'&&<><i/><i/><i/></>}</div>}
</div>;
}))}
</article>
</main>;
}