Monorepo for Tangled tangled.org
764
fork

Configure Feed

Select the types of activity you want to include in your feed.

ogre/language-circles: use proportional area instead of linear

Signed-off-by: eti <eti@eti.tf>

authored by

eti and committed by tangled.org 3e74eb7e 3bbab0bf

+82 -50
+6 -1
ogre/src/__tests__/render.test.ts
··· 43 43 writeFileSync(join(outputDir, filename), buffer); 44 44 }; 45 45 46 + const saveSvg = (filename: string, svg: string) => { 47 + writeFileSync(join(outputDir, filename), svg); 48 + }; 49 + 46 50 const renderAndSave = async <P>(component: VNode<P>, filename: string) => { 47 - const { png } = await renderCard(component as VNode); 51 + const { svg, png } = await renderCard(component as VNode); 52 + saveSvg(filename.replace(".png", ".svg"), svg); 48 53 savePng(filename, png); 49 54 }; 50 55
+14 -6
ogre/src/components/cards/repository.tsx
··· 8 8 import type { RepositoryCardData } from "../../validation"; 9 9 10 10 function repoNameFontSize(name: string): number { 11 - // Available width ~1000px (1104px card content minus language circles area). 11 + // Available width ~756px 12 12 // Inter 600 average char width is ~0.58× the font size. 13 13 const maxSize = TYPOGRAPHY.repoName.fontSize; 14 - const fitted = Math.floor(1000 / (name.length * 0.58)); 14 + const fitted = Math.floor(756 / (name.length * 0.58)); 15 15 return Math.min(maxSize, Math.max(fitted, 48)); 16 16 } 17 17 ··· 19 19 const fontSize = repoNameFontSize(data.repoName); 20 20 return ( 21 21 <Card> 22 - <LanguageCircles languages={data.languages} /> 22 + <div 23 + style={{ 24 + display: "flex", 25 + position: "absolute", 26 + right: -380, 27 + top: -380, 28 + }}> 29 + <LanguageCircles width={760} languages={data.languages} /> 30 + </div> 23 31 24 32 <Col style={{ gap: 64 }}> 25 - <Col style={{ gap: 24 }}> 26 - <span style={{ ...TYPOGRAPHY.repoName, fontSize, color: "#000000" }}> 33 + <Col style={{ gap: 24, maxWidth: 756 }}> 34 + <div style={{ ...TYPOGRAPHY.repoName, fontSize, color: "#000000" }}> 27 35 {data.repoName} 28 - </span> 36 + </div> 29 37 30 38 <Row style={{ gap: 16 }}> 31 39 <Avatar src={data.avatarUrl} size={64} />
+32 -42
ogre/src/components/shared/language-circles.tsx
··· 2 2 3 3 interface LanguageCirclesProps { 4 4 languages: Language[]; 5 + width: number; 5 6 } 6 7 7 - const MAX_RADIUS = 380; 8 - 9 - function percentageToThickness(percentage: number): number { 10 - return (percentage / 100) * MAX_RADIUS; 11 - } 8 + export function LanguageCircles({ languages, width }: LanguageCirclesProps) { 9 + const MAX_RADIUS = width || 100; 12 10 13 - export function LanguageCircles({ languages }: LanguageCirclesProps) { 14 11 const sortedLanguages = [...languages] 15 12 .sort((a, b) => b.percentage - a.percentage) 16 - .slice(0, 5) 17 - .reverse(); 13 + .slice(0, 5); 18 14 19 - let cumulativeRadius = 0; 15 + let cumulativePercentage = 0; 16 + const circles: { color: string; radius: number }[] = []; 20 17 21 - return ( 22 - <div 23 - style={{ 24 - position: "absolute", 25 - right: -MAX_RADIUS, 26 - top: -MAX_RADIUS, 27 - width: MAX_RADIUS * 2, 28 - height: MAX_RADIUS * 2, 29 - display: "flex", 30 - }}> 31 - {sortedLanguages.map((lang, i) => { 32 - const thickness = percentageToThickness(lang.percentage); 33 - const contentSize = cumulativeRadius * 2; 34 - 35 - cumulativeRadius += thickness; 18 + for (const lang of sortedLanguages) { 19 + // Radius decreases as we go inward, but ring area is proportional to percentage 20 + // Using sqrt to make area (πr²) proportional to remaining percentage 21 + const radius = Math.max( 22 + 1, 23 + Math.round(MAX_RADIUS * Math.sqrt(1 - cumulativePercentage / 100) * 100) / 24 + 100, 25 + ); 26 + circles.push({ color: lang.color, radius }); 27 + cumulativePercentage += lang.percentage; 28 + } 36 29 37 - return ( 38 - <div 39 - key={i} 40 - style={{ 41 - position: "absolute", 42 - left: "50%", 43 - top: "50%", 44 - transform: "translate(-50%, -50%)", 45 - width: contentSize, 46 - height: contentSize, 47 - borderRadius: "50%", 48 - border: `${thickness}px solid ${lang.color}`, 49 - boxSizing: "content-box", 50 - }} 51 - /> 52 - ); 53 - })} 54 - </div> 30 + return ( 31 + <svg 32 + width={MAX_RADIUS} 33 + height={MAX_RADIUS} 34 + viewBox={`0 0 ${MAX_RADIUS * 2} ${MAX_RADIUS * 2}`}> 35 + {circles.map((circle, i) => ( 36 + <circle 37 + key={i} 38 + cx="50%" 39 + cy="50%" 40 + r={circle.radius} 41 + fill={circle.color} 42 + /> 43 + ))} 44 + </svg> 55 45 ); 56 46 }
+30 -1
ogre/wrangler.jsonc
··· 6 6 "observability": { 7 7 "enabled": true, 8 8 }, 9 + "limits": { 10 + "cpu_ms": 300000 11 + }, 9 12 "routes": [ 10 13 { 11 14 "pattern": "ogre.tangled.network", ··· 18 21 "rules": [ 19 22 { 20 23 "type": "Data", 21 - "globs": ["**/*.woff"], 24 + "globs": ["**/*.wasm", "**/*.woff"], 22 25 "fallthrough": true, 23 26 }, 24 27 ], 28 + "env": { 29 + "dev": { 30 + "name": "ogre-dev", 31 + "vars": { 32 + "ENVIRONMENT": "development" 33 + }, 34 + "routes": [ 35 + { 36 + "pattern": "ogre-dev.tangled.network", 37 + "custom_domain": true 38 + } 39 + ] 40 + }, 41 + "production": { 42 + "name": "ogre", 43 + "vars": { 44 + "ENVIRONMENT": "production" 45 + }, 46 + "routes": [ 47 + { 48 + "pattern": "ogre.tangled.network", 49 + "custom_domain": true 50 + } 51 + ] 52 + } 53 + } 25 54 }