Skip to content

Commit 554e4ae

Browse files
committed
feat: ajouter des animations de transition et de changement de texte pour améliorer l'interface utilisateur
1 parent 45fd621 commit 554e4ae

3 files changed

Lines changed: 427 additions & 76 deletions

File tree

app/assets/js/scripts/landing.js

Lines changed: 153 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -86,61 +86,74 @@ const buttonAnimationTokens = new WeakMap()
8686
* Handles cancellation via per-element tokens and updates 'selected' class and image borders.
8787
*/
8888
/**
89-
* Swap selection entre deux boutons de sidebar (mode instantané pour performance)
89+
* Swap selection entre deux boutons de sidebar avec animations fluides
9090
*/
9191
async function animateButtonSwap(prevBtn, nextBtn){
92-
// Mode instantané - pas d'animations pour améliorer les performances
9392
try {
94-
// Retirer la sélection du bouton précédent
93+
// Animer la sortie du bouton précédent
9594
if(prevBtn){
96-
prevBtn.classList.remove('selected')
95+
prevBtn.classList.add('instance-fade-out')
9796
const img = prevBtn.querySelector('img')
9897
if(img){
98+
img.style.transition = 'border-color 0.3s ease-out'
9999
img.classList.remove('border-[#F8BA59]')
100100
img.classList.add('border-white/20')
101101
}
102-
// Nettoyage des classes d'animation existantes
103-
try {
104-
const labelPrev = prevBtn.querySelector('.font-semibold.text-xl.leading-tight')
105-
if(labelPrev) labelPrev.classList.remove('label-slide-out','label-slide-in')
106-
} catch (e) {}
107-
prevBtn.classList.remove('instance-btn-exit', 'instance-btn-enter')
108102
}
109103

110-
// Appliquer la sélection au nouveau bouton immédiatement
104+
// Attendre la fin de l'animation de sortie
105+
await new Promise(r => setTimeout(r, 150))
106+
107+
// Retirer la sélection et nettoyer le bouton précédent
108+
if(prevBtn){
109+
prevBtn.classList.remove('selected', 'instance-fade-out')
110+
}
111+
112+
// Appliquer la sélection au nouveau bouton avec animation d'entrée
111113
if(nextBtn){
112-
nextBtn.classList.add('selected')
114+
nextBtn.classList.add('selected', 'instance-fade-in')
113115
const img = nextBtn.querySelector('img')
114116
if(img){
117+
img.style.transition = 'border-color 0.3s ease-out'
115118
img.classList.remove('border-white/20')
116119
img.classList.add('border-[#F8BA59]')
117120
}
118-
// Nettoyage des classes d'animation existantes
119-
try {
120-
const labelNext = nextBtn.querySelector('.font-semibold.text-xl.leading-tight')
121-
if(labelNext) labelNext.classList.remove('label-slide-out','label-slide-in')
122-
} catch (e) {}
123-
nextBtn.classList.remove('instance-btn-exit', 'instance-btn-enter')
121+
122+
// Nettoyer la classe d'animation après qu'elle soit terminée
123+
setTimeout(() => {
124+
nextBtn.classList.remove('instance-fade-in')
125+
}, 300)
124126
}
125127
} catch (e) {
126128
console.debug('[Landing] animateButtonSwap error', e)
127129
}
128130
}
129131

130132
/**
131-
* Fonction de changement de texte instantanée (optimisée pour performance)
133+
* Fonction de changement de texte avec animation fade
132134
*/
133135
function animateTextSwap(el, newHTML, opts = {}){
134136
if(!el) return Promise.resolve()
135137

136-
// Mode instantané - change directement le contenu
137-
try {
138-
el.innerHTML = newHTML
139-
} catch (e) {
140-
console.debug('[Landing] animateTextSwap error', e)
141-
}
142-
143-
return Promise.resolve()
138+
return new Promise(resolve => {
139+
// Appliquer l'animation de sortie via classe CSS
140+
el.classList.add('text-fade-out')
141+
142+
setTimeout(() => {
143+
// Changer le contenu
144+
el.innerHTML = newHTML
145+
146+
// Retirer la classe de sortie et appliquer l'entrée
147+
el.classList.remove('text-fade-out')
148+
el.classList.add('text-fade-in')
149+
150+
// Nettoyer après l'animation
151+
setTimeout(() => {
152+
el.classList.remove('text-fade-in')
153+
resolve()
154+
}, 300)
155+
}, 200)
156+
})
144157
}
145158

