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
13 changed files with 325 additions and 49 deletions
+8
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)
: ""
}
+8 -1
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 } =
+45 -13
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
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;
};
+59 -20
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"),
}),
+5 -5
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
*/
+10
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");
+12 -1
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,
+10
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
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;
};
+4
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(
+6 -5
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 = ({