Files
probably-rendering-one-more…/apps/waterfall-wiki/src/studio/components/LayoutCanvas.tsx
T
codex-bot 6654a1456b
Unified application CI and deployment / verify-and-release (pull_request) Waiting to run
python CI and deployment / verify-and-deploy (pull_request) Successful in 1m12s
Unify Wiki and Studio application and simplify deployment
2026-11-04 03:41:16 +08:00

15 lines
3.8 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 {useId} from 'react';
import type {Layout,Parameters} from '../lib/layout-parameters';
export type LayerLayout=Omit<Layout,'elements'>&{source:string;background:string;tutorialUrl:string;sourceSha256:string;elements:(Layout['elements'][number]&{raster:string;shape?:string})[]};
export function LayoutCanvas({layout,params,guides=false,imageSources={}}:{layout:LayerLayout;params:Parameters;guides?:boolean;imageSources?:Record<string,string>}){
const uid=useId().replace(/[^a-z0-9]/gi,''),W=layout.width,H=layout.height,unchanged=!params.paper&&!Object.keys(params.texts).length&&!Object.keys(params.images).length&&!Object.keys(params.decorations).length;
return <svg xmlns="http://www.w3.org/2000/svg" viewBox={`0 0 ${W} ${H}`} width={W*6} height={H*6} role="img" aria-label={layout.id+' '+layout.name} data-layout-id={layout.id} style={{width:'100%',height:'auto',display:'block'}}><title>{layout.id+' · '+layout.name}</title><desc>对应原图的文字、图位和装饰。未替换构件保留原始低分辨率图层;替换文字为 SVG 文字。</desc>{unchanged?<image href={layout.source} width={W} height={H}/>:<><rect width={W} height={H} fill={params.paper||'#f4f2eb'}/><image href={layout.background} width={W} height={H}/>{params.paper&&<><defs><mask id={uid+'paper'}><image href={layout.background} width={W} height={H}/></mask></defs><rect width={W} height={H} fill={params.paper} mask={'url(#'+uid+'paper)'}/></>}{['image','decoration','text'].flatMap(kind=>layout.elements.filter(s=>s.kind===kind).map(s=>{
const [x,y,w,h]=s.box,id=uid+s.id,t=params.texts[s.id],im=params.images[s.id],d=params.decorations[s.id];
if(kind==='text'&&t){const lines=t.value.split('\n'),len=(v:string)=>Array.from(v).reduce((a,c)=>a+(/[\u3400-\u9fff]/.test(c)?1:.6),0),vertical=s.direction==='vertical',size=Math.min(vertical?w*.9:h/(lines.length*1.2),vertical?h/Math.max(1,Array.from(t.value).length):w/Math.max(1,...lines.map(len)))*(t.scale||1),align=t.align||'start';return <g key={s.id} data-slot={s.id} fill={t.color||'#26362f'} fontFamily="Arial,'Noto Sans CJK SC','PingFang SC',sans-serif"><defs><clipPath id={id+'clip'}><rect x={x} y={y} width={w} height={h}/></clipPath></defs><g clipPath={`url(#${id}clip)`}>{vertical?Array.from(t.value.replaceAll('\n','')).map((c,i)=><text key={i} x={x+w/2} y={y+size*(i+.85)} textAnchor="middle" fontSize={size}>{c}</text>):s.direction==='curved'?<><defs><path id={id+'path'} d={`M${x} ${y+h*.2} Q${x+w/2} ${y+h*1.7} ${x+w} ${y+h*.2}`}/></defs><text fontSize={size}><textPath href={'#'+id+'path'}>{t.value}</textPath></text></>:lines.map((l,i)=><text key={i} x={x+(align==='middle'?w/2:align==='end'?w:0)} y={y+size*(.88+i*1.2)} textAnchor={align} fontSize={size}>{l}</text>)}</g></g>}
if(kind==='decoration'&&d?.hidden)return null;
const fill=kind==='image'?im?.color:d?.color;
if(fill||im?.assetId)return <g key={s.id} data-slot={s.id}><defs><mask id={id} maskUnits="userSpaceOnUse" x={x} y={y} width={w} height={h} style={{maskType:'alpha'}}><image href={s.shape||s.raster} x={x} y={y} width={w} height={h}/></mask></defs><g mask={'url(#'+id+')'}>{fill&&<rect x={x} y={y} width={w} height={h} fill={fill}/>} {im?.assetId&&<image href={imageSources[im.assetId]||'/api/preview?'+new URLSearchParams({source:'/api/media/'+im.assetId,width:'960'})} x={x} y={y} width={w} height={h} preserveAspectRatio={im.fit==='contain'?'xMidYMid meet':'xMidYMid slice'}/>}</g></g>;
return <image key={s.id} data-slot={s.id} href={s.raster} x={x} y={y} width={w} height={h}/>;
}))}</>}{guides&&layout.elements.map(s=>{const[x,y,w,h]=s.box,color=s.kind==='text'?'#2765da':s.kind==='image'?'#d86b27':'#08875d';return <g key={s.id} aria-hidden="true"><rect x={x} y={y} width={w} height={h} fill="none" stroke={color} strokeWidth=".35"/><text x={x} y={y+3} fontSize="3" fill={color}>{s.id}</text></g>})}</svg>;
}