146159
/**
@@ -193,7 +206,10 @@ function updateInstanceUI() {
193206

194207
if (count > 0) {
195208
// Au moins une instance tourne - afficher les contrôles
196-
if (launchBtn) launchBtn.classList.add('hidden')
209+
if (launchBtn) {
210+
launchBtn.classList.add('hidden', 'btn-hidden')
211+
launchBtn.style.display = 'none'
212+
}
197213
if (runningControls) runningControls.classList.add('visible')
198214
if (instanceCounter) instanceCounter.classList.add('visible')
199215

@@ -219,7 +235,8 @@ function updateInstanceUI() {
219235
} else {
220236
// Aucune instance - afficher le bouton Lancer
221237
if (launchBtn) {
222-
launchBtn.classList.remove('hidden')
238+
launchBtn.classList.remove('hidden', 'btn-hidden')
239+
launchBtn.style.display = ''
223240
launchBtn.disabled = false
224241
}
225242
if (runningControls) runningControls.classList.remove('visible')
@@ -254,9 +271,28 @@ function updateLaunchUIForServer(serverId){
254271
// Ajouter cette instance au tracker si pas déjà présente
255272
addRunningInstance(serverId, state.pid)
256273

257-
// Cacher le statut de téléchargement
258-
if (launchStatus) {
259-
launchStatus.classList.add('hidden');
274+
// Afficher le bouton Stop via LaunchUI
275+
if (window.LaunchUI) {
276+
console.log('[Landing] Calling LaunchUI.showRunning()');
277+
window.LaunchUI.showRunning();
278+
} else {
279+
// Fallback direct si LaunchUI n'est pas disponible
280+
console.log('[Landing] LaunchUI not available, using direct DOM');
281+
const launchBtn = document.getElementById('launch_button');
282+
const runningControls = document.getElementById('running_controls');
283+
const launchStatus = document.getElementById('launch_status');
284+
285+
if (launchBtn) {
286+
launchBtn.classList.add('hidden', 'btn-hidden');
287+
launchBtn.style.display = 'none';
288+
}
289+
if (runningControls) {
290+
runningControls.classList.add('visible');
291+
}
292+
if (launchStatus) {
293+
launchStatus.classList.add('hidden');
294+
launchStatus.style.display = 'none';
295+
}
260296
}
261297

262298
} else if(state && state.starting){
@@ -316,7 +352,15 @@ function toggleLaunchArea(loading){
316352
// Mode chargement déjà géré par showDownloading
317353
return;
318354
} else {
319-
window.LaunchUI.showReady();
355+
// Vérifier si des instances sont en cours d'exécution
356+
const runningCount = Object.values(instanceStateMap || {}).filter(s => s.started).length;
357+
console.log('[Landing] toggleLaunchArea - running instances:', runningCount);
358+
if (runningCount > 0) {
359+
// Des instances tournent, afficher les contrôles running
360+
window.LaunchUI.showRunning();
361+
} else {
362+
window.LaunchUI.showReady();
363+
}
320364
return;
321365
}
322366
}
@@ -844,33 +888,51 @@ async function updateSelectedServer(serv, instant = true){
844888
ConfigManager.setSelectedServer(serv != null ? serv.rawServer.id : null)
845889
ConfigManager.save()
846890

847-
// Update server info in the new UI. Force instant updates for performance
891+
// Update server info in the new UI with animations
848892
const serverTitle = document.querySelector('.server-title')
849893
const serverDesc = document.querySelector('.server-desc')
850894
const serverVersion = document.querySelector('.server-version')
851895
const serverLoader = document.querySelector('.server-loader')
852896
const serverStatusName = document.querySelector('.server-status-name')
853897
const playInstance = document.querySelector('.play-instance')
854898

855-
// Always use instant updates for better performance
899+
// Animate the content change
856900
try {
857901
if (serv != null) {
858902
const titleHtml = DOMPurify.sanitize(serv.rawServer.name || '')
859903
const descHtml = DOMPurify.sanitize(serv.rawServer.description || '')
860-
if (serverTitle) serverTitle.innerHTML = titleHtml
861-
if (serverDesc) serverDesc.innerHTML = descHtml
862-
if (serverVersion) serverVersion.textContent = serv.rawServer.minecraftVersion || '--'
863-
if (serverLoader) serverLoader.textContent = serv.rawServer.loader || '--'
904+
905+
// Animate title and description
906+
animateTextSwap(serverTitle, titleHtml)
907+
animateTextSwap(serverDesc, descHtml)
908+
909+
// Update other fields with simple fade
910+
if (serverVersion) {
911+
serverVersion.style.transition = 'opacity 0.2s ease-out'
912+
serverVersion.style.opacity = '0'
913+
setTimeout(() => {
914+
serverVersion.textContent = serv.rawServer.minecraftVersion || '--'
915+
serverVersion.style.opacity = '1'
916+
}, 150)
917+
}
918+
if (serverLoader) {
919+
serverLoader.style.transition = 'opacity 0.2s ease-out'
920+
serverLoader.style.opacity = '0'
921+
setTimeout(() => {
922+
serverLoader.textContent = serv.rawServer.loader || '--'
923+
serverLoader.style.opacity = '1'
924+
}, 150)
925+
}
864926
if (serverStatusName) serverStatusName.textContent = serv.rawServer.name
865927
} else {
866-
if (serverTitle) serverTitle.innerHTML = 'Veuillez sélectionner une instance'
867-
if (serverDesc) serverDesc.innerHTML = 'Aucune instance sélectionnée.<br>Choisissez une instance pour voir ses informations.'
928+
animateTextSwap(serverTitle, 'Veuillez sélectionner une instance')
929+
animateTextSwap(serverDesc, 'Aucune instance sélectionnée.<br>Choisissez une instance pour voir ses informations.')
868930
if (serverVersion) serverVersion.textContent = '--'
869931
if (serverLoader) serverLoader.textContent = '--'
870932
if (serverStatusName) serverStatusName.textContent = 'Multigames-Studio.fr'
871933
}
872934
} catch (e) {
873-
console.debug('[Landing] instant updateSelectedServer failed', e)
935+
console.debug('[Landing] animated updateSelectedServer failed', e)
874936
}
875937

876938
// Update server technical info (mods count, RAM allocation)
@@ -984,9 +1046,10 @@ const refreshMojangStatuses = async function(){
9841046

9851047
const mojangEssEl = document.getElementById('mojangStatusEssentialContainer')
9861048
const mojangNonEssEl = document.getElementById('mojangStatusNonEssentialContainer')
1049+
const mojangStatusIcon = document.getElementById('mojang_status_icon')
9871050
if (mojangEssEl) mojangEssEl.innerHTML = DOMPurify.sanitize(tooltipEssentialHTML)
9881051
if (mojangNonEssEl) mojangNonEssEl.innerHTML = DOMPurify.sanitize(tooltipNonEssentialHTML)
989-
document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status)
1052+
if (mojangStatusIcon) mojangStatusIcon.style.color = MojangRestAPI.statusToHex(status)
9901053
}
9911054

9921055
const refreshServerStatus = async (fade = false) => {
@@ -3471,6 +3534,53 @@ window.onInstanceStateChanged = function(payload){
34713534
instanceStateMap[serverId].starting = !!payload.starting
34723535
instanceStateMap[serverId].timestamp = Date.now()
34733536

3537+
// === GESTION UI DIRECTE ===
3538+
if (payload.started === true) {
3539+
console.log('[Landing] Game started - showing stop button');
3540+
// Appel direct à LaunchUI
3541+
if (window.LaunchUI && typeof window.LaunchUI.showRunning === 'function') {
3542+
window.LaunchUI.showRunning();
3543+
} else {
3544+
// Fallback DOM direct
3545+
const launchBtn = document.getElementById('launch_button');
3546+
const runningControls = document.getElementById('running_controls');
3547+
const launchStatus = document.getElementById('launch_status');
3548+
3549+
if (launchBtn) {
3550+
launchBtn.classList.add('hidden', 'btn-hidden');
3551+
launchBtn.style.display = 'none';
3552+
}
3553+
if (runningControls) {
3554+
runningControls.classList.add('visible');
3555+
}
3556+
if (launchStatus) {
3557+
launchStatus.classList.add('hidden');
3558+
}
3559+
}
3560+
} else if (payload.starting === true) {
3561+
console.log('[Landing] Game starting - showing loading');
3562+
if (window.LaunchUI && typeof window.LaunchUI.showDownloading === 'function') {
3563+
window.LaunchUI.showDownloading('Démarrage...', 0);
3564+
}
3565+
} else if (payload.started === false) {
3566+
console.log('[Landing] Game stopped - showing launch button');
3567+
if (window.LaunchUI && typeof window.LaunchUI.showReady === 'function') {
3568+
window.LaunchUI.showReady();
3569+
} else {
3570+
// Fallback DOM direct
3571+
const launchBtn = document.getElementById('launch_button');
3572+
const runningControls = document.getElementById('running_controls');
3573+
3574+
if (launchBtn) {
3575+
launchBtn.classList.remove('hidden', 'btn-hidden');
3576+
launchBtn.style.display = '';
3577+
}
3578+
if (runningControls) {
3579+
runningControls.classList.remove('visible');
3580+
}
3581+
}
3582+
}
3583+
34743584
// Clear progress when game stops, but only if it was previously marked started
34753585
if (payload.started === false && prevStarted === true) {
34763586
setTimeout(() => {

0 commit comments

Comments
 (0)