forked from mirrored/github-readme-stats
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
430c614c1d | ||
|
|
024a469797 | ||
|
|
894f2e2c5a | ||
|
|
cec5ee3462 | ||
|
|
fa4d7ee7a1 | ||
|
|
14fe4cf1db | ||
|
|
1e0183d202 | ||
|
|
98b047751e | ||
|
|
1a2a82c3be | ||
|
|
c301289f7d | ||
|
|
e0b3d833b0 | ||
|
|
275c1fc1c7 | ||
|
|
73b0a91b7b | ||
|
|
c96e84a9ae | ||
|
|
ff2e02ba68 | ||
|
|
c8d5eda27c | ||
|
|
f9427b2a54 |
@@ -15,10 +15,15 @@ jobs:
|
|||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
filter: "1772"
|
filter: "1772"
|
||||||
label: false
|
label: true
|
||||||
dashboard: true
|
dashboard: true
|
||||||
dashboard_show_total_reactions: true
|
dashboard_show_total_reactions: true
|
||||||
top_issues: true
|
top_issues: true
|
||||||
top_bugs: true
|
top_bugs: true
|
||||||
top_features: true
|
top_features: true
|
||||||
top_pull_requests: true
|
top_pull_requests: true
|
||||||
|
custom_pull_requests_label: themes
|
||||||
|
top_custom_pull_requests_label: ":star: top themes"
|
||||||
|
top_custom_pull_requests_label_description:
|
||||||
|
The description used for the top custom pull requests.
|
||||||
|
top_custom_pull_requests_label_colour: #A23599
|
||||||
|
|||||||
@@ -44,6 +44,16 @@ export default async (req, res) => {
|
|||||||
return res.send(renderError("Something went wrong", "Locale not found"));
|
return res.send(renderError("Something went wrong", "Locale not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
layout !== undefined &&
|
||||||
|
(typeof layout !== "string" ||
|
||||||
|
!["compact", "normal", "donut", "donut-vertical", "pie"].includes(layout))
|
||||||
|
) {
|
||||||
|
return res.send(
|
||||||
|
renderError("Something went wrong", "Incorrect layout input"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const topLangs = await fetchTopLanguages(
|
const topLangs = await fetchTopLanguages(
|
||||||
username,
|
username,
|
||||||
|
|||||||
+1
-2
@@ -28,7 +28,6 @@ export default async (req, res) => {
|
|||||||
langs_count,
|
langs_count,
|
||||||
hide,
|
hide,
|
||||||
api_domain,
|
api_domain,
|
||||||
range,
|
|
||||||
border_radius,
|
border_radius,
|
||||||
border_color,
|
border_color,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
@@ -40,7 +39,7 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stats = await fetchWakatimeStats({ username, api_domain, range });
|
const stats = await fetchWakatimeStats({ username, api_domain });
|
||||||
|
|
||||||
let cacheSeconds = clampValue(
|
let cacheSeconds = clampValue(
|
||||||
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
|
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
|
||||||
|
|||||||
+1
-1
@@ -171,7 +171,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
|
|
||||||
- `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_
|
- `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - 提供四種佈局 `normal` & `compact` & `donut` & `pie` 间切换
|
- `layout` - 提供五種佈局 `normal` & `compact` & `donut` & `donut-vertical` & `pie` 间切换
|
||||||
- `card_width` - 手动设置卡片的宽度 _(number)_
|
- `card_width` - 手动设置卡片的宽度 _(number)_
|
||||||
|
|
||||||
> :warning: **重要:**
|
> :warning: **重要:**
|
||||||
|
|||||||
+1
-1
@@ -161,7 +161,7 @@ Du kannst mehrere, mit Kommas separierte, Werte in der bg_color Option angeben,
|
|||||||
|
|
||||||
- `hide` - Verbirgt die angegebenen Sprachen von der Karte _(Komma separierte Werte)_
|
- `hide` - Verbirgt die angegebenen Sprachen von der Karte _(Komma separierte Werte)_
|
||||||
- `hide_title` - _(Boolean)_
|
- `hide_title` - _(Boolean)_
|
||||||
- `layout` - Wechseln Sie zwischen den vier verfügbaren Layouts `normal` & `compact` & `donut` & `pie`
|
- `layout` - Wechseln Sie zwischen den fünf verfügbaren Layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`
|
||||||
- `card_width` - Lege die Breite der Karte manuell fest _(Zahl)_
|
- `card_width` - Lege die Breite der Karte manuell fest _(Zahl)_
|
||||||
|
|
||||||
> :warning: **Wichtig:**
|
> :warning: **Wichtig:**
|
||||||
|
|||||||
+1
-1
@@ -175,7 +175,7 @@ Puedes pasar mútliples valores separados por coma en la opción `bg_color` para
|
|||||||
|
|
||||||
- `hide` - Oculta de la tarjeta los lenguajes especificados _(valores separados por comas)_
|
- `hide` - Oculta de la tarjeta los lenguajes especificados _(valores separados por comas)_
|
||||||
- `hide_title` - _(booleano)_
|
- `hide_title` - _(booleano)_
|
||||||
- `layout` - Cambiar entre los cuatro diseños disponibles `normal` & `compact` & `donut` & `pie`
|
- `layout` - Cambiar entre los cinco diseños disponibles `normal` & `compact` & `donut` & `donut-vertical` & `pie`
|
||||||
- `card_width` - Establece el ancho de la tarjeta manualmente _(número)_
|
- `card_width` - Establece el ancho de la tarjeta manualmente _(número)_
|
||||||
- `langs_count` - Muestra más lenguajes en la tarjeta, entre 1-10, por defecto 5 _(número)_
|
- `langs_count` - Muestra más lenguajes en la tarjeta, entre 1-10, por defecto 5 _(número)_
|
||||||
- `exclude_repo` - Excluye los repositorios especificados _(valores separados por comas)_
|
- `exclude_repo` - Excluye los repositorios especificados _(valores separados por comas)_
|
||||||
|
|||||||
+2
-14
@@ -90,18 +90,6 @@ Pour masquer des statistiques spécifiques, vous pouvez passer un paramètre de
|
|||||||

|

|
||||||
```
|
```
|
||||||
|
|
||||||
### Ajouter le compte des contributions privées au compte des commits totaux
|
|
||||||
|
|
||||||
Vous pouvez ajouter le compte de toutes vos contributions privées au compte total des engagements en utilisant le paramètre de requête `?count_private=true`.
|
|
||||||
|
|
||||||
_Note: Si vous déployez vous-même ce projet, les contributions privées seront comptées par défaut ; sinon, vous devez choisir de partager les comptes de vos contributions privées._
|
|
||||||
|
|
||||||
> Options: `&count_private=true`
|
|
||||||
|
|
||||||
```md
|
|
||||||

|
|
||||||
```
|
|
||||||
|
|
||||||
### Afficher les icônes
|
### Afficher les icônes
|
||||||
|
|
||||||
Pour activer les icônes, vous pouvez passer `show_icons=true` dans le paramètre de requête, comme ceci :
|
Pour activer les icônes, vous pouvez passer `show_icons=true` dans le paramètre de requête, comme ceci :
|
||||||
@@ -160,7 +148,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co
|
|||||||
- `hide_rank` - Masquer le rang _(boolean)_
|
- `hide_rank` - Masquer le rang _(boolean)_
|
||||||
- `show_icons` - Afficher les icônes _(boolean)_
|
- `show_icons` - Afficher les icônes _(boolean)_
|
||||||
- `include_all_commits` - Compter le total de commits au lieu de ne compter que les commits de l'année en cours _(boolean)_
|
- `include_all_commits` - Compter le total de commits au lieu de ne compter que les commits de l'année en cours _(boolean)_
|
||||||
- `count_private` - Compter les commits privés _(boolean)_
|
- `count_private` - Compter les contributions privées _(boolean)_
|
||||||
- `line_height` - Fixer la hauteur de la ligne entre les textes _(number)_
|
- `line_height` - Fixer la hauteur de la ligne entre les textes _(number)_
|
||||||
|
|
||||||
#### Repo Card Exclusive Options:
|
#### Repo Card Exclusive Options:
|
||||||
@@ -171,7 +159,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co
|
|||||||
|
|
||||||
- `hide` - Masquer les langages spécifiés sur la carte _(Comma seperated values)_
|
- `hide` - Masquer les langages spécifiés sur la carte _(Comma seperated values)_
|
||||||
- `hide_title` - Masquer le titre _(boolean)_
|
- `hide_title` - Masquer le titre _(boolean)_
|
||||||
- `layout` - Alterner entre 4 mise en page `normal` & `compact` & `donut` & `pie`
|
- `layout` - Alterner entre 5 mise en page `normal` & `compact` & `donut` & `donut-vertical` & `pie`
|
||||||
- `card_width` - Fixer la largeur de la carte manuellement _(number)_
|
- `card_width` - Fixer la largeur de la carte manuellement _(number)_
|
||||||
|
|
||||||
> :warning: **Important:**
|
> :warning: **Important:**
|
||||||
|
|||||||
+1
-1
@@ -171,7 +171,7 @@ Puoi fornire valori separati da virgola nel parametro bg_color per creare un gra
|
|||||||
|
|
||||||
- `hide` - Nasconde un linguaggio specifico _(valori separati da virgola)_
|
- `hide` - Nasconde un linguaggio specifico _(valori separati da virgola)_
|
||||||
- `hide_title` - Nasconde il titolo _(booleano)_
|
- `hide_title` - Nasconde il titolo _(booleano)_
|
||||||
- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella) e `pie` (torta)
|
- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella), `donut-vertical` (ciambella verticale) e `pie` (torta)
|
||||||
- `card_width` - Specifica il valore della larghezza _(numero)_
|
- `card_width` - Specifica il valore della larghezza _(numero)_
|
||||||
|
|
||||||
> :warning: **Importante:**
|
> :warning: **Importante:**
|
||||||
|
|||||||
+1
-1
@@ -174,7 +174,7 @@ bg_color オプションで複数のカンマ区切りの値を指定してグ
|
|||||||
|
|
||||||
- `hide` - 特定の言語を隠す _(カンマ区切りで指定)_
|
- `hide` - 特定の言語を隠す _(カンマ区切りで指定)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - `normal` & `compact` & `donut` & `pie` のいずれかのレイアウトに切り替える
|
- `layout` - `normal` & `compact` & `donut` & `donut-vertical` & `pie` のいずれかのレイアウトに切り替える
|
||||||
- `card_width` - カードの横幅 _(number)_
|
- `card_width` - カードの横幅 _(number)_
|
||||||
- `langs_count` - 表示される言語の数 _(1 ~ 10, 初期値 5)_
|
- `langs_count` - 表示される言語の数 _(1 ~ 10, 初期値 5)_
|
||||||
- `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_
|
- `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_
|
||||||
|
|||||||
+1
-1
@@ -189,7 +189,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
|
|
||||||
- `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_
|
- `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_
|
||||||
- `hide_title` - 타이틀 제외 _(boolean)_
|
- `hide_title` - 타이틀 제외 _(boolean)_
|
||||||
- `layout` - 4가지 값 사용 가능, `normal` & `compact` & `donut` & `pie` 중 표시 형태 선택
|
- `layout` - 5가지 값 사용 가능, `normal` & `compact` & `donut` & `donut-vertical` & `pie` 중 표시 형태 선택
|
||||||
- `card_width` - 카드 너비 직접 설정 _(number)_
|
- `card_width` - 카드 너비 직접 설정 _(number)_
|
||||||
- `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_
|
- `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_
|
||||||
- `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_
|
- `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_
|
||||||
|
|||||||
+1
-1
@@ -176,7 +176,7 @@ Je kan meerdere komma verdeelde waarden in de bg_color optie geven om een kleure
|
|||||||
|
|
||||||
- `hide` - Verbergt specifieke talen van de kaart _(komma gescheiden waardes)_
|
- `hide` - Verbergt specifieke talen van de kaart _(komma gescheiden waardes)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - Keuze uit de vier beschikbare indelingen `normal` & `compact` & `donut` & `pie`
|
- `layout` - Kies uit de vijf beschikbare lay-outs `normal` & `compact` & `donut` & `donut-vertical` & `pie`
|
||||||
- `card_width` - Stelt de breedte van de kaart handmatig in. _(nummer)_
|
- `card_width` - Stelt de breedte van de kaart handmatig in. _(nummer)_
|
||||||
- `langs_count` - Laat meer talen op de kaart zien, waarde tussen 1-10, staat standaard op to 5 _(nummer)_
|
- `langs_count` - Laat meer talen op de kaart zien, waarde tussen 1-10, staat standaard op to 5 _(nummer)_
|
||||||
- `exclude_repo` - Verbergt specifieke repositories _(komma gescheiden waardes)_
|
- `exclude_repo` - Verbergt specifieke repositories _(komma gescheiden waardes)_
|
||||||
|
|||||||
+1
-1
@@ -174,7 +174,7 @@ You can provide multiple comma-separated values in bg_color option to render a g
|
|||||||
|
|
||||||
- `hide` - Hide the languages specified from the card _(Comma-separated values)_
|
- `hide` - Hide the languages specified from the card _(Comma-separated values)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`.
|
- `layout` - Switch between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`.
|
||||||
- `card_width` - Set the card's width manually _(number)_
|
- `card_width` - Set the card's width manually _(number)_
|
||||||
- `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_
|
- `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_
|
||||||
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_
|
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ Personalize a aparência do seu `Stats Card` ou `Repo Card` da maneira que desej
|
|||||||
|
|
||||||
- `hide` - Oculta linguagens específicas _(Valores separados por vírgulas)_
|
- `hide` - Oculta linguagens específicas _(Valores separados por vírgulas)_
|
||||||
- `hide_title` - Oculta o título _(boolean)_
|
- `hide_title` - Oculta o título _(boolean)_
|
||||||
- `layout` - Alterna entre os quatro layouts disponíveis `normal` & `compact` & `donut` & `pie`
|
- `layout` - Alternar entre os cinco layouts disponíveis `normal` & `compact` & `donut` & `donut-vertical` & `pie`
|
||||||
- `card_width` - Define a largura do cartão manualmente _(number)_
|
- `card_width` - Define a largura do cartão manualmente _(number)_
|
||||||
|
|
||||||
> :warning: **Importante:**
|
> :warning: **Importante:**
|
||||||
|
|||||||
+1
-1
@@ -176,7 +176,7 @@ bg_color içerisinde birden fazla rengi gradient olarak göstermek için virgül
|
|||||||
|
|
||||||
- `hide` - Belirli bir dili listede gizler _(Virgül ile ayırılmış değerlerle)_
|
- `hide` - Belirli bir dili listede gizler _(Virgül ile ayırılmış değerlerle)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - Dört uygun tasarım / düzen arasında geçiş `normal` & `compact` & `donut` & `pie`
|
- `layout` - Beş uygun tasarım / düzen arasında geçiş yapın `normal` & `compact` & `donut` & `donut-vertical` & `pie`
|
||||||
- `card_width` - Kartın genişliğini manuel olarak belirler _(number)_
|
- `card_width` - Kartın genişliğini manuel olarak belirler _(number)_
|
||||||
- `langs_count` - 1-10 arasında istediğiniz kadar dil gösterebilirsiniz. Varsayılan: 5 _(number)_
|
- `langs_count` - 1-10 arasında istediğiniz kadar dil gösterebilirsiniz. Varsayılan: 5 _(number)_
|
||||||
- `exclude_repo` - Belirli repoları listeden çıkartır _(Virgül ile ayırılmış değerlerle)_
|
- `exclude_repo` - Belirli repoları listeden çıkartır _(Virgül ile ayırılmış değerlerle)_
|
||||||
|
|||||||
@@ -69,12 +69,15 @@
|
|||||||
<img src="https://cfstatic.give.do/logo.png" alt="Give india logo" width="200" />
|
<img src="https://cfstatic.give.do/logo.png" alt="Give india logo" width="200" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Are you considering supporting the project by donating? Please DO NOT!!
|
Are you considering supporting the project by donating to me? Please DO NOT!!
|
||||||
|
|
||||||
Instead, Help India fight the second deadly wave of COVID-19.
|
|
||||||
Thousands of people are dying in India because of a lack of Oxygen & also COVID-related infrastructure.
|
|
||||||
|
|
||||||
Visit <https://indiafightscorona.giveindia.org> and make a small donation to help us fight COVID and overcome this crisis. A small donation goes a long way. :heart:
|
<img src="https://cfstatic.give.do/910ede2a-7892-43fe-8c8a-dea45e96d950.webp" alt="Picture of Coromandel Express train tragedy" width="35%">
|
||||||
|
|
||||||
|
India just suffered one of the most devastating train accident and your help will be immensely valuable for the people who were effected by this tragedy.
|
||||||
|
|
||||||
|
Please visit [this link](https://give.do/fundraisers/stand-beside-the-victims-of-the-coromandel-express-train-tragedy-in-odisha-donate-now) and make a small donation to help the people in need. A small donation goes a long way. :heart:
|
||||||
|
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -120,19 +123,6 @@ You can pass a query parameter `&hide=` to hide any specific stats with comma-se
|
|||||||

|

|
||||||
```
|
```
|
||||||
|
|
||||||
### Adding private contributions count to total commits count
|
|
||||||
|
|
||||||
You can add the count of all your private contributions to the total commits count by using the query parameter `&count_private=true`.
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
> If you are deploying this project yourself, the private contributions will be counted by default. If you are using the public Vercel instance, you need to choose to [share your private contributions](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/showing-your-private-contributions-and-achievements-on-your-profile).
|
|
||||||
|
|
||||||
> Options: `&count_private=true`
|
|
||||||
|
|
||||||
```md
|
|
||||||

|
|
||||||
```
|
|
||||||
|
|
||||||
### Showing icons
|
### Showing icons
|
||||||
|
|
||||||
To enable icons, you can pass `&show_icons=true` in the query param, like so:
|
To enable icons, you can pass `&show_icons=true` in the query param, like so:
|
||||||
@@ -283,7 +273,7 @@ You can provide multiple comma-separated values in the bg_color option to render
|
|||||||
- `rank_icon` - Shows alternative rank icon (i.e. `github` or `default`). Default: `default`.
|
- `rank_icon` - Shows alternative rank icon (i.e. `github` or `default`). Default: `default`.
|
||||||
- `show_icons` - _(boolean)_. Default: `false`.
|
- `show_icons` - _(boolean)_. Default: `false`.
|
||||||
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`.
|
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`.
|
||||||
- `count_private` - Count private commits _(boolean)_. Default: `false`.
|
- `count_private` - Count private contributions _(boolean)_. Default: `false`.
|
||||||
- `line_height` - Sets the line height between text _(number)_. Default: `25`.
|
- `line_height` - Sets the line height between text _(number)_. Default: `25`.
|
||||||
- `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
|
- `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
|
||||||
- `custom_title` - Sets a custom title for the card. Default: `<username> GitHub Stats`.
|
- `custom_title` - Sets a custom title for the card. Default: `<username> GitHub Stats`.
|
||||||
@@ -303,9 +293,9 @@ You can provide multiple comma-separated values in the bg_color option to render
|
|||||||
|
|
||||||
- `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`.
|
- `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`.
|
||||||
- `hide_title` - _(boolean)_. Default: `false`.
|
- `hide_title` - _(boolean)_. Default: `false`.
|
||||||
- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`.
|
- `layout` - Switch between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`.
|
||||||
- `card_width` - Set the card's width manually _(number)_. Default `300`.
|
- `card_width` - Set the card's width manually _(number)_. Default `300`.
|
||||||
- `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`.
|
- `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default: `5` for `normal` and `donut`, `6` for other layouts.
|
||||||
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
|
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
|
||||||
- `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`.
|
- `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`.
|
||||||
- `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`.
|
- `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`.
|
||||||
@@ -328,7 +318,6 @@ You can provide multiple comma-separated values in the bg_color option to render
|
|||||||
- `layout` - Switch between two available layouts `default` & `compact`. Default `default`.
|
- `layout` - Switch between two available layouts `default` & `compact`. Default `default`.
|
||||||
- `langs_count` - Limit the number of languages on the card, defaults to all reported languages _(number)_.
|
- `langs_count` - Limit the number of languages on the card, defaults to all reported languages _(number)_.
|
||||||
- `api_domain` - Set a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi) _(string)_. Default `Waka API`.
|
- `api_domain` - Set a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi) _(string)_. Default `Waka API`.
|
||||||
- `range` – Request a range different from your WakaTime default, e.g. `last_7_days`. See [WakaTime API docs](https://wakatime.com/developers#stats) for a list of available options. _(YYYY-MM, last_7_days, last_30_days, last_6_months, last_year, or all_time)_. Default `all_time`.
|
|
||||||
|
|
||||||
* * *
|
* * *
|
||||||
|
|
||||||
@@ -431,6 +420,14 @@ You can use the `&layout=donut` option to change the card design.
|
|||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Donut Vertical Chart Language Card Layout
|
||||||
|
|
||||||
|
You can use the `&layout=donut-vertical` option to change the card design.
|
||||||
|
|
||||||
|
```md
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
```
|
||||||
|
|
||||||
### Pie Chart Language Card Layout
|
### Pie Chart Language Card Layout
|
||||||
|
|
||||||
You can use the `&layout=pie` option to change the card design.
|
You can use the `&layout=pie` option to change the card design.
|
||||||
@@ -459,6 +456,10 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro
|
|||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
|
- Donut Vertical Chart layout
|
||||||
|
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
- Pie Chart layout
|
- Pie Chart layout
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
@@ -469,15 +470,15 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro
|
|||||||
|
|
||||||
# Wakatime Week Stats
|
# Wakatime Week Stats
|
||||||
|
|
||||||
|
> **Warning**
|
||||||
|
> Please be aware that we currently only show data from Wakatime profiles that are public. You therefore have to make sure that **BOTH** `Display code time publicly` and `Display languages, editors, os, categories publicly` are enabled.
|
||||||
|
|
||||||
Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**:
|
|
||||||
> Please be aware that we currently only show data from Wakatime profiles that are public.
|
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|||||||
+51
-84
@@ -1,105 +1,72 @@
|
|||||||
/**
|
function expsf(x, lambda = 1) {
|
||||||
* Calculates the probability of x taking on x or a value less than x in a normal distribution
|
return 2 ** (-lambda * x);
|
||||||
* with mean and standard deviation.
|
}
|
||||||
*
|
|
||||||
* @see https://stackoverflow.com/a/5263759/10629172
|
|
||||||
*
|
|
||||||
* @param {string} mean The mean of the normal distribution.
|
|
||||||
* @param {number} sigma The standard deviation of the normal distribution.
|
|
||||||
* @param {number} to The value to calculate the probability for.
|
|
||||||
* @returns {number} Probability.
|
|
||||||
*/
|
|
||||||
const normalcdf = (mean, sigma, to) => {
|
|
||||||
var z = (to - mean) / Math.sqrt(2 * sigma * sigma);
|
|
||||||
var t = 1 / (1 + 0.3275911 * Math.abs(z));
|
|
||||||
var a1 = 0.254829592;
|
|
||||||
var a2 = -0.284496736;
|
|
||||||
var a3 = 1.421413741;
|
|
||||||
var a4 = -1.453152027;
|
|
||||||
var a5 = 1.061405429;
|
|
||||||
var erf =
|
|
||||||
1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-z * z);
|
|
||||||
var sign = 1;
|
|
||||||
if (z < 0) {
|
|
||||||
sign = -1;
|
|
||||||
}
|
|
||||||
return (1 / 2) * (1 + sign * erf);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the users rank.
|
* Calculates the users rank.
|
||||||
*
|
*
|
||||||
* @param {object} params Parameters on which the user's rank depends.
|
* @param {object} params Parameters on which the user's rank depends.
|
||||||
* @param {number} params.totalRepos Total number of repos.
|
* @param {boolean} params.all_commits Whether `include_all_commits` was used.
|
||||||
* @param {number} params.totalCommits Total number of commits.
|
* @param {number} params.commits Number of commits.
|
||||||
* @param {number} params.contributions The number of contributions.
|
|
||||||
* @param {number} params.followers The number of followers.
|
|
||||||
* @param {number} params.prs The number of pull requests.
|
* @param {number} params.prs The number of pull requests.
|
||||||
* @param {number} params.issues The number of issues.
|
* @param {number} params.issues The number of issues.
|
||||||
* @param {number} params.stargazers The number of stars.
|
* @param {number} params.repos Total number of repos.
|
||||||
|
* @param {number} params.stars The number of stars.
|
||||||
|
* @param {number} params.followers The number of followers.
|
||||||
* @returns {{level: string, score: number}}} The users rank.
|
* @returns {{level: string, score: number}}} The users rank.
|
||||||
*/
|
*/
|
||||||
const calculateRank = ({
|
function calculateRank({
|
||||||
totalRepos,
|
all_commits,
|
||||||
totalCommits,
|
commits,
|
||||||
contributions,
|
|
||||||
followers,
|
|
||||||
prs,
|
prs,
|
||||||
issues,
|
issues,
|
||||||
stargazers,
|
repos, // unused
|
||||||
}) => {
|
stars,
|
||||||
const COMMITS_OFFSET = 1.65;
|
followers,
|
||||||
const CONTRIBS_OFFSET = 1.65;
|
}) {
|
||||||
const ISSUES_OFFSET = 1;
|
const COMMITS_MEAN = all_commits ? 1000 : 250,
|
||||||
const STARS_OFFSET = 0.75;
|
COMMITS_WEIGHT = 2;
|
||||||
const PRS_OFFSET = 0.5;
|
const PRS_MEAN = 50,
|
||||||
const FOLLOWERS_OFFSET = 0.45;
|
PRS_WEIGHT = 3;
|
||||||
const REPO_OFFSET = 1;
|
const ISSUES_MEAN = 25,
|
||||||
|
ISSUES_WEIGHT = 1;
|
||||||
|
const STARS_MEAN = 250,
|
||||||
|
STARS_WEIGHT = 4;
|
||||||
|
const FOLLOWERS_MEAN = 25,
|
||||||
|
FOLLOWERS_WEIGHT = 1;
|
||||||
|
|
||||||
const ALL_OFFSETS =
|
const TOTAL_WEIGHT =
|
||||||
CONTRIBS_OFFSET +
|
COMMITS_WEIGHT +
|
||||||
ISSUES_OFFSET +
|
PRS_WEIGHT +
|
||||||
STARS_OFFSET +
|
ISSUES_WEIGHT +
|
||||||
PRS_OFFSET +
|
STARS_WEIGHT +
|
||||||
FOLLOWERS_OFFSET +
|
FOLLOWERS_WEIGHT;
|
||||||
REPO_OFFSET;
|
|
||||||
|
|
||||||
const RANK_S_VALUE = 1;
|
const rank =
|
||||||
const RANK_DOUBLE_A_VALUE = 25;
|
(COMMITS_WEIGHT * expsf(commits, 1 / COMMITS_MEAN) +
|
||||||
const RANK_A2_VALUE = 45;
|
PRS_WEIGHT * expsf(prs, 1 / PRS_MEAN) +
|
||||||
const RANK_A3_VALUE = 60;
|
ISSUES_WEIGHT * expsf(issues, 1 / ISSUES_MEAN) +
|
||||||
const RANK_B_VALUE = 100;
|
STARS_WEIGHT * expsf(stars, 1 / STARS_MEAN) +
|
||||||
|
FOLLOWERS_WEIGHT * expsf(followers, 1 / FOLLOWERS_MEAN)) /
|
||||||
|
TOTAL_WEIGHT;
|
||||||
|
|
||||||
const TOTAL_VALUES =
|
const RANK_S_PLUS = 0.025;
|
||||||
RANK_S_VALUE +
|
const RANK_S = 0.1;
|
||||||
RANK_DOUBLE_A_VALUE +
|
const RANK_A_PLUS = 0.25;
|
||||||
RANK_A2_VALUE +
|
const RANK_A = 0.5;
|
||||||
RANK_A3_VALUE +
|
const RANK_B_PLUS = 0.75;
|
||||||
RANK_B_VALUE;
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const score = (
|
|
||||||
totalCommits * COMMITS_OFFSET +
|
|
||||||
contributions * CONTRIBS_OFFSET +
|
|
||||||
issues * ISSUES_OFFSET +
|
|
||||||
stargazers * STARS_OFFSET +
|
|
||||||
prs * PRS_OFFSET +
|
|
||||||
followers * FOLLOWERS_OFFSET +
|
|
||||||
totalRepos * REPO_OFFSET
|
|
||||||
) / 100;
|
|
||||||
|
|
||||||
const normalizedScore = normalcdf(score, TOTAL_VALUES, ALL_OFFSETS) * 100;
|
|
||||||
|
|
||||||
const level = (() => {
|
const level = (() => {
|
||||||
if (normalizedScore < RANK_S_VALUE) return "S+";
|
if (rank <= RANK_S_PLUS) return "S+";
|
||||||
if (normalizedScore < RANK_DOUBLE_A_VALUE) return "S";
|
if (rank <= RANK_S) return "S";
|
||||||
if (normalizedScore < RANK_A2_VALUE) return "A++";
|
if (rank <= RANK_A_PLUS) return "A+";
|
||||||
if (normalizedScore < RANK_A3_VALUE) return "A+";
|
if (rank <= RANK_A) return "A";
|
||||||
return "B+";
|
if (rank <= RANK_B_PLUS) return "B+";
|
||||||
|
return "B";
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return { level, score: normalizedScore };
|
return { level, score: rank * 100 };
|
||||||
};
|
}
|
||||||
|
|
||||||
export { calculateRank };
|
export { calculateRank };
|
||||||
export default calculateRank;
|
export default calculateRank;
|
||||||
|
|||||||
+165
-15
@@ -14,9 +14,15 @@ import { langCardLocales } from "../translations.js";
|
|||||||
|
|
||||||
const DEFAULT_CARD_WIDTH = 300;
|
const DEFAULT_CARD_WIDTH = 300;
|
||||||
const MIN_CARD_WIDTH = 280;
|
const MIN_CARD_WIDTH = 280;
|
||||||
const DEFAULT_LANGS_COUNT = 5;
|
|
||||||
const DEFAULT_LANG_COLOR = "#858585";
|
const DEFAULT_LANG_COLOR = "#858585";
|
||||||
const CARD_PADDING = 25;
|
const CARD_PADDING = 25;
|
||||||
|
const COMPACT_LAYOUT_BASE_HEIGHT = 90;
|
||||||
|
|
||||||
|
const NORMAL_LAYOUT_DEFAULT_LANGS_COUNT = 5;
|
||||||
|
const COMPACT_LAYOUT_DEFAULT_LANGS_COUNT = 6;
|
||||||
|
const DONUT_LAYOUT_DEFAULT_LANGS_COUNT = 5;
|
||||||
|
const PIE_LAYOUT_DEFAULT_LANGS_COUNT = 6;
|
||||||
|
const DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import("../fetchers/types").Lang} Lang
|
* @typedef {import("../fetchers/types").Lang} Lang
|
||||||
@@ -84,6 +90,16 @@ const cartesianToPolar = (centerX, centerY, x, y) => {
|
|||||||
return { radius, angleInDegrees };
|
return { radius, angleInDegrees };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates length of circle.
|
||||||
|
*
|
||||||
|
* @param {number} radius Radius of the circle.
|
||||||
|
* @returns {number} The length of the circle.
|
||||||
|
*/
|
||||||
|
const getCircleLength = (radius) => {
|
||||||
|
return 2 * Math.PI * radius;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates height for the compact layout.
|
* Calculates height for the compact layout.
|
||||||
*
|
*
|
||||||
@@ -91,7 +107,7 @@ const cartesianToPolar = (centerX, centerY, x, y) => {
|
|||||||
* @returns {number} Card height.
|
* @returns {number} Card height.
|
||||||
*/
|
*/
|
||||||
const calculateCompactLayoutHeight = (totalLangs) => {
|
const calculateCompactLayoutHeight = (totalLangs) => {
|
||||||
return 90 + Math.round(totalLangs / 2) * 25;
|
return COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,6 +130,16 @@ const calculateDonutLayoutHeight = (totalLangs) => {
|
|||||||
return 215 + Math.max(totalLangs - 5, 0) * 32;
|
return 215 + Math.max(totalLangs - 5, 0) * 32;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates height for the donut vertical layout.
|
||||||
|
*
|
||||||
|
* @param {number} totalLangs Total number of languages.
|
||||||
|
* @returns {number} Card height.
|
||||||
|
*/
|
||||||
|
const calculateDonutVerticalLayoutHeight = (totalLangs) => {
|
||||||
|
return 300 + Math.round(totalLangs / 2) * 25;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates height for the pie layout.
|
* Calculates height for the pie layout.
|
||||||
*
|
*
|
||||||
@@ -371,6 +397,76 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders donut vertical layout to display user's most frequently used programming languages.
|
||||||
|
*
|
||||||
|
* @param {Lang[]} langs Array of programming languages.
|
||||||
|
* @param {number} totalLanguageSize Total size of all languages.
|
||||||
|
* @returns {string} Compact layout card SVG object.
|
||||||
|
*/
|
||||||
|
const renderDonutVerticalLayout = (langs, totalLanguageSize) => {
|
||||||
|
// Donut vertical chart radius and total length
|
||||||
|
const radius = 80;
|
||||||
|
const totalCircleLength = getCircleLength(radius);
|
||||||
|
|
||||||
|
// SVG circles
|
||||||
|
let circles = [];
|
||||||
|
|
||||||
|
// Start indent for donut vertical chart parts
|
||||||
|
let indent = 0;
|
||||||
|
|
||||||
|
// Start delay coefficient for donut vertical chart parts
|
||||||
|
let startDelayCoefficient = 1;
|
||||||
|
|
||||||
|
// Generate each donut vertical chart part
|
||||||
|
for (const lang of langs) {
|
||||||
|
const percentage = (lang.size / totalLanguageSize) * 100;
|
||||||
|
const circleLength = totalCircleLength * (percentage / 100);
|
||||||
|
const delay = startDelayCoefficient * 100;
|
||||||
|
|
||||||
|
circles.push(`
|
||||||
|
<g class="stagger" style="animation-delay: ${delay}ms">
|
||||||
|
<circle
|
||||||
|
cx="150"
|
||||||
|
cy="100"
|
||||||
|
r="${radius}"
|
||||||
|
fill="transparent"
|
||||||
|
stroke="${lang.color}"
|
||||||
|
stroke-width="25"
|
||||||
|
stroke-dasharray="${totalCircleLength}"
|
||||||
|
stroke-dashoffset="${indent}"
|
||||||
|
size="${percentage}"
|
||||||
|
data-testid="lang-donut"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Update the indent for the next part
|
||||||
|
indent += circleLength;
|
||||||
|
// Update the start delay coefficient for the next part
|
||||||
|
startDelayCoefficient += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<svg data-testid="lang-items">
|
||||||
|
<g transform="translate(0, 0)">
|
||||||
|
<svg data-testid="donut">
|
||||||
|
${circles.join("")}
|
||||||
|
</svg>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(0, 220)">
|
||||||
|
<svg data-testid="lang-names" x="${CARD_PADDING}">
|
||||||
|
${createLanguageTextNode({
|
||||||
|
langs,
|
||||||
|
totalSize: totalLanguageSize,
|
||||||
|
hideProgress: false,
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders pie layout to display user's most frequently used programming languages.
|
* Renders pie layout to display user's most frequently used programming languages.
|
||||||
*
|
*
|
||||||
@@ -564,6 +660,39 @@ const renderDonutLayout = (langs, width, totalLanguageSize) => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the no coding activity SVG node.
|
||||||
|
*
|
||||||
|
* @param {{color: string, text: string, layout: import("./types").TopLangOptions["layout"]}} The function prop
|
||||||
|
*/
|
||||||
|
const noLanguagesDataNode = ({ color, text, layout }) => {
|
||||||
|
return `
|
||||||
|
<text x="${
|
||||||
|
layout === "pie" || layout === "donut-vertical" ? CARD_PADDING : 0
|
||||||
|
}" y="11" class="stat bold" fill="${color}">${text}</text>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default languages count for provided card layout.
|
||||||
|
*
|
||||||
|
* @param {import("./types").TopLangOptions["layout"] | undefined} layout Input layout string
|
||||||
|
* @return {number} Default languages count for input layout
|
||||||
|
*/
|
||||||
|
const getDefaultLanguagesCountByLayout = (layout) => {
|
||||||
|
if (layout === "compact") {
|
||||||
|
return COMPACT_LAYOUT_DEFAULT_LANGS_COUNT;
|
||||||
|
} else if (layout === "donut") {
|
||||||
|
return DONUT_LAYOUT_DEFAULT_LANGS_COUNT;
|
||||||
|
} else if (layout === "donut-vertical") {
|
||||||
|
return DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT;
|
||||||
|
} else if (layout === "pie") {
|
||||||
|
return PIE_LAYOUT_DEFAULT_LANGS_COUNT;
|
||||||
|
} else {
|
||||||
|
return NORMAL_LAYOUT_DEFAULT_LANGS_COUNT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders card that display user's most frequently used programming languages.
|
* Renders card that display user's most frequently used programming languages.
|
||||||
*
|
*
|
||||||
@@ -585,7 +714,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
layout,
|
layout,
|
||||||
custom_title,
|
custom_title,
|
||||||
locale,
|
locale,
|
||||||
langs_count = DEFAULT_LANGS_COUNT,
|
langs_count = getDefaultLanguagesCountByLayout(layout),
|
||||||
border_radius,
|
border_radius,
|
||||||
border_color,
|
border_color,
|
||||||
disable_animations,
|
disable_animations,
|
||||||
@@ -609,10 +738,29 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
: card_width;
|
: card_width;
|
||||||
let height = calculateNormalLayoutHeight(langs.length);
|
let height = calculateNormalLayoutHeight(langs.length);
|
||||||
|
|
||||||
|
// returns theme based colors with proper overrides and defaults
|
||||||
|
const colors = getCardColors({
|
||||||
|
title_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
border_color,
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
|
||||||
let finalLayout = "";
|
let finalLayout = "";
|
||||||
if (layout === "pie") {
|
if (langs.length === 0) {
|
||||||
|
height = COMPACT_LAYOUT_BASE_HEIGHT;
|
||||||
|
finalLayout = noLanguagesDataNode({
|
||||||
|
color: colors.textColor,
|
||||||
|
text: i18n.t("langcard.nodata"),
|
||||||
|
layout,
|
||||||
|
});
|
||||||
|
} else if (layout === "pie") {
|
||||||
height = calculatePieLayoutHeight(langs.length);
|
height = calculatePieLayoutHeight(langs.length);
|
||||||
finalLayout = renderPieLayout(langs, totalLanguageSize);
|
finalLayout = renderPieLayout(langs, totalLanguageSize);
|
||||||
|
} else if (layout === "donut-vertical") {
|
||||||
|
height = calculateDonutVerticalLayoutHeight(langs.length);
|
||||||
|
finalLayout = renderDonutVerticalLayout(langs, totalLanguageSize);
|
||||||
} else if (layout === "compact" || hide_progress == true) {
|
} else if (layout === "compact" || hide_progress == true) {
|
||||||
height =
|
height =
|
||||||
calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0);
|
calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0);
|
||||||
@@ -623,7 +771,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
totalLanguageSize,
|
totalLanguageSize,
|
||||||
hide_progress,
|
hide_progress,
|
||||||
);
|
);
|
||||||
} else if (layout?.toLowerCase() === "donut") {
|
} else if (layout === "donut") {
|
||||||
height = calculateDonutLayoutHeight(langs.length);
|
height = calculateDonutLayoutHeight(langs.length);
|
||||||
width = width + 50; // padding
|
width = width + 50; // padding
|
||||||
finalLayout = renderDonutLayout(langs, width, totalLanguageSize);
|
finalLayout = renderDonutLayout(langs, width, totalLanguageSize);
|
||||||
@@ -631,15 +779,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
finalLayout = renderNormalLayout(langs, width, totalLanguageSize);
|
finalLayout = renderNormalLayout(langs, width, totalLanguageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns theme based colors with proper overrides and defaults
|
|
||||||
const colors = getCardColors({
|
|
||||||
title_color,
|
|
||||||
text_color,
|
|
||||||
bg_color,
|
|
||||||
border_color,
|
|
||||||
theme,
|
|
||||||
});
|
|
||||||
|
|
||||||
const card = new Card({
|
const card = new Card({
|
||||||
customTitle: custom_title,
|
customTitle: custom_title,
|
||||||
defaultTitle: i18n.t("langcard.title"),
|
defaultTitle: i18n.t("langcard.title"),
|
||||||
@@ -671,6 +810,14 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.stat {
|
||||||
|
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${colors.textColor};
|
||||||
|
}
|
||||||
|
@supports(-moz-appearance: auto) {
|
||||||
|
/* Selector detects Firefox */
|
||||||
|
.stat { font-size:12px; }
|
||||||
|
}
|
||||||
|
.bold { font-weight: 700 }
|
||||||
.lang-name {
|
.lang-name {
|
||||||
font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
|
font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
|
||||||
fill: ${colors.textColor};
|
fill: ${colors.textColor};
|
||||||
@@ -688,7 +835,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (layout === "pie") {
|
if (layout === "pie" || layout === "donut-vertical") {
|
||||||
return card.render(finalLayout);
|
return card.render(finalLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,12 +852,15 @@ export {
|
|||||||
radiansToDegrees,
|
radiansToDegrees,
|
||||||
polarToCartesian,
|
polarToCartesian,
|
||||||
cartesianToPolar,
|
cartesianToPolar,
|
||||||
|
getCircleLength,
|
||||||
calculateCompactLayoutHeight,
|
calculateCompactLayoutHeight,
|
||||||
calculateNormalLayoutHeight,
|
calculateNormalLayoutHeight,
|
||||||
calculateDonutLayoutHeight,
|
calculateDonutLayoutHeight,
|
||||||
|
calculateDonutVerticalLayoutHeight,
|
||||||
calculatePieLayoutHeight,
|
calculatePieLayoutHeight,
|
||||||
donutCenterTranslation,
|
donutCenterTranslation,
|
||||||
trimTopLanguages,
|
trimTopLanguages,
|
||||||
renderTopLanguages,
|
renderTopLanguages,
|
||||||
MIN_CARD_WIDTH,
|
MIN_CARD_WIDTH,
|
||||||
|
getDefaultLanguagesCountByLayout,
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+1
-1
@@ -39,7 +39,7 @@ export type TopLangOptions = CommonOptions & {
|
|||||||
hide_border: boolean;
|
hide_border: boolean;
|
||||||
card_width: number;
|
card_width: number;
|
||||||
hide: string[];
|
hide: string[];
|
||||||
layout: "compact" | "normal" | "donut" | "pie";
|
layout: "compact" | "normal" | "donut" | "donut-vertical" | "pie";
|
||||||
custom_title: string;
|
custom_title: string;
|
||||||
langs_count: number;
|
langs_count: number;
|
||||||
disable_animations: boolean;
|
disable_animations: boolean;
|
||||||
|
|||||||
@@ -279,7 +279,11 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
: noCodingActivityNode({
|
: noCodingActivityNode({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
color: textColor,
|
color: textColor,
|
||||||
text: i18n.t("wakatimecard.nocodingactivity"),
|
text: !stats.is_coding_activity_visible
|
||||||
|
? i18n.t("wakatimecard.notpublic")
|
||||||
|
: stats.is_other_usage_visible
|
||||||
|
? i18n.t("wakatimecard.nocodingactivity")
|
||||||
|
: i18n.t("wakatimecard.nocodedetails"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -304,7 +308,11 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
noCodingActivityNode({
|
noCodingActivityNode({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
color: textColor,
|
color: textColor,
|
||||||
text: i18n.t("wakatimecard.nocodingactivity"),
|
text: !stats.is_coding_activity_visible
|
||||||
|
? i18n.t("wakatimecard.notpublic")
|
||||||
|
: stats.is_other_usage_visible
|
||||||
|
? i18n.t("wakatimecard.nocodingactivity")
|
||||||
|
: i18n.t("wakatimecard.nocodedetails"),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
gap: lheight,
|
gap: lheight,
|
||||||
@@ -312,9 +320,20 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get title range text
|
||||||
|
let titleText = i18n.t("wakatimecard.title");
|
||||||
|
switch (stats.range) {
|
||||||
|
case "last_7_days":
|
||||||
|
titleText += ` (${i18n.t("wakatimecard.last7days")})`;
|
||||||
|
break;
|
||||||
|
case "last_year":
|
||||||
|
titleText += ` (${i18n.t("wakatimecard.lastyear")})`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const card = new Card({
|
const card = new Card({
|
||||||
customTitle: custom_title,
|
customTitle: custom_title,
|
||||||
defaultTitle: i18n.t("wakatimecard.title"),
|
defaultTitle: titleText,
|
||||||
width: 495,
|
width: 495,
|
||||||
height,
|
height,
|
||||||
border_radius,
|
border_radius,
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
"Cypher": "#34c0eb",
|
"Cypher": "#34c0eb",
|
||||||
"Cython": "#fedf5b",
|
"Cython": "#fedf5b",
|
||||||
"D": "#ba595e",
|
"D": "#ba595e",
|
||||||
|
"D2": "#526ee8",
|
||||||
"DM": "#447265",
|
"DM": "#447265",
|
||||||
"Dafny": "#FFEC25",
|
"Dafny": "#FFEC25",
|
||||||
"Darcs Patch": "#8eff23",
|
"Darcs Patch": "#8eff23",
|
||||||
@@ -185,6 +186,7 @@
|
|||||||
"Go": "#00ADD8",
|
"Go": "#00ADD8",
|
||||||
"Go Checksums": "#00ADD8",
|
"Go Checksums": "#00ADD8",
|
||||||
"Go Module": "#00ADD8",
|
"Go Module": "#00ADD8",
|
||||||
|
"Go Workspace": "#00ADD8",
|
||||||
"Godot Resource": "#355570",
|
"Godot Resource": "#355570",
|
||||||
"Golo": "#88562A",
|
"Golo": "#88562A",
|
||||||
"Gosu": "#82937f",
|
"Gosu": "#82937f",
|
||||||
@@ -284,6 +286,7 @@
|
|||||||
"Lua": "#000080",
|
"Lua": "#000080",
|
||||||
"MATLAB": "#e16737",
|
"MATLAB": "#e16737",
|
||||||
"MAXScript": "#00a6a6",
|
"MAXScript": "#00a6a6",
|
||||||
|
"MDX": "#fcb32c",
|
||||||
"MLIR": "#5EC8DB",
|
"MLIR": "#5EC8DB",
|
||||||
"MQL4": "#62A8D6",
|
"MQL4": "#62A8D6",
|
||||||
"MQL5": "#4A76B8",
|
"MQL5": "#4A76B8",
|
||||||
@@ -330,11 +333,12 @@
|
|||||||
"Nu": "#c9df40",
|
"Nu": "#c9df40",
|
||||||
"NumPy": "#9C8AF9",
|
"NumPy": "#9C8AF9",
|
||||||
"Nunjucks": "#3d8137",
|
"Nunjucks": "#3d8137",
|
||||||
|
"Nushell": "#4E9906",
|
||||||
"OASv2-json": "#85ea2d",
|
"OASv2-json": "#85ea2d",
|
||||||
"OASv2-yaml": "#85ea2d",
|
"OASv2-yaml": "#85ea2d",
|
||||||
"OASv3-json": "#85ea2d",
|
"OASv3-json": "#85ea2d",
|
||||||
"OASv3-yaml": "#85ea2d",
|
"OASv3-yaml": "#85ea2d",
|
||||||
"OCaml": "#3be133",
|
"OCaml": "#ef7a08",
|
||||||
"ObjectScript": "#424893",
|
"ObjectScript": "#424893",
|
||||||
"Objective-C": "#438eff",
|
"Objective-C": "#438eff",
|
||||||
"Objective-C++": "#6866fb",
|
"Objective-C++": "#6866fb",
|
||||||
@@ -360,6 +364,7 @@
|
|||||||
"PLSQL": "#dad8d8",
|
"PLSQL": "#dad8d8",
|
||||||
"PLpgSQL": "#336790",
|
"PLpgSQL": "#336790",
|
||||||
"POV-Ray SDL": "#6bac65",
|
"POV-Ray SDL": "#6bac65",
|
||||||
|
"Pact": "#F7A8B8",
|
||||||
"Pan": "#cc0000",
|
"Pan": "#cc0000",
|
||||||
"Papyrus": "#6600cc",
|
"Papyrus": "#6600cc",
|
||||||
"Parrot": "#f3ca0a",
|
"Parrot": "#f3ca0a",
|
||||||
@@ -398,6 +403,7 @@
|
|||||||
"Quake": "#882233",
|
"Quake": "#882233",
|
||||||
"R": "#198CE7",
|
"R": "#198CE7",
|
||||||
"RAML": "#77d9fb",
|
"RAML": "#77d9fb",
|
||||||
|
"RBS": "#701516",
|
||||||
"RDoc": "#701516",
|
"RDoc": "#701516",
|
||||||
"REXX": "#d90e09",
|
"REXX": "#d90e09",
|
||||||
"RMarkdown": "#198ce7",
|
"RMarkdown": "#198ce7",
|
||||||
@@ -472,6 +478,7 @@
|
|||||||
"Swift": "#F05138",
|
"Swift": "#F05138",
|
||||||
"SystemVerilog": "#DAE1C2",
|
"SystemVerilog": "#DAE1C2",
|
||||||
"TI Program": "#A0AA87",
|
"TI Program": "#A0AA87",
|
||||||
|
"TL-Verilog": "#C40023",
|
||||||
"TLA": "#4b0079",
|
"TLA": "#4b0079",
|
||||||
"TOML": "#9c4221",
|
"TOML": "#9c4221",
|
||||||
"TSQL": "#e38c00",
|
"TSQL": "#e38c00",
|
||||||
@@ -512,6 +519,7 @@
|
|||||||
"Vyper": "#2980b9",
|
"Vyper": "#2980b9",
|
||||||
"Web Ontology Language": "#5b70bd",
|
"Web Ontology Language": "#5b70bd",
|
||||||
"WebAssembly": "#04133b",
|
"WebAssembly": "#04133b",
|
||||||
|
"WebAssembly Interface Type": "#6250e7",
|
||||||
"Whiley": "#d5c397",
|
"Whiley": "#d5c397",
|
||||||
"Wikitext": "#fc5757",
|
"Wikitext": "#fc5757",
|
||||||
"Windows Registry Entries": "#52d5ff",
|
"Windows Registry Entries": "#52d5ff",
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ const SECONDARY_ERROR_MESSAGES = {
|
|||||||
"Please add an env variable called PAT_1 with your github token in vercel",
|
"Please add an env variable called PAT_1 with your github token in vercel",
|
||||||
USER_NOT_FOUND: "Make sure the provided username is not an organization",
|
USER_NOT_FOUND: "Make sure the provided username is not an organization",
|
||||||
GRAPHQL_ERROR: "Please try again later",
|
GRAPHQL_ERROR: "Please try again later",
|
||||||
|
WAKATIME_USER_NOT_FOUND: "Make sure you have a public WakaTime profile",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -324,6 +325,7 @@ class CustomError extends Error {
|
|||||||
static MAX_RETRY = "MAX_RETRY";
|
static MAX_RETRY = "MAX_RETRY";
|
||||||
static USER_NOT_FOUND = "USER_NOT_FOUND";
|
static USER_NOT_FOUND = "USER_NOT_FOUND";
|
||||||
static GRAPHQL_ERROR = "GRAPHQL_ERROR";
|
static GRAPHQL_ERROR = "GRAPHQL_ERROR";
|
||||||
|
static WAKATIME_ERROR = "WAKATIME_ERROR";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ const fetchStats = async (
|
|||||||
totalIssues: 0,
|
totalIssues: 0,
|
||||||
totalStars: 0,
|
totalStars: 0,
|
||||||
contributedTo: 0,
|
contributedTo: 0,
|
||||||
rank: { level: "C", score: 0 },
|
rank: { level: "B", score: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = await statsFetcher(username);
|
let res = await statsFetcher(username);
|
||||||
@@ -220,53 +220,44 @@ const fetchStats = async (
|
|||||||
|
|
||||||
const user = res.data.data.user;
|
const user = res.data.data.user;
|
||||||
|
|
||||||
// populate repoToHide map for quick lookup
|
|
||||||
// while filtering out
|
|
||||||
let repoToHide = {};
|
|
||||||
if (exclude_repo) {
|
|
||||||
exclude_repo.forEach((repoName) => {
|
|
||||||
repoToHide[repoName] = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.name = user.name || user.login;
|
stats.name = user.name || user.login;
|
||||||
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
|
|
||||||
|
|
||||||
// normal commits
|
// if include_all_commits, fetch all commits using the REST API.
|
||||||
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
|
|
||||||
|
|
||||||
// if include_all_commits then just get that,
|
|
||||||
// since totalCommitsFetcher already sends totalCommits no need to +=
|
|
||||||
if (include_all_commits) {
|
if (include_all_commits) {
|
||||||
stats.totalCommits = await totalCommitsFetcher(username);
|
stats.totalCommits = await totalCommitsFetcher(username);
|
||||||
|
} else {
|
||||||
|
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if count_private then add private commits to totalCommits so far.
|
// if count_private, add private contributions to totalCommits.
|
||||||
if (count_private) {
|
if (count_private) {
|
||||||
stats.totalCommits +=
|
stats.totalCommits +=
|
||||||
user.contributionsCollection.restrictedContributionsCount;
|
user.contributionsCollection.restrictedContributionsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.totalPRs = user.pullRequests.totalCount;
|
stats.totalPRs = user.pullRequests.totalCount;
|
||||||
|
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
|
||||||
stats.contributedTo = user.repositoriesContributedTo.totalCount;
|
stats.contributedTo = user.repositoriesContributedTo.totalCount;
|
||||||
|
|
||||||
// Retrieve stars while filtering out repositories to be hidden
|
// Retrieve stars while filtering out repositories to be hidden.
|
||||||
|
let repoToHide = new Set(exclude_repo);
|
||||||
|
|
||||||
stats.totalStars = user.repositories.nodes
|
stats.totalStars = user.repositories.nodes
|
||||||
.filter((data) => {
|
.filter((data) => {
|
||||||
return !repoToHide[data.name];
|
return !repoToHide.has(data.name);
|
||||||
})
|
})
|
||||||
.reduce((prev, curr) => {
|
.reduce((prev, curr) => {
|
||||||
return prev + curr.stargazers.totalCount;
|
return prev + curr.stargazers.totalCount;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
stats.rank = calculateRank({
|
stats.rank = calculateRank({
|
||||||
totalCommits: stats.totalCommits,
|
all_commits: include_all_commits,
|
||||||
totalRepos: user.repositories.totalCount,
|
commits: stats.totalCommits,
|
||||||
followers: user.followers.totalCount,
|
|
||||||
contributions: stats.contributedTo,
|
|
||||||
stargazers: stats.totalStars,
|
|
||||||
prs: stats.totalPRs,
|
prs: stats.totalPRs,
|
||||||
issues: stats.totalIssues,
|
issues: stats.totalIssues,
|
||||||
|
repos: user.repositories.totalCount,
|
||||||
|
stars: stats.totalStars,
|
||||||
|
followers: user.followers.totalCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { MissingParamError } from "../common/utils.js";
|
import { CustomError, MissingParamError } from "../common/utils.js";
|
||||||
|
import { I18n } from "../common/I18n.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WakaTime data fetcher.
|
* WakaTime data fetcher.
|
||||||
*
|
*
|
||||||
* @param {{username: string, api_domain: string, range: string}} props Fetcher props.
|
* @param {{username: string, api_domain: string }} props Fetcher props.
|
||||||
* @returns {Promise<WakaTimeData>} WakaTime data response.
|
* @returns {Promise<WakaTimeData>} WakaTime data response.
|
||||||
*/
|
*/
|
||||||
const fetchWakatimeStats = async ({ username, api_domain, range }) => {
|
const fetchWakatimeStats = async ({ username, api_domain }) => {
|
||||||
if (!username) throw new MissingParamError(["username"]);
|
if (!username) throw new MissingParamError(["username"]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
`https://${
|
`https://${
|
||||||
api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com"
|
api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com"
|
||||||
}/api/v1/users/${username}/stats/${
|
}/api/v1/users/${username}/stats?is_including_today=true`,
|
||||||
range || "all_time"
|
|
||||||
}?is_including_today=true`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.response.status < 200 || err.response.status > 299) {
|
if (err.response.status < 200 || err.response.status > 299) {
|
||||||
throw new Error(
|
throw new CustomError(
|
||||||
"Wakatime user not found, make sure you have a wakatime profile",
|
`Could not resolve to a User with the login of '${username}'`,
|
||||||
|
"WAKATIME_USER_NOT_FOUND",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
+47
-1
@@ -283,7 +283,7 @@ const langCardLocales = {
|
|||||||
np: "अधिक प्रयोग गरिएको भाषाहरू",
|
np: "अधिक प्रयोग गरिएको भाषाहरू",
|
||||||
el: "Οι περισσότερο χρησιμοποιούμενες γλώσσες",
|
el: "Οι περισσότερο χρησιμοποιούμενες γλώσσες",
|
||||||
ru: "Наиболее часто используемые языки",
|
ru: "Наиболее часто используемые языки",
|
||||||
"uk-ua": "Найбільш часто використовувані мови",
|
"uk-ua": "Найчастіше використовувані мови",
|
||||||
id: "Bahasa Yang Paling Banyak Digunakan",
|
id: "Bahasa Yang Paling Banyak Digunakan",
|
||||||
ml: "കൂടുതൽ ഉപയോഗിച്ച ഭാഷകൾ",
|
ml: "കൂടുതൽ ഉപയോഗിച്ച ഭാഷകൾ",
|
||||||
my: "Bahasa Paling Digunakan",
|
my: "Bahasa Paling Digunakan",
|
||||||
@@ -293,6 +293,36 @@ const langCardLocales = {
|
|||||||
vi: "Ngôn Ngữ Thường Sử Dụng",
|
vi: "Ngôn Ngữ Thường Sử Dụng",
|
||||||
se: "Mest använda språken",
|
se: "Mest använda språken",
|
||||||
},
|
},
|
||||||
|
"langcard.nodata": {
|
||||||
|
ar: "لا توجد بيانات لغات.",
|
||||||
|
cn: "沒有語言數據。",
|
||||||
|
"zh-tw": "沒有語言數據。",
|
||||||
|
cs: "Žádné jazykové údaje.",
|
||||||
|
de: "Keine Sprachdaten.",
|
||||||
|
bn: "কোন ভাষার ডেটা নেই।",
|
||||||
|
en: "No languages data.",
|
||||||
|
es: "Sin datos de idiomas.",
|
||||||
|
fr: "Aucune donnée sur les langues.",
|
||||||
|
hu: "Nincsenek nyelvi adatok.",
|
||||||
|
it: "Nessun dato sulle lingue.",
|
||||||
|
ja: "言語データがありません。",
|
||||||
|
kr: "언어 데이터가 없습니다.",
|
||||||
|
nl: "Ingen sprogdata.",
|
||||||
|
"pt-pt": "Sem dados de idiomas.",
|
||||||
|
"pt-br": "Sem dados de idiomas.",
|
||||||
|
np: "कुनै भाषा डाटा छैन।",
|
||||||
|
el: "Δεν υπάρχουν δεδομένα γλωσσών.",
|
||||||
|
ru: "Нет данных о языках.",
|
||||||
|
"uk-ua": "Немає даних про мови.",
|
||||||
|
id: "Tidak ada data bahasa.",
|
||||||
|
ml: "ഭാഷാ ഡാറ്റയില്ല.",
|
||||||
|
my: "Tiada data bahasa.",
|
||||||
|
sk: "Žiadne údaje o jazykoch.",
|
||||||
|
tr: "Dil verisi yok.",
|
||||||
|
pl: "Brak danych dotyczących języków.",
|
||||||
|
vi: "Không có dữ liệu ngôn ngữ.",
|
||||||
|
se: "Inga språkdata.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const wakatimeCardLocales = {
|
const wakatimeCardLocales = {
|
||||||
@@ -326,6 +356,22 @@ const wakatimeCardLocales = {
|
|||||||
vi: "Thống Kê Wakatime",
|
vi: "Thống Kê Wakatime",
|
||||||
se: "Wakatime statistik",
|
se: "Wakatime statistik",
|
||||||
},
|
},
|
||||||
|
"wakatimecard.lastyear": {
|
||||||
|
en: "last year",
|
||||||
|
cn: "去年",
|
||||||
|
},
|
||||||
|
"wakatimecard.last7days": {
|
||||||
|
en: "last 7 days",
|
||||||
|
cn: "最近 7 天",
|
||||||
|
},
|
||||||
|
"wakatimecard.notpublic": {
|
||||||
|
en: "Wakatime user profile not public",
|
||||||
|
cn: "Wakatime 用户个人资料未公开",
|
||||||
|
},
|
||||||
|
"wakatimecard.nocodedetails": {
|
||||||
|
en: "User doesn't publicly share detailed code statistics",
|
||||||
|
cn: "用户不公开分享详细的代码统计信息",
|
||||||
|
},
|
||||||
"wakatimecard.nocodingactivity": {
|
"wakatimecard.nocodingactivity": {
|
||||||
ar: "لا يوجد نشاط برمجي لهذا الأسبوع",
|
ar: "لا يوجد نشاط برمجي لهذا الأسبوع",
|
||||||
cn: "本周没有编程活动",
|
cn: "本周没有编程活动",
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
|
|||||||
y="0"
|
y="0"
|
||||||
class="header"
|
class="header"
|
||||||
data-testid="header"
|
data-testid="header"
|
||||||
>Wakatime Stats</text>
|
>Wakatime Stats (last 7 days)</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
@@ -303,7 +303,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout w
|
|||||||
y="0"
|
y="0"
|
||||||
class="header"
|
class="header"
|
||||||
data-testid="header"
|
data-testid="header"
|
||||||
>Wakatime Stats</text>
|
>Wakatime Stats (last 7 days)</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
|||||||
+7
-38
@@ -12,17 +12,18 @@ const stats = {
|
|||||||
totalCommits: 200,
|
totalCommits: 200,
|
||||||
totalIssues: 300,
|
totalIssues: 300,
|
||||||
totalPRs: 400,
|
totalPRs: 400,
|
||||||
contributedTo: 500,
|
contributedTo: 50,
|
||||||
rank: null,
|
rank: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
stats.rank = calculateRank({
|
stats.rank = calculateRank({
|
||||||
totalCommits: stats.totalCommits,
|
all_commits: false,
|
||||||
totalRepos: 1,
|
commits: stats.totalCommits,
|
||||||
followers: 0,
|
|
||||||
contributions: stats.contributedTo,
|
|
||||||
stargazers: stats.totalStars,
|
|
||||||
prs: stats.totalPRs,
|
prs: stats.totalPRs,
|
||||||
issues: stats.totalIssues,
|
issues: stats.totalIssues,
|
||||||
|
repos: 1,
|
||||||
|
stars: stats.totalStars,
|
||||||
|
followers: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data_stats = {
|
const data_stats = {
|
||||||
@@ -229,38 +230,6 @@ describe("Test /api/", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should add private contributions", async () => {
|
|
||||||
const { req, res } = faker(
|
|
||||||
{
|
|
||||||
username: "anuraghazra",
|
|
||||||
count_private: true,
|
|
||||||
},
|
|
||||||
data_stats,
|
|
||||||
);
|
|
||||||
|
|
||||||
await api(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
|
||||||
expect(res.send).toBeCalledWith(
|
|
||||||
renderStatsCard(
|
|
||||||
{
|
|
||||||
...stats,
|
|
||||||
totalCommits: stats.totalCommits + 100,
|
|
||||||
rank: calculateRank({
|
|
||||||
totalCommits: stats.totalCommits + 100,
|
|
||||||
totalRepos: 1,
|
|
||||||
followers: 0,
|
|
||||||
contributions: stats.contributedTo,
|
|
||||||
stargazers: stats.totalStars,
|
|
||||||
prs: stats.totalPRs,
|
|
||||||
issues: stats.totalIssues,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow changing ring_color", async () => {
|
it("should allow changing ring_color", async () => {
|
||||||
const { req, res } = faker(
|
const { req, res } = faker(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,17 +2,87 @@ import "@testing-library/jest-dom";
|
|||||||
import { calculateRank } from "../src/calculateRank.js";
|
import { calculateRank } from "../src/calculateRank.js";
|
||||||
|
|
||||||
describe("Test calculateRank", () => {
|
describe("Test calculateRank", () => {
|
||||||
it("should calculate rank correctly", () => {
|
it("new user gets B rank", () => {
|
||||||
expect(
|
expect(
|
||||||
calculateRank({
|
calculateRank({
|
||||||
totalCommits: 100,
|
all_commits: false,
|
||||||
totalRepos: 5,
|
commits: 0,
|
||||||
followers: 100,
|
prs: 0,
|
||||||
contributions: 61,
|
issues: 0,
|
||||||
stargazers: 400,
|
repos: 0,
|
||||||
prs: 300,
|
stars: 0,
|
||||||
issues: 200,
|
followers: 0,
|
||||||
}),
|
}),
|
||||||
).toStrictEqual({ level: "A+", score: 49.25629684876535 });
|
).toStrictEqual({ level: "B", score: 100 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("average user gets A rank", () => {
|
||||||
|
expect(
|
||||||
|
calculateRank({
|
||||||
|
all_commits: false,
|
||||||
|
commits: 250,
|
||||||
|
prs: 50,
|
||||||
|
issues: 25,
|
||||||
|
repos: 0,
|
||||||
|
stars: 250,
|
||||||
|
followers: 25,
|
||||||
|
}),
|
||||||
|
).toStrictEqual({ level: "A", score: 50 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("average user gets A rank (include_all_commits)", () => {
|
||||||
|
expect(
|
||||||
|
calculateRank({
|
||||||
|
all_commits: true,
|
||||||
|
commits: 1000,
|
||||||
|
prs: 50,
|
||||||
|
issues: 25,
|
||||||
|
repos: 0,
|
||||||
|
stars: 250,
|
||||||
|
followers: 25,
|
||||||
|
}),
|
||||||
|
).toStrictEqual({ level: "A", score: 50 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("more than average user gets A+ rank", () => {
|
||||||
|
expect(
|
||||||
|
calculateRank({
|
||||||
|
all_commits: false,
|
||||||
|
commits: 500,
|
||||||
|
prs: 100,
|
||||||
|
issues: 50,
|
||||||
|
repos: 0,
|
||||||
|
stars: 500,
|
||||||
|
followers: 50,
|
||||||
|
}),
|
||||||
|
).toStrictEqual({ level: "A+", score: 25 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expert user gets S rank", () => {
|
||||||
|
expect(
|
||||||
|
calculateRank({
|
||||||
|
all_commits: false,
|
||||||
|
commits: 1000,
|
||||||
|
prs: 200,
|
||||||
|
issues: 100,
|
||||||
|
repos: 0,
|
||||||
|
stars: 1000,
|
||||||
|
followers: 100,
|
||||||
|
}),
|
||||||
|
).toStrictEqual({ level: "S", score: 6.25 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ezyang gets S+ rank", () => {
|
||||||
|
expect(
|
||||||
|
calculateRank({
|
||||||
|
all_commits: false,
|
||||||
|
commits: 1000,
|
||||||
|
prs: 4000,
|
||||||
|
issues: 2000,
|
||||||
|
repos: 0,
|
||||||
|
stars: 5000,
|
||||||
|
followers: 2000,
|
||||||
|
}),
|
||||||
|
).toStrictEqual({ level: "S+", score: 1.1363983154296875 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ const STATS_DATA = {
|
|||||||
totalIssues: 1,
|
totalIssues: 1,
|
||||||
totalStars: 1,
|
totalStars: 1,
|
||||||
contributedTo: 1,
|
contributedTo: 1,
|
||||||
rank: {
|
rank: { level: "B", score: 98.50610674501908 },
|
||||||
level: "A+",
|
|
||||||
score: 50.88831151384285,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const LANGS_DATA = {
|
const LANGS_DATA = {
|
||||||
@@ -54,7 +51,7 @@ const WAKATIME_DATA = {
|
|||||||
is_up_to_date: false,
|
is_up_to_date: false,
|
||||||
is_up_to_date_pending_future: false,
|
is_up_to_date_pending_future: false,
|
||||||
percent_calculated: 0,
|
percent_calculated: 0,
|
||||||
range: "last_7_days",
|
range: "all_time",
|
||||||
status: "pending_update",
|
status: "pending_update",
|
||||||
timeout: 15,
|
timeout: 15,
|
||||||
username: USER,
|
username: USER,
|
||||||
|
|||||||
+39
-62
@@ -102,13 +102,13 @@ describe("Test fetchStats", () => {
|
|||||||
it("should fetch correct stats", async () => {
|
it("should fetch correct stats", async () => {
|
||||||
let stats = await fetchStats("anuraghazra");
|
let stats = await fetchStats("anuraghazra");
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
totalCommits: 100,
|
all_commits: false,
|
||||||
totalRepos: 5,
|
commits: 100,
|
||||||
followers: 100,
|
|
||||||
contributions: 61,
|
|
||||||
stargazers: 300,
|
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
|
repos: 5,
|
||||||
|
stars: 300,
|
||||||
|
followers: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
@@ -132,13 +132,13 @@ describe("Test fetchStats", () => {
|
|||||||
|
|
||||||
let stats = await fetchStats("anuraghazra");
|
let stats = await fetchStats("anuraghazra");
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
totalCommits: 100,
|
all_commits: false,
|
||||||
totalRepos: 5,
|
commits: 100,
|
||||||
followers: 100,
|
|
||||||
contributions: 61,
|
|
||||||
stargazers: 300,
|
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
|
repos: 5,
|
||||||
|
stars: 300,
|
||||||
|
followers: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
@@ -161,49 +161,26 @@ describe("Test fetchStats", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fetch and add private contributions", async () => {
|
|
||||||
let stats = await fetchStats("anuraghazra", true);
|
|
||||||
const rank = calculateRank({
|
|
||||||
totalCommits: 150,
|
|
||||||
totalRepos: 5,
|
|
||||||
followers: 100,
|
|
||||||
contributions: 61,
|
|
||||||
stargazers: 300,
|
|
||||||
prs: 300,
|
|
||||||
issues: 200,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
|
||||||
contributedTo: 61,
|
|
||||||
name: "Anurag Hazra",
|
|
||||||
totalCommits: 150,
|
|
||||||
totalIssues: 200,
|
|
||||||
totalPRs: 300,
|
|
||||||
totalStars: 300,
|
|
||||||
rank,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fetch total commits", async () => {
|
it("should fetch total commits", async () => {
|
||||||
mock
|
mock
|
||||||
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
||||||
.reply(200, { total_count: 1000 });
|
.reply(200, { total_count: 1000 });
|
||||||
|
|
||||||
let stats = await fetchStats("anuraghazra", true, true);
|
let stats = await fetchStats("anuraghazra", false, true);
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
totalCommits: 1050,
|
all_commits: true,
|
||||||
totalRepos: 5,
|
commits: 1000,
|
||||||
followers: 100,
|
|
||||||
contributions: 61,
|
|
||||||
stargazers: 300,
|
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
|
repos: 5,
|
||||||
|
stars: 300,
|
||||||
|
followers: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
contributedTo: 61,
|
contributedTo: 61,
|
||||||
name: "Anurag Hazra",
|
name: "Anurag Hazra",
|
||||||
totalCommits: 1050,
|
totalCommits: 1000,
|
||||||
totalIssues: 200,
|
totalIssues: 200,
|
||||||
totalPRs: 300,
|
totalPRs: 300,
|
||||||
totalStars: 300,
|
totalStars: 300,
|
||||||
@@ -216,21 +193,21 @@ describe("Test fetchStats", () => {
|
|||||||
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
||||||
.reply(200, { total_count: 1000 });
|
.reply(200, { total_count: 1000 });
|
||||||
|
|
||||||
let stats = await fetchStats("anuraghazra", true, true, ["test-repo-1"]);
|
let stats = await fetchStats("anuraghazra", false, true, ["test-repo-1"]);
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
totalCommits: 1050,
|
all_commits: true,
|
||||||
totalRepos: 5,
|
commits: 1000,
|
||||||
followers: 100,
|
|
||||||
contributions: 61,
|
|
||||||
stargazers: 200,
|
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
|
repos: 5,
|
||||||
|
stars: 200,
|
||||||
|
followers: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
contributedTo: 61,
|
contributedTo: 61,
|
||||||
name: "Anurag Hazra",
|
name: "Anurag Hazra",
|
||||||
totalCommits: 1050,
|
totalCommits: 1000,
|
||||||
totalIssues: 200,
|
totalIssues: 200,
|
||||||
totalPRs: 300,
|
totalPRs: 300,
|
||||||
totalStars: 200,
|
totalStars: 200,
|
||||||
@@ -243,13 +220,13 @@ describe("Test fetchStats", () => {
|
|||||||
|
|
||||||
let stats = await fetchStats("anuraghazra");
|
let stats = await fetchStats("anuraghazra");
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
totalCommits: 100,
|
all_commits: false,
|
||||||
totalRepos: 5,
|
commits: 100,
|
||||||
followers: 100,
|
|
||||||
contributions: 61,
|
|
||||||
stargazers: 400,
|
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
|
repos: 5,
|
||||||
|
stars: 400,
|
||||||
|
followers: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
@@ -268,13 +245,13 @@ describe("Test fetchStats", () => {
|
|||||||
|
|
||||||
let stats = await fetchStats("anuraghazra");
|
let stats = await fetchStats("anuraghazra");
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
totalCommits: 100,
|
all_commits: false,
|
||||||
totalRepos: 5,
|
commits: 100,
|
||||||
followers: 100,
|
|
||||||
contributions: 61,
|
|
||||||
stargazers: 300,
|
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
|
repos: 5,
|
||||||
|
stars: 300,
|
||||||
|
followers: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
@@ -293,13 +270,13 @@ describe("Test fetchStats", () => {
|
|||||||
|
|
||||||
let stats = await fetchStats("anuraghazra");
|
let stats = await fetchStats("anuraghazra");
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
totalCommits: 100,
|
all_commits: false,
|
||||||
totalRepos: 5,
|
commits: 100,
|
||||||
followers: 100,
|
|
||||||
contributions: 61,
|
|
||||||
stargazers: 300,
|
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
|
repos: 5,
|
||||||
|
stars: 300,
|
||||||
|
followers: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ describe("Wakatime fetcher", () => {
|
|||||||
const username = "anuraghazra";
|
const username = "anuraghazra";
|
||||||
mock
|
mock
|
||||||
.onGet(
|
.onGet(
|
||||||
`https://wakatime.com/api/v1/users/${username}/stats/all_time?is_including_today=true`,
|
`https://wakatime.com/api/v1/users/${username}/stats?is_including_today=true`,
|
||||||
)
|
)
|
||||||
.reply(200, wakaTimeData);
|
.reply(200, wakaTimeData);
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,17 @@ import {
|
|||||||
radiansToDegrees,
|
radiansToDegrees,
|
||||||
polarToCartesian,
|
polarToCartesian,
|
||||||
cartesianToPolar,
|
cartesianToPolar,
|
||||||
|
getCircleLength,
|
||||||
calculateCompactLayoutHeight,
|
calculateCompactLayoutHeight,
|
||||||
calculateNormalLayoutHeight,
|
calculateNormalLayoutHeight,
|
||||||
calculateDonutLayoutHeight,
|
calculateDonutLayoutHeight,
|
||||||
|
calculateDonutVerticalLayoutHeight,
|
||||||
calculatePieLayoutHeight,
|
calculatePieLayoutHeight,
|
||||||
donutCenterTranslation,
|
donutCenterTranslation,
|
||||||
trimTopLanguages,
|
trimTopLanguages,
|
||||||
renderTopLanguages,
|
renderTopLanguages,
|
||||||
MIN_CARD_WIDTH,
|
MIN_CARD_WIDTH,
|
||||||
|
getDefaultLanguagesCountByLayout,
|
||||||
} from "../src/cards/top-languages-card.js";
|
} from "../src/cards/top-languages-card.js";
|
||||||
|
|
||||||
// adds special assertions like toHaveTextContent
|
// adds special assertions like toHaveTextContent
|
||||||
@@ -70,6 +73,20 @@ const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => {
|
|||||||
return (endAngle - startAngle) / 3.6;
|
return (endAngle - startAngle) / 3.6;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate language percentage for donut vertical chart SVG.
|
||||||
|
*
|
||||||
|
* @param {number} partLength Length of current chart part..
|
||||||
|
* @param {number} totalCircleLength Total length of circle.
|
||||||
|
* @return {number} Chart part percentage.
|
||||||
|
*/
|
||||||
|
const langPercentFromDonutVerticalLayoutSvg = (
|
||||||
|
partLength,
|
||||||
|
totalCircleLength,
|
||||||
|
) => {
|
||||||
|
return (partLength / totalCircleLength) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the language percentage from the pie chart SVG.
|
* Retrieve the language percentage from the pie chart SVG.
|
||||||
*
|
*
|
||||||
@@ -230,6 +247,20 @@ describe("Test renderTopLanguages helper functions", () => {
|
|||||||
expect(calculateDonutLayoutHeight(10)).toBe(375);
|
expect(calculateDonutLayoutHeight(10)).toBe(375);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("calculateDonutVerticalLayoutHeight", () => {
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(0)).toBe(300);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(1)).toBe(325);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(2)).toBe(325);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(3)).toBe(350);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(4)).toBe(350);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(5)).toBe(375);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(6)).toBe(375);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(7)).toBe(400);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(8)).toBe(400);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(9)).toBe(425);
|
||||||
|
expect(calculateDonutVerticalLayoutHeight(10)).toBe(425);
|
||||||
|
});
|
||||||
|
|
||||||
it("calculatePieLayoutHeight", () => {
|
it("calculatePieLayoutHeight", () => {
|
||||||
expect(calculatePieLayoutHeight(0)).toBe(300);
|
expect(calculatePieLayoutHeight(0)).toBe(300);
|
||||||
expect(calculatePieLayoutHeight(1)).toBe(325);
|
expect(calculatePieLayoutHeight(1)).toBe(325);
|
||||||
@@ -258,6 +289,18 @@ describe("Test renderTopLanguages helper functions", () => {
|
|||||||
expect(donutCenterTranslation(10)).toBe(35);
|
expect(donutCenterTranslation(10)).toBe(35);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("getCircleLength", () => {
|
||||||
|
expect(getCircleLength(20)).toBeCloseTo(125.663);
|
||||||
|
expect(getCircleLength(30)).toBeCloseTo(188.495);
|
||||||
|
expect(getCircleLength(40)).toBeCloseTo(251.327);
|
||||||
|
expect(getCircleLength(50)).toBeCloseTo(314.159);
|
||||||
|
expect(getCircleLength(60)).toBeCloseTo(376.991);
|
||||||
|
expect(getCircleLength(70)).toBeCloseTo(439.822);
|
||||||
|
expect(getCircleLength(80)).toBeCloseTo(502.654);
|
||||||
|
expect(getCircleLength(90)).toBeCloseTo(565.486);
|
||||||
|
expect(getCircleLength(100)).toBeCloseTo(628.318);
|
||||||
|
});
|
||||||
|
|
||||||
it("trimTopLanguages", () => {
|
it("trimTopLanguages", () => {
|
||||||
expect(trimTopLanguages([])).toStrictEqual({
|
expect(trimTopLanguages([])).toStrictEqual({
|
||||||
langs: [],
|
langs: [],
|
||||||
@@ -286,6 +329,15 @@ describe("Test renderTopLanguages helper functions", () => {
|
|||||||
totalLanguageSize: 300,
|
totalLanguageSize: 300,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("getDefaultLanguagesCountByLayout", () => {
|
||||||
|
expect(getDefaultLanguagesCountByLayout("normal")).toStrictEqual(5);
|
||||||
|
expect(getDefaultLanguagesCountByLayout(undefined)).toStrictEqual(5);
|
||||||
|
expect(getDefaultLanguagesCountByLayout("compact")).toStrictEqual(6);
|
||||||
|
expect(getDefaultLanguagesCountByLayout("donut")).toStrictEqual(5);
|
||||||
|
expect(getDefaultLanguagesCountByLayout("donut-vertical")).toStrictEqual(6);
|
||||||
|
expect(getDefaultLanguagesCountByLayout("pie")).toStrictEqual(6);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Test renderTopLanguages", () => {
|
describe("Test renderTopLanguages", () => {
|
||||||
@@ -569,6 +621,104 @@ describe("Test renderTopLanguages", () => {
|
|||||||
"circle",
|
"circle",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should render with layout donut vertical", () => {
|
||||||
|
document.body.innerHTML = renderTopLanguages(langs, {
|
||||||
|
layout: "donut-vertical",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(queryByTestId(document.body, "header")).toHaveTextContent(
|
||||||
|
"Most Used Languages",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
|
||||||
|
"HTML 40.00%",
|
||||||
|
);
|
||||||
|
expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
|
||||||
|
"size",
|
||||||
|
"40",
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalCircleLength = queryAllByTestId(
|
||||||
|
document.body,
|
||||||
|
"lang-donut",
|
||||||
|
)[0].getAttribute("stroke-dasharray");
|
||||||
|
|
||||||
|
const HTMLLangPercent = langPercentFromDonutVerticalLayoutSvg(
|
||||||
|
queryAllByTestId(document.body, "lang-donut")[1].getAttribute(
|
||||||
|
"stroke-dashoffset",
|
||||||
|
) -
|
||||||
|
queryAllByTestId(document.body, "lang-donut")[0].getAttribute(
|
||||||
|
"stroke-dashoffset",
|
||||||
|
),
|
||||||
|
totalCircleLength,
|
||||||
|
);
|
||||||
|
expect(HTMLLangPercent).toBeCloseTo(40);
|
||||||
|
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
|
||||||
|
"javascript 40.00%",
|
||||||
|
);
|
||||||
|
expect(queryAllByTestId(document.body, "lang-donut")[1]).toHaveAttribute(
|
||||||
|
"size",
|
||||||
|
"40",
|
||||||
|
);
|
||||||
|
const javascriptLangPercent = langPercentFromDonutVerticalLayoutSvg(
|
||||||
|
queryAllByTestId(document.body, "lang-donut")[2].getAttribute(
|
||||||
|
"stroke-dashoffset",
|
||||||
|
) -
|
||||||
|
queryAllByTestId(document.body, "lang-donut")[1].getAttribute(
|
||||||
|
"stroke-dashoffset",
|
||||||
|
),
|
||||||
|
totalCircleLength,
|
||||||
|
);
|
||||||
|
expect(javascriptLangPercent).toBeCloseTo(40);
|
||||||
|
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
|
||||||
|
"css 20.00%",
|
||||||
|
);
|
||||||
|
expect(queryAllByTestId(document.body, "lang-donut")[2]).toHaveAttribute(
|
||||||
|
"size",
|
||||||
|
"20",
|
||||||
|
);
|
||||||
|
const cssLangPercent = langPercentFromDonutVerticalLayoutSvg(
|
||||||
|
totalCircleLength -
|
||||||
|
queryAllByTestId(document.body, "lang-donut")[2].getAttribute(
|
||||||
|
"stroke-dashoffset",
|
||||||
|
),
|
||||||
|
totalCircleLength,
|
||||||
|
);
|
||||||
|
expect(cssLangPercent).toBeCloseTo(20);
|
||||||
|
|
||||||
|
expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render with layout donut vertical full donut circle of one language is 100%", () => {
|
||||||
|
document.body.innerHTML = renderTopLanguages(
|
||||||
|
{ HTML: langs.HTML },
|
||||||
|
{ layout: "donut-vertical" },
|
||||||
|
);
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
|
||||||
|
"HTML 100.00%",
|
||||||
|
);
|
||||||
|
expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
|
||||||
|
"size",
|
||||||
|
"100",
|
||||||
|
);
|
||||||
|
const totalCircleLength = queryAllByTestId(
|
||||||
|
document.body,
|
||||||
|
"lang-donut",
|
||||||
|
)[0].getAttribute("stroke-dasharray");
|
||||||
|
|
||||||
|
const HTMLLangPercent = langPercentFromDonutVerticalLayoutSvg(
|
||||||
|
totalCircleLength -
|
||||||
|
queryAllByTestId(document.body, "lang-donut")[0].getAttribute(
|
||||||
|
"stroke-dashoffset",
|
||||||
|
),
|
||||||
|
totalCircleLength,
|
||||||
|
);
|
||||||
|
expect(HTMLLangPercent).toBeCloseTo(100);
|
||||||
|
});
|
||||||
|
|
||||||
it("should render with layout pie", () => {
|
it("should render with layout pie", () => {
|
||||||
document.body.innerHTML = renderTopLanguages(langs, { layout: "pie" });
|
document.body.innerHTML = renderTopLanguages(langs, { layout: "pie" });
|
||||||
|
|
||||||
@@ -657,7 +807,7 @@ describe("Test renderTopLanguages", () => {
|
|||||||
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
|
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render langs with specified langs_count", async () => {
|
it("should render langs with specified langs_count", () => {
|
||||||
const options = {
|
const options = {
|
||||||
langs_count: 1,
|
langs_count: 1,
|
||||||
};
|
};
|
||||||
@@ -667,7 +817,7 @@ describe("Test renderTopLanguages", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render langs with specified langs_count even when hide is set", async () => {
|
it("should render langs with specified langs_count even when hide is set", () => {
|
||||||
const options = {
|
const options = {
|
||||||
hide: ["HTML"],
|
hide: ["HTML"],
|
||||||
langs_count: 2,
|
langs_count: 2,
|
||||||
@@ -677,4 +827,11 @@ describe("Test renderTopLanguages", () => {
|
|||||||
options.langs_count,
|
options.langs_count,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show "No languages data." message instead of empty card when nothing to show', () => {
|
||||||
|
document.body.innerHTML = renderTopLanguages({});
|
||||||
|
expect(document.querySelector(".stat").textContent).toBe(
|
||||||
|
"No languages data.",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe("Test Render Wakatime Card", () => {
|
|||||||
expect(
|
expect(
|
||||||
document.querySelector('g[transform="translate(0, 0)"]>text.stat.bold')
|
document.querySelector('g[transform="translate(0, 0)"]>text.stat.bold')
|
||||||
.textContent,
|
.textContent,
|
||||||
).toBe("本周没有编程活动");
|
).toBe("Wakatime 用户个人资料未公开");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render without rounding", () => {
|
it("should render without rounding", () => {
|
||||||
|
|||||||
+22
-1
@@ -123,7 +123,7 @@ describe("Test /api/top-langs", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render error card on error", async () => {
|
it("should render error card on user data fetch error", async () => {
|
||||||
const req = {
|
const req = {
|
||||||
query: {
|
query: {
|
||||||
username: "anuraghazra",
|
username: "anuraghazra",
|
||||||
@@ -140,4 +140,25 @@ describe("Test /api/top-langs", () => {
|
|||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||||
expect(res.send).toBeCalledWith(renderError(error.errors[0].message));
|
expect(res.send).toBeCalledWith(renderError(error.errors[0].message));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should render error card on incorrect layout input", async () => {
|
||||||
|
const req = {
|
||||||
|
query: {
|
||||||
|
username: "anuraghazra",
|
||||||
|
layout: ["pie"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const res = {
|
||||||
|
setHeader: jest.fn(),
|
||||||
|
send: jest.fn(),
|
||||||
|
};
|
||||||
|
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
|
||||||
|
|
||||||
|
await topLangs(req, res);
|
||||||
|
|
||||||
|
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||||
|
expect(res.send).toBeCalledWith(
|
||||||
|
renderError("Something went wrong", "Incorrect layout input"),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user