Add MojangAPI integration for skin and cape management
Introduces MojangAPI utility for handling skin uploads, cape management, and username changes via new backend endpoints. Updates Program.cs to support new IPC commands for skin selection, uploading, cape toggling, and username changes. Refactors frontend (logged.js, logged.html, logged.css) to provide UI for skin uploads, cape selection, and username changes, including new styles and blank cape asset. Refactors UserRecord to allow username mutation.
This commit is contained in:
@@ -252,6 +252,11 @@ canvas#skin {
|
||||
|
||||
div.profile > section.cosmectics > article {
|
||||
margin-bottom: 40px;
|
||||
height: calc((100% / 2) - (103px + 10px));
|
||||
}
|
||||
|
||||
div.profile > section.cosmectics > article.username {
|
||||
height: 103px;
|
||||
}
|
||||
|
||||
div.profile > section.cosmectics > article.username > div > button {
|
||||
@@ -273,4 +278,59 @@ div.profile > section.cosmectics > article.username > div > button:hover {
|
||||
div.profile > section.cosmectics > article.username > div > input {
|
||||
height: 38px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
div.profile > section.cosmectics > article.skin > div.skinUpload {
|
||||
margin-top: 10px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: calc(100% - 20px);
|
||||
border: 4px dashed #7c7c7c;
|
||||
border-radius: 5px;
|
||||
background-color: #1d1d1dce;
|
||||
}
|
||||
|
||||
div.profile > section.cosmectics > article.skin > div.skinUpload > * {
|
||||
filter: brightness(0.90);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
div.profile > section.cosmectics > article.capes > div.capes {
|
||||
width: 100%;
|
||||
height: calc(100% - 40px);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
border-radius: 5px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
div.profile > section.cosmectics > article.capes > div.capes > div.cape {
|
||||
width: 100%;
|
||||
aspect-ratio: 10 / 16;
|
||||
background-color: #b6b6b61f;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
filter: brightness(0.75);
|
||||
transition: .3s;
|
||||
background-repeat: no-repeat;
|
||||
image-rendering: pixelated;
|
||||
background-size: 640% 200%;
|
||||
background-position: 1.8% 6%;
|
||||
}
|
||||
|
||||
div.profile > section.cosmectics > article.capes > div.capes > div.cape:hover {
|
||||
filter: brightness(0.85);
|
||||
}
|
||||
|
||||
div.profile > section.cosmectics > article.capes > div.capes > div.cape.active,
|
||||
div.profile > section.cosmectics > article.capes > div.capes > div.cape.active:hover {
|
||||
filter: brightness(1);
|
||||
}
|
||||
BIN
wwwroot/assets/img/blankCape.png
Normal file
BIN
wwwroot/assets/img/blankCape.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 247 B |
@@ -1,16 +1,20 @@
|
||||
const player = await system.call("launcher::profile")
|
||||
const buttons = document.querySelectorAll("button[frame]")
|
||||
const dynmapFrame = document.querySelector("article.frame.dynmap > iframe")
|
||||
const capesSelector = document.querySelector("article.capes > div.capes")
|
||||
|
||||
const skinViewer = new skinview3d.SkinViewer({
|
||||
let viewerInstance = new skinview3d.SkinViewer({
|
||||
canvas: document.getElementById("skin"),
|
||||
width: 390,
|
||||
height: 490,
|
||||
skin: "assets/img/debug_skin.png"
|
||||
skin: "https://yggdrasil.azures.fr/textures/texture/steve.png"
|
||||
})
|
||||
|
||||
skinViewer.animation = new skinview3d.IdleAnimation()
|
||||
skinViewer.animation.speed = 1
|
||||
viewerInstance.animation = new skinview3d.IdleAnimation()
|
||||
viewerInstance.animation.speed = 1
|
||||
|
||||
function wait(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function setActiveButton(frameIdentifier) {
|
||||
for (const button of buttons) {
|
||||
@@ -27,22 +31,25 @@ function fixedTo(number, n) {
|
||||
return Number.isInteger(result) ? result.toFixed(2) : result
|
||||
}
|
||||
|
||||
function flattenSettings(obj, prefix = "") {
|
||||
return Object.keys(obj).reduce((acc, k) => {
|
||||
const pre = prefix.length ? prefix + ".": "";
|
||||
if (typeof obj[k] === "object" && obj[k] !== null && !Array.isArray(obj[k])) {
|
||||
Object.assign(acc, flattenSettings(obj[k], pre + k));
|
||||
} else {
|
||||
acc[pre + k] = obj[k];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
window.getPlayer = async function getPlayer() {
|
||||
const result = await system.call("network::getPlayer")
|
||||
console.log(result)
|
||||
if (result.error) {
|
||||
console.error("Impossible de récupéré le profil")
|
||||
return null
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
window.profile = await getPlayer()
|
||||
|
||||
window.refreshProfile = async function refreshProfile() {
|
||||
window.profile = await getPlayer()
|
||||
}
|
||||
|
||||
window.setting = {}
|
||||
|
||||
window.setting.set = async function settingSet(key, value) {
|
||||
console.log(key, value)
|
||||
await system.call("settings::set", { key, value })
|
||||
}
|
||||
|
||||
@@ -83,30 +90,145 @@ window.initSettings = async function initSettings() {
|
||||
|
||||
window.initSkin = async function initSkin() {
|
||||
const container = document.querySelector(".skinview3d")
|
||||
|
||||
if (container.clientWidth === 0 || container.clientHeight === 0) {
|
||||
requestAnimationFrame(initSkin)
|
||||
return
|
||||
}
|
||||
|
||||
const skinViewer = new skinview3d.SkinViewer({
|
||||
viewerInstance = new skinview3d.SkinViewer({
|
||||
canvas: document.getElementById("skin"),
|
||||
width: container.clientWidth,
|
||||
height: container.clientHeight,
|
||||
skin: "assets/img/debug_skin.png"
|
||||
skin: `https://yggdrasil.azures.fr/textures/${profile.skins.find(skin => skin.state == "ACTIVE").url}`.replace(/\/+$/, "")
|
||||
})
|
||||
|
||||
const activeCape = window.profile.capes.find(s => s.state === "ACTIVE") || null
|
||||
const capeUrl = activeCape == null ? null : `https://yggdrasil.azures.fr/textures/${activeCape.url.replace(/^\//, "")}?t=${Date.now()}`
|
||||
|
||||
viewerInstance.animation = new skinview3d.IdleAnimation()
|
||||
viewerInstance.nameTag = profile.name
|
||||
viewerInstance.zoom = 0.7
|
||||
viewerInstance.loadCape(capeUrl)
|
||||
|
||||
|
||||
const ro = new ResizeObserver(() => {
|
||||
skinViewer.width = container.clientWidth
|
||||
skinViewer.height = container.clientHeight
|
||||
viewerInstance.width = container.clientWidth
|
||||
viewerInstance.height = container.clientHeight
|
||||
})
|
||||
|
||||
skinViewer.animation = new skinview3d.IdleAnimation()
|
||||
window.skinViewer = skinViewer
|
||||
|
||||
ro.observe(container)
|
||||
}
|
||||
|
||||
initSkin()
|
||||
initSettings()
|
||||
window.selectSkin = async function selectSkin() {
|
||||
const skin = await system.call("dialog::selectSkin")
|
||||
if (!skin.success) {
|
||||
return iziToast.error({ title: "Erreur", message: "Impossible d'envoyer le skin" })
|
||||
}
|
||||
return validateSkinSelection(skin.path)
|
||||
}
|
||||
|
||||
window.validateSkinSelection = async function validateSkinSelection(localPath) {
|
||||
const isSlim = confirm("Modèle Slim ?")
|
||||
const variant = isSlim ? "slim" : "classic"
|
||||
|
||||
const result = await system.call("network::uploadSkin", {
|
||||
path: localPath,
|
||||
variant: variant
|
||||
})
|
||||
|
||||
if (result.success && viewerInstance) {
|
||||
await wait(500)
|
||||
await refreshProfile()
|
||||
const activeSkin = window.profile.skins.find(s => s.state === "ACTIVE")
|
||||
const activeCape = window.profile.capes.find(s => s.state === "ACTIVE") || null
|
||||
|
||||
if (activeSkin) {
|
||||
const skinUrl = `https://yggdrasil.azures.fr/textures/${activeSkin.url.replace(/^\//, "")}?t=${Date.now()}`
|
||||
const capeUrl = activeCape == null ? null : `https://yggdrasil.azures.fr/textures/${activeCape.url.replace(/^\//, "")}?t=${Date.now()}`
|
||||
await viewerInstance.loadSkin(skinUrl, {
|
||||
model: variant.toLowerCase() === "slim" ? "slim" : "default"
|
||||
})
|
||||
await viewerInstance.loadCape(capeUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.initCapesSelector = async function initCapesSelector() {
|
||||
capesSelector.innerHTML = ""
|
||||
const blankCape = document.createElement("div")
|
||||
blankCape.classList.add("cape")
|
||||
blankCape.setAttribute("onclick", `hideCape()`)
|
||||
blankCape.title = "Cacher ma cape"
|
||||
capesSelector.appendChild(blankCape)
|
||||
await viewerInstance.loadCape(null)
|
||||
for (const cape of profile.capes) {
|
||||
const $cape = document.createElement("div")
|
||||
$cape.classList.add("cape")
|
||||
$cape.setAttribute("title", cape.alias)
|
||||
$cape.setAttribute("style", `background-image: url("https://yggdrasil.azures.fr/textures/${cape.url.replace(/^\//, "")}?t=${Date.now()}") !important;`)
|
||||
$cape.setAttribute("onclick", `showCape("${cape.id}")`)
|
||||
if (cape.state == "ACTIVE") {
|
||||
$cape.classList.add("active")
|
||||
await viewerInstance.loadCape(`https://yggdrasil.azures.fr/textures/${cape.url.replace(/^\//, "")}?t=${Date.now()}`)
|
||||
}
|
||||
capesSelector.appendChild($cape)
|
||||
}
|
||||
}
|
||||
|
||||
window.showCape = async function showCape(id) {
|
||||
const result = await system.call("network::showCape", { capeId: id })
|
||||
|
||||
if (result.success) {
|
||||
await refreshProfile()
|
||||
await initCapesSelector()
|
||||
|
||||
if (typeof viewerInstance !== "undefined" && window.profile) {
|
||||
const activeCape = window.profile.capes.find(c => c.state === "ACTIVE")
|
||||
if (activeCape) {
|
||||
const fullUrl = `https://yggdrasil.azures.fr/textures/${activeCape.url.replace(/^\//, "")}?t=${Date.now()}`
|
||||
await viewerInstance.loadCape(fullUrl)
|
||||
}
|
||||
}
|
||||
|
||||
return result.data
|
||||
} else {
|
||||
alert("Erreur d'équipement : " + result.error)
|
||||
}
|
||||
}
|
||||
|
||||
window.hideCape = async function hideCape() {
|
||||
const result = await system.call("network::hideCape")
|
||||
|
||||
if (result.success) {
|
||||
await refreshProfile()
|
||||
await initCapesSelector()
|
||||
|
||||
if (typeof viewerInstance !== "undefined") {
|
||||
viewerInstance.loadCape(null)
|
||||
}
|
||||
|
||||
return result.data
|
||||
} else {
|
||||
alert("Erreur lors du retrait : " + result.error)
|
||||
}
|
||||
}
|
||||
|
||||
window.changeUsername = async function changeUsername(newName) {
|
||||
if (!newName || newName.length < 3) return alert("Pseudo trop court !");
|
||||
|
||||
const result = await system.call("network::changeUsername", {
|
||||
username: newName
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
viewerInstance.nameTag = result.profile.name
|
||||
window.profile = result.profile
|
||||
} else {
|
||||
alert("Erreur : " + result.error)
|
||||
}
|
||||
}
|
||||
|
||||
await initSkin()
|
||||
await initSettings()
|
||||
await initCapesSelector()
|
||||
showFrame("profile")
|
||||
@@ -43,11 +43,11 @@
|
||||
Pseudo
|
||||
</h2>
|
||||
<p>
|
||||
Tu veux changer de pseudo l'ami ? C'est ici
|
||||
Tu veux changer de blaze mon frérot ? Ça se passe ici.
|
||||
</p>
|
||||
<div>
|
||||
<input type="text" name="usernameChange" id="usernameChange" placeholder="Jeb_">
|
||||
<button>
|
||||
<button onclick="changeUsername(usernameChange.value)">
|
||||
Valider
|
||||
</button>
|
||||
</div>
|
||||
@@ -57,8 +57,13 @@
|
||||
Capes
|
||||
</h2>
|
||||
<p>
|
||||
T'a froid ? Couvre toi un peu mon frère
|
||||
Tu te sens nu ? Viens t'couvrir avec une cape mon reuf.
|
||||
</p>
|
||||
<div class="capes">
|
||||
<div id="blank" class="cape">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="skin">
|
||||
<h2>
|
||||
@@ -67,6 +72,11 @@
|
||||
<p>
|
||||
Tu veux rafraîchir un peu ton style ? Par là
|
||||
</p>
|
||||
<div class="skinUpload" onclick="selectSkin()">
|
||||
<h3>
|
||||
Clique ici frérot
|
||||
</h3>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
@@ -152,7 +162,7 @@
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<script type="module" crossorigin src="./assets/js/skinview3d.bundle.js"></script>
|
||||
<script type="module" src="./assets/js/skinview3d.bundle.js"></script>
|
||||
<script src="./assets/js/ipc.js"></script>
|
||||
<script src="./assets/js/common.js"></script>
|
||||
<script src="./assets/js/frames.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user