-
-
Notifications
You must be signed in to change notification settings - Fork 25.1k
Creates a new Streaks Card #3338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e044cc8
f48d5c4
53decae
b19934f
982375f
575766b
aa03a75
b20a4b1
1766373
dd8b478
33f132b
0c3cd3d
c873703
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import renderStreaksCard from "../src/cards/streak-card.js"; | ||
import { blacklist } from "../src/common/blacklist.js"; | ||
import { | ||
clampValue, | ||
CONSTANTS, | ||
parseArray, | ||
parseBoolean, | ||
renderError, | ||
} from "../src/common/utils.js"; | ||
|
||
import { isLocaleAvailable } from "../src/translations.js"; | ||
import { streakFetcher } from "../src/fetchers/streak-fetcher.js"; | ||
import axios from "axios"; | ||
export default async (req, res) => { | ||
const { | ||
username, | ||
hide, | ||
hide_border, | ||
yaten2302 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
card_width, | ||
line_height, | ||
title_color, | ||
text_color, | ||
text_bold, | ||
bg_color, | ||
theme, | ||
cache_seconds, | ||
custom_title, | ||
locale, | ||
disable_animations, | ||
border_radius, | ||
number_format, | ||
border_color, | ||
} = req.query; | ||
res.setHeader("Content-Type", "image/svg+xml"); | ||
|
||
if (blacklist.includes(username)) { | ||
return res.send(renderError("Something went wrong")); | ||
} | ||
|
||
if (locale && !isLocaleAvailable(locale)) { | ||
return res.send(renderError("Something went wrong", "Language not found")); | ||
} | ||
|
||
try { | ||
const streaks = await streakFetcher(username); | ||
|
||
let cacheSeconds = clampValue( | ||
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10), | ||
CONSTANTS.FOUR_HOURS, | ||
CONSTANTS.ONE_DAY, | ||
); | ||
cacheSeconds = process.env.CACHE_SECONDS | ||
? parseInt(process.env.CACHE_SECONDS, 10) || cacheSeconds | ||
: cacheSeconds; | ||
|
||
res.setHeader( | ||
"Cache-Control", | ||
`max-age=${ | ||
cacheSeconds / 2 | ||
}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, | ||
); | ||
|
||
return res.send( | ||
renderStreaksCard(streaks, { | ||
hide: parseArray(hide), | ||
hide_border: parseBoolean(hide_border), | ||
card_width: parseInt(card_width, 10), | ||
line_height, | ||
title_color, | ||
text_color, | ||
text_bold: parseBoolean(text_bold), | ||
bg_color, | ||
theme, | ||
custom_title, | ||
border_radius, | ||
border_color, | ||
number_format, | ||
locale: locale ? locale : null, | ||
disable_animations: parseBoolean(disable_animations), | ||
}), | ||
); | ||
|
||
} catch (err) { | ||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses. | ||
yaten2302 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return res.send(renderError(err.message, err.secondaryMessage)); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -111,6 +111,9 @@ Please visit [this link](https://give.do/fundraisers/stand-beside-the-victims-of | |||||||
- [Demo](#demo-2) | ||||||||
- [Wakatime Stats Card](#wakatime-stats-card) | ||||||||
- [Demo](#demo-3) | ||||||||
- [GitHub Streaks Card](#github-streaks-card) | ||||||||
- [Usage](#usage-3) | ||||||||
- [Hide individual elements from the streaks card](#hide-individual-elements-from-the-streaks-card) | ||||||||
- [All Demos](#all-demos) | ||||||||
- [Quick Tip (Align The Cards)](#quick-tip-align-the-cards) | ||||||||
- [Deploy on your own](#deploy-on-your-own) | ||||||||
|
@@ -624,6 +627,24 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username. | |||||||
|
||||||||
*** | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
|
||||||||
# GitHub Streaks Card | ||||||||
|
||||||||
GitHub Streaks Card allows you to show your streak of your contributions on GitHub(like total contributions, weekly and longest streak on daily and weekly basis). | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
|
||||||||
### Usage | ||||||||
|
||||||||
Copy-paste this code into your readme and change the links. | ||||||||
|
||||||||
Endpoint: `api/streak/?username=YOUR_USERNAME` | ||||||||
|
||||||||
```md | ||||||||
 | ||||||||
``` | ||||||||
|
||||||||
### Hide individual elements from the streaks card | ||||||||
|
||||||||
To hide individual elements form the card, use - `&hide=params`, where `params` = `totalContributions`, `weeklyLongest`, `weeklyCurrent`, `dailyLongest`, `dailyCurrent` | ||||||||
|
||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
# All Demos | ||||||||
|
||||||||
* Default | ||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import Card from "../common/Card.js"; | ||
import I18n from "../common/I18n.js"; | ||
import { flexLayout, getCardColors, kFormatter } from "../common/utils.js"; | ||
import { getStyles } from "../getStyles.js"; | ||
import { streakCardLocales } from "../translations.js"; | ||
|
||
const CARD_MIN_WIDTH = 300; | ||
const CARD_DEFAULT_WIDTH = 300; | ||
|
||
const createTextNode = ({ | ||
yaten2302 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
value, | ||
label, | ||
id, | ||
index, | ||
shiftValuePos, | ||
bold, | ||
number_format, | ||
}) => { | ||
const kValue = number_format === "long" ? value : kFormatter(value); | ||
const staggerDelay = (index + 3) * 150; | ||
|
||
return ` | ||
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25,0)"> | ||
<text class="stat ${bold ? "bold" : "not_bold"}" y="12.5">${label}:</text> | ||
<text class="stat ${bold ? "bold" : "not_bold"}" x="${ | ||
shiftValuePos + 120 | ||
}" y="12.5" data-testid="${id}">${kValue}</text> | ||
</g> | ||
`; | ||
}; | ||
|
||
const renderStreaksCard = (streaksData, options = {}) => { | ||
yaten2302 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const { | ||
user_name, | ||
yaten2302 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
longest_streak_weekly, | ||
current_streak_weekly, | ||
longest_streak_daily, | ||
current_streak_daily, | ||
total_contributions, | ||
} = streaksData; | ||
const { | ||
hide = [], | ||
hide_border = false, | ||
card_width, | ||
line_height = 25, | ||
title_color, | ||
text_color, | ||
text_bold = true, | ||
bg_color, | ||
theme = "default", | ||
custom_title, | ||
border_radius, | ||
border_color, | ||
number_format = "short", | ||
locale, | ||
disable_animations = false, | ||
} = options; | ||
|
||
const lheight = String(parseInt(line_height), 10); | ||
|
||
const { titleColor, textColor, bgColor, borderColor } = getCardColors({ | ||
title_color, | ||
text_color, | ||
bg_color, | ||
border_color, | ||
theme, | ||
}); | ||
|
||
const apostrophe = ["x", "s"].includes(user_name.slice(-1)) ? "" : "s"; | ||
|
||
const i18n = new I18n({ | ||
locale, | ||
translations: streakCardLocales({ user_name, apostrophe }), | ||
}); | ||
|
||
const STREAKS = {}; | ||
|
||
STREAKS.totalContributions = { | ||
label: i18n.t("streakcard.totalcontributions"), | ||
value: total_contributions, | ||
id: "total contributions", | ||
}; | ||
|
||
STREAKS.weeklyLongest = { | ||
label: i18n.t("streakcard.longest"), | ||
value: longest_streak_weekly + " weeks", | ||
id: "weekly longest streak", | ||
}; | ||
|
||
STREAKS.weeklyCurrent = { | ||
label: i18n.t("streakcard.current"), | ||
value: current_streak_weekly + " weeks", | ||
id: "weekly current streak", | ||
}; | ||
|
||
STREAKS.dailyLongest = { | ||
label: i18n.t("streakcard.longest"), | ||
value: longest_streak_daily + " days", | ||
id: "daily longest streak", | ||
}; | ||
|
||
STREAKS.dailyCurrent = { | ||
label: i18n.t("streakcard.current"), | ||
value: current_streak_daily + " days", | ||
id: "daily longest streak", | ||
}; | ||
const longLocales = [ | ||
"cn", | ||
"es", | ||
"fr", | ||
"pt-br", | ||
"ru", | ||
"uk-ua", | ||
"id", | ||
"ml", | ||
"my", | ||
"pl", | ||
"de", | ||
"nl", | ||
"zh-tw", | ||
"uz", | ||
]; | ||
const isLongLocale = locale ? longLocales.includes(locale) : false; | ||
|
||
const streakItems = Object.keys(STREAKS) | ||
.filter((key) => !hide.includes(key)) | ||
.map((key, index) => | ||
// create the text nodes, and pass index so that we can calculate the line spacing | ||
createTextNode({ | ||
label: STREAKS[key].label, | ||
value: STREAKS[key].value, | ||
id: STREAKS[key].id, | ||
index, | ||
shiftValuePos: 79.01 + (isLongLocale ? 50 : 0), | ||
bold: text_bold, | ||
number_format, | ||
}), | ||
); | ||
|
||
const cssStyles = getStyles({ | ||
titleColor, | ||
textColor, | ||
}); | ||
|
||
let height = 45 + (streakItems.length + 1) * lheight; | ||
|
||
const minCardWidth = CARD_MIN_WIDTH; | ||
const defaultCardWidth = CARD_DEFAULT_WIDTH; | ||
let width = card_width | ||
? isNaN(card_width) | ||
? defaultCardWidth | ||
: card_width | ||
: defaultCardWidth; | ||
if (width < minCardWidth) { | ||
width = minCardWidth; | ||
} | ||
|
||
const card = new Card({ | ||
customTitle: custom_title, | ||
defaultTitle: streakItems.length | ||
? i18n.t("streakcard.title") | ||
: i18n.t("streakcard.title"), | ||
width, | ||
height, | ||
border_radius, | ||
colors: { | ||
titleColor, | ||
textColor, | ||
bgColor, | ||
borderColor, | ||
}, | ||
}); | ||
|
||
card.setHideBorder(hide_border); | ||
card.setCSS(cssStyles); | ||
|
||
if (disable_animations) card.disableAnimations(); | ||
|
||
card.setAccessibilityLabel({ | ||
title: `${card.title}`, | ||
}); | ||
|
||
return card.render(` | ||
<svg x="0" y="0"> | ||
${flexLayout({ | ||
items: streakItems, | ||
gap: Number(lheight), | ||
direction: "column", | ||
}).join("")} | ||
</svg> | ||
`); | ||
}; | ||
|
||
export { renderStreaksCard }; | ||
export default renderStreaksCard; |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -167,13 +167,23 @@ const iconWithLabel = (icon, label, testid, iconSize) => { | |||
/** | ||||
* Retrieves num with suffix k(thousands) precise to 1 decimal if greater than 999. | ||||
* | ||||
* @param {number} num The number to format. | ||||
* @returns {string|number} The formatted number. | ||||
* num The number to format. | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are the typings removed 🤔 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess, I removed those typings by mistake, will add those again 👍 |
||||
* The formatted number. | ||||
*/ | ||||
const kFormatter = (num) => { | ||||
return Math.abs(num) > 999 | ||||
/* return Math.abs(num) > 999 | ||||
? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k" | ||||
: Math.sign(num) * Math.abs(num); | ||||
: Math.sign(num) * Math.abs(num); */ | ||||
|
||||
if (typeof num == "number") { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does this need to be changed? This function should only accept numbers. In its current form strings should be converted before they enter the function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this because while rendering the streaks card, it requires some additional information in the value (like - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. You should handle this problem outside the kFormatter function. Maybe only use the kformatter function when the value is a number:
|
||||
return Math.abs(num) > 999 | ||||
? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k" | ||||
: Math.sign(num) * Math.abs(num); | ||||
} | ||||
|
||||
if (typeof num == "string") { | ||||
return num; | ||||
} | ||||
}; | ||||
|
||||
/** | ||||
|
Uh oh!
There was an error while loading. Please reload this page.