Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
8acb6dc246
|
|||
|
f1e915c379
|
@@ -32,6 +32,7 @@ const progressStats = (game: Game) => {
|
||||
total,
|
||||
speed,
|
||||
eta: etaSeconds,
|
||||
activePeerCount: progress?.active_peer_count ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -52,6 +53,8 @@ export const DownloadProgress = ({ game, size = 'md', full = false, onCancel }:
|
||||
};
|
||||
|
||||
if (size === 'lg') {
|
||||
const peerUnit = stats.activePeerCount === 1 ? 'peer' : 'peers';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
@@ -75,6 +78,20 @@ export const DownloadProgress = ({ game, size = 'md', full = false, onCancel }:
|
||||
</span>
|
||||
<span className="dl-sep">·</span>
|
||||
<span className="dl-speed">{formatDownloadSpeed(stats.speed)}</span>
|
||||
{stats.activePeerCount > 0 && (
|
||||
<>
|
||||
<span className="dl-sep dl-sep-peers">·</span>
|
||||
<span
|
||||
className="dl-peers"
|
||||
title={`Downloading from ${stats.activePeerCount} ${peerUnit} on the LAN`}
|
||||
>
|
||||
<Icon.users />
|
||||
<span>
|
||||
from <strong>{stats.activePeerCount}</strong> {peerUnit}
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span className="dl-sep dl-sep-eta">·</span>
|
||||
<span className="dl-eta">{formatDownloadEta(stats.eta)} left</span>
|
||||
</div>
|
||||
|
||||
@@ -1045,6 +1045,21 @@
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dl-lg-secondary .dl-peers {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: var(--t-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dl-lg-secondary .dl-peers strong {
|
||||
color: var(--t-1);
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.dl-lg-secondary .dl-peers svg {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.dl-lg-secondary .dl-eta {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
@@ -1060,6 +1075,12 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@container (max-width: 300px) {
|
||||
.dl-lg-secondary .dl-peers,
|
||||
.dl-lg-secondary .dl-sep-peers {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.dl-lg-pct {
|
||||
grid-area: pct;
|
||||
color: var(--t-1);
|
||||
|
||||
+8
-5
@@ -251,20 +251,22 @@ grid-template-areas:
|
||||
```
|
||||
|
||||
- **Primary row** (`.dl-lg-primary`, top-left) — pulse dot + the uppercase live label `DOWNLOADING` in `color-mix(in srgb, var(--accent) 80%, white)`, 13px / 600, `letter-spacing: 0.02em`. This is the only place the word "Downloading" appears in the component.
|
||||
- **Secondary row** (`.dl-lg-secondary`, bottom-left) — the live stats. 12px, three groups separated by `·` (0.45 opacity):
|
||||
- **Secondary row** (`.dl-lg-secondary`, bottom-left) — the live stats. 12px, four groups separated by `·` (0.45 opacity):
|
||||
1. `<strong>11.4 GB</strong> / 35 GB` (`var(--t-1)` strong + `var(--t-2)` rest)
|
||||
2. `47.6 MB/s` (`var(--t-1)`)
|
||||
3. `8 min left` (`var(--t-2)`)
|
||||
3. `[users-icon] from <strong>5</strong> peers` — `.dl-peers`, inline-flex with 5px gap, icon at 0.7 opacity, count bold + tabular-nums in `var(--t-1)`, rest in `var(--t-2)`. Singular/plural switches on `peers === 1`. Hidden entirely when `game.peers` is falsy. Communicates that this is a LAN swarm transfer, not a single-source pull.
|
||||
4. `8 min left` (`var(--t-2)`)
|
||||
- **pct column** — large percentage, 20px / 700, `letter-spacing: -0.01em`, `var(--t-1)`. `%` glyph at 12px / 600 / 0.55 opacity.
|
||||
- **cancel column** — 28×28 square, `1px solid var(--bd-2)`, `border-radius: 6px`, X icon. Hover: bg `rgba(239,68,68,0.12)`, border `rgba(239,68,68,0.40)`, text `#fca5a5`. Cancelling reverts the game to its prior state (`local` if any data was kept, `none` otherwise) — dev decides the underlying behavior.
|
||||
|
||||
**Graceful degradation in narrow modals:**
|
||||
|
||||
```css
|
||||
@container (max-width: 380px) { .dl-lg-secondary .dl-eta, .dl-lg-secondary .dl-sep-eta { display: none; } }
|
||||
@container (max-width: 380px) { .dl-lg-secondary .dl-eta, .dl-lg-secondary .dl-sep-eta { display: none; } }
|
||||
@container (max-width: 300px) { .dl-lg-secondary .dl-peers, .dl-lg-secondary .dl-sep-peers { display: none; } }
|
||||
```
|
||||
|
||||
The ETA drops first if the modal narrows; bytes + speed stay (they're the actionable numbers). The pct/cancel column never collapses.
|
||||
ETA drops first, then peers; bytes + speed always stay (they're the actionable numbers). The pct/cancel column never collapses.
|
||||
|
||||
### Number formatting
|
||||
|
||||
@@ -287,10 +289,11 @@ type Game = {
|
||||
state: 'installed' | 'local' | 'downloading' | 'none';
|
||||
progress?: number; // 0–1, only when state === 'downloading'
|
||||
speed?: number; // current throughput in MB/s
|
||||
peers?: number; // number of LAN peers currently seeding
|
||||
};
|
||||
```
|
||||
|
||||
In the real app, `progress` and `speed` come from the download worker (Tauri command emitting events). The mock's `useLiveDownload(game)` hook (in `components.jsx`) is just a placeholder — 600ms `setInterval` advancing `progress` proportional to `speed`, with `speed` smoothed via a low-pass filter and small random drift so the number doesn't look fake. Replace with a `useEffect` that subscribes to your real progress events; the rendering layer needs nothing else.
|
||||
In the real app, `progress`, `speed`, and `peers` come from the download worker (Tauri command emitting events). The mock's `useLiveDownload(game)` hook (in `components.jsx`) is just a placeholder — 600ms `setInterval` advancing `progress` proportional to `speed`, with `speed` smoothed via a low-pass filter and small random drift so the number doesn't look fake. `peers` is read straight off the game object (static in the mock); in production, push updates as peers join/leave the swarm — the `.dl-peers` chip re-renders silently. Replace the hook with a `useEffect` that subscribes to your real progress events; the rendering layer needs nothing else.
|
||||
|
||||
Filter changes:
|
||||
- `Local` filter includes `installed` + `local` + `downloading` (in-flight downloads belong on the Local tab — you're managing them).
|
||||
|
||||
@@ -159,6 +159,15 @@ function DownloadProgress({ game, accent, size = 'md', full = false }) {
|
||||
</span>
|
||||
<span className="dl-sep">·</span>
|
||||
<span className="dl-speed">{fmtSpeed(speed)}</span>
|
||||
{game.peers > 0 && (
|
||||
<React.Fragment>
|
||||
<span className="dl-sep dl-sep-peers">·</span>
|
||||
<span className="dl-peers" title={`Downloading from ${game.peers} ${game.peers === 1 ? 'peer' : 'peers'} on the LAN`}>
|
||||
<Icon.users/>
|
||||
<span>from <strong>{game.peers}</strong> {game.peers === 1 ? 'peer' : 'peers'}</span>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<span className="dl-sep dl-sep-eta">·</span>
|
||||
<span className="dl-eta">{fmtEta(etaSec)} left</span>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ const GAMES = [
|
||||
{
|
||||
id: 'avp', title: 'Aliens vs. Predator', size: 35.0, version: '2019.10.01',
|
||||
desc: "Three campaigns, three nightmares. Be the alien stalking the dark, the predator hunting both, or the marine just trying to make it home with a working flashlight.",
|
||||
state: 'downloading', progress: 0.32, speed: 49.4, players: '2–16', tags: ['FPS', 'Horror', 'Multiplayer'],
|
||||
state: 'downloading', progress: 0.32, speed: 49.4, peers: 5, players: '2–16', tags: ['FPS', 'Horror', 'Multiplayer'],
|
||||
cover: { c1: '#064e3b', c2: '#020617', accent: '#34d399', mood: 'dark' },
|
||||
},
|
||||
{
|
||||
@@ -120,7 +120,7 @@ const GAMES = [
|
||||
{
|
||||
id: 'quake3', title: 'Quake III Arena', size: 0.5, version: '2010.08.15',
|
||||
desc: "Arena FPS in its most distilled form. Rocket jumps, rail-gun duels, and a netcode that's still the benchmark.",
|
||||
state: 'downloading', progress: 0.71, speed: 12.8, players: '2–16', tags: ['FPS', 'Arena', 'LAN'],
|
||||
state: 'downloading', progress: 0.71, speed: 12.8, peers: 3, players: '2–16', tags: ['FPS', 'Arena', 'LAN'],
|
||||
cover: { c1: '#7f1d1d', c2: '#0a0a0a', accent: '#fbbf24', mood: 'dark' },
|
||||
},
|
||||
{
|
||||
|
||||
@@ -784,6 +784,12 @@
|
||||
}
|
||||
.dl-lg-secondary .dl-bytes .dl-of { color: var(--t-2); font-weight: 500; }
|
||||
.dl-lg-secondary .dl-speed { color: var(--t-1); font-weight: 600; white-space: nowrap; }
|
||||
.dl-lg-secondary .dl-peers {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
color: var(--t-2); white-space: nowrap;
|
||||
}
|
||||
.dl-lg-secondary .dl-peers strong { color: var(--t-1); font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.dl-lg-secondary .dl-peers svg { opacity: 0.7; }
|
||||
.dl-lg-secondary .dl-eta { white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
||||
.dl-sep { opacity: 0.45; }
|
||||
|
||||
@@ -792,6 +798,11 @@
|
||||
.dl-lg-secondary .dl-eta,
|
||||
.dl-lg-secondary .dl-sep-eta { display: none; }
|
||||
}
|
||||
/* Even narrower: drop peers too, keep size + speed */
|
||||
@container (max-width: 300px) {
|
||||
.dl-lg-secondary .dl-peers,
|
||||
.dl-lg-secondary .dl-sep-peers { display: none; }
|
||||
}
|
||||
|
||||
.dl-lg-pct {
|
||||
grid-area: pct;
|
||||
|
||||
Reference in New Issue
Block a user