Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: theme #9

Merged
merged 3 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 21 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,40 +42,42 @@ If you want to include a GitHub stats card in your own README and have it displa
## Documentation
### Github Stats Card Options

| Name | Description | Default value |
| --- |------------------------------|---------------|
| `background` | Set background color/image. Supports a subset of CSS background property values | `#434343` |
| `color` | Set text color to any valid CSS color value | `white` |
| `include_all_commits` | Count all commits | `false` |
| `pixelate_avatar` | Apply pixelation to avatar | `true` |
| `screen_effect` | Enable curved screen effect | `false` |
| `show_avatar` | Display avatar | `true` |
| `show_rank` | Display rank value | `true` |
| `show_total_stars` | Display total stars earned | `true` |
| `username` | GitHub username | '' |

### Complex background CSS enables cool tricks! Some examples:
#### 1. Pride Flag (light mode)
| Name | Description | Default value |
|-----------------------|---------------------------------------------------------------------------------|---------------|
| `background` | Set background color/image. Supports a subset of CSS background property values | `#434343` |
| `color` | Set text color to any valid CSS color value | `white` |
| `include_all_commits` | Count all commits | `false` |
| `pixelate_avatar` | Apply pixelation to avatar | `true` |
| `screen_effect` | Enable curved screen effect | `false` |
| `show_avatar` | Display avatar | `true` |
| `show_rank` | Display rank value | `true` |
| `show_total_stars` | Display total stars earned | `true` |
| `username` | GitHub username | '' |
| `theme` | Check out the built-in themes below | '' |

### Themes
Below are some prebuilt themes to get started. However, cards are fully customizable by passing in background and color props. Feel free to ditch the premade themes and design unique cards by selecting your own colors and backgrounds!
#### 1. Rainbow
```html
https://pixel-profile.vercel.app/api/github-stats?username=LuciNyan&screen_effect=false&background=linear-gradient(to%20bottom%2C%20%23CD001A%200%25%2C%20%23CD001A%2014.72%25%2C%20%23F06400%2014.72%25%2C%20%23F06400%2028.56%25%2C%20%23F2CD00%2028.56%25%2C%20%23F2CD00%2042.84%25%2C%20%2379c300%2042.84%25%2C%20%2379c300%2057.12%25%2C%20%231961ae%2057.12%25%2C%20%231961ae%2071.4%25%2C%20%2331137c%2071.4%25%2C%20%2331137c%2085.24%25%2C%20%2361007d%2085.24%25%2C%20%2361007d%20100%25)
https://pixel-profile.vercel.app/api/github-stats?username=LuciNyan&theme=rainbow
```
![Pride flag light](.github/img/pride-flag-light.png)

#### 2. Pride Flag (dark mode). Set your profile to dark theme before trying this, I promise MAXIMUM wow factor! 🤩
#### 2. Rainbow(dark mode). Set your profile to dark theme before trying this, I promise MAXIMUM wow factor! 🤩
```html
https://pixel-profile.vercel.app/api/github-stats?username=LuciNyan&screen_effect=true&background=linear-gradient(to%20bottom%2C%20%23CD001A%200%25%2C%20%23CD001A%2014.72%25%2C%20%23F06400%2014.72%25%2C%20%23F06400%2028.56%25%2C%20%23F2CD00%2028.56%25%2C%20%23F2CD00%2042.84%25%2C%20%2379c300%2042.84%25%2C%20%2379c300%2057.12%25%2C%20%231961ae%2057.12%25%2C%20%231961ae%2071.4%25%2C%20%2331137c%2071.4%25%2C%20%2331137c%2085.24%25%2C%20%2361007d%2085.24%25%2C%20%2361007d%20100%25)
https://pixel-profile.vercel.app/api/github-stats?username=LuciNyan&theme=rainbow&screen_effect=true
```
![Pride flag dark](.github/img/pride-flag-dark.png)

#### 3. Monica
```html
https://pixel-profile.vercel.app/api/github-stats?username=LuciNyan&background=linear-gradient(to%20bottom%20right%2C%20%230c7bb3%2C%20%23f2bae8)
https://pixel-profile.vercel.app/api/github-stats?username=LuciNyan&theme=monica
```
![Summer](.github/img/monica.png)

#### 4. Summer
```html
https://pixel-profile.vercel.app/api/github-stats?username=LuciNyan&screen_effect=false&background=linear-gradient(to bottom right%2C %2374dcc4%2C %234597e9)
https://pixel-profile.vercel.app/api/github-stats?username=LuciNyan&theme=summer
```
![Chill summer](.github/img/summer.png)

Expand Down
4 changes: 3 additions & 1 deletion api/github-stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export default async (req: VercelRequest, res: VercelResponse) => {
show_avatar,
show_rank,
show_total_stars,
username
username,
theme
} = req.query

res.setHeader('Content-Type', 'image/png')
Expand Down Expand Up @@ -54,6 +55,7 @@ export default async (req: VercelRequest, res: VercelResponse) => {
color: parseString(color),
background: parseString(background),
pixelateAvatar: parseBoolean(pixelate_avatar),
theme: parseString(theme),
includeAllCommits
}

