Created UI for showing and adding skins

This commit is contained in:
2025-06-11 01:19:14 +02:00
parent 98776d51a5
commit d59ff01139
3 changed files with 104 additions and 14 deletions

View File

@ -66,6 +66,8 @@ public class Skin implements JSON {
public static Skin loadFromImage(InputStream is, String label) throws SkinException, IOException, NoSuchAlgorithmException {
BufferedImage image = ImageIO.read(is);
if (image == null)
throw new SkinException("Failed to load the image");
int width = image.getWidth();
int height = image.getHeight();

View File

@ -1,5 +1,16 @@
api = {
getSkins: async function() {
return fetch("api/skins").then(res=>res.json());
return (await fetch("api/skins").then(res=>res.json())).map(skin=>{return {
hash: skin.hash,
label: skin.label,
png: `${location.protocol}//${location.host}${skin.png}`,
png_old: `${location.protocol}//${location.host}${skin.png_old}`,
}});
},
addSkin: async function(skin, label) {
let data = new FormData();
data.append("skin", skin);
data.append("label", label);
await fetch("api/skin", {"method": "POST", body: data});
},
};

View File

@ -5,21 +5,98 @@
<script src="api.js"></script>
<script src="thirdparty/skinview3d.js.gz"></script>
<script src="thirdparty/alpine.js.gz" defer></script>
<script>
const skinViewer = new skinview3d.SkinViewer({
width: 200,
height: 300,
renderPaused: true,
});
skinViewer.camera.rotation.x = -0.62;
skinViewer.camera.rotation.y = 0.534;
skinViewer.camera.rotation.z = 0.348;
skinViewer.camera.position.x = 17.0;
skinViewer.camera.position.y = 19.0;
skinViewer.camera.position.z = 23.0;
async function renderSkin(url)
{
await skinViewer.loadSkin(url),
skinViewer.render();
const image = skinViewer.canvas.toDataURL();
return image;
}
</script>
<style>
dialog
{
position: fixed;
top: 0;
left: 0;
margin: 0;
background-color: var(--bg);
transform: translate(50%, 50%);
min-width: 50vw;
aspect-ratio: 1.5/1;
}
.screenblock
{
position: fixed;
top: 0;
left: 0;
margin: 0;
min-width: 100vw;
min-height: 100vh;
}
.flex-container
{
display: flex;
}
.flex
{
flex: 1;
}
#skin-cards
{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.skin-card
{
min-width: 30%;
max-width: 30%;
margin: 0.5em;
margin-left: 1.666%;
margin-right: 1.666%;
}
</style>
<title>Skinner</title>
</head>
<body>
<h1>Skinner</h1>
<article>
<form method="POST" action="api/skin" enctype="multipart/form-data">
<input name="label" type="text" placeholder="Skin name">
<input name="skin" type="file" required>
<input type="submit" value="Add new skin">
<body x-data="{add_dialog: false, blur: false, skins: api.getSkins()}">
<main x-bind:style="blur ? 'filter: blur(10px);' : ''">
<h1>Skinner</h1>
<div class="flex-container">
<input class="flex" type="search" placeholder="Search skins..." disabled title="Not implemented">
<button @click="add_dialog = true; blur = true;">Add new</button>
</div>
<div id="skin-cards"">
<template x-for="skin in skins">
<article class="skin-card" x-data="{}">
<img x-bind:src="await renderSkin(skin.png)">
<big x-text="skin.label || 'Unnamed'"></big>
</article>
</template>
</div>
</main>
<div class="screenblock" x-show="blur"><!-- Block to prevent mouse clicks --></div>
<dialog x-bind:open="add_dialog">
<form method="POST" action="api/skin" enctype="multipart/form-data" x-data="{files: null, label: '', processing: false}" @submit.prevent="processing = true; api.addSkin(files, label).then(async ()=>{skins = await api.getSkins(); blur = false; add_dialog = false; processing = false;})">
<h1>Add Skin</h1><br>
<input type="text" placeholder="Skin name" x-model="label">
<input type="file" accept="image/png" @change="files = $el.files[0]" required><br><br><br>
<input type="submit" value="Add new skin" x-bind:disabled="processing">
<button @click="blur = false; add_dialog = false;" x-bind:disabled="processing">Cancel</button>
</form>
</article>
<div x-data="{skins: api.getSkins()}">
<template x-for="skin in skins">
<p x-text="skin.label + ' <<>> ' + skin.hash"></p>
</template>
</div>
</dialog>
</body>
</html>