feat(app/web): add index.html [skip ci]
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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`))
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
//Data initialization
|
||||
data: {
|
||||
version: "",
|
||||
user: "",
|
||||
user: new URLSearchParams(location.search).get("user") || "",
|
||||
tab: "overview",
|
||||
palette: "light",
|
||||
clipboard: null,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user