Add web frontend

This commit is contained in:
Paul-Christian Volkmer 2024-05-20 12:49:36 +02:00
parent 79fc47068b
commit 5befff0716
3 changed files with 276 additions and 1 deletions

View File

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2021 Paul-Christian Volkmer
Copyright (c) 2024 Paul-Christian Volkmer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,7 @@
package main
import (
"embed"
"flag"
"fmt"
"github.com/BurntSushi/toml"
@ -13,6 +14,15 @@ import (
"strconv"
)
//go:embed static
var static embed.FS
func pageRequestHandler(w http.ResponseWriter, _ *http.Request) {
p, _ := static.ReadFile("static/index.html")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write(p)
}
func requestHandler(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
@ -106,6 +116,7 @@ func main() {
configure(*configFile)
router := mux.NewRouter()
router.HandleFunc("/avatar", pageRequestHandler)
router.HandleFunc("/avatar/{id}", requestHandler)
log.Printf("Starting on port %d ...\n", *port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), router))

264
static/index.html Normal file
View File

@ -0,0 +1,264 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Idicon</title>
<style>
:root {
--blue: #649ed7;
}
* {
box-sizing: border-box;
color: #333;
}
body {
font-family: sans-serif;
margin: 0;
}
main {
text-align: center;
min-height: calc(100vh - 12em);
}
main > div {
margin: 1em 0;
}
footer {
margin-top: 2em;
border-top: 1px solid lightgray;
background-color: #eee;
height: 8em;
padding: 1em;
}
footer svg {
height: 1.2em;
}
.content {
max-width: 720px;
margin: 0 auto;
}
footer .content {
text-align: center;
}
footer .content div {
padding: .5em;
}
h1 {
padding: 1em 0;
margin: 1em 0;
border-bottom: 1px solid lightgray;
}
input {
box-shadow: inset 1px 1px 4px lightgray;
}
input, select {
outline: none;
}
button {
cursor: pointer;
}
button, input, img, select {
border: 1px solid lightgray;
padding: .25em;
border-radius: .25em;
}
fieldset {
margin: 1em 4em;
border: 1px solid lightgray;
border-radius: .25em;
font-size: small;
}
#action-notice {
font-size: small;
margin: 0 4em;
}
#action-notice > div {
margin: .5em 0;
padding: .25em;
color: darkred;
background-color: #f001;
border: 1px solid darkred;
border-radius: .25em;
transition: opacity .5s;
}
#action-notice > div.tohide {
opacity: 0;
}
#value-input > input {
font-size: larger;
width: 90%;
}
#size-input, #color-input, #type-input {
display: flex;
flex-direction: column;
margin: 0 auto;
}
#size-input > input, #color-input > select, #type-input > select {
width: 8em;
}
#settings {
grid-template-columns: repeat(3, 1fr);
display: grid;
}
label {
margin: .2em;
text-align: left;
}
#value, #size, #color, #type {
transition: box-shadow .1s, border .1s;
}
#value:focus, #size:focus, #color:focus, #type:focus {
box-shadow: 0 0 3px var(--blue);
border: 1px solid var(--blue);
}
label:has(+ *:focus) {
color: var(--blue);
}
img {
width: 200px;
height: 200px;
margin: 4em;
background: #f0f0f0;
}
.small {
font-size: x-small;
}
</style>
</head>
<body>
<main class="content">
<h1>Generate an identicon</h1>
<div id="value-input">
<input id="value" placeholder="Mail address, GitHub username, ..." oninput="idicon(this.value)" />
</div>
<div>
<img id="idicon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIAAAUAAeImBZsAAAAASUVORK5CYII=" />
</div>
<fieldset id="settings">
<legend>Settings</legend>
<div id="size-input" class="small">
<label for="size">Size</label>
<input id="size" type="number" value="200" min="8" max="512" step="8" oninput="newsize(this.value)" />
</div>
<div id="color-input" class="small">
<label for="color">Colorscheme</label>
<select id="color" onchange="newcolor(this.value)">
<option value="v1">V1</option>
<option value="v2">V2</option>
<option value="gh" selected>GH</option>
</select>
</div>
<div id="type-input" class="small">
<label for="type">Type</label>
<select id="type" onchange="newtype(this.value)">
<option value="">Default</option>
<option value="gh" selected>GH</option>
</select>
</div>
</fieldset>
<fieldset id="actions">
<legend>Actions</legend>
<button id="fetchGhId" onclick="fetchGhId()">Fetch GitHub ID</button>
</fieldset>
<div id="action-notice"></div>
</main>
<footer>
<div class="content">
<div>
A simple implementation of an identicon service.
</div>
<div>
Copyright &copy; 2024 Paul-Christian Volkmer
</div>
<div>
<a href="https://github.com/pcvolkmer/idicon">
<svg fill="currentColor" viewBox="0 0 24 24" class="h-6 w-6" aria-hidden="true"><path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path></svg>
</a>
</div>
</div>
</footer>
</body>
<script>
let currentsize = document.getElementById('size').value;
let currentcolor = document.getElementById('color').value;
let currenttype = document.getElementById('type').value;
idicon(document.getElementById('value').value);
function fetchGhId() {
let username = document.getElementById('value').value;
if (username.trim() !== '') {
fetch(`https://api.github.com/users/${username}`)
.then(res => res.json())
.then(json => {
if (json !== undefined && json.id !== undefined) {
document.getElementById('value').value = json.id;
idicon(document.getElementById('value').value);
} else {
let node = document.createElement('div');
node.innerText = `Fetching GitHub ID for username '${username}' not possible!`;
document.getElementById('action-notice').append(node);
setTimeout(() => {
node.className = 'tohide';
}, 4500);
setTimeout(() => {
node.remove();
}, 5000);
}
});
}
}
function newsize(value) {
currentsize = value;
idicon(document.getElementById('value').value);
}
function newcolor(value) {
currentcolor = value;
idicon(document.getElementById('value').value);
}
function newtype(value) {
currenttype = value;
idicon(document.getElementById('value').value);
}
function idicon(value) {
if (value.trim() === '') {
document.getElementById('idicon').src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIAAAUAAeImBZsAAAAASUVORK5CYII=';
return;
}
document.getElementById('idicon').src = `./avatar/${value}?s=${currentsize}&c=${currentcolor}&d=${currenttype}`
}
</script>
</html>