Skip to content

Commit 112efb4

Browse files
authored
refactor: language card renderer refactor (#1184)
* refactor: language card render refactor * chore: change error msg
1 parent 99aea7f commit 112efb4

File tree

3 files changed

+158
-114
lines changed

3 files changed

+158
-114
lines changed

api/top-langs.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ module.exports = async (req, res) => {
4040
}
4141

4242
if (locale && !isLocaleAvailable(locale)) {
43-
return res.send(renderError("Something went wrong", "Language not found"));
43+
return res.send(renderError("Something went wrong", "Locale not found"));
4444
}
4545

4646
try {

src/cards/top-languages-card.js

+143-102
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ const { langCardLocales } = require("../translations");
44
const { createProgressNode } = require("../common/createProgressNode");
55
const { clampValue, getCardColors, flexLayout } = require("../common/utils");
66

7+
const DEFAULT_CARD_WIDTH = 300;
8+
const DEFAULT_LANGS_COUNT = 5;
9+
const DEFAULT_LANG_COLOR = "#858585";
10+
const CARD_PADDING = 25;
11+
12+
const lowercaseTrim = (name) => name.toLowerCase().trim();
13+
714
const createProgressTextNode = ({ width, color, name, progress }) => {
815
const paddingRight = 95;
916
const progressTextX = width - paddingRight + 10;
@@ -58,35 +65,99 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
5865
});
5966
};
6067

61-
const lowercaseTrim = (name) => name.toLowerCase().trim();
68+
/**
69+
*
70+
* @param {any[]} langs
71+
* @param {number} width
72+
* @param {number} totalLanguageSize
73+
* @returns {string}
74+
*/
75+
const renderNormalLayout = (langs, width, totalLanguageSize) => {
76+
return flexLayout({
77+
items: langs.map((lang) => {
78+
return createProgressTextNode({
79+
width: width,
80+
name: lang.name,
81+
color: lang.color || DEFAULT_LANG_COLOR,
82+
progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
83+
});
84+
}),
85+
gap: 40,
86+
direction: "column",
87+
}).join("");
88+
};
6289

63-
const renderTopLanguages = (topLangs, options = {}) => {
64-
const {
65-
hide_title,
66-
hide_border,
67-
card_width,
68-
title_color,
69-
text_color,
70-
bg_color,
71-
hide,
72-
theme,
73-
layout,
74-
custom_title,
75-
locale,
76-
langs_count = 5,
77-
border_radius,
78-
border_color,
79-
} = options;
90+
/**
91+
*
92+
* @param {any[]} langs
93+
* @param {number} width
94+
* @param {number} totalLanguageSize
95+
* @returns {string}
96+
*/
97+
const renderCompactLayout = (langs, width, totalLanguageSize) => {
98+
const paddingRight = 50;
99+
const offsetWidth = width - paddingRight;
100+
// progressOffset holds the previous language's width and used to offset the next language
101+
// so that we can stack them one after another, like this: [--][----][---]
102+
let progressOffset = 0;
103+
const compactProgressBar = langs
104+
.map((lang) => {
105+
const percentage = parseFloat(
106+
((lang.size / totalLanguageSize) * offsetWidth).toFixed(2),
107+
);
80108

81-
const i18n = new I18n({
82-
locale,
83-
translations: langCardLocales,
84-
});
109+
const progress = percentage < 10 ? percentage + 10 : percentage;
110+
111+
const output = `
112+
<rect
113+
mask="url(#rect-mask)"
114+
data-testid="lang-progress"
115+
x="${progressOffset}"
116+
y="0"
117+
width="${progress}"
118+
height="8"
119+
fill="${lang.color || "#858585"}"
120+
/>
121+
`;
122+
progressOffset += percentage;
123+
return output;
124+
})
125+
.join("");
126+
127+
return `
128+
<mask id="rect-mask">
129+
<rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5" />
130+
</mask>
131+
${compactProgressBar}
132+
${createLanguageTextNode({
133+
x: 0,
134+
y: 25,
135+
langs,
136+
totalSize: totalLanguageSize,
137+
}).join("")}
138+
`;
139+
};
140+
141+
/**
142+
* @param {number} totalLangs
143+
* @returns {number}
144+
*/
145+
const calculateCompactLayoutHeight = (totalLangs) => {
146+
return 90 + Math.round(totalLangs / 2) * 25;
147+
};
85148

149+
/**
150+
* @param {number} totalLangs
151+
* @returns {number}
152+
*/
153+
const calculateNormalLayoutHeight = (totalLangs) => {
154+
return 45 + (totalLangs + 1) * 40;
155+
};
156+
157+
const useLanguages = (topLangs, hide, langs_count) => {
86158
let langs = Object.values(topLangs);
87159
let langsToHide = {};
88-
89-
langsCount = clampValue(parseInt(langs_count), 1, 10);
160+
let langsCount = clampValue(parseInt(langs_count), 1, 10);
90161

91162
// populate langsToHide map for quick lookup
92163
// while filtering out
@@ -104,110 +175,80 @@ const renderTopLanguages = (topLangs, options = {}) => {
104175
})
105176
.slice(0, langsCount);
106177

107-
const totalLanguageSize = langs.reduce((acc, curr) => {
108-
return acc + curr.size;
109-
}, 0);
178+
const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0);
110179

