Skip to content

Commit 6e73a00

Browse files
Willian Rodriguesanuraghazra
andauthored
feat: add wakatime card (#392)
* Adds wakatime card route * Adds language progress on wakatime card * Adds wakatime card on README * Adds no coding activity node * Remove percent displayed on wakatime's card * Update readme * refactor: refactored code & added tests Co-authored-by: Anurag <hazru.anurag@gmail.com>
1 parent e377770 commit 6e73a00

File tree

7 files changed

+586
-0
lines changed

7 files changed

+586
-0
lines changed

api/wakatime.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
require("dotenv").config();
2+
const {
3+
renderError,
4+
parseBoolean,
5+
clampValue,
6+
CONSTANTS,
7+
} = require("../src/common/utils");
8+
const { fetchLast7Days } = require("../src/fetchers/wakatime-fetcher");
9+
const wakatimeCard = require("../src/cards/wakatime-card");
10+
11+
module.exports = async (req, res) => {
12+
const {
13+
username,
14+
title_color,
15+
icon_color,
16+
hide_border,
17+
line_height,
18+
text_color,
19+
bg_color,
20+
theme,
21+
cache_seconds,
22+
hide_title,
23+
hide_progress,
24+
} = req.query;
25+
26+
res.setHeader("Content-Type", "image/svg+xml");
27+
28+
try {
29+
const last7Days = await fetchLast7Days({ username });
30+
31+
let cacheSeconds = clampValue(
32+
parseInt(cache_seconds || CONSTANTS.TWO_HOURS, 10),
33+
CONSTANTS.TWO_HOURS,
34+
CONSTANTS.ONE_DAY
35+
);
36+
37+
if (!cache_seconds) {
38+
cacheSeconds = CONSTANTS.FOUR_HOURS;
39+
}
40+
41+
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
42+
43+
return res.send(
44+
wakatimeCard(last7Days, {
45+
hide_title: parseBoolean(hide_title),
46+
hide_border: parseBoolean(hide_border),
47+
line_height,
48+
title_color,
49+
icon_color,
50+
text_color,
51+
bg_color,
52+
theme,
53+
hide_progress,
54+
})
55+
);
56+
} catch (err) {
57+
return res.send(renderError(err.message, err.secondaryMessage));
58+
}
59+
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"dotenv": "^8.2.0",
2828
"emoji-name-map": "^1.2.8",
2929
"github-username-regex": "^1.0.0",
30+
"prettier": "^2.1.2",
3031
"word-wrap": "^1.2.3"
3132
},
3233
"husky": {

readme.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
- [GitHub Stats Card](#github-stats-card)
5959
- [GitHub Extra Pins](#github-extra-pins)
6060
- [Top Languages Card](#top-languages-card)
61+
- [Wakatime Week Stats](#wakatime-week-stats)
6162
- [Themes](#themes)
6263
- [Customization](#customization)
6364
- [Deploy Yourself](#deploy-on-your-own-vercel-instance)
@@ -171,6 +172,13 @@ You can provide multiple comma-separated values in bg_color option to render a g
171172
> Language names should be uri-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
172173
> (i.e: `c++` should become `c%2B%2B`, `jupyter notebook` should become `jupyter%20notebook`, etc.)
173174
175+
#### Wakatime Card Exclusive Options:
176+
177+
- `hide_title` - _(boolean)_
178+
- `hide_border` - _(boolean)_
179+
- `line_height` - Sets the line-height between text _(number)_
180+
- `hide_progress` - Hides the progress bar and percentage _(boolean)_
181+
174182
---
175183

176184
# GitHub Extra Pins
@@ -245,6 +253,20 @@ You can use the `&layout=compact` option to change the card design.
245253

246254
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats)
247255

256+
# Wakatime Week Stats
257+
258+
Change the `?username=` value to your Wakatime username.
259+
260+
```md
261+
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)
262+
```
263+
264+
### Demo
265+
266+
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)
267+
268+
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats)
269+
248270
---
249271

250272
### All Demos
@@ -287,6 +309,10 @@ Choose from any of the [default themes](#themes)
287309

288310
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)
289311

312+
- Wakatime card
313+
314+
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)
315+
290316
---
291317

292318
### Quick Tip (Align The Repo Cards)

src/cards/wakatime-card.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
const { getCardColors, FlexLayout, clampValue } = require("../common/utils");
2+
const { getStyles } = require("../getStyles");
3+
const icons = require("../common/icons");
4+
const Card = require("../common/Card");
5+
6+
const noCodingActivityNode = ({ color }) => {
7+
return `
8+
<text x="25" y="11" class="stat bold" fill="${color}">No coding activity this week</text>
9+
`;
10+
};
11+
12+
const createProgressNode = ({
13+
width,
14+
color,
15+
progress,
16+
progressBarBackgroundColor,
17+
}) => {
18+
const progressPercentage = clampValue(progress, 2, 100);
19+
20+
return `
21+
<svg width="${width}" overflow="auto">
22+
<rect rx="5" ry="5" x="110" y="4" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
23+
<rect
24+
height="8"
25+
fill="${color}"
26+
rx="5" ry="5" x="110" y="4"
27+
data-testid="lang-progress"
28+
width="${progressPercentage}%"
29+
>
30+
</rect>
31+
</svg>
32+
`;
33+
};
34+
35+
const createTextNode = ({
36+
id,
37+
label,
38+
value,
39+
index,
40+
percent,
41+
hideProgress,
42+
progressBarColor,
43+
progressBarBackgroundColor,
44+
}) => {
45+
const staggerDelay = (index + 3) * 150;
46+
47+
const cardProgress = hideProgress
48+
? null
49+
: createProgressNode({
50+
progress: percent,
51+
color: progressBarColor,
52+
width: 220,
53+
name: label,
54+
progressBarBackgroundColor,
55+
});
56+
57+
return `
58+
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
59+
<text class="stat bold" y="12.5">${label}:</text>
60+
<text
61+
class="stat"
62+
x="${hideProgress ? 170 : 350}"
63+
y="12.5"
64+
data-testid="${id}"
65+
>${value}</text>
66+
${cardProgress}
67+
</g>
68+
`;
69+
};
70+
71+
const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
72+
const { languages } = stats;
73+
const {
74+
hide_title = false,
75+
hide_border = false,
76+
line_height = 25,
77+
title_color,
78+
icon_color,
79+
text_color,
80+
bg_color,
81+
theme = "default",
82+
hide_progress,
83+
} = options;
84+
85+
const lheight = parseInt(line_height, 10);
86+
87+
// returns theme based colors with proper overrides and defaults
88+
const { titleColor, textColor, iconColor, bgColor } = getCardColors({
89+
title_color,
90+
icon_color,
91+
text_color,
92+
bg_color,
93+
theme,
94+
});
95+
96+
const statItems = languages
97+
? languages
98+
.filter((language) => language.hours || language.minutes)
99+
.map((language) => {
100+
return createTextNode({
101+
id: language.name,
102+
label: language.name,
103+
value: language.text,
104+
percent: language.percent,
105+
progressBarColor: titleColor,
106+
progressBarBackgroundColor: textColor,
107+
hideProgress: hide_progress,
108+
});
109+
})
110+
: [];
111+
112+
// Calculate the card height depending on how many items there are
113+
// but if rank circle is visible clamp the minimum height to `150`
114+
let height = Math.max(45 + (statItems.length + 1) * lheight, 150);
115+
116+
const cssStyles = getStyles({
117+
titleColor,
118+
textColor,
119+
iconColor,
120+
});
121+
122+
const card = new Card({
123+
title: "Wakatime week stats",
124+
width: 495,
125+
height,
126+
colors: {
127+
titleColor,
128+
textColor,
129+
iconColor,
130+
bgColor,
131+
},
132+
});
133+
134+
card.setHideBorder(hide_border);
135+
card.setHideTitle(hide_title);
136+
card.setCSS(
137+
`
138+
${cssStyles}
139+
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
140+
`
141+
);
142+
143+
return card.render(`
144+
<svg x="0" y="0" width="100%">
145+
${FlexLayout({
146+
items: statItems.length
147+
? statItems
148+
: [noCodingActivityNode({ color: textColor })],
149+
gap: lheight,
150+
direction: "column",
151+
}).join("")}
152+
</svg>
153+
`);
154+
};
155+
156+
module.exports = renderWakatimeCard;

src/fetchers/wakatime-fetcher.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const axios = require("axios");
2+
3+
const fetchLast7Days = async ({ username }) => {
4+
const { data } = await axios.get(
5+
`https://wakatime.com/api/v1/users/${username}/stats/last_7_days?is_including_today=true`
6+
);
7+
8+
return data.data;
9+
};
10+
11+
module.exports = {
12+
fetchLast7Days,
13+
};

0 commit comments

Comments
 (0)