mirror of
https://github.com/gradenGnostic/LegacyLauncher.git
synced 2026-08-19 20:17:47 +00:00
Merge pull request #204 from rubiidev18alt/main
Add loading circle + better looking downloading/connecting text
This commit is contained in:
commit
90d40ba2af
14
index.html
14
index.html
|
|
@ -22,7 +22,7 @@
|
||||||
<div id="loader">
|
<div id="loader">
|
||||||
<div class="loader-content">
|
<div class="loader-content">
|
||||||
<div class="loader-spinner"></div>
|
<div class="loader-spinner"></div>
|
||||||
<div id="loader-text">CONNECTING...</div>
|
<div id="loader-text"><span id="loader-text-label">CONNECTING</span><span class="loading-dots animate" id="loader-dots" aria-hidden="true"><span>.</span><span>.</span><span>.</span></span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -86,13 +86,19 @@
|
||||||
<div class="skin-column" style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; min-width: 300px; padding-right: 0px;">
|
<div class="skin-column" style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; min-width: 300px; padding-right: 0px;">
|
||||||
<div id="main-skin-viewer" style="width: 100%; height: 500px; cursor: grab; display: flex; align-items: center; justify-content: center;"></div>
|
<div id="main-skin-viewer" style="width: 100%; height: 500px; cursor: grab; display: flex; align-items: center; justify-content: center;"></div>
|
||||||
|
|
||||||
<div class="btn-mc nav-item" onclick="openSkinManager()" style="width: 250px; height: 48px; font-size: 20px; margin-top: -20px; z-index: 10; margin-left: auto; margin-right: auto;" tabindex="0">
|
<div class="skin-action-row" style="margin-top: -20px; z-index: 10; margin-left: auto; margin-right: auto;">
|
||||||
CHANGE SKIN
|
<div class="btn-mc nav-item skin-action-btn skin-action-btn-main" onclick="openSkinManager()" tabindex="0">
|
||||||
|
CHANGE SKIN
|
||||||
|
</div>
|
||||||
|
<div id="btn-skin-render-mode" class="btn-mc nav-item skin-action-btn skin-action-btn-mode" onclick="toggleMainSkinRenderMode()" tabindex="0">3D</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="progress-container" id="progress-container">
|
<div class="progress-container" id="progress-container">
|
||||||
<div class="progress-text" id="progress-text">Downloading...</div>
|
<div class="progress-text-wrap" id="progress-text-wrap">
|
||||||
|
<span class="loader-spinner progress-spinner" id="progress-spinner" aria-hidden="true"></span>
|
||||||
|
<div class="progress-text" id="progress-text"><span id="progress-text-label">Downloading</span><span class="loading-dots" id="progress-dots" aria-hidden="true"><span>.</span><span>.</span><span>.</span></span><span id="progress-text-suffix"></span></div>
|
||||||
|
</div>
|
||||||
<div class="progress-bar-bg">
|
<div class="progress-bar-bg">
|
||||||
<div class="progress-bar-fill" id="progress-bar-fill"></div>
|
<div class="progress-bar-fill" id="progress-bar-fill"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
29
renderer.js
29
renderer.js
|
|
@ -1125,7 +1125,11 @@ async function fetchGitHubData() {
|
||||||
const offlineInd = document.getElementById('offline-indicator');
|
const offlineInd = document.getElementById('offline-indicator');
|
||||||
|
|
||||||
if (loader) loader.style.display = 'flex';
|
if (loader) loader.style.display = 'flex';
|
||||||
if (loaderText) loaderText.textContent = "SYNCING: " + repo;
|
if (loaderText) {
|
||||||
|
const loaderLabel = document.getElementById('loader-text-label');
|
||||||
|
if (loaderLabel) loaderLabel.textContent = "SYNCING: " + repo;
|
||||||
|
else loaderText.textContent = "SYNCING: " + repo;
|
||||||
|
}
|
||||||
|
|
||||||
const hideLoader = () => {
|
const hideLoader = () => {
|
||||||
if (loader) {
|
if (loader) {
|
||||||
|
|
@ -1416,8 +1420,29 @@ function setProcessingState(active) {
|
||||||
function updateProgress(percent, text) {
|
function updateProgress(percent, text) {
|
||||||
const bar = document.getElementById('progress-bar-fill');
|
const bar = document.getElementById('progress-bar-fill');
|
||||||
if (bar) bar.style.width = percent + "%";
|
if (bar) bar.style.width = percent + "%";
|
||||||
|
|
||||||
|
const label = document.getElementById('progress-text-label');
|
||||||
|
const dots = document.getElementById('progress-dots');
|
||||||
|
const suffix = document.getElementById('progress-text-suffix');
|
||||||
const txt = document.getElementById('progress-text');
|
const txt = document.getElementById('progress-text');
|
||||||
if (text && txt) txt.textContent = text;
|
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
if (label && dots && suffix) {
|
||||||
|
const match = text.match(/^(Downloading)(?:\.{0,3})?(.*)$/i);
|
||||||
|
if (match) {
|
||||||
|
label.textContent = match[1];
|
||||||
|
suffix.textContent = match[2] || '';
|
||||||
|
dots.classList.add('animate');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
label.textContent = text;
|
||||||
|
suffix.textContent = '';
|
||||||
|
dots.classList.remove('animate');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txt) txt.textContent = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleElectronFlow(url) {
|
async function handleElectronFlow(url) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
let mainMenuScene, mainMenuCamera, mainMenuRenderer, mainMenuPlayerGroup;
|
let mainMenuScene, mainMenuCamera, mainMenuRenderer, mainMenuPlayerGroup;
|
||||||
let isMainSkinDragging = false;
|
let isMainSkinDragging = false;
|
||||||
|
let mainMenuSkinRenderMode = '3d';
|
||||||
|
|
||||||
let skinScene, skinCamera, skinRenderer, skinPlayerGroup;
|
let skinScene, skinCamera, skinRenderer, skinPlayerGroup;
|
||||||
let isSkinDragging = false;
|
let isSkinDragging = false;
|
||||||
|
|
@ -75,12 +76,13 @@ function initMainMenuSkinViewer() {
|
||||||
// Interaction for Main Menu Viewer
|
// Interaction for Main Menu Viewer
|
||||||
let prevX = 0;
|
let prevX = 0;
|
||||||
container.addEventListener('mousedown', (e) => {
|
container.addEventListener('mousedown', (e) => {
|
||||||
|
if (mainMenuSkinRenderMode !== '3d') return;
|
||||||
isMainSkinDragging = true;
|
isMainSkinDragging = true;
|
||||||
prevX = e.clientX;
|
prevX = e.clientX;
|
||||||
});
|
});
|
||||||
window.addEventListener('mouseup', () => isMainSkinDragging = false);
|
window.addEventListener('mouseup', () => isMainSkinDragging = false);
|
||||||
window.addEventListener('mousemove', (e) => {
|
window.addEventListener('mousemove', (e) => {
|
||||||
if (isMainSkinDragging && mainMenuPlayerGroup) {
|
if (mainMenuSkinRenderMode === '3d' && isMainSkinDragging && mainMenuPlayerGroup) {
|
||||||
mainMenuPlayerGroup.rotation.y += (e.clientX - prevX) * 0.01;
|
mainMenuPlayerGroup.rotation.y += (e.clientX - prevX) * 0.01;
|
||||||
prevX = e.clientX;
|
prevX = e.clientX;
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +91,9 @@ function initMainMenuSkinViewer() {
|
||||||
// Auto-rotate slowly
|
// Auto-rotate slowly
|
||||||
function animateMain() {
|
function animateMain() {
|
||||||
requestAnimationFrame(animateMain);
|
requestAnimationFrame(animateMain);
|
||||||
if (!isMainSkinDragging && mainMenuPlayerGroup) mainMenuPlayerGroup.rotation.y += 0.005;
|
if (mainMenuSkinRenderMode === '3d' && !isMainSkinDragging && mainMenuPlayerGroup) {
|
||||||
|
mainMenuPlayerGroup.rotation.y += 0.005;
|
||||||
|
}
|
||||||
if (mainMenuRenderer && mainMenuScene && mainMenuCamera) mainMenuRenderer.render(mainMenuScene, mainMenuCamera);
|
if (mainMenuRenderer && mainMenuScene && mainMenuCamera) mainMenuRenderer.render(mainMenuScene, mainMenuCamera);
|
||||||
}
|
}
|
||||||
animateMain();
|
animateMain();
|
||||||
|
|
@ -123,7 +127,7 @@ async function loadMainMenuSkin() {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
const isLegacy = img.height === 32;
|
const isLegacy = img.height === 32;
|
||||||
updateSkinModel(img.src, isLegacy, mainMenuPlayerGroup);
|
updateSkinModel(img.src, isLegacy, mainMenuPlayerGroup, mainMenuSkinRenderMode);
|
||||||
};
|
};
|
||||||
img.src = url;
|
img.src = url;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -134,7 +138,25 @@ async function loadMainMenuSkin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSkinModel(dataUrl, isLegacy, targetGroup) {
|
function toggleMainSkinRenderMode() {
|
||||||
|
mainMenuSkinRenderMode = mainMenuSkinRenderMode === '3d' ? '2d' : '3d';
|
||||||
|
const modeButton = document.getElementById('btn-skin-render-mode');
|
||||||
|
if (modeButton) modeButton.textContent = mainMenuSkinRenderMode.toUpperCase();
|
||||||
|
|
||||||
|
const container = document.getElementById('main-skin-viewer');
|
||||||
|
if (container) {
|
||||||
|
container.style.cursor = mainMenuSkinRenderMode === '3d' ? 'grab' : 'default';
|
||||||
|
}
|
||||||
|
isMainSkinDragging = false;
|
||||||
|
|
||||||
|
if (mainMenuSkinRenderMode === '2d' && mainMenuPlayerGroup) {
|
||||||
|
mainMenuPlayerGroup.rotation.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMainMenuSkin();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSkinModel(dataUrl, isLegacy, targetGroup, renderMode = '3d') {
|
||||||
if (!targetGroup) return;
|
if (!targetGroup) return;
|
||||||
|
|
||||||
new THREE.TextureLoader().load(dataUrl, (texture) => {
|
new THREE.TextureLoader().load(dataUrl, (texture) => {
|
||||||
|
|
@ -178,6 +200,50 @@ function updateSkinModel(dataUrl, isLegacy, targetGroup) {
|
||||||
left: [x+8, y+4, 4, 12], back: [x+12, y+4, 4, 12]
|
left: [x+8, y+4, 4, 12], back: [x+12, y+4, 4, 12]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const create2DPart = (tex, uvFront, width, height, x, y, scale = 1) => {
|
||||||
|
const texWidth = tex.image.width;
|
||||||
|
const texHeight = tex.image.height;
|
||||||
|
const partTex = tex.clone();
|
||||||
|
partTex.magFilter = THREE.NearestFilter;
|
||||||
|
partTex.minFilter = THREE.NearestFilter;
|
||||||
|
partTex.repeat.set(uvFront[2] / texWidth, uvFront[3] / texHeight);
|
||||||
|
partTex.offset.set(uvFront[0] / texWidth, 1 - (uvFront[1] + uvFront[3]) / texHeight);
|
||||||
|
partTex.needsUpdate = true;
|
||||||
|
|
||||||
|
const geometry = new THREE.PlaneGeometry(width, height);
|
||||||
|
const material = new THREE.MeshBasicMaterial({ map: partTex, transparent: true, alphaTest: 0.5, side: THREE.DoubleSide });
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
mesh.position.set(x, y, 0);
|
||||||
|
mesh.scale.set(scale, scale, 1);
|
||||||
|
return mesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (renderMode === '2d') {
|
||||||
|
const frontParts = [
|
||||||
|
create2DPart(texture, [8, 8, 8, 8], 8, 8, 0, 10),
|
||||||
|
create2DPart(texture, [40, 8, 8, 8], 8, 8, 0, 10, 1.12),
|
||||||
|
create2DPart(texture, [20, 20, 8, 12], 8, 12, 0, 0),
|
||||||
|
create2DPart(texture, [44, 20, 4, 12], 4, 12, -6, 0),
|
||||||
|
create2DPart(texture, isLegacy ? [44, 20, 4, 12] : [36, 52, 4, 12], 4, 12, 6, 0),
|
||||||
|
create2DPart(texture, [4, 20, 4, 12], 4, 12, -2, -12),
|
||||||
|
create2DPart(texture, isLegacy ? [4, 20, 4, 12] : [20, 52, 4, 12], 4, 12, 2, -12)
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isLegacy) {
|
||||||
|
frontParts.push(
|
||||||
|
create2DPart(texture, [20, 36, 8, 12], 8, 12, 0, 0, 1.05),
|
||||||
|
create2DPart(texture, [44, 36, 4, 12], 4, 12, -6, 0, 1.05),
|
||||||
|
create2DPart(texture, [52, 52, 4, 12], 4, 12, 6, 0, 1.05),
|
||||||
|
create2DPart(texture, [4, 36, 4, 12], 4, 12, -2, -12, 1.05),
|
||||||
|
create2DPart(texture, [4, 52, 4, 12], 4, 12, 2, -12, 1.05)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
frontParts.forEach((part) => targetGroup.add(part));
|
||||||
|
targetGroup.rotation.y = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Head
|
// Head
|
||||||
const headUvs = { top: [8, 0, 8, 8], bottom: [16, 0, 8, 8], right: [0, 8, 8, 8], left: [16, 8, 8, 8], front: [8, 8, 8, 8], back: [24, 8, 8, 8] };
|
const headUvs = { top: [8, 0, 8, 8], bottom: [16, 0, 8, 8], right: [0, 8, 8, 8], left: [16, 8, 8, 8], front: [8, 8, 8, 8], back: [24, 8, 8, 8] };
|
||||||
const head = createBodyPart(8, 8, 8, texture, headUvs);
|
const head = createBodyPart(8, 8, 8, texture, headUvs);
|
||||||
|
|
@ -218,6 +284,8 @@ function updateSkinModel(dataUrl, isLegacy, targetGroup) {
|
||||||
targetGroup.add(layer);
|
targetGroup.add(layer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
targetGroup.rotation.y = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,7 +386,7 @@ function processSkinImage(img, srcUrl, isInitialLoad = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skinScene) initPreviewEngine();
|
if (!skinScene) initPreviewEngine();
|
||||||
updateSkinModel(srcUrl, isLegacy, skinPlayerGroup);
|
updateSkinModel(srcUrl, isLegacy, skinPlayerGroup, '3d');
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPreviewEngine() {
|
function initPreviewEngine() {
|
||||||
|
|
@ -398,3 +466,4 @@ async function saveSkinToDisk() {
|
||||||
window.openSkinManager = openSkinManager;
|
window.openSkinManager = openSkinManager;
|
||||||
window.initMainMenuSkinViewer = initMainMenuSkinViewer;
|
window.initMainMenuSkinViewer = initMainMenuSkinViewer;
|
||||||
window.loadMainMenuSkin = loadMainMenuSkin;
|
window.loadMainMenuSkin = loadMainMenuSkin;
|
||||||
|
window.toggleMainSkinRenderMode = toggleMainSkinRenderMode;
|
||||||
|
|
|
||||||
93
style.css
93
style.css
|
|
@ -568,12 +568,71 @@ body.steamdeck-mode .sidebar {
|
||||||
transition: width 0.2s ease-out;
|
transition: width 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-text-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-spinner {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-width: 2px;
|
||||||
|
border-color: #333;
|
||||||
|
border-top-color: var(--mc-progress-fill);
|
||||||
|
margin: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
animation-duration: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
.progress-text {
|
.progress-text {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
text-shadow: 2px 2px 0 #000;
|
text-shadow: 2px 2px 0 #000;
|
||||||
margin-bottom: 6px;
|
text-align: left;
|
||||||
text-align: center;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 1.8em;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots span {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots.animate span:nth-child(1) {
|
||||||
|
animation: dotCycle1 1.1s steps(1, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots.animate span:nth-child(2) {
|
||||||
|
animation: dotCycle2 1.1s steps(1, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots.animate span:nth-child(3) {
|
||||||
|
animation: dotCycle3 1.1s steps(1, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dotCycle1 {
|
||||||
|
0%, 49% { visibility: hidden; }
|
||||||
|
50%, 100% { visibility: visible; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dotCycle2 {
|
||||||
|
0%, 32% { visibility: hidden; }
|
||||||
|
33%, 66% { visibility: visible; }
|
||||||
|
67%, 100% { visibility: hidden; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dotCycle3 {
|
||||||
|
0%, 16% { visibility: hidden; }
|
||||||
|
17%, 82% { visibility: visible; }
|
||||||
|
83%, 100% { visibility: hidden; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
|
|
@ -803,12 +862,18 @@ body.steamdeck-mode .sidebar {
|
||||||
background: rgba(0,0,0,0.8);
|
background: rgba(0,0,0,0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.loader-content {
|
.loader-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#loader-text {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.loader-spinner {
|
.loader-spinner {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
|
@ -1333,3 +1398,27 @@ body.classic-theme .sidebar-title {
|
||||||
body.classic-theme .music-btn {
|
body.classic-theme .music-btn {
|
||||||
bottom: 92px;
|
bottom: 92px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skin-action-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skin-action-btn {
|
||||||
|
height: 48px !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skin-action-btn-main {
|
||||||
|
width: 250px;
|
||||||
|
font-size: 20px;
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skin-action-btn-mode {
|
||||||
|
width: 64px;
|
||||||
|
font-size: 20px;
|
||||||
|
border-left-width: 1px;
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue