feat(app/web): add index.html [skip ci]

This commit is contained in:
lowlighter
2022-07-05 23:44:00 -04:00
parent 87e2f6ced5
commit 1713f5e47f
10 changed files with 174 additions and 114 deletions

View File

@@ -24,8 +24,6 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
//Initialization
const pending = []
const {queries} = conf
const extras = {css: imports.metadata.plugins.core.extras("extras_css", {...conf.settings, error:false}) ? q["extras.css"] ?? "" : "", js: imports.metadata.plugins.core.extras("extras_js", {...conf.settings, error:false}) ? q["extras.js"] ?? "" : ""}
const data = {q, animated: true, large: false, base: {}, config: {}, errors: [], plugins: {}, computed: {}, extras, postscripts: []}
const imports = {
plugins: Plugins,
templates: Templates,
@@ -40,6 +38,8 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
}
: null),
}
const extras = {css: imports.metadata.plugins.core.extras("extras_css", {...conf.settings, error:false}) ? q["extras.css"] ?? "" : "", js: imports.metadata.plugins.core.extras("extras_js", {...conf.settings, error:false}) ? q["extras.js"] ?? "" : ""}
const data = {q, animated: true, large: false, base: {}, config: {}, errors: [], plugins: {}, computed: {}, extras, postscripts: []}
const experimental = new Set(decodeURIComponent(q["experimental.features"] ?? "").split(" ").map(x => x.trim().toLocaleLowerCase()).filter(x => x))
if (conf.settings["debug.headless"])
imports.puppeteer.headless = false

View File

@@ -130,6 +130,8 @@ export default async function({sandbox = false} = {}) {
app.get("/.templates/:template", limiter, (req, res) => req.params.template in conf.templates ? res.status(200).json(conf.templates[req.params.template]) : res.sendStatus(404))
for (const template in conf.templates)
app.use(`/.templates/${template}/partials`, express.static(`${conf.paths.templates}/${template}/partials`))
//Modes
app.get("/.modes", limiter, (req, res) => res.status(200).json(conf.settings.modes))
//Styles
app.get("/.css/style.css", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/style.css`))
app.get("/.css/style.vars.css", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/style.vars.css`))

View File

@@ -6,7 +6,6 @@
async mounted() {
//Interpolate config from browser
try {
this.config.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
this.palette = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
}
catch (error) {}
@@ -17,26 +16,6 @@
const {data: requests} = await axios.get("/.requests")
this.requests = requests
})(),
//Templates
(async () => {
const {data: templates} = await axios.get("/.templates")
templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name))
this.templates.list = templates
this.templates.selected = templates[0]?.name || "classic"
})(),
//Plugins
(async () => {
const {data: plugins} = await axios.get("/.plugins")
this.plugins.list = plugins.filter(({name}) => metadata[name]?.supports.includes("user") || metadata[name]?.supports.includes("organization"))
const categories = [...new Set(this.plugins.list.map(({category}) => category))]
this.plugins.categories = Object.fromEntries(categories.map(category => [category, this.plugins.list.filter(value => category === value.category)]))
})(),
//Base
(async () => {
const {data: base} = await axios.get("/.plugins.base")
this.plugins.base = base
this.plugins.enabled.base = Object.fromEntries(base.map(key => [key, true]))
})(),
//Version
(async () => {
const {data: version} = await axios.get("/.version")
@@ -47,28 +26,15 @@
const {data: hosted} = await axios.get("/.hosted")
this.hosted = hosted
})(),
//Modes
(async () => {
const {data: modes} = await axios.get("/.modes")
this.modes = modes
})(),
])
//Generate placeholder
this.mock({timeout: 200})
setInterval(() => {
const marker = document.querySelector("#metrics-end")
if (marker) {
this.mockresize()
marker.remove()
}
}, 100)
},
//Watchers
watch: {
tab: {
immediate: true,
handler(current) {
if (current === "action")
this.clipboard = new ClipboardJS(".copy-action")
else
this.clipboard?.destroy()
},
},
palette: {
immediate: true,
handler(current, previous) {
@@ -80,14 +46,12 @@
//Data initialization
data: {
version: "",
user: "",
tab: "overview",
user1: "",
user2: "",
palette: "light",
clipboard: null,
requests: {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}},
cached: new Map(),
hosted: null,
modes: [],
},
//Computed data
computed: {
@@ -95,6 +59,22 @@
preview() {
return /-preview$/.test(this.version)
},
//Rate limit reset
rlreset() {
const reset = new Date(Math.max(this.requests.graphql.reset, this.requests.rest.reset))
return `${reset.getHours()}:${reset.getMinutes()}`
},
},
//Methods
methods:{
//Metrics insights
async insights() {
window.location.href = `/insights?user=${this.user1}`
},
//Metrics embed
async embed() {
window.location.href = `/embed?user=${this.user2}`
}
}
})
})()

View File

@@ -85,7 +85,7 @@
//Data initialization
data: {
version: "",
user: "",
user: new URLSearchParams(location.search).get("user") || "",
tab: "overview",
palette: "light",
clipboard: null,

View File

@@ -19,7 +19,7 @@
<header>
<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>
<a href="https://github.com/lowlighter/metrics">Metrics {{ version }}</a>
<a href="https://github.com/lowlighter/metrics">Metrics Embed {{ version }}</a>
</header>
<div class="ui top">

View File

@@ -21,11 +21,62 @@
<a href="https://github.com/lowlighter/metrics">Metrics {{ version }}</a>
</header>
<main>
<section class="container center">
Hi
</section>
</main>
<section class="container" v-if="modes.includes('embed')">
<div class="search app">
<div class="about">
<h2>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M11.134 1.535C9.722 2.562 8.16 4.057 6.889 5.312 5.8 6.387 5.041 7.401 4.575 8.294a3.745 3.745 0 00-3.227 1.054c-.43.431-.69 1.066-.86 1.657a11.982 11.982 0 00-.358 1.914A21.263 21.263 0 000 15.203v.054l.75-.007-.007.75h.054a14.404 14.404 0 00.654-.012 21.243 21.243 0 001.63-.118c.62-.07 1.3-.18 1.914-.357.592-.17 1.226-.43 1.657-.861a3.745 3.745 0 001.055-3.217c.908-.461 1.942-1.216 3.04-2.3 1.279-1.262 2.764-2.825 3.775-4.249.501-.706.923-1.428 1.125-2.096.2-.659.235-1.469-.368-2.07-.606-.607-1.42-.55-2.069-.34-.66.213-1.376.646-2.076 1.155zm-3.95 8.48a3.76 3.76 0 00-1.19-1.192 9.758 9.758 0 011.161-1.607l1.658 1.658a9.853 9.853 0 01-1.63 1.142zM.742 16l.007-.75-.75.008A.75.75 0 00.743 16zM12.016 2.749c-1.224.89-2.605 2.189-3.822 3.384l1.718 1.718c1.21-1.205 2.51-2.597 3.387-3.833.47-.662.78-1.227.912-1.662.134-.444.032-.551.009-.575h-.001V1.78c-.014-.014-.112-.113-.548.027-.432.14-.995.462-1.655.942zM1.62 13.089a19.56 19.56 0 00-.104 1.395 19.55 19.55 0 001.396-.104 10.528 10.528 0 001.668-.309c.526-.151.856-.325 1.011-.48a2.25 2.25 0 00-3.182-3.182c-.155.155-.329.485-.48 1.01a10.515 10.515 0 00-.309 1.67z"></path></svg>
Create your own metrics
</h2>
<small>
Choose among dozens of plugins and hundreds of options to craft your own custom metrics infographics. Preview renders and auto-generate a configuration file!
</small>
</div>
<div class="inputs">
<input type="text" v-model="user2" @keyup.enter="embed">
<button @click="embed">
Let's start!
</button>
</div>
</div>
</section>
<section class="container" v-if="modes.includes('insights')">
<div class="search app">
<div class="about">
<h2>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M11.5 7a4.499 4.499 0 11-8.998 0A4.499 4.499 0 0111.5 7zm-.82 4.74a6 6 0 111.06-1.06l3.04 3.04a.75.75 0 11-1.06 1.06l-3.04-3.04z"></path></svg>
Search a GitHub user
</h2>
<small>
Display rankings, highlights, contributions, repositories, user reactions, stars, commits history, used languages and recent activity from any user account!
</small>
</div>
<div class="inputs">
<input type="text" v-model="user1" @keyup.enter="insights">
<button @click="insights">
Search user!
</button>
</div>
</div>
</section>
<section class="container">
<div class="search">
<div class="about">
<small class="warning mb1" v-if="preview">
Metrics insights are rendered by <a href="https://metrics.lecoq.io/">metrics.lecoq.io</a> in preview mode.<br>
Any backend editions won't be reflected but client-side rendering can still be tested.
</small>
<div class="warning mb1" v-if="(!requests.rest.remaining)||(!requests.graphql.remaining)">
This web instance has run out of GitHub API requests.
Please wait until {{ rlreset }} to generate metrics again.
</div>
<small :class="{'error-text':(!requests.rest.remaining)||(!requests.graphql.remaining)}">Remaining GitHub requests: {{ requests.rest.remaining }} REST / {{ requests.graphql.remaining }} GraphQL</small>
<small>Send feedback on <a href="https://github.com/lowlighter/metrics/discussions" target="_blank">GitHub discussions</a>!</small>
</div>
</div>
</section>
<footer>
<a href="https://github.com/lowlighter/metrics">Repository</a>

View File

@@ -52,7 +52,7 @@
Please wait until {{ rlreset }} to generate metrics again.
</div>
<small class="info">
Display rankings, contributions, highlights, commits calendar, used languages and recent activity from any user account!
Display rankings, highlights, contributions, repositories, user reactions, stars, commits history, used languages and recent activity from any user account!
</small>
<small class="info" v-if="metrics">
Share this profile using <a :href="url">{{ url }}</a>

View File

@@ -20,7 +20,12 @@
await this.search()
}
else {
const user = new URLSearchParams(location.search).get("user")
this.searchable = true
if (user) {
this.user = user
this.search()
}
}
//Init
await Promise.all([

View File

@@ -1,63 +1,3 @@
/* Containers */
.container {
padding: 0 1rem;
display: flex;
flex-direction: column;
justify-content: center;
max-width: 920px;
margin: auto;
}
.center {
align-items: center;
margin-top: 1rem;
}
.text-center {
text-align: center;
}
/* Search */
.search {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin: 3rem 0 1rem;
}
.search h2 {
margin: 0;
padding: 0;
}
.search .about {
width: 100%;
display: flex;
flex-direction: column;
}
.search .about small {
font-size: .8rem;
color: var(--color-text-secondary);
text-align: left;
}
.search .inputs {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.search .inputs > * {
margin: .25rem;
}
.search .inputs input {
flex-grow: 1;
}
.search .info {
color: var(--color-text-secondary);
margin-top: 1rem;
}
.search .info svg {
fill: currentColor;
}
/* Contributions */
.contributions {
display: flex;

View File

@@ -361,3 +361,85 @@
@keyframes loading-dots-keyframes {
0% { transform: translateX(-100%); }
}
/* Containers */
.container {
padding: 0 1rem;
display: flex;
flex-direction: column;
justify-content: center;
max-width: 920px;
margin: auto;
align-items: center;
margin: 2rem auto 1rem;
}
.text-center {
text-align: center;
}
.mb1 {
margin-bottom: 1rem;
}
/* Search */
.search {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.search.app {
padding: 1.5rem;
border: 1px solid var(--color-border-primary);
border-radius: 6px;
}
.search h2 {
margin: 0;
padding: 0;
display: flex;
align-items: center;
font-weight: normal;
}
.search h2 svg {
margin-right: .25rem;
fill: currentColor;
height: 1.25rem;
width: 1.25rem;
}
.search .about {
width: 100%;
display: flex;
flex-direction: column;
}
.search .about small {
font-size: .8rem;
color: var(--color-text-secondary);
text-align: left;
}
.search .inputs {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.search .inputs > * {
margin: .25rem;
}
.search .inputs input {
flex-grow: 1;
}
.search .inputs button {
min-width: 7rem;
}
.search .info {
color: var(--color-text-secondary);
margin-top: 1rem;
}
.search .info svg {
fill: currentColor;
}
@media only screen and (min-width: 740px) {
.search {
width: 520px;
}
}