Full plugins and server redesign
- Languages, issues and pr are now plugins (but enabled by default for retro-compatibility) - Query parameters are now parsed correctly - Redesigned server index with vue.js
This commit is contained in:
@@ -9,93 +9,172 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1><a href="https://github.com/lowlighter/metrics">GitHub metrics</a></h1>
|
||||
<main :class="[palette]">
|
||||
|
||||
<p>
|
||||
Enter your GitHub username below to generate your metrics.
|
||||
</p>
|
||||
<h1><a href="https://github.com/lowlighter/metrics">Generate your metrics !</a></h1>
|
||||
|
||||
<label>
|
||||
<input type="text" name="user" placeholder="@username" value="">
|
||||
</label>
|
||||
|
||||
<div id="metrics">
|
||||
<img class="placeholder" src="/placeholder.svg">
|
||||
<img class="generated" src="/placeholder.svg">
|
||||
</div>
|
||||
|
||||
<aside>
|
||||
Embed these metrics on your GitHub profile by adding the markdown below in your <i>README.md</i> at <a id="user-repo" href="#"><span class="user"></span>/<span class="user"></span></a>
|
||||
<br>
|
||||
<div class="code">
|
||||

|
||||
<div class="step">
|
||||
<h2>1. Enter your GitHub username</h2>
|
||||
<label>
|
||||
<input type="text" v-model="user" maxlength="39" placeholder="GitHub username" :disabled="generated.pending">
|
||||
</label>
|
||||
</div>
|
||||
For even more metrics (coding habits, PageSpeed performances, number of line of code you wrote, page views, etc.), setup this as a <a href="https://github.com/marketplace/actions/github-metrics-as-svg-image">GitHub action</a> on your repository !<br>
|
||||
Check out <a href="https://github.com/lowlighter/metrics">lowlighter/metrics</a> for more informations
|
||||
</aside>
|
||||
|
||||
<div class="step">
|
||||
<h2>2. Select a template and enable additional plugins</h2>
|
||||
<div class="templates">
|
||||
<label v-for="template in templates.list" :key="template">
|
||||
<input type="radio" v-model="templates.selected" :value="template" @change="load" :disabled="generated.pending">
|
||||
{{ templates.descriptions[template] || template }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="plugins">
|
||||
<label v-for="plugin in plugins.list" :key="plugin">
|
||||
<input type="checkbox" v-model="plugins.enabled[plugin]" @change="load" :disabled="generated.pending">
|
||||
{{ plugins.descriptions[plugin] || plugin }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="cache-notice">
|
||||
*To reduce server overhead, metrics are cached. Changes may not be reflected until cache expiration.
|
||||
</div>
|
||||
<div class="palette">
|
||||
Generated metrics use transparency and colors which matches both light and dark modes
|
||||
<div>
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="light">
|
||||
☀️ Light mode
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="dark">
|
||||
🌙 Night mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<h2>3. Generate your metrics</h2>
|
||||
<template v-if="generated.content">
|
||||
<img :src="generated.content" alt="metrics">
|
||||
</template>
|
||||
<template v-else>
|
||||
<img :src="templates.placeholder" alt="metrics">
|
||||
<button @click="generate" :disabled="(!user)||(generated.pending)">{{ generated.pending ? "Generating your metrics..." : user ? "Generate your metrics" : "Enter your GitHub username first" }}</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<h2>4. Embed these metrics on your GitHub profile</h2>
|
||||
<template v-if="user">
|
||||
Add the markdown below in your <i>README.md</i> at <a :href="repo">{{ user }}/{{ user }}</a>
|
||||
<div class="code">
|
||||

|
||||
</div>
|
||||
</template>
|
||||
For even more features, setup <a href="https://github.com/lowlighter/metrics">lowlighter/metrics</a> as a <a href="https://github.com/marketplace/actions/github-metrics-as-svg-image">GitHub action</a> !
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/axios.min.js"></script>
|
||||
<script src="/ejs.min.js"></script>
|
||||
<script src="/vue.min.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
//User updater
|
||||
let timeout = null
|
||||
document.querySelector("input[name=user]").onkeyup = function (event) {
|
||||
//Retrieve user value
|
||||
clearTimeout(timeout)
|
||||
const user = event.target.value
|
||||
//Display placeholder
|
||||
document.querySelector("#metrics .placeholder").style.opacity = 1
|
||||
document.querySelector("#metrics .generated").style.opacity = 0
|
||||
document.querySelector("aside").style.opacity = 0
|
||||
//Update github user
|
||||
document.querySelector(".code .url").innerText = `${window.location.href}${user}`
|
||||
document.querySelector("#user-repo").href = `https://github.com/${user}/${user}`
|
||||
document.querySelectorAll(".user").forEach(node => node.innerText = user)
|
||||
//Update metrics
|
||||
if (event.key === "Enter")
|
||||
metrics(user)
|
||||
else
|
||||
timeout = setTimeout(() => metrics(user), 1800)
|
||||
}
|
||||
//Metrics updater
|
||||
let current = null
|
||||
function metrics(user) {
|
||||
if (!user.trim().length)
|
||||
return
|
||||
if (current === user) {
|
||||
document.querySelector("#metrics .placeholder").style.opacity = 0
|
||||
document.querySelector("#metrics .generated").style.opacity = 1
|
||||
document.querySelector("aside").style.opacity = 1
|
||||
}
|
||||
else {
|
||||
current = user
|
||||
document.querySelector("#metrics .generated").src = `${window.location.href}${user}`
|
||||
document.querySelector("#metrics .generated").onload = function () {
|
||||
document.querySelector("#metrics .placeholder").style.opacity = 0
|
||||
document.querySelector("#metrics .generated").style.opacity = 1
|
||||
document.querySelector("aside").style.opacity = 1
|
||||
;(async function() {
|
||||
new Vue({
|
||||
el:"main",
|
||||
async mounted() {
|
||||
await this.load()
|
||||
},
|
||||
data:{
|
||||
user:"",
|
||||
palette:"light",
|
||||
plugins:{
|
||||
list:(await axios.get("/plugins.list")).data,
|
||||
enabled:{languages:true, followup:true},
|
||||
descriptions:{
|
||||
pagespeed:"Website performances",
|
||||
languages:"Most used languages",
|
||||
followup:"Owned repositories issues and pull requests",
|
||||
traffic:"Pages views",
|
||||
lines:"Lines of code changed",
|
||||
habits:"Coding habits",
|
||||
selfskip:"Skip metrics commits",
|
||||
},
|
||||
},
|
||||
templates:{
|
||||
list:(await axios.get("/templates.list")).data,
|
||||
loaded:{},
|
||||
selected:(await axios.get("/templates.list")).data[0],
|
||||
placeholder:"",
|
||||
descriptions:{
|
||||
classic:"Classic template",
|
||||
},
|
||||
},
|
||||
generated:{
|
||||
pending:false,
|
||||
content:"",
|
||||
},
|
||||
},
|
||||
computed:{
|
||||
repo() {
|
||||
return `https://github.com/${this.user}/${this.user}`
|
||||
},
|
||||
url() {
|
||||
const plugins = Object.entries(this.plugins.enabled)
|
||||
.filter(([key, value]) => /^languages|followup$/.test(key) ? !value : value)
|
||||
.map(([key, value]) => `${key}=${+value}`)
|
||||
.join("&")
|
||||
return `${window.location.href}${this.user}${plugins.length ? `?${plugins}` : ""}`
|
||||
},
|
||||
},
|
||||
methods:{
|
||||
async load() {
|
||||
const template = this.templates.selected
|
||||
if (!this.templates.loaded[template]) {
|
||||
const {data:{placeholder, style}} = await axios.get(`/placeholder.svg?template=${template}`)
|
||||
this.templates.loaded[template] = {placeholder, style}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const {placeholder = "", style = {}} = this.templates.loaded[this.templates.selected] || {}
|
||||
this.templates.placeholder = placeholder ? this.serialize(ejs.render(placeholder, {plugins:this.plugins.enabled, style})) : "#"
|
||||
},
|
||||
async generate() {
|
||||
this.generated.pending = true
|
||||
this.generated.content = this.serialize((await axios.get(this.url)).data)
|
||||
},
|
||||
serialize(svg) {
|
||||
return `data:image/svg+xml;base64,${btoa(svg)}`
|
||||
},
|
||||
},
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
main {
|
||||
background-color: #FFFFFF;
|
||||
color: #1B1F23;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 2rem;
|
||||
width: 100%;
|
||||
transition: background-color .3s;
|
||||
}
|
||||
h1 {
|
||||
margin: 4rem 0 0;
|
||||
font-size: 1.6rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
h2 {
|
||||
margin: 1.5rem 0 1rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
a, a:hover, a:visited {
|
||||
color: #0366D6;
|
||||
@@ -108,39 +187,47 @@
|
||||
transition: color .4s;
|
||||
cursor: pointer;
|
||||
}
|
||||
input {
|
||||
input, button, select {
|
||||
border-radius: .5rem;
|
||||
padding: .5rem 1rem;
|
||||
padding: .25rem .5rem;
|
||||
outline: none;
|
||||
border: 1px solid #E1E4E8;
|
||||
background-color: #FAFBFC;
|
||||
color: #1B1F23;
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
margin: 0 0 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
#metrics {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
width: 480px;
|
||||
height: 485px;
|
||||
input[type=text], select, button {
|
||||
min-width: 50%;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
#metrics img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
transition: opacity .4s;
|
||||
}
|
||||
aside {
|
||||
opacity: 0;
|
||||
padding: .25rem;
|
||||
transition: opacity .4s;
|
||||
option {
|
||||
text-align: center;
|
||||
margin: 1rem 0 2rem;
|
||||
}
|
||||
label, button {
|
||||
margin: 1rem;
|
||||
}
|
||||
input[disabled], button[disabled], select[disabled] {
|
||||
opacity: .5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.step {
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
}
|
||||
.plugins {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.plugins label {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
.code {
|
||||
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
|
||||
@@ -152,6 +239,29 @@
|
||||
.code .md-alt {
|
||||
color: #6F42C1;
|
||||
}
|
||||
.cache-notice {
|
||||
margin-top: .5rem;
|
||||
font-size: .9rem;
|
||||
opacity: .8;
|
||||
}
|
||||
.palette {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
main.dark {
|
||||
background-color: #181A1B;
|
||||
color: #D4D1C5;
|
||||
}
|
||||
.dark a, .dark a:visited {
|
||||
color: #4CACEE;
|
||||
}
|
||||
.dark input, .dark button {
|
||||
color: #D4D1C5;
|
||||
background-color: #1A1C1E;
|
||||
border-color: #373C3E;
|
||||
}
|
||||
.dark .code {
|
||||
background-color: #1A1C1E;
|
||||
}
|
||||
</style>
|
||||
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user