From ad46677119943e2b090c305f15b3869b6809b4af Mon Sep 17 00:00:00 2001 From: SemUijen Date: Sat, 9 Nov 2024 12:07:05 +0100 Subject: [PATCH 1/4] Added RepoLanguages to repo fetcher --- src/fetchers/repo-fetcher.js | 48 ++++++++++++++++++++++++++++++++++++ src/fetchers/types.d.ts | 5 ++++ tests/fetchRepo.test.js | 8 ++++++ 3 files changed, 61 insertions(+) diff --git a/src/fetchers/repo-fetcher.js b/src/fetchers/repo-fetcher.js index 6438f8895cfb6..12090a6ed4c57 100644 --- a/src/fetchers/repo-fetcher.js +++ b/src/fetchers/repo-fetcher.js @@ -55,6 +55,38 @@ const fetcher = (variables, token) => { }, ); }; +/** + * Language data fetcher. + * + * @param {AxiosRequestHeaders} variables Fetcher variables. + * @param {string} token GitHub token. + * @returns {Promise} The response. + */ +const fetcherLanguage = (variables, token) => { + return request( + { + query: ` + query getRepoLanguages($login: String!, $repo: String!) { + repository(owner: $login, name: $repo) { + languages(first: 10) { + edges { + node { + name + color + } + size + } + } + } + } + `, + variables, + }, + { + Authorization: `token ${token}`, + }, + ); +}; const urlExample = "/api/pin?username=USERNAME&repo=REPO_NAME"; @@ -91,6 +123,20 @@ const fetchRepo = async (username, reponame) => { const isUser = data.organization === null && data.user; const isOrg = data.user === null && data.organization; + let resLanguage = await retryer(fetcherLanguage, { + login: username, + repo: reponame, + }); + + const languages = resLanguage.data.data.repository?.languages.edges.map( + (edge) => ({ + name: edge.node.name, + color: edge.node.color, + size: edge.size, + }), + ); + + console.log(languages); if (isUser) { if (!data.user.repository || data.user.repository.isPrivate) { throw new Error("User Repository Not found"); @@ -98,6 +144,7 @@ const fetchRepo = async (username, reponame) => { return { ...data.user.repository, starCount: data.user.repository.stargazers.totalCount, + languagesBreakdown: languages, }; } @@ -111,6 +158,7 @@ const fetchRepo = async (username, reponame) => { return { ...data.organization.repository, starCount: data.organization.repository.stargazers.totalCount, + languagesBreakdown: languages, }; } diff --git a/src/fetchers/types.d.ts b/src/fetchers/types.d.ts index affb407b816b0..7512a25e0139c 100644 --- a/src/fetchers/types.d.ts +++ b/src/fetchers/types.d.ts @@ -20,6 +20,11 @@ export type RepositoryData = { id: string; name: string; }; + languagesBreakdown: { + color: string; + size: int; + name: string; + }[]; forkCount: number; starCount: number; }; diff --git a/tests/fetchRepo.test.js b/tests/fetchRepo.test.js index a980917f3d628..3f0c47be6d583 100644 --- a/tests/fetchRepo.test.js +++ b/tests/fetchRepo.test.js @@ -15,6 +15,12 @@ const data_repo = { name: "TypeScript", }, forkCount: 100, + languageBreakdown: [ + { name: "HTML", color: "#e34c26", size: 1808 }, + { name: "TypeScript", color: "#3178c6", size: 296797 }, + { name: "CSS", color: "#563d7c", size: 787 }, + { name: "JavaScript", color: "#f1e05a", size: 4989 }, + ], }, }; @@ -47,6 +53,7 @@ describe("Test fetchRepo", () => { expect(repo).toStrictEqual({ ...data_repo.repository, starCount: data_repo.repository.stargazers.totalCount, + languagesBreakdown: data_repo.languageBreakdown, }); }); @@ -57,6 +64,7 @@ describe("Test fetchRepo", () => { expect(repo).toStrictEqual({ ...data_repo.repository, starCount: data_repo.repository.stargazers.totalCount, + languagesBreakdown: data_repo.languageBreakdown, }); }); From 2753d0a2de81615f5c2f9ea633212f8579ffcb1e Mon Sep 17 00:00:00 2001 From: SemUijen Date: Sun, 10 Nov 2024 15:31:58 +0100 Subject: [PATCH 2/4] Added multiple Lang feature to repocard --- api/pin.js | 7 +++ src/cards/repo-card.js | 84 ++++++++++++++++++++++++++------- src/cards/top-languages-card.js | 1 + src/cards/types.d.ts | 4 ++ src/fetchers/repo-fetcher.js | 28 +++++++---- src/fetchers/types.d.ts | 6 +-- tests/fetchRepo.test.js | 77 ++++++++++++++++++++++++------ tests/pin.test.js | 62 +++++++++++++++++++++--- tests/renderRepoCard.test.js | 17 +++++++ 9 files changed, 233 insertions(+), 53 deletions(-) diff --git a/api/pin.js b/api/pin.js index bede7d87f5972..ddc56cf694d1c 100644 --- a/api/pin.js +++ b/api/pin.js @@ -5,6 +5,7 @@ import { CONSTANTS, parseBoolean, renderError, + parseArray, } from "../src/common/utils.js"; import { fetchRepo } from "../src/fetchers/repo-fetcher.js"; import { isLocaleAvailable } from "../src/translations.js"; @@ -25,6 +26,9 @@ export default async (req, res) => { border_radius, border_color, description_lines_count, + hide, + langs_count, + hide_progress, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -83,6 +87,9 @@ export default async (req, res) => { show_owner: parseBoolean(show_owner), locale: locale ? locale.toLowerCase() : null, description_lines_count, + hide: parseArray(hide), + langs_count, + hide_progress, }), ); } catch (err) { diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js index bbfda52d47778..70bc06b351f87 100644 --- a/src/cards/repo-card.js +++ b/src/cards/repo-card.js @@ -15,11 +15,16 @@ import { clampValue, } from "../common/utils.js"; import { repoCardLocales } from "../translations.js"; +import { + renderCompactLayout, + trimTopLanguages, + getDefaultLanguagesCountByLayout, +} from "./top-languages-card.js"; const ICON_SIZE = 16; const DESCRIPTION_LINE_WIDTH = 59; const DESCRIPTION_MAX_LINES = 3; - +const CARD_PADDING = 25; /** * Retrieves the repository description and wraps it to fit the card width. * @@ -64,6 +69,7 @@ const renderRepoCard = (repo, options = {}) => { isTemplate, starCount, forkCount, + languagesBreakdown, } = repo; const { hide_border = false, @@ -77,8 +83,18 @@ const renderRepoCard = (repo, options = {}) => { border_color, locale, description_lines_count, + hide_progress, + layout = "compact", + langs_count = getDefaultLanguagesCountByLayout({ layout, hide_progress }), + hide, } = options; + const { langs, totalLanguageSize } = trimTopLanguages( + languagesBreakdown, + langs_count, + hide, + ); + const lineHeight = 10; const header = show_owner ? nameWithOwner : name; const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified"; @@ -149,6 +165,13 @@ const renderRepoCard = (repo, options = {}) => { gap: 25, }).join(""); + const languageBar = renderCompactLayout( + langs, + 400, + totalLanguageSize, + hide_progress, + ); + const card = new Card({ defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header, titlePrefixIcon: icons.contribs, @@ -167,26 +190,51 @@ const renderRepoCard = (repo, options = {}) => { .icon { fill: ${colors.iconColor} } .badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; } .badge rect { opacity: 0.2 } + .lang-name { font: 400 11px "Segoe UI", Ubuntu, Sans-Serif; fill: ${colors.textColor} } `); - return card.render(` - ${ - isTemplate - ? // @ts-ignore - getBadgeSVG(i18n.t("repocard.template"), colors.textColor) - : isArchived + const temp = true; + if (temp) { + return card.render(` + ${ + isTemplate ? // @ts-ignore - getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) - : "" - } - - - ${descriptionSvg} - - - - ${starAndForkCount} - + getBadgeSVG(i18n.t("repocard.template"), colors.textColor) + : isArchived + ? // @ts-ignore + getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) + : "" + } + + + ${descriptionSvg} + + + + ${starAndForkCount} + + + `); + } + return card.render(` + ${ + isTemplate + ? // @ts-ignore + getBadgeSVG(i18n.t("repocard.template"), colors.textColor) + : isArchived + ? // @ts-ignore + getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) + : "" + } + + + ${descriptionSvg} + + + + ${languageBar} + + `); }; diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index 9385f4a7ebed3..749bffd290ab7 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -871,6 +871,7 @@ const renderTopLanguages = (topLangs, options = {}) => { }; export { + renderCompactLayout, getLongestLang, degreesToRadians, radiansToDegrees, diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index 9a21be4a0160a..b637bba1d359f 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -33,6 +33,10 @@ export type StatCardOptions = CommonOptions & { export type RepoCardOptions = CommonOptions & { show_owner: boolean; description_lines_count: number; + hide_progress: boolean; + layout: "compact"; + langs_count: number; + hide: string[]; }; export type TopLangOptions = CommonOptions & { diff --git a/src/fetchers/repo-fetcher.js b/src/fetchers/repo-fetcher.js index 12090a6ed4c57..5acb72cc1d401 100644 --- a/src/fetchers/repo-fetcher.js +++ b/src/fetchers/repo-fetcher.js @@ -128,15 +128,25 @@ const fetchRepo = async (username, reponame) => { repo: reponame, }); - const languages = resLanguage.data.data.repository?.languages.edges.map( - (edge) => ({ - name: edge.node.name, - color: edge.node.color, - size: edge.size, - }), + const data_languages = resLanguage.data.data; + + const toplanguages = data_languages.repository.languages.edges.reduce( + (acc, edge) => { + const { name, color } = edge.node; + const size = edge.size; + + if (acc[name]) { + acc[name].size += size; + acc[name].count += 1; + } else { + acc[name] = { name, color, size, count: 1 }; + } + + return acc; + }, + {}, ); - console.log(languages); if (isUser) { if (!data.user.repository || data.user.repository.isPrivate) { throw new Error("User Repository Not found"); @@ -144,7 +154,7 @@ const fetchRepo = async (username, reponame) => { return { ...data.user.repository, starCount: data.user.repository.stargazers.totalCount, - languagesBreakdown: languages, + languagesBreakdown: toplanguages, }; } @@ -158,7 +168,7 @@ const fetchRepo = async (username, reponame) => { return { ...data.organization.repository, starCount: data.organization.repository.stargazers.totalCount, - languagesBreakdown: languages, + languagesBreakdown: toplanguages, }; } diff --git a/src/fetchers/types.d.ts b/src/fetchers/types.d.ts index 7512a25e0139c..31dc927c7e793 100644 --- a/src/fetchers/types.d.ts +++ b/src/fetchers/types.d.ts @@ -20,11 +20,7 @@ export type RepositoryData = { id: string; name: string; }; - languagesBreakdown: { - color: string; - size: int; - name: string; - }[]; + languagesBreakdown: TopLangData; forkCount: number; starCount: number; }; diff --git a/tests/fetchRepo.test.js b/tests/fetchRepo.test.js index 3f0c47be6d583..44e762923c452 100644 --- a/tests/fetchRepo.test.js +++ b/tests/fetchRepo.test.js @@ -15,12 +15,29 @@ const data_repo = { name: "TypeScript", }, forkCount: 100, - languageBreakdown: [ - { name: "HTML", color: "#e34c26", size: 1808 }, - { name: "TypeScript", color: "#3178c6", size: 296797 }, - { name: "CSS", color: "#563d7c", size: 787 }, - { name: "JavaScript", color: "#f1e05a", size: 4989 }, - ], + }, +}; + +const data_languages = { + languagesBreakdown: { + HTML: { + color: "#0f0", + name: "HTML", + size: 200, + count: 1, + }, + javascript: { + color: "#0ff", + name: "javascript", + size: 200, + count: 1, + }, + css: { + color: "#ff0", + name: "css", + size: 100, + count: 1, + }, }, }; @@ -38,6 +55,19 @@ const data_org = { }, }; +const data_languages_resp = { + data: { + repository: { + languages: { + edges: [ + { node: { name: "HTML", color: "#0f0" }, size: 200 }, + { node: { name: "javascript", color: "#0ff" }, size: 200 }, + { node: { name: "css", color: "#ff0" }, size: 100 }, + ], + }, + }, + }, +}; const mock = new MockAdapter(axios); afterEach(() => { @@ -46,32 +76,42 @@ afterEach(() => { describe("Test fetchRepo", () => { it("should fetch correct user repo", async () => { - mock.onPost("https://api.github.com/graphql").reply(200, data_user); + mock.onPost("https://api.github.com/graphql").replyOnce(200, data_user); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_languages_resp); let repo = await fetchRepo("anuraghazra", "convoychat"); + console.log(repo); expect(repo).toStrictEqual({ ...data_repo.repository, + languagesBreakdown: data_languages.languagesBreakdown, starCount: data_repo.repository.stargazers.totalCount, - languagesBreakdown: data_repo.languageBreakdown, }); }); it("should fetch correct org repo", async () => { - mock.onPost("https://api.github.com/graphql").reply(200, data_org); + mock.onPost("https://api.github.com/graphql").replyOnce(200, data_org); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_languages_resp); let repo = await fetchRepo("anuraghazra", "convoychat"); expect(repo).toStrictEqual({ ...data_repo.repository, starCount: data_repo.repository.stargazers.totalCount, - languagesBreakdown: data_repo.languageBreakdown, + languagesBreakdown: data_languages.languagesBreakdown, }); }); it("should throw error if user is found but repo is null", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, { + data: { user: { repository: null }, organization: null }, + }); mock .onPost("https://api.github.com/graphql") - .reply(200, { data: { user: { repository: null }, organization: null } }); + .replyOnce(200, data_languages_resp); await expect(fetchRepo("anuraghazra", "convoychat")).rejects.toThrow( "User Repository Not found", @@ -79,9 +119,12 @@ describe("Test fetchRepo", () => { }); it("should throw error if org is found but repo is null", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, { + data: { user: null, organization: { repository: null } }, + }); mock .onPost("https://api.github.com/graphql") - .reply(200, { data: { user: null, organization: { repository: null } } }); + .replyOnce(200, data_languages_resp); await expect(fetchRepo("anuraghazra", "convoychat")).rejects.toThrow( "Organization Repository Not found", @@ -91,7 +134,10 @@ describe("Test fetchRepo", () => { it("should throw error if both user & org data not found", async () => { mock .onPost("https://api.github.com/graphql") - .reply(200, { data: { user: null, organization: null } }); + .replyOnce(200, { data: { user: null, organization: null } }); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_languages_resp); await expect(fetchRepo("anuraghazra", "convoychat")).rejects.toThrow( "Not found", @@ -99,12 +145,15 @@ describe("Test fetchRepo", () => { }); it("should throw error if repository is private", async () => { - mock.onPost("https://api.github.com/graphql").reply(200, { + mock.onPost("https://api.github.com/graphql").replyOnce(200, { data: { user: { repository: { ...data_repo, isPrivate: true } }, organization: null, }, }); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_languages_resp); await expect(fetchRepo("anuraghazra", "convoychat")).rejects.toThrow( "User Repository Not found", diff --git a/tests/pin.test.js b/tests/pin.test.js index 2583ddfe9e9af..daad7ad1fe925 100644 --- a/tests/pin.test.js +++ b/tests/pin.test.js @@ -22,6 +22,23 @@ const data_repo = { }, forkCount: 100, isTemplate: false, + languagesBreakdown: { + HTML: { + color: "#0f0", + name: "HTML", + size: 200, + }, + javascript: { + color: "#0ff", + name: "javascript", + size: 200, + }, + css: { + color: "#ff0", + name: "css", + size: 100, + }, + }, }, }; @@ -31,6 +48,19 @@ const data_user = { organization: null, }, }; +const data_languages_resp = { + data: { + repository: { + languages: { + edges: [ + { node: { name: "HTML", color: "#0f0" }, size: 200 }, + { node: { name: "javascript", color: "#0ff" }, size: 200 }, + { node: { name: "css", color: "#ff0" }, size: 100 }, + ], + }, + }, + }, +}; const mock = new MockAdapter(axios); @@ -50,8 +80,10 @@ describe("Test /api/pin", () => { setHeader: jest.fn(), send: jest.fn(), }; - mock.onPost("https://api.github.com/graphql").reply(200, data_user); - + mock.onPost("https://api.github.com/graphql").replyOnce(200, data_user); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_languages_resp); await pin(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); @@ -79,7 +111,11 @@ describe("Test /api/pin", () => { setHeader: jest.fn(), send: jest.fn(), }; - mock.onPost("https://api.github.com/graphql").reply(200, data_user); + // To Api calls + mock.onPost("https://api.github.com/graphql").replyOnce(200, data_user); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_languages_resp); await pin(req, res); @@ -106,9 +142,12 @@ describe("Test /api/pin", () => { setHeader: jest.fn(), send: jest.fn(), }; + mock.onPost("https://api.github.com/graphql").replyOnce(200, { + data: { user: { repository: null }, organization: null }, + }); mock .onPost("https://api.github.com/graphql") - .reply(200, { data: { user: { repository: null }, organization: null } }); + .replyOnce(200, data_languages_resp); await pin(req, res); @@ -127,9 +166,12 @@ describe("Test /api/pin", () => { setHeader: jest.fn(), send: jest.fn(), }; + mock.onPost("https://api.github.com/graphql").replyOnce(200, { + data: { user: null, organization: { repository: null } }, + }); mock .onPost("https://api.github.com/graphql") - .reply(200, { data: { user: null, organization: { repository: null } } }); + .replyOnce(200, data_languages_resp); await pin(req, res); @@ -150,7 +192,10 @@ describe("Test /api/pin", () => { setHeader: jest.fn(), send: jest.fn(), }; - mock.onPost("https://api.github.com/graphql").reply(200, data_user); + mock.onPost("https://api.github.com/graphql").replyOnce(200, data_user); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_languages_resp); await pin(req, res); @@ -172,7 +217,10 @@ describe("Test /api/pin", () => { setHeader: jest.fn(), send: jest.fn(), }; - mock.onPost("https://api.github.com/graphql").reply(200, data_user); + mock.onPost("https://api.github.com/graphql").replyOnce(200, data_user); + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, data_languages_resp); await pin(req, res); diff --git a/tests/renderRepoCard.test.js b/tests/renderRepoCard.test.js index abbad4dbe2a3b..e78cbfd1cadbd 100644 --- a/tests/renderRepoCard.test.js +++ b/tests/renderRepoCard.test.js @@ -18,6 +18,23 @@ const data_repo = { }, starCount: 38000, forkCount: 100, + languagesBreakdown: { + HTML: { + color: "#0f0", + name: "HTML", + size: 200, + }, + javascript: { + color: "#0ff", + name: "javascript", + size: 200, + }, + css: { + color: "#ff0", + name: "css", + size: 100, + }, + }, }, }; From 204c1d3ec657d6b6ed87518cb50c4c3257030857 Mon Sep 17 00:00:00 2001 From: SemUijen Date: Sun, 10 Nov 2024 15:54:19 +0100 Subject: [PATCH 3/4] added show_lang_bar tp switch beteen old and new RepoCard --- api/pin.js | 4 +++- src/cards/repo-card.js | 51 +++++++++++++++++++++--------------------- src/cards/types.d.ts | 1 + 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/api/pin.js b/api/pin.js index ddc56cf694d1c..5595394a5b460 100644 --- a/api/pin.js +++ b/api/pin.js @@ -26,6 +26,7 @@ export default async (req, res) => { border_radius, border_color, description_lines_count, + show_lang_bar, hide, langs_count, hide_progress, @@ -87,9 +88,10 @@ export default async (req, res) => { show_owner: parseBoolean(show_owner), locale: locale ? locale.toLowerCase() : null, description_lines_count, + show_lang_bar: parseBoolean(show_lang_bar), hide: parseArray(hide), langs_count, - hide_progress, + hide_progress: parseBoolean(hide_progress), }), ); } catch (err) { diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js index 70bc06b351f87..de147057092b6 100644 --- a/src/cards/repo-card.js +++ b/src/cards/repo-card.js @@ -83,8 +83,9 @@ const renderRepoCard = (repo, options = {}) => { border_color, locale, description_lines_count, + show_lang_bar = false, hide_progress, - layout = "compact", + layout = "compact", // add more layouts in the future langs_count = getDefaultLanguagesCountByLayout({ layout, hide_progress }), hide, } = options; @@ -193,8 +194,7 @@ const renderRepoCard = (repo, options = {}) => { .lang-name { font: 400 11px "Segoe UI", Ubuntu, Sans-Serif; fill: ${colors.textColor} } `); - const temp = true; - if (temp) { + if (show_lang_bar) { return card.render(` ${ isTemplate @@ -205,28 +205,7 @@ const renderRepoCard = (repo, options = {}) => { getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) : "" } - - - ${descriptionSvg} - - - - ${starAndForkCount} - - - `); - } - return card.render(` - ${ - isTemplate - ? // @ts-ignore - getBadgeSVG(i18n.t("repocard.template"), colors.textColor) - : isArchived - ? // @ts-ignore - getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) - : "" - } - + ${descriptionSvg} @@ -235,6 +214,28 @@ const renderRepoCard = (repo, options = {}) => { ${languageBar} + `); + } + + return card.render(` + ${ + isTemplate + ? // @ts-ignore + getBadgeSVG(i18n.t("repocard.template"), colors.textColor) + : isArchived + ? // @ts-ignore + getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) + : "" + } + + + ${descriptionSvg} + + + + ${starAndForkCount} + + `); }; diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index b637bba1d359f..5e47a74cf0702 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -33,6 +33,7 @@ export type StatCardOptions = CommonOptions & { export type RepoCardOptions = CommonOptions & { show_owner: boolean; description_lines_count: number; + show_lang_bar: boolean; hide_progress: boolean; layout: "compact"; langs_count: number; From 59d024f01c0cd280398ac64c04770fdcbecc74af Mon Sep 17 00:00:00 2001 From: SemUijen Date: Sun, 10 Nov 2024 16:16:00 +0100 Subject: [PATCH 4/4] Added function for calculating the extra needed height for progress bar and multiple languages --- src/cards/repo-card.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js index de147057092b6..074341a892b5a 100644 --- a/src/cards/repo-card.js +++ b/src/cards/repo-card.js @@ -47,6 +47,18 @@ const getBadgeSVG = (label, textColor) => ` `; +/** + * Calculates extra height needed for the languages bar. + * + * @param {number} totalLangs Total number of languages. + * @param {boolean} hideProgress Flag to hide progress bar. + * @returns {number} Card height. + */ +const calculateExtraLangHeigth = (totalLangs, hideProgress) => { + const baseHeight = Math.ceil(totalLangs / 2) * 25; + return hideProgress ? baseHeight - 30 : baseHeight; +}; + /** * @typedef {import("../fetchers/types").RepositoryData} RepositoryData Repository data. * @typedef {import("./types").RepoCardOptions} RepoCardOptions Repo card options. @@ -84,7 +96,7 @@ const renderRepoCard = (repo, options = {}) => { locale, description_lines_count, show_lang_bar = false, - hide_progress, + hide_progress = false, layout = "compact", // add more layouts in the future langs_count = getDefaultLanguagesCountByLayout({ layout, hide_progress }), hide, @@ -177,7 +189,11 @@ const renderRepoCard = (repo, options = {}) => { defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header, titlePrefixIcon: icons.contribs, width: 400, - height, + height: + height + + (show_lang_bar + ? calculateExtraLangHeigth(langs.length, hide_progress) + : 0), border_radius, colors, });