From 21e8ca6bd86b7a9e396e9d2e3865d23d81ab640c Mon Sep 17 00:00:00 2001 From: tonhowtf Date: Fri, 31 Jul 2026 11:51:45 -0300 Subject: [PATCH] feat(league): skeletons de carregamento e menos custo por comando no LCU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Skeleton.svelte novo (tokens do tema, prefers-reduced-motion) ligado ao perfil, histórico, detalhe de partida, análise e scouting — o histórico não mostra mais "nenhuma partida" enquanto ainda está carregando - um reqwest::Client estático em vez de construir um novo (TLS + pool) a cada request ao cliente - get_client não faz mais o round-trip de verificação quando o websocket já prova que o cliente está vivo - no Windows, um probe barato com tasklist (~50ms) roda antes do PowerShell+CIM (segundos), que só dispara se o LeagueClientUx existir - home: o fundo de cantos retos do painel do mascote vazava sobre os cantos arredondados do workspace; raio interno correspondente aplicado --- src-tauri/src/commands/league/locator.rs | 10 ++++++ src-tauri/src/commands/league/mod.rs | 14 ++++++-- src/components/league/AnalysisTab.svelte | 29 ++++++++++++++++ src/components/league/HistoryTab.svelte | 32 +++++++++++++++-- src/components/league/OverviewTab.svelte | 17 ++++++++- src/components/league/Skeleton.svelte | 44 ++++++++++++++++++++++++ src/lib/style/macos-shell.css | 2 ++ src/routes/league/+page.svelte | 31 ++++++++++++++++- 8 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 src/components/league/Skeleton.svelte diff --git a/src-tauri/src/commands/league/locator.rs b/src-tauri/src/commands/league/locator.rs index 4d910be9..e5a967be 100644 --- a/src-tauri/src/commands/league/locator.rs +++ b/src-tauri/src/commands/league/locator.rs @@ -179,6 +179,16 @@ pub fn default_lockfile_paths() -> Vec { async fn read_process_command_lines() -> Result { #[cfg(windows)] { + // tasklist costs ~50ms; the CIM query costs seconds and stutters the + // whole app, so it only runs once the cheap probe sees the process. + let probe = crate::core::process::command("tasklist") + .args(["/FI", "IMAGENAME eq LeagueClientUx.exe", "/NH"]) + .output() + .await + .map_err(|e| format!("failed to probe processes: {}", e))?; + if !String::from_utf8_lossy(&probe.stdout).contains("LeagueClientUx.exe") { + return Ok(String::new()); + } let output = crate::core::process::command("powershell") .args([ "-NoProfile", diff --git a/src-tauri/src/commands/league/mod.rs b/src-tauri/src/commands/league/mod.rs index 915a450c..95e58c19 100644 --- a/src-tauri/src/commands/league/mod.rs +++ b/src-tauri/src/commands/league/mod.rs @@ -53,7 +53,9 @@ async fn get_client() -> Result { // behind one network round trip, and a slow client stalls all of them. let cached = { CACHED_CLIENT.lock().await.clone() }; if let Some(client) = cached { - if lcu_reachable(&client).await { + // The websocket task already proves the client is alive; probing it + // again would add a round-trip to every single command. + if ws::is_connected() || lcu_reachable(&client).await { return Ok(client); } } @@ -65,13 +67,19 @@ async fn get_client() -> Result { Ok(discovered) } -fn http_client() -> Result { +static HTTP_CLIENT: Lazy> = Lazy::new(|| { reqwest::Client::builder() .danger_accept_invalid_certs(true) .timeout(std::time::Duration::from_secs(10)) .no_proxy() .build() - .map_err(|e| e.to_string()) + .ok() +}); + +fn http_client() -> Result { + HTTP_CLIENT + .clone() + .ok_or_else(|| "http client init failed".to_string()) } async fn lcu_reachable(client: &LcuClient) -> bool { diff --git a/src/components/league/AnalysisTab.svelte b/src/components/league/AnalysisTab.svelte index 7cb1c470..e86d7f4a 100644 --- a/src/components/league/AnalysisTab.svelte +++ b/src/components/league/AnalysisTab.svelte @@ -5,6 +5,7 @@ import { markedPlayers, winrateSquad } from "$lib/league-scouting"; import { availability, featureById, type Platform } from "./registry"; import { writeText } from "@tauri-apps/plugin-clipboard-manager"; + import Skeleton from "./Skeleton.svelte"; let { analysis, @@ -160,6 +161,17 @@ {/if} + {:else if analysisLoading} +
+
+

{$t("league.win_title")}

+
+ +
+ + +
+
{:else}

{analysisAvailability.available ? $t("league.win_unavailable") : $t(analysisAvailability.reasonKey)}

@@ -167,6 +179,23 @@
{/if} + {#if (phase === "ChampSelect" || phase === "InProgress") && scoutPlayers.length === 0 && scoutLoading} +
+
+

{$t("league.scout_title")}

+
+
+ {#each Array(4) as _, i (i)} +
+ + + +
+ {/each} +
+
+ {/if} + {#if (phase === "ChampSelect" || phase === "InProgress") && scoutPlayers.length > 0}
diff --git a/src/components/league/HistoryTab.svelte b/src/components/league/HistoryTab.svelte index cc10f6ee..f96a30f0 100644 --- a/src/components/league/HistoryTab.svelte +++ b/src/components/league/HistoryTab.svelte @@ -5,6 +5,7 @@ import { CDRAGON, assetUrl, type Champion } from "./shared"; import { filterByQueue, queuesInGames, summarise } from "$lib/league-history"; import { findTeam, teamBans, teamObjectives } from "$lib/league-match-detail"; + import Skeleton from "./Skeleton.svelte"; let { games, @@ -171,7 +172,21 @@

{$t("league.history_title")}

- {#if games.length === 0} + {#if loading && games.length === 0} +
+ {#each Array(4) as _, i (i)} +
+ +
+ + +
+ + +
+ {/each} +
+ {:else if games.length === 0}

{$t("league.history_empty")}

{:else} {#if availableQueues.length > 1} @@ -219,7 +234,20 @@ {#if expandedGame === game.gameId} {#if gameDetailLoading === game.gameId} -

+
+ {#each Array(2) as _, ti (ti)} +
+ + {#each Array(5) as _, si (si)} +
+ + + +
+ {/each} +
+ {/each} +
{:else if gameDetails[game.gameId]}
{#each scoreboardTeams(gameDetails[game.gameId]) as team (team.teamId)} diff --git a/src/components/league/OverviewTab.svelte b/src/components/league/OverviewTab.svelte index 9271b00c..aeaabfdc 100644 --- a/src/components/league/OverviewTab.svelte +++ b/src/components/league/OverviewTab.svelte @@ -2,9 +2,11 @@ import { invoke } from "@tauri-apps/api/core"; import { t } from "$lib/i18n"; import { CDRAGON, formatGameTime, type Champion, type RankedEntry, type LobbyQueue } from "./shared"; + import Skeleton from "./Skeleton.svelte"; let { summoner, + profileLoading = false, ranked, phase, champSelect, @@ -19,6 +21,7 @@ active, }: { summoner: any; + profileLoading?: boolean; ranked: Record; phase: string; champSelect: any; @@ -156,7 +159,19 @@ {#if active !== false} - {#if summoner} + {#if !summoner && profileLoading} +
+ +
+ + +
+
+ + +
+
+ {:else if summoner}
+ let { + w = "100%", + h = "14px", + round = "6px", + }: { + w?: string; + h?: string; + round?: string; + } = $props(); + + + + + diff --git a/src/lib/style/macos-shell.css b/src/lib/style/macos-shell.css index e5c7c610..1e24f193 100644 --- a/src/lib/style/macos-shell.css +++ b/src/lib/style/macos-shell.css @@ -515,6 +515,7 @@ align-items: flex-start; justify-content: center; background: color-mix(in srgb, var(--surface) 92%, var(--bg)); + border-radius: calc(var(--radius-md) - 1px) 0 0 calc(var(--radius-md) - 1px); overflow: visible; } @@ -547,6 +548,7 @@ border-bottom: 1px solid var(--border); padding: var(--space-3); justify-content: flex-start; + border-radius: calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px) 0 0; } .mac-inspector { diff --git a/src/routes/league/+page.svelte b/src/routes/league/+page.svelte index 2c5cee18..326ae247 100644 --- a/src/routes/league/+page.svelte +++ b/src/routes/league/+page.svelte @@ -33,6 +33,7 @@ let phase = $state(""); let games = $state([]); let loadingHistory = $state(false); + let profileLoading = $state(false); let liveTimer: ReturnType | null = null; let unlisteners: UnlistenFn[] = []; @@ -345,10 +346,12 @@ } async function loadProfile() { + profileLoading = true; try { summoner = await invoke("league_summoner"); } catch { summoner = null; + profileLoading = false; return; } try { @@ -356,6 +359,8 @@ ranked = stats?.queueMap ?? {}; } catch { ranked = {}; + } finally { + profileLoading = false; } loadHistory(); } @@ -497,7 +502,7 @@ timers, expanded games) survives switching, and the meta tab's auto-rune effect keeps working from any tab. -->
- +
@@ -1861,6 +1866,30 @@ border-color: var(--accent); } + .league-page :global(.game-row-skeleton) { + display: flex; + align-items: center; + gap: 12px; + padding: 9px 12px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: calc(var(--border-radius) - 2px); + } + + .league-page :global(.game-skeleton-info) { + display: flex; + flex-direction: column; + gap: 5px; + flex: 1; + } + + .league-page :global(.skeleton-lines) { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 10px; + } + .league-page :global(.champ-icon) { width: 34px; height: 34px;