mirror of
https://github.com/anuraghazra/github-readme-stats.git
synced 2024-12-15 06:04:17 +08:00
feat: added Top languages card
This commit is contained in:
parent
938df3f7ea
commit
411d8f1ffa
44
api/top-langs.js
Normal file
44
api/top-langs.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
require("dotenv").config();
|
||||||
|
const { renderError, clampValue, CONSTANTS } = require("../src/utils");
|
||||||
|
const fetchTopLanguages = require("../src/fetchTopLanguages");
|
||||||
|
const renderTopLanguages = require("../src/renderTopLanguages");
|
||||||
|
|
||||||
|
module.exports = async (req, res) => {
|
||||||
|
const {
|
||||||
|
username,
|
||||||
|
card_width,
|
||||||
|
title_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
theme,
|
||||||
|
cache_seconds,
|
||||||
|
} = req.query;
|
||||||
|
let topLangs;
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "image/svg+xml");
|
||||||
|
|
||||||
|
try {
|
||||||
|
topLangs = await fetchTopLanguages(username);
|
||||||
|
} catch (err) {
|
||||||
|
return res.send(renderError(err.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheSeconds = clampValue(
|
||||||
|
parseInt(cache_seconds || CONSTANTS.THIRTY_MINUTES, 10),
|
||||||
|
CONSTANTS.THIRTY_MINUTES,
|
||||||
|
CONSTANTS.ONE_DAY
|
||||||
|
);
|
||||||
|
|
||||||
|
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
|
||||||
|
|
||||||
|
res.send(
|
||||||
|
renderTopLanguages(topLangs, {
|
||||||
|
theme,
|
||||||
|
card_width: parseInt(card_width, 10),
|
||||||
|
title_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
theme,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
84
src/fetchTopLanguages.js
Normal file
84
src/fetchTopLanguages.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
const { request } = require("./utils");
|
||||||
|
const retryer = require("./retryer");
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
const fetcher = (variables, token) => {
|
||||||
|
return request(
|
||||||
|
{
|
||||||
|
query: `
|
||||||
|
query userInfo($login: String!) {
|
||||||
|
user(login: $login) {
|
||||||
|
repositories(isFork: false, first: 100) {
|
||||||
|
nodes {
|
||||||
|
languages(first: 1) {
|
||||||
|
edges {
|
||||||
|
size
|
||||||
|
node {
|
||||||
|
color
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Authorization: `bearer ${token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fetchTopLanguages(username) {
|
||||||
|
if (!username) throw Error("Invalid username");
|
||||||
|
|
||||||
|
let res = await retryer(fetcher, { login: username });
|
||||||
|
|
||||||
|
if (res.data.errors) {
|
||||||
|
console.log(res.data.errors);
|
||||||
|
throw Error(res.data.errors[0].message || "Could not fetch user");
|
||||||
|
}
|
||||||
|
|
||||||
|
let repoNodes = res.data.data.user.repositories.nodes;
|
||||||
|
|
||||||
|
// TODO: perf improvement
|
||||||
|
repoNodes = repoNodes
|
||||||
|
.filter((node) => {
|
||||||
|
return node.languages.edges.length > 0;
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
return b.languages.edges[0].size - a.languages.edges[0].size;
|
||||||
|
})
|
||||||
|
.map((node) => {
|
||||||
|
return node.languages.edges[0];
|
||||||
|
})
|
||||||
|
.reduce((acc, prev) => {
|
||||||
|
let langSize = prev.size;
|
||||||
|
if (acc[prev.node.name] && prev.node.name === acc[prev.node.name].name) {
|
||||||
|
langSize = prev.size + acc[prev.node.name].size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[prev.node.name]: {
|
||||||
|
name: prev.node.name,
|
||||||
|
color: prev.node.color,
|
||||||
|
size: langSize,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const topLangs = Object.keys(repoNodes)
|
||||||
|
.slice(0, 5)
|
||||||
|
.reduce((result, key) => {
|
||||||
|
result[key] = repoNodes[key];
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return topLangs;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = fetchTopLanguages;
|
67
src/renderTopLanguages.js
Normal file
67
src/renderTopLanguages.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const { getCardColors, FlexLayout, clampValue } = require("../src/utils");
|
||||||
|
|
||||||
|
const createProgressNode = ({ width, color, name, progress }) => {
|
||||||
|
const paddingRight = 95;
|
||||||
|
const progressTextX = width - paddingRight + 10;
|
||||||
|
const progressWidth = width - paddingRight;
|
||||||
|
const progressPercentage = clampValue(progress, 2, 100);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<text x="2" y="15" class="lang-name">${name}</text>
|
||||||
|
<text x="${progressTextX}" y="34" class="lang-name">${progress}%</text>
|
||||||
|
<svg width="${progressWidth}">
|
||||||
|
<rect rx="5" ry="5" x="0" y="25" width="${progressWidth}" height="8" fill="#ddd"></rect>
|
||||||
|
<rect rx="5" ry="5" x="0" y="25" width="${progressPercentage}%" height="8" fill="${color}"></rect>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTopLanguages = (topLangs, options = {}) => {
|
||||||
|
const { title_color, text_color, bg_color, theme, card_width } = options;
|
||||||
|
|
||||||
|
const langs = Object.values(topLangs);
|
||||||
|
|
||||||
|
const totalSize = 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,
|
||||||
|
});
|
||||||
|
|
||||||
|
const width = isNaN(card_width) ? 300 : card_width;
|
||||||
|
const height = 45 + (langs.length + 1) * 40;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<style>
|
||||||
|
.header { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${titleColor} }
|
||||||
|
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
||||||
|
</style>
|
||||||
|
<rect data-testid="card-bg" x="0.5" y="0.5" width="99.7%" height="99%" rx="4.5" fill="${bgColor}" stroke="#E4E2E2"/>
|
||||||
|
|
||||||
|
<text x="25" y="35" class="header">Top Languages</text>
|
||||||
|
|
||||||
|
<svg x="25" y="55">
|
||||||
|
${FlexLayout({
|
||||||
|
items: langs.map((lang) => {
|
||||||
|
return createProgressNode({
|
||||||
|
width: width,
|
||||||
|
name: lang.name,
|
||||||
|
color: lang.color || "#858585",
|
||||||
|
progress: ((lang.size / totalSize) * 100).toFixed(2),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
gap: 40,
|
||||||
|
direction: "column",
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = renderTopLanguages;
|
Loading…
Reference in New Issue
Block a user