refactor(cards): added typings for cards and fetchers (#1596)

* refactor(cards): added typings for cards and fetchers

* chore: move types to separate file
This commit is contained in:
Anurag Hazra 2022-02-23 20:15:01 +05:30 committed by GitHub
parent 8b9f9317d8
commit d57251cdf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 325 additions and 49 deletions

View File

@ -47,10 +47,10 @@ module.exports = async (req, res) => {
);
/*
if star count & fork count is over 1k then we are kFormating the text
and if both are zero we are not showing the stats
so we can just make the cache longer, since there is no need to frequent updates
*/
if star count & fork count is over 1k then we are kFormating the text
and if both are zero we are not showing the stats
so we can just make the cache longer, since there is no need to frequent updates
*/
const stars = repoData.starCount;
const forks = repoData.forkCount;
const isBothOver1K = stars > 1000 && forks > 1000;

View File

@ -1,3 +1,4 @@
// @ts-check
const {
kFormatter,
encodeHTML,
@ -65,6 +66,11 @@ const iconWithLabel = (icon, label, testid) => {
return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
};
/**
* @param {import('../fetchers/types').RepositoryData} repo
* @param {Partial<import("./types").RepoCardOptions>} options
* @returns {string}
*/
const renderRepoCard = (repo, options = {}) => {
const {
name,
@ -161,8 +167,10 @@ const renderRepoCard = (repo, options = {}) => {
return card.render(`
${
isTemplate
// @ts-ignore
? getBadgeSVG(i18n.t("repocard.template"), colors.textColor)
: isArchived
// @ts-ignore
? getBadgeSVG(i18n.t("repocard.archived"), colors.textColor)
: ""
}

View File

@ -1,3 +1,4 @@
// @ts-check
const I18n = require("../common/I18n");
const Card = require("../common/Card");
const icons = require("../common/icons");
@ -45,6 +46,12 @@ const createTextNode = ({
`;
};
/**
* @param {Partial<import('../fetchers/types').StatsData>} stats
* @param {Partial<import("./types").StatCardOptions>} options
* @returns {string}
*/
const renderStatsCard = (stats = {}, options = { hide: [] }) => {
const {
name,
@ -75,7 +82,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
disable_animations = false,
} = options;
const lheight = parseInt(line_height, 10);
const lheight = parseInt(String(line_height), 10);
// returns theme based colors with proper overrides and defaults
const { titleColor, textColor, iconColor, bgColor, borderColor } =

View File

@ -1,3 +1,4 @@
// @ts-check
const Card = require("../common/Card");
const I18n = require("../common/I18n");
const { langCardLocales } = require("../translations");
@ -16,6 +17,28 @@ const DEFAULT_LANGS_COUNT = 5;
const DEFAULT_LANG_COLOR = "#858585";
const CARD_PADDING = 25;
/**
* @typedef {import("../fetchers/types").Lang} Lang
*/
/**
* @param {Lang[]} arr
*/
const getLongestLang = (arr) =>
arr.reduce(
(savedLang, lang) =>
lang.name.length > savedLang.name.length ? lang : savedLang,
{ name: "", size: null, color: "" },
);
/**
* @param {{
* width: number,
* color: string,
* name: string,
* progress: string
* }} props
*/
const createProgressTextNode = ({ width, color, name, progress }) => {
const paddingRight = 95;
const progressTextX = width - paddingRight + 10;
@ -35,6 +58,9 @@ const createProgressTextNode = ({ width, color, name, progress }) => {
`;
};
/**
* @param {{ lang: Lang, totalSize: number }} props
*/
const createCompactLangNode = ({ lang, totalSize }) => {
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
const color = lang.color || "#858585";
@ -49,21 +75,19 @@ const createCompactLangNode = ({ lang, totalSize }) => {
`;
};
const getLongestLang = (arr) =>
arr.reduce(
(savedLang, lang) =>
lang.name.length > savedLang.name.length ? lang : savedLang,
{ name: "" },
);
/**
* @param {{ langs: Lang[], totalSize: number }} props
*/
const createLanguageTextNode = ({ langs, totalSize }) => {
const longestLang = getLongestLang(langs);
const chunked = chunkArray(langs, langs.length / 2);
const layouts = chunked.map((array) => {
// @ts-ignore
const items = array.map((lang, index) =>
createCompactLangNode({
lang,
totalSize,
// @ts-ignore
index,
}),
);
@ -84,8 +108,7 @@ const createLanguageTextNode = ({ langs, totalSize }) => {
};
/**
*
* @param {any[]} langs
* @param {Lang[]} langs
* @param {number} width
* @param {number} totalLanguageSize
* @returns {string}
@ -106,8 +129,7 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => {
};
/**
*
* @param {any[]} langs
* @param {Lang[]} langs
* @param {number} width
* @param {number} totalLanguageSize
* @returns {string}
@ -152,7 +174,6 @@ const renderCompactLayout = (langs, width, totalLanguageSize) => {
${createLanguageTextNode({
langs,
totalSize: totalLanguageSize,
width,
})}
</g>
`;
@ -174,6 +195,12 @@ const calculateNormalLayoutHeight = (totalLangs) => {
return 45 + (totalLangs + 1) * 40;
};
/**
*
* @param {Record<string, Lang>} topLangs
* @param {string[]} hide
* @param {string} langs_count
*/
const useLanguages = (topLangs, hide, langs_count) => {
let langs = Object.values(topLangs);
let langsToHide = {};
@ -200,6 +227,11 @@ const useLanguages = (topLangs, hide, langs_count) => {
return { langs, totalLanguageSize };
};
/**
* @param {import('../fetchers/types').TopLangData} topLangs
* @param {Partial<import("./types").TopLangOptions>} options
* @returns {string}
*/
const renderTopLanguages = (topLangs, options = {}) => {
const {
hide_title,
@ -226,7 +258,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
const { langs, totalLanguageSize } = useLanguages(
topLangs,
hide,
langs_count,
String(langs_count),
);
let width = isNaN(card_width) ? DEFAULT_CARD_WIDTH : card_width;

50
src/cards/types.d.ts vendored Normal file
View File

@ -0,0 +1,50 @@
type ThemeNames = keyof typeof import("../../themes");
export type CommonOptions = {
title_color: string;
icon_color: string;
text_color: string;
bg_color: string;
theme: ThemeNames;
border_radius: number;
border_color: string;
locale: string;
};
export type StatCardOptions = CommonOptions & {
hide: string[];
show_icons: boolean;
hide_title: boolean;
hide_border: boolean;
hide_rank: boolean;
include_all_commits: boolean;
line_height: number | string;
custom_title: string;
disable_animations: boolean;
};
export type RepoCardOptions = CommonOptions & {
hide_border: boolean;
show_owner: boolean;
};
export type TopLangOptions = CommonOptions & {
hide_title: boolean;
hide_border: boolean;
card_width: number;
hide: string[];
layout: "compact" | "normal";
custom_title: string;
langs_count: number;
};
type WakaTimeOptions = CommonOptions & {
hide_title: boolean;
hide_border: boolean;
hide: string[];
line_height: string;
hide_progress: boolean;
custom_title: string;
layout: "compact" | "normal";
langs_count: number;
};

View File

@ -1,3 +1,4 @@
// @ts-check
const Card = require("../common/Card");
const I18n = require("../common/I18n");
const { getStyles } = require("../getStyles");
@ -11,12 +12,24 @@ const {
lowercaseTrim,
} = require("../common/utils");
/**
* @param {{color: string, text: string}} param0
*/
const noCodingActivityNode = ({ color, text }) => {
return `
<text x="25" y="11" class="stat bold" fill="${color}">${text}</text>
`;
};
/**
*
* @param {{
* lang: import("../fetchers/types").WakaTimeLang,
* totalSize: number,
* x: number,
* y: number
* }} props
*/
const createCompactLangNode = ({ lang, totalSize, x, y }) => {
const color = languageColors[lang.name] || "#858585";
@ -30,6 +43,14 @@ const createCompactLangNode = ({ lang, totalSize, x, y }) => {
`;
};
/**
* @param {{
* langs: import("../fetchers/types").WakaTimeLang[],
* totalSize: number,
* x: number,
* y: number
* }} props
*/
const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
return langs.map((lang, index) => {
if (index % 2 === 0) {
@ -38,7 +59,6 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
x: 25,
y: 12.5 * index + y,
totalSize,
index,
});
}
return createCompactLangNode({
@ -46,11 +66,23 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
x: 230,
y: 12.5 + 12.5 * index,
totalSize,
index,
});
});
};
/**
*
* @param {{
* id: string;
* label: string;
* value: string;
* index: number;
* percent: number;
* hideProgress: boolean;
* progressBarColor: string;
* progressBarBackgroundColor: string
* }} props
*/
const createTextNode = ({
id,
label,
@ -71,6 +103,7 @@ const createTextNode = ({
progress: percent,
color: progressBarColor,
width: 220,
// @ts-ignore
name: label,
progressBarBackgroundColor,
});
@ -88,6 +121,9 @@ const createTextNode = ({
`;
};
/**
* @param {import("../fetchers/types").WakaTimeLang[]} languages
*/
const recalculatePercentages = (languages) => {
// recalculating percentages so that,
// compact layout's progress bar does not break when hiding languages
@ -95,12 +131,17 @@ const recalculatePercentages = (languages) => {
(totalSum, language) => totalSum + language.percent,
0,
);
const weight = (100 / totalSum).toFixed(2);
const weight = +(100 / totalSum).toFixed(2);
languages.forEach((language) => {
language.percent = (language.percent * weight).toFixed(2);
language.percent = +(language.percent * weight).toFixed(2);
});
};
/**
* @param {Partial<import('../fetchers/types').WakaTimeData>} stats
* @param {Partial<import('./types').WakaTimeOptions>} options
* @returns {string}
*/
const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
let { languages } = stats;
const {
@ -136,25 +177,20 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
translations: wakatimeCardLocales,
});
const lheight = parseInt(line_height, 10);
const lheight = parseInt(String(line_height), 10);
const langsCount = clampValue(parseInt(langs_count), 1, langs_count);
const langsCount = clampValue(parseInt(String(langs_count)), 1, langs_count);
// returns theme based colors with proper overrides and defaults
const {
titleColor,
textColor,
iconColor,
bgColor,
borderColor,
} = getCardColors({
title_color,
icon_color,
text_color,
bg_color,
border_color,
theme,
});
const { titleColor, textColor, iconColor, bgColor, borderColor } =
getCardColors({
title_color,
icon_color,
text_color,
bg_color,
border_color,
theme,
});
const filteredLanguages = languages
? languages
@ -228,13 +264,16 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
label: language.name,
value: language.text,
percent: language.percent,
// @ts-ignore
progressBarColor: titleColor,
// @ts-ignore
progressBarBackgroundColor: textColor,
hideProgress: hide_progress,
});
})
: [
noCodingActivityNode({
// @ts-ignore
color: textColor,
text: i18n.t("wakatimecard.nocodingactivity"),
}),

View File

@ -161,11 +161,11 @@ function flexLayout({ items, gap, direction, sizes = [] }) {
/**
* @typedef {object} CardColors
* @prop {string} title_color
* @prop {string} text_color
* @prop {string} icon_color
* @prop {string} bg_color
* @prop {string} border_color
* @prop {string?=} title_color
* @prop {string?=} text_color
* @prop {string?=} icon_color
* @prop {string?=} bg_color
* @prop {string?=} border_color
* @prop {keyof typeof import('../../themes')?=} fallbackTheme
* @prop {keyof typeof import('../../themes')?=} theme
*/

View File

@ -1,6 +1,11 @@
// @ts-check
const retryer = require("../common/retryer");
const { request } = require("../common/utils");
/**
* @param {import('Axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const fetcher = (variables, token) => {
return request(
{
@ -43,6 +48,11 @@ const fetcher = (variables, token) => {
);
};
/**
* @param {string} username
* @param {string} reponame
* @returns {Promise<import("./types").RepositoryData>}
*/
async function fetchRepo(username, reponame) {
if (!username || !reponame) {
throw new Error("Invalid username or reponame");

View File

@ -1,4 +1,5 @@
const axios = require("axios");
// @ts-check
const axios = require("axios").default;
const githubUsernameRegex = require("github-username-regex");
const retryer = require("../common/retryer");
@ -7,6 +8,10 @@ const { request, logger, CustomError } = require("../common/utils");
require("dotenv").config();
/**
* @param {import('axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const fetcher = (variables, token) => {
return request(
{
@ -87,6 +92,12 @@ const totalCommitsFetcher = async (username) => {
}
};
/**
* @param {string} username
* @param {boolean} count_private
* @param {boolean} include_all_commits
* @returns {Promise<import("./types").StatsData>}
*/
async function fetchStats(
username,
count_private = false,

View File

@ -1,7 +1,12 @@
// @ts-check
const { request, logger } = require("../common/utils");
const retryer = require("../common/retryer");
require("dotenv").config();
/**
* @param {import('Axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const fetcher = (variables, token) => {
return request(
{
@ -34,6 +39,11 @@ const fetcher = (variables, token) => {
);
};
/**
* @param {string} username
* @param {string[]} exclude_repo
* @returns {Promise<import("./types").TopLangData>}
*/
async function fetchTopLanguages(username, exclude_repo = []) {
if (!username) throw Error("Invalid username");

104
src/fetchers/types.d.ts vendored Normal file
View File

@ -0,0 +1,104 @@
export type RepositoryData = {
name: string;
nameWithOwner: string;
isPrivate: boolean;
isArchived: boolean;
isTemplate: boolean;
stargazers: { totalCount: number };
description: string;
primaryLanguage: {
color: string;
id: string;
name: string;
};
forkCount: number;
starCount: number;
};
export type StatsData = {
name: string;
totalPRs: number;
totalCommits: number;
totalIssues: number;
totalStars: number;
contributedTo: number;
rank: { level: string; score: number };
};
export type Lang = {
name: string;
color: string;
size: number;
};
export type TopLangData = Record<string, Lang>;
export type WakaTimeData = {
categories: {
digital: string;
hours: number;
minutes: number;
name: string;
percent: number;
text: string;
total_seconds: number;
}[];
daily_average: number;
daily_average_including_other_language: number;
days_including_holidays: number;
days_minus_holidays: number;
editors: {
digital: string;
hours: number;
minutes: number;
name: string;
percent: number;
text: string;
total_seconds: number;
}[];
holidays: number;
human_readable_daily_average: string;
human_readable_daily_average_including_other_language: string;
human_readable_total: string;
human_readable_total_including_other_language: string;
id: string;
is_already_updating: boolean;
is_coding_activity_visible: boolean;
is_including_today: boolean;
is_other_usage_visible: boolean;
is_stuck: boolean;
is_up_to_date: boolean;
languages: {
digital: string;
hours: number;
minutes: number;
name: string;
percent: number;
text: string;
total_seconds: number;
}[];
operating_systems: {
digital: string;
hours: number;
minutes: number;
name: string;
percent: number;
text: string;
total_seconds: number;
}[];
percent_calculated: number;
range: string;
status: string;
timeout: number;
total_seconds: number;
total_seconds_including_other_language: number;
user_id: string;
username: string;
writes_only: boolean;
};
export type WakaTimeLang = {
name: string;
text: string;
percent: number;
};

View File

@ -1,5 +1,9 @@
const axios = require("axios");
/**
* @param {{username: string, api_domain: string, range: string}} props
* @returns {Promise<WakaTimeData>}
*/
const fetchWakatimeStats = async ({ username, api_domain, range }) => {
try {
const { data } = await axios.get(

View File

@ -1,3 +1,4 @@
// @ts-check
/**
* @param {number} value
*/
@ -53,11 +54,11 @@ const getAnimations = () => {
/**
* @param {{
* titleColor: string;
* textColor: string;
* iconColor: string;
* show_icons: boolean;
* progress: number;
* titleColor?: string | string[]
* textColor?: string | string[]
* iconColor?: string | string[]
* show_icons?: boolean;
* progress?: number;
* }} args
*/
const getStyles = ({