peer count for all games

This commit is contained in:
2025-11-13 19:38:21 +01:00
parent d96d191c13
commit 2d7f7513ad
5 changed files with 145 additions and 21 deletions
+57 -1
View File
@@ -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;
}
+67 -19
View File
@@ -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<Game[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [gameDir, setGameDir] = useState('');
const [currentFilter, setCurrentFilter] = useState<GameFilter>('all');
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())
);
@@ -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 = () => {
<h1 className="align-center">SoftLAN Launcher</h1>
<div className="main-header">
{gameDir ? (
<div className="search-settings-wrapper">
<div></div>
<div className="search-container">
<input
type="text"
placeholder="Search games..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
<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="settings-container">
<button onClick={dialogGameDir} className="settings-button">Set Game Directory</button>
<span className="settings-text">{gameDir}</span>
<div className="search-settings-wrapper">
<div></div>
<div className="search-container">
<input
type="text"
placeholder="Search games..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
</div>
<div className="settings-container">
<button onClick={dialogGameDir} className="settings-button">Set Game Directory</button>
<span className="settings-text">{gameDir}</span>
</div>
</div>
</div>
) : (
@@ -410,16 +449,16 @@ const App = () => {
</div>
</div>
<div className="grid-container">
{gameDir && filteredGames.length === 0 && gameItems.length === 0 ? (
{gameDir && filteredAndSearchedGames.length === 0 && gameItems.length === 0 ? (
<div className="no-games-message">
Scanning for games in your directory...
</div>
) : gameDir && filteredGames.length === 0 && gameItems.length > 0 ? (
) : gameDir && filteredAndSearchedGames.length === 0 && gameItems.length > 0 ? (
<div className="no-games-message">
No games found matching your search.
No games found matching your search and filters.
</div>
) : 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'}
</div>
<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>
);