peer count for all games
This commit is contained in:
@@ -64,6 +64,7 @@ impl From<EtiGame> for Game {
|
|||||||
installed: false,
|
installed: false,
|
||||||
eti_game_version: None,
|
eti_game_version: None,
|
||||||
local_version: None,
|
local_version: None,
|
||||||
|
peer_count: 0, // ETI games start with 0 peers until peer system discovers them
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ pub struct Game {
|
|||||||
pub eti_game_version: Option<String>,
|
pub eti_game_version: Option<String>,
|
||||||
/// Local game version from version.ini (YYYYMMDD format)
|
/// Local game version from version.ini (YYYYMMDD format)
|
||||||
pub local_version: Option<String>,
|
pub local_version: Option<String>,
|
||||||
|
/// Number of peers that have this game available
|
||||||
|
pub peer_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Game {
|
impl fmt::Debug for Game {
|
||||||
|
|||||||
@@ -204,6 +204,16 @@ impl PeerGameDB {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_all_games(&self) -> Vec<Game> {
|
pub fn get_all_games(&self) -> Vec<Game> {
|
||||||
let mut aggregated: HashMap<String, Game> = HashMap::new();
|
let mut aggregated: HashMap<String, Game> = HashMap::new();
|
||||||
|
let mut peer_counts: HashMap<String, u32> = 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 peer in self.peers.values() {
|
||||||
for game in peer.games.values() {
|
for game in peer.games.values() {
|
||||||
aggregated
|
aggregated
|
||||||
@@ -218,8 +228,14 @@ impl PeerGameDB {
|
|||||||
} else if existing.eti_game_version.is_none() {
|
} else if existing.eti_game_version.is_none() {
|
||||||
existing.eti_game_version.clone_from(&game.eti_game_version);
|
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<GameDB> {
|
|||||||
installed: true,
|
installed: true,
|
||||||
eti_game_version: version.clone(),
|
eti_game_version: version.clone(),
|
||||||
local_version: version,
|
local_version: version,
|
||||||
|
peer_count: 0, // Local games start with 0 peers
|
||||||
};
|
};
|
||||||
games.push(game);
|
games.push(game);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,3 +228,59 @@ h1.align-center {
|
|||||||
.item-info.error {
|
.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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ enum InstallStatus {
|
|||||||
|
|
||||||
type StatusLevel = 'info' | 'error';
|
type StatusLevel = 'info' | 'error';
|
||||||
|
|
||||||
|
type GameFilter = 'all' | 'available' | 'installed';
|
||||||
|
|
||||||
interface Game {
|
interface Game {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -33,15 +35,29 @@ interface Game {
|
|||||||
local_version?: string;
|
local_version?: string;
|
||||||
status_message?: string;
|
status_message?: string;
|
||||||
status_level?: StatusLevel;
|
status_level?: StatusLevel;
|
||||||
|
peer_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [gameItems, setGameItems] = useState<Game[]>([]);
|
const [gameItems, setGameItems] = useState<Game[]>([]);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [gameDir, setGameDir] = useState('');
|
const [gameDir, setGameDir] = useState('');
|
||||||
|
const [currentFilter, setCurrentFilter] = useState<GameFilter>('all');
|
||||||
const checkingPeersTimeouts = useRef<Record<string, ReturnType<typeof setTimeout>>>({});
|
const checkingPeersTimeouts = useRef<Record<string, ReturnType<typeof setTimeout>>>({});
|
||||||
|
|
||||||
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())
|
item.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -211,6 +227,7 @@ const App = () => {
|
|||||||
install_status: installStatus,
|
install_status: installStatus,
|
||||||
status_message: previous?.status_message,
|
status_message: previous?.status_message,
|
||||||
status_level: previous?.status_level,
|
status_level: previous?.status_level,
|
||||||
|
peer_count: game.peer_count ?? 0, // Ensure peer_count is always set
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -381,6 +398,27 @@ const App = () => {
|
|||||||
<h1 className="align-center">SoftLAN Launcher</h1>
|
<h1 className="align-center">SoftLAN Launcher</h1>
|
||||||
<div className="main-header">
|
<div className="main-header">
|
||||||
{gameDir ? (
|
{gameDir ? (
|
||||||
|
<div>
|
||||||
|
<div className="filter-container">
|
||||||
|
<button
|
||||||
|
className={`filter-button ${currentFilter === 'all' ? 'active' : ''}`}
|
||||||
|
onClick={() => setCurrentFilter('all')}
|
||||||
|
>
|
||||||
|
All Games
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`filter-button ${currentFilter === 'available' ? 'active' : ''}`}
|
||||||
|
onClick={() => setCurrentFilter('available')}
|
||||||
|
>
|
||||||
|
Available
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`filter-button ${currentFilter === 'installed' ? 'active' : ''}`}
|
||||||
|
onClick={() => setCurrentFilter('installed')}
|
||||||
|
>
|
||||||
|
Installed
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="search-settings-wrapper">
|
<div className="search-settings-wrapper">
|
||||||
<div></div>
|
<div></div>
|
||||||
<div className="search-container">
|
<div className="search-container">
|
||||||
@@ -397,6 +435,7 @@ const App = () => {
|
|||||||
<span className="settings-text">{gameDir}</span>
|
<span className="settings-text">{gameDir}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="no-directory-container">
|
<div className="no-directory-container">
|
||||||
<div className="no-directory-message">
|
<div className="no-directory-message">
|
||||||
@@ -410,16 +449,16 @@ const App = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid-container">
|
<div className="grid-container">
|
||||||
{gameDir && filteredGames.length === 0 && gameItems.length === 0 ? (
|
{gameDir && filteredAndSearchedGames.length === 0 && gameItems.length === 0 ? (
|
||||||
<div className="no-games-message">
|
<div className="no-games-message">
|
||||||
Scanning for games in your directory...
|
Scanning for games in your directory...
|
||||||
</div>
|
</div>
|
||||||
) : gameDir && filteredGames.length === 0 && gameItems.length > 0 ? (
|
) : gameDir && filteredAndSearchedGames.length === 0 && gameItems.length > 0 ? (
|
||||||
<div className="no-games-message">
|
<div className="no-games-message">
|
||||||
No games found matching your search.
|
No games found matching your search and filters.
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{filteredGames.map((item) => {
|
{filteredAndSearchedGames.map((item) => {
|
||||||
const uint8Array = new Uint8Array(item.thumbnail);
|
const uint8Array = new Uint8Array(item.thumbnail);
|
||||||
const binaryString = uint8Array.reduce((acc, byte) => acc + String.fromCharCode(byte), '');
|
const binaryString = uint8Array.reduce((acc, byte) => acc + String.fromCharCode(byte), '');
|
||||||
const thumbnailUrl = `data:image/jpeg;base64,${btoa(binaryString)}`;
|
const thumbnailUrl = `data:image/jpeg;base64,${btoa(binaryString)}`;
|
||||||
@@ -454,7 +493,16 @@ const App = () => {
|
|||||||
: 'Play'}
|
: 'Play'}
|
||||||
</div>
|
</div>
|
||||||
<div className={`item-info${item.status_level ? ` ${item.status_level}` : ''}`}>
|
<div className={`item-info${item.status_level ? ` ${item.status_level}` : ''}`}>
|
||||||
{item.status_message ?? ''}
|
<div className="status-left">
|
||||||
|
{item.status_message && item.peer_count === 0 && !item.installed ? item.status_message : ''}
|
||||||
|
</div>
|
||||||
|
<div className="status-right">
|
||||||
|
{item.peer_count > 0 && (
|
||||||
|
<span className="peer-count">
|
||||||
|
👥 {item.peer_count}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user