codestats-readme/src/cards/top-languages-card.tsx
Florian Bouillon 4702a72bff Added Profile
Signed-off-by: Florian Bouillon <florian.bouillon@delta-wings.net>
2020-09-15 16:26:59 +02:00

242 lines
5.7 KiB
TypeScript

import { getCardColors, FlexLayout, clampValue } from "../common/utils"
import Card from '../common/Card'
import { data } from "../fetcher";
import React from 'react'
import themes from "../../themes";
export interface parsedQuery {
hide?: Array<string>
hide_title?: boolean
hide_border?: boolean
card_width?: number
title_color?: string
text_color?: string
bg_color?: string
language_count?: number
show_level?: string
theme?: keyof typeof themes
cache_seconds?: string
layout?: string
}
const createProgressNode = ({ width, color, name, progress, progress2 }: {width: number ,color: string, name: string, progress: number, progress2:number}) => {
const paddingRight = 60;
const progressWidth = width - paddingRight;
const progressPercentage = clampValue(progress, 2, 100);
const progress2Percentage = clampValue(progress2, 2, 100);
return (
<>
<text data-testid="lang-name" x="2" y="15" className="lang-name">{name} {progress}%{progress2 > progress ? ` + ${progress2 - progress}%` : ''}</text>
<svg width={progressWidth}>
<rect rx="5" ry="5" x="0" y="25" width={progressWidth} height="8" fill="#ddd" />
<rect
height="8"
fill="#f2b866"
rx="5" ry="5" x="1" y="25"
width={`calc(${progress2Percentage}% - 1px)`}
/>
<rect
height="8"
fill={color}
rx="5" ry="5" x="0" y="25"
data-testid="lang-progress"
width={`${progressPercentage}%`}
/>
</svg>
</>
)
};
const createCompactLangNode = ({ lang, totalSize, x, y }: {lang: data, totalSize: number, x: number, y: number}) => {
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
const color = lang.color || "#858585";
return (
<g transform={`translate(${x}, ${y})`}>
<circle cx="5" cy="6" r="5" fill={color} />
<text data-testid="lang-name" x="15" y="10" className='lang-name'>
{lang.name} {percentage}%
</text>
</g>
)
};
const createLanguageTextNode = ({ langs, totalSize, x, y }: { langs: Array<data>, totalSize: number, x: number, y: number}) => {
return langs.map((lang, index) => {
if (index % 2 === 0) {
return createCompactLangNode({
lang,
x,
y: 12.5 * index + y,
totalSize
});
}
return createCompactLangNode({
lang,
x: 150,
y: 12.5 + 12.5 * index,
totalSize
});
});
};
const lowercaseTrim = (name: string) => name.toLowerCase().trim();
const renderTopLanguages = (topLangs: Record<string, data>, options: parsedQuery = {}) => {
const {
hide_title,
hide_border,
card_width,
title_color,
text_color,
bg_color,
hide,
language_count,
theme,
layout,
} = options;
let langs = Object.values(topLangs);
let langsToHide: Record<string, boolean> = {};
// populate langsToHide map for quick lookup
// while filtering out
if (hide) {
hide.forEach((langName) => {
langsToHide[lowercaseTrim(langName)] = true;
});
}
// filter out langauges to be hidden
langs = langs
.sort((a, b) => b.size - a.size)
.filter((lang) => {
return !langsToHide[lowercaseTrim(lang.name)];
})
.slice(0, language_count || 5);
const totalLanguageSize = langs.reduce((acc, curr) => {
return acc + curr.size;
}, 0);
// returns theme based colors with proper overrides and defaults
const { titleColor, textColor, bgColor } = getCardColors({
title_color,
text_color,
bg_color,
theme,
});
let width = typeof card_width !== 'number' ? 300 : isNaN(card_width) ? 300 : card_width;
let height = 45 + (langs.length + 1) * 40;
let finalLayout: JSX.Element | Array<JSX.Element>;
// RENDER COMPACT LAYOUT
if (layout === "compact") {
width = width + 50;
height = 30 + (langs.length / 2 + 1) * 40;
// progressOffset holds the previous language's width and used to offset the next language
// so that we can stack them one after another, like this: [--][----][---]
let progressOffset = 0;
const compactProgressBar = langs
.map((lang, index) => {
const percentage = parseFloat((
(lang.size / totalLanguageSize) *
(width - 50)
).toFixed(2));
const progress =
percentage < 10 ? percentage + 10 : percentage;
const output = (
<rect
key={index}
mask="url(#rect-mask)"
data-testid="lang-progress"
x={progressOffset}
y="0"
width={progress}
height="8"
fill={lang.color || "#858585"}
/>
)
progressOffset += percentage;
return output;
})
finalLayout = (
<>
<mask id="rect-mask">
<rect x="0" y="0" width={
width - 50
} height="8" fill="white" rx="5" />
</mask>
{compactProgressBar}
{createLanguageTextNode({
x: 0,
y: 25,
langs,
totalSize: totalLanguageSize,
})}
</>
)
finalLayout = (
<>
<mask id="rect-mask">
<rect x="0" y="0" width={width - 50} height="8" fill="white" rx="5" />
</mask>
{compactProgressBar}
{createLanguageTextNode({
x: 0,
y: 25,
langs,
totalSize: totalLanguageSize,
})}
</>
)
} else {
finalLayout = FlexLayout({
items: langs.map((lang) => {
return createProgressNode({
width: width,
name: lang.name,
color: lang.color || "#858585",
progress: parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)),
progress2: parseFloat(((lang.recentSize / totalLanguageSize) * 100).toFixed(2)),
});
}),
gap: 40,
direction: "column",
})
}
const card = new Card(
width,
height,
{
titleColor,
textColor,
bgColor,
},
"Most Used Languages",
)
card.disableAnimations();
card.setHideBorder(hide_border || false);
card.setHideTitle(hide_title || false);
card.setCSS(`
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
`);
return card.render(
<svg data-testid="lang-items" x="25">
{finalLayout}
</svg>
)
};
export default renderTopLanguages