Expand Down
30 changes: 23 additions & 7 deletions packages/pixel-profile/src/cards/stats.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { curve, pixelate } from '../shaders'
import { AVATAR_SIZE, CARD_SIZE, makeGithubStats } from '../templates/github-stats'
import {
AVATAR_SIZE,
CARD_SIZE,
defaultTemplateOptions,
makeGithubStats,
TemplateOptions
} from '../templates/github-stats'
import { getThemeOptions } from '../theme'
import { getBase64FromPixels, getPixelsFromPngBuffer, getPngBufferFromPixels, kFormatter, Rank } from '../utils'
import { filterNotEmpty } from '../utils/filter'
import { Resvg } from '@resvg/resvg-js'
import axios from 'axios'
import { readFile } from 'node:fs/promises'
Expand All @@ -20,6 +28,7 @@ export type Stats = {
}

type Options = {
theme?: string
screenEffect?: boolean
color?: string
showRank?: boolean
Expand All @@ -33,12 +42,15 @@ export async function renderStats(stats: Stats, options: Options = {}): Promise<

const {
screenEffect = false,
color = 'white',
background = '#434343',
color,
background,
pixelateAvatar = true,
includeAllCommits = false
includeAllCommits = false,
theme = ''
} = options

const themeOptions = getThemeOptions(theme)

const cardSize = rank ? CARD_SIZE.BIG : CARD_SIZE.SMALL

const width = cardSize.CARD_WIDTH
Expand All @@ -64,9 +76,13 @@ export async function renderStats(stats: Stats, options: Options = {}): Promise<

let isMissingFont = false

const templateOptions = {
color,
background,
const templateOptions: TemplateOptions = {
...defaultTemplateOptions,
...themeOptions,
...filterNotEmpty({
color,
background
}),
includeAllCommits
}

Expand Down
9 changes: 7 additions & 2 deletions packages/pixel-profile/src/templates/github-stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ export type Stats = {
avatar: string
}

type Options = {
export type TemplateOptions = {
color: string
background: string
includeAllCommits: boolean
}

export const defaultTemplateOptions = {
color: 'white',
background: '#434343'
}

export const CARD_SIZE = {
BIG: {
CARD_WIDTH: 1226,
Expand All @@ -33,7 +38,7 @@ export const AVATAR_SIZE = {
AVATAR_HEIGHT: 280
}

export function makeGithubStats(stats: Stats, options: Options) {
export function makeGithubStats(stats: Stats, options: TemplateOptions) {
const { name, totalStars, totalCommits, totalPRs, totalIssues, contributedTo, rank, avatar } = stats

const { color, background, includeAllCommits } = options
Expand Down
37 changes: 37 additions & 0 deletions packages/pixel-profile/src/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
type Theme = Record<
string,
{
color: string
background: string
}
>

export const THEME: Theme = {
summer: {
color: 'white',
background: 'linear-gradient(to bottom right, #74dcc4, #4597e9)'
},
blue_chill: {
color: 'white',
background: 'linear-gradient(to bottom right, #5580eb, #2aeeff)'
},
rainbow: {
color: 'white',
background:
'linear-gradient(to bottom, #CD001A 0%, #CD001A 14.72%, #F06400 14.72%, ' +
'#F06400 28.56%, #F2CD00 28.56%, #F2CD00 42.84%, #79c300 42.84%, ' +
'#79c300 57.12%, #1961ae 57.12%, #1961ae 71.4%, #31137c 71.4%, ' +
'#31137c 85.24%, #61007d 85.24%, #61007d 100%)'
},
monica: {
color: 'white',
background: 'linear-gradient(to bottom right, #0c7bb3, #f2bae8)'
}
}

export function getThemeOptions(theme: string): {
color?: string
background?: string
} {
return THEME[theme] ?? {}
}
17 changes: 17 additions & 0 deletions packages/pixel-profile/src/utils/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function filterNotEmpty<T extends Record<string, unknown>>(obj: T): FilteredObj<T> {
const result = {} as T

for (const key in obj) {
const value = obj[key]
if (value !== null && value !== undefined) {
result[key] = value
}
}

// TODO: type
return result as FilteredObj<T>
}

export type FilteredObj<T> = {
[K in keyof T as Exclude<T[K], undefined | null> extends never ? never : K]: Exclude<T[K], undefined | null>
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions packages/pixel-profile/test/theme.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { renderStats } from '../src'
// @ts-expect-error ...
import { toMatchImageSnapshot } from 'jest-image-snapshot'
import { describe, expect, it } from 'vitest'

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace jest {
interface Matchers<R> {
toMatchImageSnapshot(): R
}
}
}

expect.extend({ toMatchImageSnapshot })

const stats = {
name: 'User Name',
username: 'username',
totalStars: 999,
totalCommits: 99999,
totalIssues: 99,
totalPRs: 9,
contributedTo: 9999,
avatarUrl: '',
rank: {
level: 'S',
percentile: 0,
score: 0
}
}

describe('Theme', () => {
it('Render card with summer theme and custom color', async () => {
const png = await renderStats(stats, { theme: 'summer', color: 'yellow' })
expect(png).toMatchImageSnapshot()
})

it('Render card with summer theme and custom background', async () => {
const png = await renderStats(stats, { theme: 'summer', background: 'black' })
expect(png).toMatchImageSnapshot()
})

it('Render card with summer theme', async () => {
const png = await renderStats(stats, { theme: 'summer' })
expect(png).toMatchImageSnapshot()
})

it('Render card with blue_chill theme', async () => {
const png = await renderStats(stats, { theme: 'blue_chill' })
expect(png).toMatchImageSnapshot()
})

it('Render card with rainbow theme', async () => {
const png = await renderStats(stats, { theme: 'rainbow' })
expect(png).toMatchImageSnapshot()
})

it('Render card with monica theme', async () => {
const png = await renderStats(stats, { theme: 'monica' })
expect(png).toMatchImageSnapshot()
})
})
Loading