From d57251cdf1cb5e7b6cea6081147eb9daf8257eef Mon Sep 17 00:00:00 2001 From: Anurag Hazra Date: Wed, 23 Feb 2022 20:15:01 +0530 Subject: [PATCH] refactor(cards): added typings for cards and fetchers (#1596) * refactor(cards): added typings for cards and fetchers * chore: move types to separate file --- api/pin.js | 8 +- src/cards/repo-card.js | 8 ++ src/cards/stats-card.js | 9 ++- src/cards/top-languages-card.js | 58 ++++++++++---- src/cards/types.d.ts | 50 +++++++++++++ src/cards/wakatime-card.js | 79 ++++++++++++++----- src/common/utils.js | 10 +-- src/fetchers/repo-fetcher.js | 10 +++ src/fetchers/stats-fetcher.js | 13 +++- src/fetchers/top-languages-fetcher.js | 10 +++ src/fetchers/types.d.ts | 104 ++++++++++++++++++++++++++ src/fetchers/wakatime-fetcher.js | 4 + src/getStyles.js | 11 +-- 13 files changed, 325 insertions(+), 49 deletions(-) create mode 100644 src/cards/types.d.ts create mode 100644 src/fetchers/types.d.ts diff --git a/api/pin.js b/api/pin.js index 1df7fd0..feffabd 100644 --- a/api/pin.js +++ b/api/pin.js @@ -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; diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js index 5295b17..bb5c178 100644 --- a/src/cards/repo-card.js +++ b/src/cards/repo-card.js @@ -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} 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) : "" } diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index 22e4be7..14fd3e5 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -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} stats + * @param {Partial} 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 } = diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index c8109f3..4e079ff 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -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, })} `; @@ -174,6 +195,12 @@ const calculateNormalLayoutHeight = (totalLangs) => { return 45 + (totalLangs + 1) * 40; }; +/** + * + * @param {Record} 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} 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; diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts new file mode 100644 index 0000000..4580a2e --- /dev/null +++ b/src/cards/types.d.ts @@ -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; +}; diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index 905e40a..5ab2f29 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -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} `; }; +/** + * + * @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} stats + * @param {Partial} 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"), }), diff --git a/src/common/utils.js b/src/common/utils.js index 92018a6..baa93c6 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -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 */ diff --git a/src/fetchers/repo-fetcher.js b/src/fetchers/repo-fetcher.js index 9ddaadf..4490adb 100644 --- a/src/fetchers/repo-fetcher.js +++ b/src/fetchers/repo-fetcher.js @@ -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} + */ async function fetchRepo(username, reponame) { if (!username || !reponame) { throw new Error("Invalid username or reponame"); diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index 1a03b46..1caa62d 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -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} + */ async function fetchStats( username, count_private = false, diff --git a/src/fetchers/top-languages-fetcher.js b/src/fetchers/top-languages-fetcher.js index cd02e64..555b454 100644 --- a/src/fetchers/top-languages-fetcher.js +++ b/src/fetchers/top-languages-fetcher.js @@ -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} + */ async function fetchTopLanguages(username, exclude_repo = []) { if (!username) throw Error("Invalid username"); diff --git a/src/fetchers/types.d.ts b/src/fetchers/types.d.ts new file mode 100644 index 0000000..854e131 --- /dev/null +++ b/src/fetchers/types.d.ts @@ -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; + +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; +}; diff --git a/src/fetchers/wakatime-fetcher.js b/src/fetchers/wakatime-fetcher.js index 92371ff..f8080e8 100644 --- a/src/fetchers/wakatime-fetcher.js +++ b/src/fetchers/wakatime-fetcher.js @@ -1,5 +1,9 @@ const axios = require("axios"); +/** + * @param {{username: string, api_domain: string, range: string}} props + * @returns {Promise} + */ const fetchWakatimeStats = async ({ username, api_domain, range }) => { try { const { data } = await axios.get( diff --git a/src/getStyles.js b/src/getStyles.js index be60b63..e5013e5 100644 --- a/src/getStyles.js +++ b/src/getStyles.js @@ -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 = ({