refactor: improve type definitions and enhance state management in profile and daily components

This commit is contained in:
2026-03-14 16:31:16 +01:00
parent 4e95abf09f
commit e5a21cb0af
5 changed files with 139 additions and 76 deletions

View File

@@ -1,23 +1,96 @@
<script lang="ts">
import { onMount } from 'svelte';
import { onDestroy, onMount } from 'svelte';
import YesterdayCharacter from '$lib/components/YesterdayCharacter.svelte';
import HintsPanel from '$lib/components/HintsPanel.svelte';
import CharacterSearchInput from '$lib/components/CharacterSearchInput.svelte';
import GuessHistoryTable from '$lib/components/GuessHistoryTable.svelte';
import WinPanel from '$lib/components/WinPanel.svelte';
import type { CharacterWithRelations } from '$lib/server/daily-character.js';
export let data;
let selectedCharacters: any[] = [];
let selectedCharacters: CharacterWithRelations[] = [];
let isLoaded = false;
let isGeckoMoriaWin = false;
let wasOriginAvailable = false;
let wasFruitAvailable = false;
let wasAffiliationAvailable = false;
let showOriginUnlock = false;
let showFruitUnlock = false;
let showAffiliationUnlock = false;
let originUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
let fruitUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
let affiliationUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
function clearUnlockTimeout(timeout: ReturnType<typeof setTimeout> | null) {
if (timeout) {
clearTimeout(timeout);
}
}
function pulseUnlock(type: 'origin' | 'fruit' | 'affiliation') {
if (type === 'origin') {
clearUnlockTimeout(originUnlockTimeout);
showOriginUnlock = true;
originUnlockTimeout = setTimeout(() => {
showOriginUnlock = false;
originUnlockTimeout = null;
}, 600);
return;
}
if (type === 'fruit') {
clearUnlockTimeout(fruitUnlockTimeout);
showFruitUnlock = true;
fruitUnlockTimeout = setTimeout(() => {
showFruitUnlock = false;
fruitUnlockTimeout = null;
}, 600);
return;
}
clearUnlockTimeout(affiliationUnlockTimeout);
showAffiliationUnlock = true;
affiliationUnlockTimeout = setTimeout(() => {
showAffiliationUnlock = false;
affiliationUnlockTimeout = null;
}, 600);
}
function syncHintAvailability(previousGuessCount: number, nextGuessCount: number, animateUnlocks = false) {
const nextOriginAvailable = nextGuessCount >= 5;
const nextFruitAvailable = nextGuessCount >= 10;
const nextAffiliationAvailable = nextGuessCount >= 15;
if (animateUnlocks && nextOriginAvailable && previousGuessCount < 5) {
pulseUnlock('origin');
}
if (animateUnlocks && nextFruitAvailable && previousGuessCount < 10) {
pulseUnlock('fruit');
}
if (animateUnlocks && nextAffiliationAvailable && previousGuessCount < 15) {
pulseUnlock('affiliation');
}
if (!nextOriginAvailable) {
showOriginUnlock = false;
clearUnlockTimeout(originUnlockTimeout);
originUnlockTimeout = null;
}
if (!nextFruitAvailable) {
showFruitUnlock = false;
clearUnlockTimeout(fruitUnlockTimeout);
fruitUnlockTimeout = null;
}
if (!nextAffiliationAvailable) {
showAffiliationUnlock = false;
clearUnlockTimeout(affiliationUnlockTimeout);
affiliationUnlockTimeout = null;
}
}
// Load from localStorage on mount
onMount(() => {
@@ -37,8 +110,8 @@
// Reconstruct character objects from IDs
if (Array.isArray(storedIds)) {
selectedCharacters = storedIds
.map((id: string) => data.characters.find((c: any) => c.id === id))
.filter((c: any) => c !== undefined);
.map((id: string) => data.characters.find((c: CharacterWithRelations) => c.id === id))
.filter((c: CharacterWithRelations | undefined): c is CharacterWithRelations => !!c);
}
} catch (e) {
console.error('Failed to parse stored history', e);
@@ -50,10 +123,18 @@
if (dailyCurrentCharacterId) {
localStorage.setItem('dailyCurrentCharacterId', dailyCurrentCharacterId);
}
syncHintAvailability(0, selectedCharacters.length);
isLoaded = true;
});
onDestroy(() => {
clearUnlockTimeout(originUnlockTimeout);
clearUnlockTimeout(fruitUnlockTimeout);
clearUnlockTimeout(affiliationUnlockTimeout);
});
// Save to localStorage whenever selectedCharacters changes (only store IDs)
$: if (isLoaded && selectedCharacters) {
const ids = selectedCharacters.map(char => char.id);
@@ -66,39 +147,15 @@
$: columnVisibility = data.columnVisibility || {};
$: hasWon = selectedCharacters.some(char => char.id === dailyCharacter.id);
// Hint availability tracking for unlock animations
$: isOriginAvailable = selectedCharacters.length >= 5;
$: isFruitAvailable = selectedCharacters.length >= 10;
$: isAffiliationAvailable = selectedCharacters.length >= 15;
// Track hint unlocks
$: if (isLoaded) {
if (isOriginAvailable && !wasOriginAvailable) {
showOriginUnlock = true;
setTimeout(() => showOriginUnlock = false, 600);
}
wasOriginAvailable = isOriginAvailable;
if (isFruitAvailable && !wasFruitAvailable) {
showFruitUnlock = true;
setTimeout(() => showFruitUnlock = false, 600);
}
wasFruitAvailable = isFruitAvailable;
if (isAffiliationAvailable && !wasAffiliationAvailable) {
showAffiliationUnlock = true;
setTimeout(() => showAffiliationUnlock = false, 600);
}
wasAffiliationAvailable = isAffiliationAvailable;
}
function handleCharacterSelect(event: CustomEvent) {
const character = event.detail;
selectCharacter(character);
}
function selectCharacter(character: any) {
function selectCharacter(character: CharacterWithRelations) {
const previousGuessCount = selectedCharacters.length;
selectedCharacters = [character, ...selectedCharacters];
syncHintAvailability(previousGuessCount, selectedCharacters.length, isLoaded);
// Check if player won
if (character.id === dailyCharacter.id) {
@@ -122,7 +179,9 @@
}
function resetHistory() {
const previousGuessCount = selectedCharacters.length;
selectedCharacters = [];
syncHintAvailability(previousGuessCount, 0);
localStorage.removeItem('dailyCharacterHistory');
}
</script>
@@ -198,7 +257,7 @@
<main
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100 {isGeckoMoriaWin ? 'moria-screen-chaos' : ''}"
>
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
<div class="absolute inset-0 bg-linear-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-8 sm:py-10">
@@ -257,7 +316,7 @@
<section class="mt-6 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100 text-center">Tes amis aujourd'hui</p>
<div class="mt-4 space-y-2">
{#each data.friendsTodayResults as friendResult}
{#each data.friendsTodayResults as friendResult (friendResult.userId)}
<div class="flex items-center justify-between rounded-lg border border-white/10 bg-slate-950/50 px-4 py-2">
<div class="flex items-center gap-3">
{#if friendResult.image}