111-
// returns theme based colors with proper overrides and defaults
112-
const { titleColor, textColor, bgColor, borderColor } = getCardColors({
180+
return { langs, totalLanguageSize };
181+
};
182+
183+
const renderTopLanguages = (topLangs, options = {}) => {
184+
const {
185+
hide_title,
186+
hide_border,
187+
card_width,
113188
title_color,
114189
text_color,
115190
bg_color,
116-
border_color,
191+
hide,
117192
theme,
193+
layout,
194+
custom_title,
195+
locale,
196+
langs_count = DEFAULT_LANGS_COUNT,
197+
border_radius,
198+
border_color,
199+
} = options;
200+
201+
const i18n = new I18n({
202+
locale,
203+
translations: langCardLocales,
118204
});
119205

120-
let width = isNaN(card_width) ? 300 : card_width;
121-
let height = 45 + (langs.length + 1) * 40;
206+
const { langs, totalLanguageSize } = useLanguages(
207+
topLangs,
208+
hide,
209+
langs_count,
210+
);
122211

123-
let finalLayout = "";
212+
let width = isNaN(card_width) ? DEFAULT_CARD_WIDTH : card_width;
213+
let height = calculateNormalLayoutHeight(langs.length);
124214

125-
// RENDER COMPACT LAYOUT
215+
let finalLayout = "";
126216
if (layout === "compact") {
127-
width = width + 50;
128-
height = 90 + Math.round(langs.length / 2) * 25;
129-
130-
// progressOffset holds the previous language's width and used to offset the next language
131-
// so that we can stack them one after another, like this: [--][----][---]
132-
let progressOffset = 0;
133-
const compactProgressBar = langs
134-
.map((lang) => {
135-
const percentage = (
136-
(lang.size / totalLanguageSize) *
137-
(width - 50)
138-
).toFixed(2);
139-
140-
const progress =
141-
percentage < 10 ? parseFloat(percentage) + 10 : percentage;
142-
143-
const output = `
144-
<rect
145-
mask="url(#rect-mask)"
146-
data-testid="lang-progress"
147-
x="${progressOffset}"
148-
y="0"
149-
width="${progress}"
150-
height="8"
151-
fill="${lang.color || "#858585"}"
152-
/>
153-
`;
154-
progressOffset += parseFloat(percentage);
155-
return output;
156-
})
157-
.join("");
158-
159-
finalLayout = `
160-
<mask id="rect-mask">
161-
<rect x="0" y="0" width="${
162-
width - 50
163-
}" height="8" fill="white" rx="5" />
164-
</mask>
165-
${compactProgressBar}
166-
${createLanguageTextNode({
167-
x: 0,
168-
y: 25,
169-
langs,
170-
totalSize: totalLanguageSize,
171-
}).join("")}
172-
`;
217+
width = width + 50; // padding
218+
height = calculateCompactLayoutHeight(langs.length);
219+
220+
finalLayout = renderCompactLayout(langs, width, totalLanguageSize);
173221
} else {
174-
finalLayout = flexLayout({
175-
items: langs.map((lang) => {
176-
return createProgressTextNode({
177-
width: width,
178-
name: lang.name,
179-
color: lang.color || "#858585",
180-
progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
181-
});
182-
}),
183-
gap: 40,
184-
direction: "column",
185-
}).join("");
222+
finalLayout = renderNormalLayout(langs, width, totalLanguageSize);
186223
}
187224

225+
// returns theme based colors with proper overrides and defaults
226+
const colors = getCardColors({
227+
title_color,
228+
text_color,
229+
bg_color,
230+
border_color,
231+
theme,
232+
});
233+
188234
const card = new Card({
189235
customTitle: custom_title,
190236
defaultTitle: i18n.t("langcard.title"),
191237
width,
192238
height,
193239
border_radius,
194-
colors: {
195-
titleColor,
196-
textColor,
197-
bgColor,
198-
borderColor,
199-
},
240+
colors,
200241
});
201242

202243
card.disableAnimations();
203244
card.setHideBorder(hide_border);
204245
card.setHideTitle(hide_title);
205-
card.setCSS(`
206-
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
207-
`);
246+
card.setCSS(
247+
`.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }`,
248+
);
208249

209250
return card.render(`
210-
<svg data-testid="lang-items" x="25">
251+
<svg data-testid="lang-items" x="${CARD_PADDING}">
211252
${finalLayout}
212253
</svg>
213254
`);

tests/renderTopLanguages.test.js

+14-11
Original file line numberDiff line numberDiff line change
@@ -198,23 +198,23 @@ describe("Test renderTopLanguages", () => {
198198
);
199199
expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute(
200200
"width",
201-
"120.00",
201+
"120",
202202
);
203203

204204
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
205205
"javascript 40.00%",
206206
);
207207
expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute(
208208
"width",
209-
"120.00",
209+
"120",
210210
);
211211

212212
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
213213
"css 20.00%",
214214
);
215215
expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute(
216216
"width",
217-
"60.00",
217+
"60",
218218
);
219219
});
220220

