Skip to content

Commit 91bebb4

Browse files
authored
Add GitHub Skyline plugin (lowlighter#147)
1 parent 66d7c79 commit 91bebb4

File tree

9 files changed

+734
-4
lines changed

9 files changed

+734
-4
lines changed

package-lock.json

Lines changed: 543 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"express-rate-limit": "^5.2.6",
3434
"faker": "^5.4.0",
3535
"image-to-base64": "^2.1.1",
36+
"jimp": "^0.16.1",
3637
"js-yaml": "^4.0.0",
3738
"memory-cache": "^0.2.0",
3839
"prismjs": "^1.23.0",

source/app/metrics/utils.mjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import imgb64 from "image-to-base64"
1111
import git from "simple-git"
1212
import twemojis from "twemoji-parser"
13+
import jimp from "jimp"
1314

15+
//Exports
1416
export {fs, os, paths, url, util, processes, axios, puppeteer, imgb64, git}
1517

1618
/**Returns module __dirname */
@@ -189,3 +191,19 @@
189191
export async function wait(seconds) {
190192
await new Promise(solve => setTimeout(solve, seconds*1000)) //eslint-disable-line no-promise-executor-return
191193
}
194+
195+
/**Create gif from puppeteer browser */
196+
export async function puppeteergif({page, width, height, frames, scale = 1, quality = 80, x = 0, y = 0, delay = 150}) {
197+
//Register images frames
198+
const images = []
199+
for (let i = 0; i < frames; i++) {
200+
images.push(await page.screenshot({type:"png", clip:{width, height, x, y}}))
201+
await wait(delay/1000)
202+
if (i%10 === 0)
203+
console.debug(`metrics/puppeteergif > processed ${i}/${frames} frames`)
204+
}
205+
console.debug(`metrics/puppeteergif > processed ${frames}/${frames} frames`)
206+
//Post-processing
207+
console.debug("metrics/puppeteergif > applying post-processing")
208+
return Promise.all(images.map(async buffer => (await jimp.read(buffer)).scale(scale).quality(quality).getBase64Async("image/png")))
209+
}

source/plugins/skyline/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
### 🌇 GitHub Skyline 3D calendar
2+
3+
⚠️ This plugin significantly increase file size, prefer using it as standalone.
4+
5+
The *skyline* plugin lets you display your 3D commits calendar from [skyline.github.com](https://skyline.github.com/).
6+
7+
<table>
8+
<td align="center">
9+
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.skyline.svg">
10+
<img width="900" height="1" alt="">
11+
</td>
12+
</table>
13+
14+
This uses puppeteer to generate collect image frames, and use CSS animations to create an animated rendering (GIF images are not animated in GitHub flavored markdown rendering which is why this design choice was made).
15+
16+
#### ℹ️ Examples workflows
17+
18+
[➡️ Available options for this plugin](metadata.yml)
19+
20+
```yaml
21+
- uses: lowlighter/metrics@latest
22+
with:
23+
# ... other options
24+
plugin_skyline: yes
25+
plugin_skyline_year: 0 # Set to 0 to display current year
26+
plugin_skyline_frames: 60 # Use 60 frames (half-loop)
27+
```

source/plugins/skyline/index.mjs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//Setup
2+
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
3+
//Plugin execution
4+
try {
5+
//Check if plugin is enabled and requirements are met
6+
if ((!enabled)||(!q.skyline))
7+
return null
8+
9+
//Load inputs
10+
let {year, frames} = imports.metadata.plugins.skyline.inputs({data, account, q})
11+
if (Number.isNaN(year)) {
12+
year = new Date().getFullYear()
13+
console.debug(`metrics/compute/${login}/plugins > skyline > year set to ${year}`)
14+
}
15+
const width = 454
16+
const height = 284
17+
18+
//Start puppeteer and navigate to skyline.github.com
19+
console.debug(`metrics/compute/${login}/plugins > skyline > starting browser`)
20+
const browser = await imports.puppeteer.launch({headless:true, executablePath:process.env.PUPPETEER_BROWSER_PATH, args:["--no-sandbox", "--disable-extensions", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]})
21+
console.debug(`metrics/compute/${login}/plugins > skyline > started ${await browser.version()}`)
22+
const page = await browser.newPage()
23+
await page.setViewport({width, height})
24+
25+
//Load page
26+
console.debug(`metrics/compute/${login}/plugins > skyline > loading skyline.github.com/${login}/${year}`)
27+
await page.goto(`https://skyline.github.com/${login}/${year}`, {timeout:90*1000})
28+
console.debug(`metrics/compute/${login}/plugins > skyline > waiting for initial render`)
29+
const frame = page.mainFrame()
30+
await page.waitForFunction('[...document.querySelectorAll("span")].map(span => span.innerText).includes("Download STL file")', {timeout:90*1000})
31+
await frame.evaluate(() => [...document.querySelectorAll("button, footer, a")].map(element => element.remove()))
32+
33+
//Generate gif
34+
console.debug(`metrics/compute/${login}/plugins > skyline > generating frames`)
35+
const framed = await imports.puppeteergif({page, width, height, frames, scale:0.5})
36+
37+
//Close puppeteer
38+
await browser.close()
39+
40+
//Results
41+
return {frames:framed}
42+
}
43+
//Handle errors
44+
catch (error) {
45+
throw {error:{message:"An error occured", instance:error}}
46+
}
47+
}
48+
49+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: "🌇 GitHub Skyline 3D calendar"
2+
cost: N/A
3+
categorie: github
4+
supports:
5+
- user
6+
inputs:
7+
8+
# Enable or disable plugin
9+
plugin_skyline:
10+
description: Display GitHub Skyline 3D calendar
11+
type: boolean
12+
default: no
13+
14+
# Displayed year
15+
plugin_skyline_year:
16+
description: Displayed year
17+
type: number
18+
default: "current-year"
19+
min: 2008
20+
21+
# Number of frames
22+
# Use 120 for a full-loop and 60 for a half-loop
23+
plugin_skyline_frames:
24+
description: Number of frames
25+
type: number
26+
default: 60
27+
min: 1
28+
max: 120

source/plugins/skyline/tests.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- name: Skyline plugin (default)
2+
uses: lowlighter/metrics@latest
3+
with:
4+
token: NOT_NEEDED
5+
plugin_skyline: yes
6+
timeout: 1800000
7+
modes:
8+
- action

source/templates/classic/partials/_.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020
"people",
2121
"activity",
2222
"anilist",
23-
"wakatime"
23+
"wakatime",
24+
"skyline"
2425
]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<% if (plugins.skyline) { %>
2+
<section>
3+
<h2 class="field">
4+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
5+
GitHub Skyline
6+
</h2>
7+
<div class="row">
8+
<section>
9+
<% if (plugins.skyline.error) { %>
10+
<div class="field error">
11+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
12+
<%= plugins.skyline.error.message %>
13+
</div>
14+
<% } else { %>
15+
<div class="skyline-animation">
16+
<div class="frames">
17+
<% for (const frame of plugins.skyline.frames) { %>
18+
<div class="frame">
19+
<img class="skyline" src="<%= frame %>" width="454" height="284" alt=""/>
20+
</div>
21+
<% } %>
22+
</div>
23+
</div>
24+
<% { const n = plugins.skyline.frames.length, width = 454, height = 284 %>
25+
<style>
26+
@keyframes skyline-animation-frame {
27+
100% { transform: translateX(-100%); }
28+
}
29+
.skyline-animation {
30+
margin: 2px 13px 6px;
31+
border-radius: 10px;
32+
width: <%= width %>px;
33+
height: <%= height %>px;
34+
background-color: #030D21;
35+
overflow: hidden;
36+
}
37+
.skyline-animation .frames {
38+
animation: skyline-animation-frame <%= 150*n %>ms infinite;
39+
animation-timing-function: steps(<%= n %>);
40+
display: flex;
41+
width: <%= n*width %>px;
42+
height: <%= height %>px;
43+
}
44+
.skyline-animation .frames .frame {
45+
display: block;
46+
width: <%= width %>px;
47+
flex-basis: <%= width %>px;
48+
}
49+
.skyline-animation .frames .frame img {
50+
width: <%= width %>px;
51+
}
52+
</style>
53+
<% } %>
54+
<% } %>
55+
</section>
56+
</div>
57+
</section>
58+
<% } %>

0 commit comments

Comments
 (0)