Skill Tracer
Skills › Archify › archify › layout.mjs

layout.mjs

35 lines · archify/explainer/layout.mjs at a406594

1/** Order-preserving twelve-column packing. Explicit spans never change.
2 * A panel may shrink from its preferred width to its minimum to finish the current row,
3 * so a wide panel does not strand the conclusion on a row of its own.
4 */
5export function layoutExplainerPanels(document, diagrams = {}) {
6 const contentSize = value => typeof value === 'string' ? Array.from(value).length : Array.isArray(value) ? value.reduce((n,v)=>n+contentSize(v),0) : value && typeof value === 'object' ? Object.entries(value).filter(([key])=>!['id','type','span','target','source','diagramType','path','url','revision'].includes(key)).reduce((n,[,v])=>n+contentSize(v),0) : 0;
7 const widths = block => {
8 if (block.type === 'comparison') return block.columns.length + 1 > 3 ? [8,7] : [7,6];
9 if (['code','diff'].includes(block.type)) return [8,7];
10 if (block.type === 'chart') return [6,5];
11 if (block.type === 'snippet') { const longest = Math.max(...block.text.split('\n').map(line => line.length)); return longest > 72 ? [8,7] : longest > 48 ? [6,6] : [5,4]; }
12 if (block.type === 'diagram') {
13 const diagram=diagrams[block.id], viewBox=diagram?.svg?.match(/viewBox=["']([^"']+)["']/)?.[1].split(/[ ,]+/).map(Number);
14 return (viewBox?.[2] || diagram?.spec?.meta?.canvas?.width || 0) > 1000 ? [12,12] : [8,8];
15 }
16 return [4,4];
17 };
18 const sized = panel => panel.blocks.map(widths).reduce((best,next)=>next[0]>best[0]?next:best,[4,4]);
19 const items=[{id:'answer',span:5,min:4,weight:contentSize(document.answer),answer:true},...document.panels.map(panel=>{const [span,min]=sized(panel);return{id:panel.id,span:panel.span ?? span,min:panel.span ?? min,explicit:panel.span!==undefined,weight:contentSize(panel),panel};})];
20 const rows=[];let row=[],used=0;
21 function finish(){if(!row.length)return;const remaining=12-used;const flexible=row.filter(item=>!item.explicit).sort((a,b)=>b.weight-a.weight);if(remaining&&flexible.length)flexible[0].span+=remaining;else if(remaining)row.push({spacer:true,span:remaining});rows.push(row);row=[];used=0;}
22 for(const item of items){
23 if(used+item.span>12){
24 if(row.length&&row.reduce((n,x)=>n+x.min,0)+item.min<=12){
25 let deficit=used+item.span-12;
26 const cut=x=>{const d=Math.min(deficit,x.span-x.min);x.span-=d;deficit-=d;};
27 cut(item);for(const x of [...row].sort((a,b)=>(b.span-b.min)-(a.span-a.min)))cut(x);
28 used=row.reduce((n,x)=>n+x.span,0);
29 } else finish();
30 }
31 row.push(item);used+=item.span;if(used===12)finish();
32 }
33 finish();
34 return rows;