Files
MrixsCraft-server/internal/templates/html/profile.html
Vladimir Zagainov f4f7a52749
All checks were successful
CI / lint (push) Successful in 16s
CI / docker (push) Successful in 1m10s
CI / test (push) Successful in 40s
CI / build (push) Successful in 17s
fix: remove inline onerror from skin/cape images in template
Inline onerror was firing on empty src, hiding images before JS could load them
2026-06-06 20:27:18 +03:00

250 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "content"}}
<div id="authCheck" class="card text-center">
<p class="muted">Загрузка...</p>
</div>
<div id="profileContent" style="display:none">
<!-- Profile header -->
<div class="card">
<div class="profile-header">
<img class="profile-avatar" id="skinPreview" src="" alt="Аватар" onerror="this.src='data:image/svg+xml,<svg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 8 8%27><rect fill=%27%23334155%27 width=%278%27 height=%278%27/></svg>'">
<div class="profile-info">
<h2 id="profUsername"></h2>
<p class="uuid" id="profUuid"></p>
</div>
</div>
</div>
<!-- Skin & Cape -->
<h2 class="section-title">👤 Скин и плащ</h2>
<div class="grid-2">
<div class="card">
<h3>Скин</h3>
<div id="currentSkin" class="text-center" style="margin-bottom:1rem">
<img id="skinImg" src="" alt="Скин" style="width:120px;height:160px;image-rendering:pixelated;background:var(--surface-light);border-radius:var(--radius-sm)">
<p class="muted text-sm mt-1" id="noSkinMsg">Скин не загружен</p>
</div>
<div id="alertSkin" class="alert"></div>
<form id="skinForm" enctype="multipart/form-data">
<label for="skinFile">Загрузить скин (PNG, 64×64 или 64×32)</label>
<input type="file" id="skinFile" name="file" accept=".png">
<div class="flex gap-1">
<button type="submit" class="btn btn-sm">Загрузить</button>
<button type="button" id="deleteSkin" class="btn btn-sm btn-danger">Удалить</button>
</div>
</form>
</div>
<div class="card">
<h3>Плащ</h3>
<div id="currentCape" class="text-center" style="margin-bottom:1rem">
<img id="capeImg" src="" alt="Плащ" style="width:100px;height:160px;image-rendering:pixelated;background:var(--surface-light);border-radius:var(--radius-sm)">
<p class="muted text-sm mt-1" id="noCapeMsg">Плащ не загружен</p>
</div>
<div id="alertCape" class="alert"></div>
<form id="capeForm" enctype="multipart/form-data">
<label for="capeFile">Загрузить плащ (PNG, 64×32)</label>
<input type="file" id="capeFile" name="file" accept=".png">
<div class="flex gap-1">
<button type="submit" class="btn btn-sm">Загрузить</button>
<button type="button" id="deleteCape" class="btn btn-sm btn-danger">Удалить</button>
</div>
</form>
</div>
</div>
<!-- Launcher download -->
<h2 class="section-title">💾 Лаунчер</h2>
<div class="card">
<p class="muted">Скачай лаунчер для своей операционной системы. Он автоматически загрузит все файлы модпаков.</p>
<div class="flex gap-1 mt-2" id="launcherLinks">
<a href="#" class="btn btn-sm" id="dl-win">🪟 Windows</a>
<a href="#" class="btn btn-sm" id="dl-mac">🍎 macOS</a>
<a href="#" class="btn btn-sm" id="dl-lin">🐧 Linux</a>
</div>
<p class="text-sm muted mt-2" id="latestVersion">Последняя версия: —</p>
</div>
</div>
<script>
(function() {
const token = localStorage.getItem('token');
const savedUuid = localStorage.getItem('uuid');
const savedUsername = localStorage.getItem('username');
if (!token) {
document.getElementById('authCheck').innerHTML = '<h2>🔒 Требуется авторизация</h2><p class="muted">Войди в аккаунт, чтобы увидеть профиль.</p><p class="mt-2"><a href="/login" class="btn">Войти</a></p>';
return;
}
document.getElementById('authCheck').style.display = 'none';
document.getElementById('profileContent').style.display = 'block';
// Load profile
loadProfile();
async function loadProfile() {
try {
const res = await fetch('/api/web/profile/' + savedUuid, {
headers: {'Authorization': 'Bearer ' + token}
});
if (!res.ok) throw new Error('Failed to load');
const data = await res.json();
document.getElementById('profUsername').textContent = data.username || savedUsername || '—';
document.getElementById('profUuid').textContent = data.uuid || savedUuid || '—';
// Skin
if (data.textures && data.textures.skin) {
const skinImg = document.getElementById('skinImg');
const noSkinMsg = document.getElementById('noSkinMsg');
skinImg.onerror = function() { this.style.display = 'none'; noSkinMsg.style.display = 'block'; };
skinImg.onload = function() { this.style.display = 'inline'; noSkinMsg.style.display = 'none'; };
skinImg.src = '/skins/' + data.textures.skin_hash;
} else {
document.getElementById('skinImg').style.display = 'none';
document.getElementById('noSkinMsg').style.display = 'block';
}
// Cape
if (data.textures && data.textures.cape) {
const capeImg = document.getElementById('capeImg');
const noCapeMsg = document.getElementById('noCapeMsg');
capeImg.onerror = function() { this.style.display = 'none'; noCapeMsg.style.display = 'block'; };
capeImg.onload = function() { this.style.display = 'inline'; noCapeMsg.style.display = 'none'; };
capeImg.src = '/skins/' + data.textures.cape_hash;
} else {
document.getElementById('capeImg').style.display = 'none';
document.getElementById('noCapeMsg').style.display = 'block';
}
} catch (err) {
console.error('Profile load error:', err);
}
}
// Load latest launcher version
loadLauncherInfo();
async function loadLauncherInfo() {
try {
const os = navigator.userAgent.includes('Mac') ? 'darwin' :
navigator.userAgent.includes('Win') ? 'windows' : 'linux';
const arch = navigator.userAgent.includes('x86_64') || navigator.userAgent.includes('WOW64') ? 'amd64' :
navigator.userAgent.includes('arm64') || navigator.userAgent.includes('aarch64') ? 'arm64' : 'amd64';
const res = await fetch('/api/launcher/latest?os=' + os + '&arch=' + arch);
if (!res.ok) return;
const data = await res.json();
document.getElementById('latestVersion').textContent = 'Последняя версия: ' + (data.version || '—');
if (data.downloads) {
if (data.downloads.windows) document.getElementById('dl-win').href = data.downloads.windows;
if (data.downloads.darwin) document.getElementById('dl-mac').href = data.downloads.darwin;
if (data.downloads.linux) document.getElementById('dl-lin').href = data.downloads.linux;
}
} catch (err) {}
}
// Skin upload
document.getElementById('skinForm').addEventListener('submit', async function(e) {
e.preventDefault();
const file = document.getElementById('skinFile').files[0];
if (!file) return;
const fd = new FormData();
fd.append('skin', file);
const alert = document.getElementById('alertSkin');
try {
const res = await fetch('/api/web/profile/skin', {
method: 'POST',
headers: {'Authorization': 'Bearer ' + token},
body: fd
});
const data = await res.json();
if (!res.ok) {
alert.className = 'alert alert-error show';
alert.textContent = data.error || 'Ошибка загрузки';
} else {
alert.className = 'alert alert-success show';
alert.textContent = 'Скин загружен!';
loadProfile();
}
} catch (err) {
alert.className = 'alert alert-error show';
alert.textContent = 'Ошибка сети';
}
});
// Cape upload
document.getElementById('capeForm').addEventListener('submit', async function(e) {
e.preventDefault();
const file = document.getElementById('capeFile').files[0];
if (!file) return;
const fd = new FormData();
fd.append('cape', file);
const alert = document.getElementById('alertCape');
try {
const res = await fetch('/api/web/profile/cape', {
method: 'POST',
headers: {'Authorization': 'Bearer ' + token},
body: fd
});
const data = await res.json();
if (!res.ok) {
alert.className = 'alert alert-error show';
alert.textContent = data.error || 'Ошибка загрузки';
} else {
alert.className = 'alert alert-success show';
alert.textContent = 'Плащ загружен!';
loadProfile();
}
} catch (err) {
alert.className = 'alert alert-error show';
alert.textContent = 'Ошибка сети';
}
});
// Delete skin
document.getElementById('deleteSkin').addEventListener('click', async function() {
const alert = document.getElementById('alertSkin');
try {
const res = await fetch('/api/web/profile/skin', {
method: 'DELETE',
headers: {'Authorization': 'Bearer ' + token}
});
if (!res.ok) {
alert.className = 'alert alert-error show';
alert.textContent = 'Ошибка удаления';
} else {
alert.className = 'alert alert-success show';
alert.textContent = 'Скин удалён';
loadProfile();
}
} catch (err) {
alert.className = 'alert alert-error show';
alert.textContent = 'Ошибка сети';
}
});
// Delete cape
document.getElementById('deleteCape').addEventListener('click', async function() {
const alert = document.getElementById('alertCape');
try {
const res = await fetch('/api/web/profile/cape', {
method: 'DELETE',
headers: {'Authorization': 'Bearer ' + token}
});
if (!res.ok) {
alert.className = 'alert alert-error show';
alert.textContent = 'Ошибка удаления';
} else {
alert.className = 'alert alert-success show';
alert.textContent = 'Плащ удалён';
loadProfile();
}
} catch (err) {
alert.className = 'alert alert-error show';
alert.textContent = 'Ошибка сети';
}
});
})();
</script>
{{end}}