From 2d7f7513ad75e210488b58d2c5e064d05bb28276 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 13 Nov 2025 19:38:21 +0100 Subject: [PATCH] peer count for all games --- crates/lanspread-compat/src/eti.rs | 1 + crates/lanspread-db/src/db.rs | 2 + crates/lanspread-peer/src/lib.rs | 19 ++++- crates/lanspread-tauri-deno-ts/src/App.css | 58 ++++++++++++++- crates/lanspread-tauri-deno-ts/src/App.tsx | 86 +++++++++++++++++----- 5 files changed, 145 insertions(+), 21 deletions(-) diff --git a/crates/lanspread-compat/src/eti.rs b/crates/lanspread-compat/src/eti.rs index 18d1f7a..00ea833 100644 --- a/crates/lanspread-compat/src/eti.rs +++ b/crates/lanspread-compat/src/eti.rs @@ -64,6 +64,7 @@ impl From for Game { installed: false, eti_game_version: None, local_version: None, + peer_count: 0, // ETI games start with 0 peers until peer system discovers them } } } diff --git a/crates/lanspread-db/src/db.rs b/crates/lanspread-db/src/db.rs index 8068039..d8f778d 100644 --- a/crates/lanspread-db/src/db.rs +++ b/crates/lanspread-db/src/db.rs @@ -60,6 +60,8 @@ pub struct Game { pub eti_game_version: Option, /// Local game version from version.ini (YYYYMMDD format) pub local_version: Option, + /// Number of peers that have this game available + pub peer_count: u32, } impl fmt::Debug for Game { diff --git a/crates/lanspread-peer/src/lib.rs b/crates/lanspread-peer/src/lib.rs index de04ccd..9590e25 100644 --- a/crates/lanspread-peer/src/lib.rs +++ b/crates/lanspread-peer/src/lib.rs @@ -204,6 +204,16 @@ impl PeerGameDB { #[must_use] pub fn get_all_games(&self) -> Vec { let mut aggregated: HashMap = HashMap::new(); + let mut peer_counts: HashMap = HashMap::new(); + + // Count peers per game + for peer in self.peers.values() { + for game_id in peer.games.keys() { + *peer_counts.entry(game_id.clone()).or_insert(0) += 1; + } + } + + // Aggregate games with peer counts for peer in self.peers.values() { for game in peer.games.values() { aggregated @@ -218,8 +228,14 @@ impl PeerGameDB { } else if existing.eti_game_version.is_none() { existing.eti_game_version.clone_from(&game.eti_game_version); } + // Update peer count + existing.peer_count = peer_counts[&game.id]; }) - .or_insert_with(|| game.clone()); + .or_insert_with(|| { + let mut game_clone = game.clone(); + game_clone.peer_count = peer_counts[&game.id]; + game_clone + }); } } @@ -1109,6 +1125,7 @@ async fn load_local_game_db(game_dir: &str) -> eyre::Result { installed: true, eti_game_version: version.clone(), local_version: version, + peer_count: 0, // Local games start with 0 peers }; games.push(game); } diff --git a/crates/lanspread-tauri-deno-ts/src/App.css b/crates/lanspread-tauri-deno-ts/src/App.css index ffce765..5146aa0 100644 --- a/crates/lanspread-tauri-deno-ts/src/App.css +++ b/crates/lanspread-tauri-deno-ts/src/App.css @@ -226,5 +226,61 @@ h1.align-center { } .item-info.error { - color: #ff6666; + color: #ff6666; +} + +.filter-container { + display: flex; + justify-content: center; + gap: 10px; + margin: 10px 0; +} + +.filter-button { + padding: 8px 16px; + background: linear-gradient(45deg, #09305a, #37529c); + color: #D5DBFE; + border: 1px solid transparent; + border-radius: 20px; + font-size: 14px; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 8px rgba(0, 191, 255, 0.2); +} + +.filter-button:hover { + background: linear-gradient(45deg, #09305a, #4866b9); + box-shadow: 0 8px 12px rgba(0, 191, 255, 0.4); + transform: translateY(-2px); +} + +.filter-button.active { + background: linear-gradient(45deg, #09305a, #4866b9); + border: 1px solid rgba(0, 191, 255, 0.6); + box-shadow: 0 8px 12px rgba(0, 191, 255, 0.4); +} + +.item-info { + display: flex; + justify-content: space-between; + align-items: center; + min-height: 18px; + margin: 8px 10px 16px; + font-size: 0.85em; + color: #8892b0; + text-align: left; +} + +.status-left { + flex: 1; + text-align: left; +} + +.status-right { + text-align: right; +} + +.peer-count { + font-weight: bold; + color: #4866b9; } diff --git a/crates/lanspread-tauri-deno-ts/src/App.tsx b/crates/lanspread-tauri-deno-ts/src/App.tsx index b706a57..048aedb 100644 --- a/crates/lanspread-tauri-deno-ts/src/App.tsx +++ b/crates/lanspread-tauri-deno-ts/src/App.tsx @@ -21,6 +21,8 @@ enum InstallStatus { type StatusLevel = 'info' | 'error'; +type GameFilter = 'all' | 'available' | 'installed'; + interface Game { id: string; name: string; @@ -33,15 +35,29 @@ interface Game { local_version?: string; status_message?: string; status_level?: StatusLevel; + peer_count: number; } const App = () => { const [gameItems, setGameItems] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [gameDir, setGameDir] = useState(''); + const [currentFilter, setCurrentFilter] = useState('all'); const checkingPeersTimeouts = useRef>>({}); - const filteredGames = gameItems.filter(item => + const getFilteredGames = (games: Game[], filter: GameFilter): Game[] => { + switch (filter) { + case 'available': + return games.filter(game => game.peer_count > 0); + case 'installed': + return games.filter(game => game.installed); + case 'all': + default: + return games; + } + }; + + const filteredAndSearchedGames = getFilteredGames(gameItems, currentFilter).filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()) ); @@ -211,6 +227,7 @@ const App = () => { install_status: installStatus, status_message: previous?.status_message, status_level: previous?.status_level, + peer_count: game.peer_count ?? 0, // Ensure peer_count is always set }; }); }); @@ -381,20 +398,42 @@ const App = () => {

SoftLAN Launcher

{gameDir ? ( -
-
-
- setSearchTerm(e.target.value)} - className="search-input" - /> +
+
+ + +
-
- - {gameDir} +
+
+
+ setSearchTerm(e.target.value)} + className="search-input" + /> +
+
+ + {gameDir} +
) : ( @@ -410,16 +449,16 @@ const App = () => {
- {gameDir && filteredGames.length === 0 && gameItems.length === 0 ? ( + {gameDir && filteredAndSearchedGames.length === 0 && gameItems.length === 0 ? (
Scanning for games in your directory...
- ) : gameDir && filteredGames.length === 0 && gameItems.length > 0 ? ( + ) : gameDir && filteredAndSearchedGames.length === 0 && gameItems.length > 0 ? (
- No games found matching your search. + No games found matching your search and filters.
) : null} - {filteredGames.map((item) => { + {filteredAndSearchedGames.map((item) => { const uint8Array = new Uint8Array(item.thumbnail); const binaryString = uint8Array.reduce((acc, byte) => acc + String.fromCharCode(byte), ''); const thumbnailUrl = `data:image/jpeg;base64,${btoa(binaryString)}`; @@ -454,7 +493,16 @@ const App = () => { : 'Play'}
- {item.status_message ?? ''} +
+ {item.status_message && item.peer_count === 0 && !item.installed ? item.status_message : ''} +
+
+ {item.peer_count > 0 && ( + + 👥 {item.peer_count} + + )} +
);