diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 198bc80e..367ae4f3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,7 +15,7 @@ body: required: true - type: textarea attributes: - label: Expected behaviour + label: Expected behavior description: A clear and concise description of what you expected to happen. - type: textarea diff --git a/.github/workflows/prs-cache-clean.yml b/.github/workflows/prs-cache-clean.yml new file mode 100644 index 00000000..8ee4670e --- /dev/null +++ b/.github/workflows/prs-cache-clean.yml @@ -0,0 +1,33 @@ +name: prs cache clean +on: + pull_request: + types: + - closed + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cleanup + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe34668d..e8fc84a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,4 +34,4 @@ jobs: npm run format:check - name: Code Coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 diff --git a/docs/readme_cn.md b/docs/readme_cn.md index d6487b66..3bd2c149 100644 --- a/docs/readme_cn.md +++ b/docs/readme_cn.md @@ -171,7 +171,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_ - `hide_title` - _(boolean)_ -- `layout` - 在两个可用布局 `default` & `compact` 间切换 +- `layout` - 提供四種佈局 `normal` & `compact` & `donut` & `pie` 间切换 - `card_width` - 手动设置卡片的宽度 _(number)_ > :warning: **重要:** diff --git a/docs/readme_de.md b/docs/readme_de.md index 8756670e..a94e8c2c 100644 --- a/docs/readme_de.md +++ b/docs/readme_de.md @@ -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_title` - _(Boolean)_ -- `layout` - Wechsel zwischen den zwei verfügbaren Layouts `default` & `compact` +- `layout` - Wechseln Sie zwischen den vier verfügbaren Layouts `normal` & `compact` & `donut` & `pie` - `card_width` - Lege die Breite der Karte manuell fest _(Zahl)_ > :warning: **Wichtig:** @@ -253,18 +253,18 @@ Du kannst die `&layout=compact` Option nutzen, um das Kartendesign zu ändern. Ändere `?username=` in den eigenen [Wakatime](https://wakatime.com)-Benutzernamen. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Beispiel -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - Kompaktes Layout -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_es.md b/docs/readme_es.md index b477e5c7..a353502c 100644 --- a/docs/readme_es.md +++ b/docs/readme_es.md @@ -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_title` - _(booleano)_ -- `layout` - Cambia entre los dos diseños disponibles `default` & `compact` +- `layout` - Cambiar entre los cuatro diseños disponibles `normal` & `compact` & `donut` & `pie` - `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)_ - `exclude_repo` - Excluye los repositorios especificados _(valores separados por comas)_ @@ -282,18 +282,18 @@ Puedes usar la opción `& layout = compact` para cambiar el diseño de la tarjet cambia el valor del parámetro `?username=` a tu username en [Wakatime](https://wakatime.com). ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Ejemplo -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - Diseño compacto -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) --- @@ -343,7 +343,7 @@ Escoja cualquiera de los [temas por defecto](#themes) - Tarjeta de Wakatime -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_fr.md b/docs/readme_fr.md index ba9d8da3..ddd003a4 100644 --- a/docs/readme_fr.md +++ b/docs/readme_fr.md @@ -159,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_title` - Masquer le titre _(boolean)_ -- `layout` - Alterner entre 2 mise en page `default` & `compact` +- `layout` - Alterner entre 4 mise en page `normal` & `compact` & `donut` & `pie` - `card_width` - Fixer la largeur de la carte manuellement _(number)_ > :warning: **Important:** diff --git a/docs/readme_it.md b/docs/readme_it.md index e54af7dc..4418c333 100644 --- a/docs/readme_it.md +++ b/docs/readme_it.md @@ -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_title` - Nasconde il titolo _(booleano)_ -- `layout` - Specifica il tipo di layout, `default` (esteso) o `compact` (compatto) +- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella) e `pie` (torta) - `card_width` - Specifica il valore della larghezza _(numero)_ > :warning: **Importante:** diff --git a/docs/readme_ja.md b/docs/readme_ja.md index 2c2def7f..e6629ae2 100644 --- a/docs/readme_ja.md +++ b/docs/readme_ja.md @@ -174,7 +174,7 @@ bg_color オプションで複数のカンマ区切りの値を指定してグ - `hide` - 特定の言語を隠す _(カンマ区切りで指定)_ - `hide_title` - _(boolean)_ -- `layout` - `default` か `compact` のいずれかのレイアウトに切り替える +- `layout` - `normal` & `compact` & `donut` & `pie` のいずれかのレイアウトに切り替える - `card_width` - カードの横幅 _(number)_ - `langs_count` - 表示される言語の数 _(1 ~ 10, 初期値 5)_ - `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_ diff --git a/docs/readme_kr.md b/docs/readme_kr.md index 4a1c57cc..28207af5 100644 --- a/docs/readme_kr.md +++ b/docs/readme_kr.md @@ -189,7 +189,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_ - `hide_title` - 타이틀 제외 _(boolean)_ -- `layout` - 사용 가능한 두 가지 값, `default` & `compact` 중 표시 형태 선택 +- `layout` - 4가지 값 사용 가능, `normal` & `compact` & `donut` & `pie` 중 표시 형태 선택 - `card_width` - 카드 너비 직접 설정 _(number)_ - `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_ - `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_ @@ -298,18 +298,18 @@ _참고: `?username=` 속성의 값을 [Wakatime](https://wakatime.com) 계정의 사용자 명(닉네임)으로 바꿔주세요. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### 미리보기 -[![willianrod 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - 컴팩트한 레이아웃 -[![willianrod 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) --- @@ -359,7 +359,7 @@ _참고: - Wakatime 카드 -[![willianrod 님의 Wakatime 카드](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok 님의 Wakatime 카드](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_nl.md b/docs/readme_nl.md index b279c4f7..04ba9cf4 100644 --- a/docs/readme_nl.md +++ b/docs/readme_nl.md @@ -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_title` - _(boolean)_ -- `layout` - Keuze voor de twee beschikbare layouts `default` & `compact` +- `layout` - Keuze uit de vier beschikbare indelingen `normal` & `compact` & `donut` & `pie` - `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)_ - `exclude_repo` - Verbergt specifieke repositories _(komma gescheiden waardes)_ @@ -283,14 +283,14 @@ Je kan de `&layout=compact` optie gebruiken om het kaart ontwerp aan te passen. Verander de `?username=` waarde naar je [Wakatime](https://wakatime.com) gebruikersnaam. ```md -[![willianrod's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Demo -[![willianrod's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) --- @@ -340,7 +340,7 @@ Kies uit de [standaard thema\'s](#themes) - Wakatime kaart -[![willianrod's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_np.md b/docs/readme_np.md index e90ee57b..088039dd 100644 --- a/docs/readme_np.md +++ b/docs/readme_np.md @@ -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_title` - _(boolean)_ -- `layout` - Switch between two available layouts `default` & `compact` +- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`. - `card_width` - Set the card's width manually _(number)_ - `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_ - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_ @@ -279,14 +279,14 @@ You can use the `&langs_count=` option to increase or decrease the number of lan Change the `?username=` value to your [Wakatime](https://wakatime.com) username. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### डेमो -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) --- @@ -336,7 +336,7 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username. - वक समय कार्ड -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_pt-BR.md b/docs/readme_pt-BR.md index 1ac57716..bcb58f2b 100644 --- a/docs/readme_pt-BR.md +++ b/docs/readme_pt-BR.md @@ -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_title` - Oculta o título _(boolean)_ -- `layout` - Alterna entre os dois layouts disponíveis `default` & `compact` +- `layout` - Alterna entre os quatro layouts disponíveis `normal` & `compact` & `donut` & `pie` - `card_width` - Define a largura do cartão manualmente _(number)_ > :warning: **Importante:** @@ -242,14 +242,14 @@ Utilize a opção `&layout=compact` para mudar o layout do cartão. Altere o valor de `?username=` para o seu username do Wakatime. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Demonstração -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_tr.md b/docs/readme_tr.md index d8ae9778..d08a00cb 100644 --- a/docs/readme_tr.md +++ b/docs/readme_tr.md @@ -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_title` - _(boolean)_ -- `layout` - Uygun olan iki tasarım / layout arasında değişiklik yapar `default` & `compact` +- `layout` - Dört uygun tasarım / düzen arasında geçiş `normal` & `compact` & `donut` & `pie` - `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)_ - `exclude_repo` - Belirli repoları listeden çıkartır _(Virgül ile ayırılmış değerlerle)_ @@ -282,18 +282,18 @@ Endpoint: `api/top-langs?username=mustafacagri` `?username=` değerini [Wakatime](https://wakatime.com)'daki kullanıcı adınızla değiştirin. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Demo -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - Kompakt Düzen -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) --- @@ -343,7 +343,7 @@ Endpoint: `api/top-langs?username=mustafacagri` - Wakatime kart -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/package-lock.json b/package-lock.json index ebc7570a..a2156910 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1495,31 +1495,19 @@ } }, "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1778,12 +1766,6 @@ "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "node_modules/browserslist": { "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", @@ -2509,20 +2491,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3865,18 +3833,18 @@ } }, "node_modules/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-pksjj7Rqoa+wdpkKcLzQRHhJCEE42qQhl/xLMUKHgoSejaKOdaXEAnqs6uDNwMl/fciHTzKeR8Wm8cw7N+g98A==", "dev": true, "dependencies": { "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", + "acorn": "^8.8.0", + "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", + "decimal.js": "^10.4.1", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", @@ -3884,18 +3852,17 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", + "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^3.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", - "ws": "^8.8.0", + "ws": "^8.9.0", "xml-name-validator": "^4.0.0" }, "engines": { @@ -5397,15 +5364,6 @@ "node": ">=10.12.0" } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, "node_modules/w3c-xmlserializer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", @@ -5581,9 +5539,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", - "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", "dev": true, "engines": { "node": ">= 14" @@ -6830,27 +6788,19 @@ "dev": true }, "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, "agent-base": { @@ -7049,12 +6999,6 @@ "fill-range": "^7.0.1" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "browserslist": { "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", @@ -7590,13 +7534,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -8609,18 +8546,18 @@ } }, "jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-pksjj7Rqoa+wdpkKcLzQRHhJCEE42qQhl/xLMUKHgoSejaKOdaXEAnqs6uDNwMl/fciHTzKeR8Wm8cw7N+g98A==", "dev": true, "requires": { "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", + "acorn": "^8.8.0", + "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", + "decimal.js": "^10.4.1", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", @@ -8628,18 +8565,17 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", + "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^3.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", - "ws": "^8.8.0", + "ws": "^8.9.0", "xml-name-validator": "^4.0.0" } }, @@ -9724,15 +9660,6 @@ "convert-source-map": "^1.6.0" } }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, "w3c-xmlserializer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", @@ -9855,9 +9782,9 @@ "dev": true }, "yaml": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", - "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", "dev": true }, "yargs": { diff --git a/readme.md b/readme.md index 45fba4a3..5a430769 100644 --- a/readme.md +++ b/readme.md @@ -290,7 +290,7 @@ 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_title` - _(boolean)_. Default: `false`. -- `layout` - Switch between two available layouts `default` & `compact`. Default: `default`. +- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`. - `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`. - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. @@ -410,6 +410,22 @@ You can use the `&layout=compact` option to change the card design. [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) ``` +### Donut Chart Language Card Layout + +You can use the `&layout=donut` option to change the card design. + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats) +``` + +### Pie Chart Language Card Layout + +You can use the `&layout=pie` option to change the card design. + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=pie)](https://github.com/anuraghazra/github-readme-stats) +``` + ### Hide Progress Bars You can use the `&hide_progress=true` option to hide the percentages and the progress bars (layout will be automatically set to `compact`). @@ -426,6 +442,14 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +- Donut Chart layout + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats) + +- Pie Chart layout + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=pie)](https://github.com/anuraghazra/github-readme-stats) + - Hidden progress bars [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) @@ -435,7 +459,7 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro Change the `?username=` value to your [Wakatime](https://wakatime.com) username. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` > **Note**: @@ -443,13 +467,13 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username. ### Demo -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - Compact layout -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) * * * @@ -507,7 +531,7 @@ Choose from any of the [default themes](#themes) - WakaTime card -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) * * * diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index 92701d81..4761d023 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -20,14 +20,16 @@ const RANK_CARD_DEFAULT_WIDTH = 450; /** * Create a stats card text item. * - * @param {object[]} createTextNodeParams Object that contains the createTextNode parameters. + * @param {object} createTextNodeParams Object that contains the createTextNode parameters. + * @param {string} createTextNodeParams.icon The icon to display. * @param {string} createTextNodeParams.label The label to display. - * @param {string} createTextNodeParams.value The value to display. + * @param {number} createTextNodeParams.value The value to display. * @param {string} createTextNodeParams.id The id of the stat. * @param {number} createTextNodeParams.index The index of the stat. * @param {boolean} createTextNodeParams.showIcons Whether to show icons. * @param {number} createTextNodeParams.shiftValuePos Number of pixels the value has to be shifted to the right. * @param {boolean} createTextNodeParams.bold Whether to bold the label. + * @param {string} createTextNodeParams.number_format The format of numbers on card. * @returns */ const createTextNode = ({ diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index ce8e12a8..e03e8bcb 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -36,13 +36,144 @@ const getLongestLang = (arr) => ); /** - * Creates a node to display usage of a programming language in percentage - * using text and a horizontal progress bar. + * Convert degrees to radians. + * + * @param {number} angleInDegrees Angle in degrees. + * @returns Angle in radians. + */ +const degreesToRadians = (angleInDegrees) => angleInDegrees * (Math.PI / 180.0); + +/** + * Convert radians to degrees. + * + * @param {number} angleInRadians Angle in radians. + * @returns Angle in degrees. + */ +const radiansToDegrees = (angleInRadians) => angleInRadians / (Math.PI / 180.0); + +/** + * Convert polar coordinates to cartesian coordinates. + * + * @param {number} centerX Center x coordinate. + * @param {number} centerY Center y coordinate. + * @param {number} radius Radius of the circle. + * @param {number} angleInDegrees Angle in degrees. + * @returns {{x: number, y: number}} Cartesian coordinates. + */ +const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => { + const rads = degreesToRadians(angleInDegrees); + return { + x: centerX + radius * Math.cos(rads), + y: centerY + radius * Math.sin(rads), + }; +}; + +/** + * Convert cartesian coordinates to polar coordinates. + * + * @param {number} centerX Center x coordinate. + * @param {number} centerY Center y coordinate. + * @param {number} x Point x coordinate. + * @param {number} y Point y coordinate. + * @returns {{radius: number, angleInDegrees: number}} Polar coordinates. + */ +const cartesianToPolar = (centerX, centerY, x, y) => { + const radius = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)); + let angleInDegrees = radiansToDegrees(Math.atan2(y - centerY, x - centerX)); + if (angleInDegrees < 0) angleInDegrees += 360; + return { radius, angleInDegrees }; +}; + +/** + * Calculates height for the compact layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculateCompactLayoutHeight = (totalLangs) => { + return 90 + Math.round(totalLangs / 2) * 25; +}; + +/** + * Calculates height for the normal layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculateNormalLayoutHeight = (totalLangs) => { + return 45 + (totalLangs + 1) * 40; +}; + +/** + * Calculates height for the donut layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculateDonutLayoutHeight = (totalLangs) => { + return 215 + Math.max(totalLangs - 5, 0) * 32; +}; + +/** + * Calculates height for the pie layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculatePieLayoutHeight = (totalLangs) => { + return 300 + Math.round(totalLangs / 2) * 25; +}; + +/** + * Calculates the center translation needed to keep the donut chart centred. + * @param {number} totalLangs Total number of languages. + * @returns {number} Donut center translation. + */ +const donutCenterTranslation = (totalLangs) => { + return -45 + Math.max(totalLangs - 5, 0) * 16; +}; + +/** + * Trim top languages to lang_count while also hiding certain languages. + * + * @param {Record} topLangs Top languages. + * @param {string[]} hide Languages to hide. + * @param {string} langs_count Number of languages to show. + * @returns {{topLangs: Record, totalSize: number}} Trimmed top languages and total size. + */ +const trimTopLanguages = (topLangs, hide, langs_count) => { + let langs = Object.values(topLangs); + let langsToHide = {}; + let langsCount = clampValue(parseInt(langs_count), 1, 10); + + // populate langsToHide map for quick lookup + // while filtering out + if (hide) { + hide.forEach((langName) => { + langsToHide[lowercaseTrim(langName)] = true; + }); + } + + // filter out languages to be hidden + langs = langs + .sort((a, b) => b.size - a.size) + .filter((lang) => { + return !langsToHide[lowercaseTrim(lang.name)]; + }) + .slice(0, langsCount); + + const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0); + + return { langs, totalLanguageSize }; +}; + +/** + * Create progress bar text item for a programming language. * * @param {object} props Function properties. * @param {number} props.width The card width - * @param {string} props.name Name of the programming language. * @param {string} props.color Color of the programming language. + * @param {string} props.name Name of the programming language. * @param {string} props.progress Usage of the programming language in percentage. * @param {number} props.index Index of the programming language. * @returns {string} Programming language SVG node. @@ -71,7 +202,7 @@ const createProgressTextNode = ({ width, color, name, progress, index }) => { }; /** - * Creates a text only node to display usage of a programming language in percentage. + * Creates compact text item for a programming language. * * @param {object} props Function properties. * @param {Lang} props.lang Programming language object. @@ -96,9 +227,9 @@ const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => { }; /** - * Creates compact layout of text only language nodes. + * Create compact languages text items for all programming languages. * - * @param {object[]} props Function properties. + * @param {object} props Function properties. * @param {Lang[]} props.langs Array of programming languages. * @param {number} props.totalSize Total size of all languages. * @param {boolean} props.hideProgress Whether to hide percentage. @@ -134,7 +265,30 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => { }; /** - * Renders layout to display user's most frequently used programming languages. + * Create donut languages text items for all programming languages. + * + * @param {object} props Function properties. + * @param {Lang[]} props.langs Array of programming languages. + * @param {number} props.totalSize Total size of all languages. + * @returns {string} Donut layout programming language SVG node. + */ +const createDonutLanguagesNode = ({ langs, totalSize }) => { + return flexLayout({ + items: langs.map((lang, index) => { + return createCompactLangNode({ + lang, + totalSize, + hideProgress: false, + index, + }); + }), + gap: 32, + direction: "column", + }).join(""); +}; + +/** + * Renders the default language card layout. * * @param {Lang[]} langs Array of programming languages. * @param {number} width Card width. @@ -158,7 +312,7 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => { }; /** - * Renders compact layout to display user's most frequently used programming languages. + * Renders the compact language card layout. * * @param {Lang[]} langs Array of programming languages. * @param {number} width Card width. @@ -218,60 +372,200 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => { }; /** - * Calculates height for the compact layout. + * Renders pie layout to display user's most frequently used programming languages. * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. + * @param {Lang[]} langs Array of programming languages. + * @param {number} totalLanguageSize Total size of all languages. + * @returns {string} Compact layout card SVG object. */ -const calculateCompactLayoutHeight = (totalLangs) => { - return 90 + Math.round(totalLangs / 2) * 25; -}; +const renderPieLayout = (langs, totalLanguageSize) => { + // Pie chart radius and center coordinates + const radius = 90; + const centerX = 150; + const centerY = 100; -/** - * Calculates height for the normal layout. - * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. - */ -const calculateNormalLayoutHeight = (totalLangs) => { - return 45 + (totalLangs + 1) * 40; -}; + // Start angle for the pie chart parts + let startAngle = 0; -/** - * Hides languages and trims the list to show only the top N languages. - * - * @param {Record} topLangs Top languages. - * @param {string[]} hide Languages to hide. - * @param {string} langs_count Number of languages to show. - */ -const useLanguages = (topLangs, hide, langs_count) => { - let langs = Object.values(topLangs); - let langsToHide = {}; - let langsCount = clampValue(parseInt(langs_count), 1, 10); + // Start delay coefficient for the pie chart parts + let startDelayCoefficient = 1; - // populate langsToHide map for quick lookup - // while filtering out - if (hide) { - hide.forEach((langName) => { - langsToHide[lowercaseTrim(langName)] = true; - }); + // SVG paths + const paths = []; + + // Generate each pie chart part + for (const lang of langs) { + if (langs.length === 1) { + paths.push(` + + `); + break; + } + + const langSizePart = lang.size / totalLanguageSize; + const percentage = langSizePart * 100; + // Calculate the angle for the current part + const angle = langSizePart * 360; + + // Calculate the end angle + const endAngle = startAngle + angle; + + // Calculate the coordinates of the start and end points of the arc + const startPoint = polarToCartesian(centerX, centerY, radius, startAngle); + const endPoint = polarToCartesian(centerX, centerY, radius, endAngle); + + // Determine the large arc flag based on the angle + const largeArcFlag = angle > 180 ? 1 : 0; + + // Calculate delay + const delay = startDelayCoefficient * 100; + + // SVG arc markup + paths.push(` + + + + `); + + // Update the start angle for the next part + startAngle = endAngle; + // Update the start delay coefficient for the next part + startDelayCoefficient += 1; } - // filter out languages to be hidden - langs = langs - .sort((a, b) => b.size - a.size) - .filter((lang) => { - return !langsToHide[lowercaseTrim(lang.name)]; - }) - .slice(0, langsCount); - - const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0); - - return { langs, totalLanguageSize }; + return ` + + + + ${paths.join("")} + + + + + ${createLanguageTextNode({ + langs, + totalSize: totalLanguageSize, + hideProgress: false, + })} + + + + `; }; /** - * Renders card to display user's most frequently used programming languages. + * Creates the SVG paths for the language donut chart. + * + * @param {number} cx Donut center x-position. + * @param {number} cy Donut center y-position. + * @param {number} radius Donut arc Radius. + * @param {number[]} percentages Array with donut section percentages. + * @returns {{d: string, percent: number}[]} Array of svg path elements + */ +const createDonutPaths = (cx, cy, radius, percentages) => { + const paths = []; + let startAngle = 0; + let endAngle = 0; + + const totalPercent = percentages.reduce((acc, curr) => acc + curr, 0); + for (let i = 0; i < percentages.length; i++) { + const tmpPath = {}; + + let percent = parseFloat( + ((percentages[i] / totalPercent) * 100).toFixed(2), + ); + + endAngle = 3.6 * percent + startAngle; + const startPoint = polarToCartesian(cx, cy, radius, endAngle - 90); // rotate donut 90 degrees counter-clockwise. + const endPoint = polarToCartesian(cx, cy, radius, startAngle - 90); // rotate donut 90 degrees counter-clockwise. + const largeArc = endAngle - startAngle <= 180 ? 0 : 1; + + tmpPath.percent = percent; + tmpPath.d = `M ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArc} 0 ${endPoint.x} ${endPoint.y}`; + + paths.push(tmpPath); + startAngle = endAngle; + } + + return paths; +}; + +/** + * Renders the donut language card layout. + * + * @param {Lang[]} langs Array of programming languages. + * @param {number} width Card width. + * @param {number} totalLanguageSize Total size of all languages. + * @returns {string} Donut layout card SVG object. + */ +const renderDonutLayout = (langs, width, totalLanguageSize) => { + const centerX = width / 3; + const centerY = width / 3; + const radius = centerX - 60; + const strokeWidth = 12; + + const colors = langs.map((lang) => lang.color); + const langsPercents = langs.map((lang) => + parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)), + ); + + const langPaths = createDonutPaths(centerX, centerY, radius, langsPercents); + + const donutPaths = + langs.length === 1 + ? `` + : langPaths + .map((section, index) => { + const staggerDelay = (index + 3) * 100; + const delay = staggerDelay + 300; + + const output = ` + + + + + `; + + return output; + }) + .join(""); + + const donut = `${donutPaths}`; + + return ` + + + ${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize })} + + + + ${donut} + + + `; +}; + +/** + * Renders card that display user's most frequently used programming languages. * * @param {import('../fetchers/types').TopLangData} topLangs User's most frequently used programming languages. * @param {Partial} options Card options. @@ -302,7 +596,7 @@ const renderTopLanguages = (topLangs, options = {}) => { translations: langCardLocales, }); - const { langs, totalLanguageSize } = useLanguages( + const { langs, totalLanguageSize } = trimTopLanguages( topLangs, hide, String(langs_count), @@ -316,7 +610,10 @@ const renderTopLanguages = (topLangs, options = {}) => { let height = calculateNormalLayoutHeight(langs.length); let finalLayout = ""; - if (layout === "compact" || hide_progress == true) { + if (layout === "pie") { + height = calculatePieLayoutHeight(langs.length); + finalLayout = renderPieLayout(langs, totalLanguageSize); + } else if (layout === "compact" || hide_progress == true) { height = calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0); @@ -326,6 +623,10 @@ const renderTopLanguages = (topLangs, options = {}) => { totalLanguageSize, hide_progress, ); + } else if (layout?.toLowerCase() === "donut") { + height = calculateDonutLayoutHeight(langs.length); + width = width + 50; // padding + finalLayout = renderDonutLayout(langs, width, totalLanguageSize); } else { finalLayout = renderNormalLayout(langs, width, totalLanguageSize); } @@ -387,6 +688,10 @@ const renderTopLanguages = (topLangs, options = {}) => { `, ); + if (layout === "pie") { + return card.render(finalLayout); + } + return card.render(` ${finalLayout} @@ -394,4 +699,18 @@ const renderTopLanguages = (topLangs, options = {}) => { `); }; -export { renderTopLanguages, MIN_CARD_WIDTH }; +export { + getLongestLang, + degreesToRadians, + radiansToDegrees, + polarToCartesian, + cartesianToPolar, + calculateCompactLayoutHeight, + calculateNormalLayoutHeight, + calculateDonutLayoutHeight, + calculatePieLayoutHeight, + donutCenterTranslation, + trimTopLanguages, + renderTopLanguages, + MIN_CARD_WIDTH, +}; diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index 02a41b57..7945118c 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -39,7 +39,7 @@ export type TopLangOptions = CommonOptions & { hide_border: boolean; card_width: number; hide: string[]; - layout: "compact" | "normal"; + layout: "compact" | "normal" | "donut" | "pie"; custom_title: string; langs_count: number; disable_animations: boolean; diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index 2c329558..dacc4227 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -36,11 +36,11 @@ const noCodingActivityNode = ({ color, text }) => { /** * Create compact WakaTime layout. * - * @param {Object[]} args The function arguments. - * @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array. - * @param {number} totalSize The total size of the languages. - * @param {number} x The x position of the language node. - * @param {number} y The y position of the language node. + * @param {Object} args The function arguments. + * @param {import("../fetchers/types").WakaTimeLang} args.lang The languages array. + * @param {number} args.totalSize The total size of the languages. + * @param {number} args.x The x position of the language node. + * @param {number} args.y The y position of the language node. */ const createCompactLangNode = ({ lang, totalSize, x, y }) => { const color = languageColors[lang.name] || "#858585"; @@ -58,11 +58,11 @@ const createCompactLangNode = ({ lang, totalSize, x, y }) => { /** * Create WakaTime language text node item. * - * @param {Object[]} args The function arguments. - * @param {import("../fetchers/types").WakaTimeLang} lang The language object. - * @param {number} totalSize The total size of the languages. - * @param {number} x The x position of the language node. - * @param {number} y The y position of the language node. + * @param {Object} args The function arguments. + * @param {import("../fetchers/types").WakaTimeLang[]} args.langs The language objects. + * @param {number} args.totalSize The total size of the languages. + * @param {number} args.x The x position of the language node. + * @param {number} args.y The y position of the language node. */ const createLanguageTextNode = ({ langs, totalSize, x, y }) => { return langs.map((lang, index) => { @@ -86,14 +86,15 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => { /** * Create WakaTime text item. * - * @param {Object[]} args The function arguments. - * @param {string} id The id of the text node item. - * @param {string} label The label of the text node item. - * @param {string} value The value of the text node item. - * @param {number} index The index of the text node item. - * @param {percent} percent Percentage of the text node item. - * @param {boolean} hideProgress Whether to hide the progress bar. - * @param {string} progressBarBackgroundColor The color of the progress bar background. + * @param {Object} args The function arguments. + * @param {string} args.id The id of the text node item. + * @param {string} args.label The label of the text node item. + * @param {string} args.value The value of the text node item. + * @param {number} args.index The index of the text node item. + * @param {string} args.percent Percentage of the text node item. + * @param {boolean} args.hideProgress Whether to hide the progress bar. + * @param {string} args.progressBarColor The color of the progress bar. + * @param {string} args.progressBarBackgroundColor The color of the progress bar background. */ const createTextNode = ({ id, @@ -139,7 +140,7 @@ const createTextNode = ({ * hiding languages. * * @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array. - * @return {import("../fetchers/types").WakaTimeLang[]} The recalculated languages array. + * @return {void} The recalculated languages array. */ const recalculatePercentages = (languages) => { const totalSum = languages.reduce( @@ -267,12 +268,20 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { ${compactProgressBar} - ${createLanguageTextNode({ - x: 0, - y: 25, - langs: filteredLanguages, - totalSize: 100, - }).join("")} + ${ + filteredLanguages.length + ? createLanguageTextNode({ + x: 0, + y: 25, + langs: filteredLanguages, + totalSize: 100, + }).join("") + : noCodingActivityNode({ + // @ts-ignore + color: textColor, + text: i18n.t("wakatimecard.nocodingactivity"), + }) + } `; } else { finalLayout = flexLayout({ diff --git a/src/common/Card.js b/src/common/Card.js index 2f1d9c29..c61b17db 100644 --- a/src/common/Card.js +++ b/src/common/Card.js @@ -12,6 +12,12 @@ class Card { * @param {string?=} args.customTitle Card custom title. * @param {string?=} args.defaultTitle Card default title. * @param {string?=} args.titlePrefixIcon Card title prefix icon. + * @param {object?=} args.colors Card colors arguments. + * @param {string} args.colors.titleColor Card title color. + * @param {string} args.colors.textColor Card text color. + * @param {string} args.colors.iconColor Card icon color. + * @param {string|Array} args.colors.bgColor Card background color. + * @param {string} args.colors.borderColor Card border color. * @returns {Card} Card instance. */ constructor({ diff --git a/src/fetchers/repo-fetcher.js b/src/fetchers/repo-fetcher.js index ff7a2be8..412d1d4c 100644 --- a/src/fetchers/repo-fetcher.js +++ b/src/fetchers/repo-fetcher.js @@ -5,9 +5,9 @@ import { MissingParamError, request } from "../common/utils.js"; /** * Repo data fetcher. * - * @param {import('Axios').AxiosRequestHeaders} variables Fetcher variables. + * @param {import('axios').AxiosRequestHeaders} variables Fetcher variables. * @param {string} token GitHub token. - * @returns {Promise} The response. + * @returns {Promise} The response. */ const fetcher = (variables, token) => { return request( diff --git a/src/fetchers/top-languages-fetcher.js b/src/fetchers/top-languages-fetcher.js index 45b2ba7d..b57d901a 100644 --- a/src/fetchers/top-languages-fetcher.js +++ b/src/fetchers/top-languages-fetcher.js @@ -11,7 +11,7 @@ import { /** * Top languages fetcher object. * - * @param {import('Axios').AxiosRequestHeaders} variables Fetcher variables. + * @param {import('axios').AxiosRequestHeaders} variables Fetcher variables. * @param {string} token GitHub token. * @returns {Promise} Languages fetcher response. */ diff --git a/src/fetchers/wakatime-fetcher.js b/src/fetchers/wakatime-fetcher.js index fa1f3d89..2af8fa99 100644 --- a/src/fetchers/wakatime-fetcher.js +++ b/src/fetchers/wakatime-fetcher.js @@ -14,7 +14,9 @@ const fetchWakatimeStats = async ({ username, api_domain, range }) => { const { data } = await axios.get( `https://${ api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com" - }/api/v1/users/${username}/stats/${range || ""}?is_including_today=true`, + }/api/v1/users/${username}/stats/${ + range || "all_time" + }?is_including_today=true`, ); return data.data; diff --git a/src/getStyles.js b/src/getStyles.js index f7b90f4a..7e611392 100644 --- a/src/getStyles.js +++ b/src/getStyles.js @@ -65,10 +65,11 @@ const getAnimations = () => { /** * Retrieves CSS styles for a card. * - * @param {Object[]} colors The colors to use for the card. + * @param {Object} colors The colors to use for the card. * @param {string} colors.titleColor The title color. * @param {string} colors.textColor The text color. * @param {string} colors.iconColor The icon color. + * @param {string} colors.ringColor The ring color. * @param {boolean} colors.show_icons Whether to show icons. * @param {number} colors.progress The progress value to animate to. * @returns {string} Card CSS styles. diff --git a/tests/api.test.js b/tests/api.test.js index 0f82de5e..0f143783 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -44,7 +44,7 @@ const data_stats = { nodes: [{ stargazers: { totalCount: 100 } }], pageInfo: { hasNextPage: false, - cursor: "cursor", + endCursor: "cursor", }, }, }, diff --git a/tests/fetchStats.test.js b/tests/fetchStats.test.js index 870dd3d1..3b27e8a6 100644 --- a/tests/fetchStats.test.js +++ b/tests/fetchStats.test.js @@ -27,7 +27,7 @@ const data_stats = { ], pageInfo: { hasNextPage: true, - cursor: "cursor", + endCursor: "cursor", }, }, }, @@ -44,7 +44,7 @@ const data_repo = { ], pageInfo: { hasNextPage: false, - cursor: "cursor", + endCursor: "cursor", }, }, }, @@ -64,7 +64,7 @@ const data_repo_zero_stars = { ], pageInfo: { hasNextPage: true, - cursor: "cursor", + endCursor: "cursor", }, }, }, @@ -86,11 +86,12 @@ const mock = new MockAdapter(axios); beforeEach(() => { process.env.FETCH_MULTI_PAGE_STARS = "false"; // Set to `false` to fetch only one page of stars. - mock - .onPost("https://api.github.com/graphql") - .replyOnce(200, data_stats) - .onPost("https://api.github.com/graphql") - .replyOnce(200, data_repo); + mock.onPost("https://api.github.com/graphql").reply((cfg) => { + return [ + 200, + cfg.data.includes("contributionsCollection") ? data_stats : data_repo, + ]; + }); }); afterEach(() => { diff --git a/tests/fetchWakatime.test.js b/tests/fetchWakatime.test.js index 47ca25b7..04c01ba4 100644 --- a/tests/fetchWakatime.test.js +++ b/tests/fetchWakatime.test.js @@ -105,7 +105,7 @@ describe("Wakatime fetcher", () => { const username = "anuraghazra"; mock .onGet( - `https://wakatime.com/api/v1/users/${username}/stats/?is_including_today=true`, + `https://wakatime.com/api/v1/users/${username}/stats/all_time?is_including_today=true`, ) .reply(200, wakaTimeData); diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index de9e21f1..a8bc873a 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -1,9 +1,21 @@ import { queryAllByTestId, queryByTestId } from "@testing-library/dom"; import { cssToObject } from "@uppercod/css-to-object"; import { - MIN_CARD_WIDTH, + getLongestLang, + degreesToRadians, + radiansToDegrees, + polarToCartesian, + cartesianToPolar, + calculateCompactLayoutHeight, + calculateNormalLayoutHeight, + calculateDonutLayoutHeight, + calculatePieLayoutHeight, + donutCenterTranslation, + trimTopLanguages, renderTopLanguages, + MIN_CARD_WIDTH, } from "../src/cards/top-languages-card.js"; + // adds special assertions like toHaveTextContent import "@testing-library/jest-dom"; @@ -27,6 +39,255 @@ const langs = { }, }; +/** + * Retrieve number array from SVG path definition string. + * + * @param {string} d SVG path definition string. + * @return {number[]} Resulting numbers array. + */ +const getNumbersFromSvgPathDefinitionAttribute = (d) => { + return d + .split(" ") + .filter((x) => !isNaN(x)) + .map((x) => parseFloat(x)); +}; + +/** + * Retrieve the language percentage from the donut chart SVG. + * + * @param {string} d The SVG path element. + * @param {number} centerX The center X coordinate of the donut chart. + * @param {number} centerY The center Y coordinate of the donut chart. + * @returns {number} The percentage of the language. + */ +const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => { + const dTmp = getNumbersFromSvgPathDefinitionAttribute(d); + const endAngle = + cartesianToPolar(centerX, centerY, dTmp[0], dTmp[1]).angleInDegrees + 90; + let startAngle = + cartesianToPolar(centerX, centerY, dTmp[7], dTmp[8]).angleInDegrees + 90; + if (startAngle > endAngle) startAngle -= 360; + return (endAngle - startAngle) / 3.6; +}; + +/** + * Retrieve the language percentage from the pie chart SVG. + * + * @param {string} d The SVG path element. + * @param {number} centerX The center X coordinate of the pie chart. + * @param {number} centerY The center Y coordinate of the pie chart. + * @returns {number} The percentage of the language. + */ +const langPercentFromPieLayoutSvg = (d, centerX, centerY) => { + const dTmp = getNumbersFromSvgPathDefinitionAttribute(d); + const startAngle = cartesianToPolar( + centerX, + centerY, + dTmp[2], + dTmp[3], + ).angleInDegrees; + let endAngle = cartesianToPolar( + centerX, + centerY, + dTmp[9], + dTmp[10], + ).angleInDegrees; + return ((endAngle - startAngle) / 360) * 100; +}; + +describe("Test renderTopLanguages helper functions", () => { + it("getLongestLang", () => { + const langArray = Object.values(langs); + expect(getLongestLang(langArray)).toBe(langs.javascript); + }); + + it("degreesToRadians", () => { + expect(degreesToRadians(0)).toBe(0); + expect(degreesToRadians(90)).toBe(Math.PI / 2); + expect(degreesToRadians(180)).toBe(Math.PI); + expect(degreesToRadians(270)).toBe((3 * Math.PI) / 2); + expect(degreesToRadians(360)).toBe(2 * Math.PI); + }); + + it("radiansToDegrees", () => { + expect(radiansToDegrees(0)).toBe(0); + expect(radiansToDegrees(Math.PI / 2)).toBe(90); + expect(radiansToDegrees(Math.PI)).toBe(180); + expect(radiansToDegrees((3 * Math.PI) / 2)).toBe(270); + expect(radiansToDegrees(2 * Math.PI)).toBe(360); + }); + + it("polarToCartesian", () => { + expect(polarToCartesian(100, 100, 60, 0)).toStrictEqual({ x: 160, y: 100 }); + expect(polarToCartesian(100, 100, 60, 45)).toStrictEqual({ + x: 142.42640687119285, + y: 142.42640687119285, + }); + expect(polarToCartesian(100, 100, 60, 90)).toStrictEqual({ + x: 100, + y: 160, + }); + expect(polarToCartesian(100, 100, 60, 135)).toStrictEqual({ + x: 57.573593128807154, + y: 142.42640687119285, + }); + expect(polarToCartesian(100, 100, 60, 180)).toStrictEqual({ + x: 40, + y: 100.00000000000001, + }); + expect(polarToCartesian(100, 100, 60, 225)).toStrictEqual({ + x: 57.57359312880714, + y: 57.573593128807154, + }); + expect(polarToCartesian(100, 100, 60, 270)).toStrictEqual({ + x: 99.99999999999999, + y: 40, + }); + expect(polarToCartesian(100, 100, 60, 315)).toStrictEqual({ + x: 142.42640687119285, + y: 57.57359312880714, + }); + expect(polarToCartesian(100, 100, 60, 360)).toStrictEqual({ + x: 160, + y: 99.99999999999999, + }); + }); + + it("cartesianToPolar", () => { + expect(cartesianToPolar(100, 100, 160, 100)).toStrictEqual({ + radius: 60, + angleInDegrees: 0, + }); + expect( + cartesianToPolar(100, 100, 142.42640687119285, 142.42640687119285), + ).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 45 }); + expect(cartesianToPolar(100, 100, 100, 160)).toStrictEqual({ + radius: 60, + angleInDegrees: 90, + }); + expect( + cartesianToPolar(100, 100, 57.573593128807154, 142.42640687119285), + ).toStrictEqual({ radius: 60, angleInDegrees: 135 }); + expect(cartesianToPolar(100, 100, 40, 100.00000000000001)).toStrictEqual({ + radius: 60, + angleInDegrees: 180, + }); + expect( + cartesianToPolar(100, 100, 57.57359312880714, 57.573593128807154), + ).toStrictEqual({ radius: 60, angleInDegrees: 225 }); + expect(cartesianToPolar(100, 100, 99.99999999999999, 40)).toStrictEqual({ + radius: 60, + angleInDegrees: 270, + }); + expect( + cartesianToPolar(100, 100, 142.42640687119285, 57.57359312880714), + ).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 315 }); + expect(cartesianToPolar(100, 100, 160, 99.99999999999999)).toStrictEqual({ + radius: 60, + angleInDegrees: 360, + }); + }); + + it("calculateCompactLayoutHeight", () => { + expect(calculateCompactLayoutHeight(0)).toBe(90); + expect(calculateCompactLayoutHeight(1)).toBe(115); + expect(calculateCompactLayoutHeight(2)).toBe(115); + expect(calculateCompactLayoutHeight(3)).toBe(140); + expect(calculateCompactLayoutHeight(4)).toBe(140); + expect(calculateCompactLayoutHeight(5)).toBe(165); + expect(calculateCompactLayoutHeight(6)).toBe(165); + expect(calculateCompactLayoutHeight(7)).toBe(190); + expect(calculateCompactLayoutHeight(8)).toBe(190); + expect(calculateCompactLayoutHeight(9)).toBe(215); + expect(calculateCompactLayoutHeight(10)).toBe(215); + }); + + it("calculateNormalLayoutHeight", () => { + expect(calculateNormalLayoutHeight(0)).toBe(85); + expect(calculateNormalLayoutHeight(1)).toBe(125); + expect(calculateNormalLayoutHeight(2)).toBe(165); + expect(calculateNormalLayoutHeight(3)).toBe(205); + expect(calculateNormalLayoutHeight(4)).toBe(245); + expect(calculateNormalLayoutHeight(5)).toBe(285); + expect(calculateNormalLayoutHeight(6)).toBe(325); + expect(calculateNormalLayoutHeight(7)).toBe(365); + expect(calculateNormalLayoutHeight(8)).toBe(405); + expect(calculateNormalLayoutHeight(9)).toBe(445); + expect(calculateNormalLayoutHeight(10)).toBe(485); + }); + + it("calculateDonutLayoutHeight", () => { + expect(calculateDonutLayoutHeight(0)).toBe(215); + expect(calculateDonutLayoutHeight(1)).toBe(215); + expect(calculateDonutLayoutHeight(2)).toBe(215); + expect(calculateDonutLayoutHeight(3)).toBe(215); + expect(calculateDonutLayoutHeight(4)).toBe(215); + expect(calculateDonutLayoutHeight(5)).toBe(215); + expect(calculateDonutLayoutHeight(6)).toBe(247); + expect(calculateDonutLayoutHeight(7)).toBe(279); + expect(calculateDonutLayoutHeight(8)).toBe(311); + expect(calculateDonutLayoutHeight(9)).toBe(343); + expect(calculateDonutLayoutHeight(10)).toBe(375); + }); + + it("calculatePieLayoutHeight", () => { + expect(calculatePieLayoutHeight(0)).toBe(300); + expect(calculatePieLayoutHeight(1)).toBe(325); + expect(calculatePieLayoutHeight(2)).toBe(325); + expect(calculatePieLayoutHeight(3)).toBe(350); + expect(calculatePieLayoutHeight(4)).toBe(350); + expect(calculatePieLayoutHeight(5)).toBe(375); + expect(calculatePieLayoutHeight(6)).toBe(375); + expect(calculatePieLayoutHeight(7)).toBe(400); + expect(calculatePieLayoutHeight(8)).toBe(400); + expect(calculatePieLayoutHeight(9)).toBe(425); + expect(calculatePieLayoutHeight(10)).toBe(425); + }); + + it("donutCenterTranslation", () => { + expect(donutCenterTranslation(0)).toBe(-45); + expect(donutCenterTranslation(1)).toBe(-45); + expect(donutCenterTranslation(2)).toBe(-45); + expect(donutCenterTranslation(3)).toBe(-45); + expect(donutCenterTranslation(4)).toBe(-45); + expect(donutCenterTranslation(5)).toBe(-45); + expect(donutCenterTranslation(6)).toBe(-29); + expect(donutCenterTranslation(7)).toBe(-13); + expect(donutCenterTranslation(8)).toBe(3); + expect(donutCenterTranslation(9)).toBe(19); + expect(donutCenterTranslation(10)).toBe(35); + }); + + it("trimTopLanguages", () => { + expect(trimTopLanguages([])).toStrictEqual({ + langs: [], + totalLanguageSize: 0, + }); + expect(trimTopLanguages([langs.javascript])).toStrictEqual({ + langs: [langs.javascript], + totalLanguageSize: 200, + }); + expect( + trimTopLanguages([langs.javascript, langs.HTML], [], 5), + ).toStrictEqual({ + langs: [langs.javascript, langs.HTML], + totalLanguageSize: 400, + }); + expect(trimTopLanguages(langs, [], 5)).toStrictEqual({ + langs: Object.values(langs), + totalLanguageSize: 500, + }); + expect(trimTopLanguages(langs, [], 2)).toStrictEqual({ + langs: Object.values(langs).slice(0, 2), + totalLanguageSize: 400, + }); + expect(trimTopLanguages(langs, ["javascript"], 5)).toStrictEqual({ + langs: [langs.HTML, langs.css], + totalLanguageSize: 300, + }); + }); +}); + describe("Test renderTopLanguages", () => { it("should render correctly", () => { document.body.innerHTML = renderTopLanguages(langs); @@ -236,6 +497,152 @@ describe("Test renderTopLanguages", () => { ); }); + it("should render with layout donut", () => { + document.body.innerHTML = renderTopLanguages(langs, { layout: "donut" }); + + 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 d = getNumbersFromSvgPathDefinitionAttribute( + queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"), + ); + const center = { x: d[7], y: d[7] }; + const HTMLLangPercent = langPercentFromDonutLayoutSvg( + queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"), + center.x, + center.y, + ); + 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 = langPercentFromDonutLayoutSvg( + queryAllByTestId(document.body, "lang-donut")[1].getAttribute("d"), + center.x, + center.y, + ); + 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 = langPercentFromDonutLayoutSvg( + queryAllByTestId(document.body, "lang-donut")[2].getAttribute("d"), + center.x, + center.y, + ); + expect(cssLangPercent).toBeCloseTo(20); + + expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100); + + // Should render full donut (circle) if one language is 100%. + document.body.innerHTML = renderTopLanguages( + { HTML: langs.HTML }, + { layout: "donut" }, + ); + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML 100.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute( + "size", + "100", + ); + expect(queryAllByTestId(document.body, "lang-donut")).toHaveLength(1); + expect(queryAllByTestId(document.body, "lang-donut")[0].tagName).toBe( + "circle", + ); + }); + it("should render with layout pie", () => { + document.body.innerHTML = renderTopLanguages(langs, { layout: "pie" }); + + 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-pie")[0]).toHaveAttribute( + "size", + "40", + ); + + const d = getNumbersFromSvgPathDefinitionAttribute( + queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"), + ); + const center = { x: d[0], y: d[1] }; + const HTMLLangPercent = langPercentFromPieLayoutSvg( + queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"), + center.x, + center.y, + ); + expect(HTMLLangPercent).toBeCloseTo(40); + + expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( + "javascript 40.00%", + ); + expect(queryAllByTestId(document.body, "lang-pie")[1]).toHaveAttribute( + "size", + "40", + ); + const javascriptLangPercent = langPercentFromPieLayoutSvg( + queryAllByTestId(document.body, "lang-pie")[1].getAttribute("d"), + center.x, + center.y, + ); + expect(javascriptLangPercent).toBeCloseTo(40); + + expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( + "css 20.00%", + ); + expect(queryAllByTestId(document.body, "lang-pie")[2]).toHaveAttribute( + "size", + "20", + ); + const cssLangPercent = langPercentFromPieLayoutSvg( + queryAllByTestId(document.body, "lang-pie")[2].getAttribute("d"), + center.x, + center.y, + ); + expect(cssLangPercent).toBeCloseTo(20); + + expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100); + + // Should render full pie (circle) if one language is 100%. + document.body.innerHTML = renderTopLanguages( + { HTML: langs.HTML }, + { layout: "pie" }, + ); + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML 100.00%", + ); + expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute( + "size", + "100", + ); + expect(queryAllByTestId(document.body, "lang-pie")).toHaveLength(1); + expect(queryAllByTestId(document.body, "lang-pie")[0].tagName).toBe( + "circle", + ); + }); + it("should render a translated title", () => { document.body.innerHTML = renderTopLanguages(langs, { locale: "cn" }); expect(document.getElementsByClassName("header")[0].textContent).toBe( diff --git a/tests/renderWakatimeCard.test.js b/tests/renderWakatimeCard.test.js index 25e7ab4d..630f2596 100644 --- a/tests/renderWakatimeCard.test.js +++ b/tests/renderWakatimeCard.test.js @@ -67,4 +67,19 @@ describe("Test Render Wakatime Card", () => { "No coding activity this week", ); }); + + it('should show "no coding activity this week" message when using compact layout and there has not been activity', () => { + document.body.innerHTML = renderWakatimeCard( + { + ...wakaTimeData.data, + languages: undefined, + }, + { + layout: "compact", + }, + ); + expect(document.querySelector(".stat").textContent).toBe( + "No coding activity this week", + ); + }); });