@@ -228,25 +228,28 @@ describe("Test renderTopLanguages", () => {
228228
it("should render without rounding", () => {
229229
document.body.innerHTML = renderTopLanguages(langs, { border_radius: "0" });
230230
expect(document.querySelector("rect")).toHaveAttribute("rx", "0");
231-
document.body.innerHTML = renderTopLanguages(langs, { });
231+
document.body.innerHTML = renderTopLanguages(langs, {});
232232
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
233233
});
234234

235235
it("should render langs with specified langs_count", async () => {
236236
options = {
237-
langs_count: 1
238-
}
237+
langs_count: 1,
238+
};
239239
document.body.innerHTML = renderTopLanguages(langs, { ...options });
240-
expect(queryAllByTestId(document.body, "lang-name").length).toBe(options.langs_count)
240+
expect(queryAllByTestId(document.body, "lang-name").length).toBe(
241+
options.langs_count,
242+
);
241243
});
242244

243245
it("should render langs with specified langs_count even when hide is set", async () => {
244246
options = {
245247
hide: ["HTML"],
246-
langs_count: 2
247-
}
248+
langs_count: 2,
249+
};
248250
document.body.innerHTML = renderTopLanguages(langs, { ...options });
249-
expect(queryAllByTestId(document.body, "lang-name").length).toBe(options.langs_count)
251+
expect(queryAllByTestId(document.body, "lang-name").length).toBe(
252+
options.langs_count,
253+
);
250254
});
251-
252255
});

0 commit comments

Comments
 (0)