Merge pull request #150 from Pandipipas/cache-logos-from-http-server

Use configurable assets base URL for game logos and construct remote asset paths
This commit is contained in:
Pandipipas
2026-03-03 20:59:20 +01:00
committed by GitHub
4 changed files with 57 additions and 10 deletions
+31 -1
View File
@@ -49,14 +49,45 @@ La descarga de assets usa **únicamente HTTP**. Debes configurar un servidor pro
```text ```text
games/ games/
street-fighter-6/ street-fighter-6/
street-fighter-6.png
manifest.json manifest.json
fighting-characters.json fighting-characters.json
characters/... characters/...
tekken-8/ tekken-8/
tekken-8.png
manifest.json manifest.json
... ...
``` ```
## Logos en servidor HTTP (sin logos locales en el bundle)
La vista de "Game Assets" carga los logos directamente desde:
```text
{assetsBaseUrl}/games/{repoFolder}/{logoFile}
```
Ejemplos:
- `http://TU_SERVIDOR/games/street-fighter-6/street-fighter-6.png`
- `http://TU_SERVIDOR/games/tekken-8/tekken-8.png`
### Cómo guardarlos en la carpeta HTTP
1. Crea la carpeta del juego en tu web root (si no existe).
2. Copia el logo con el nombre esperado (`logoFile` de `src/shared/fighting-games.ts`).
3. Verifica desde navegador o `curl` que responde `200`.
Ejemplo rápido en Linux (Nginx/Apache):
```bash
sudo mkdir -p /var/www/assets/games/street-fighter-6
sudo cp ./street-fighter-6.png /var/www/assets/games/street-fighter-6/street-fighter-6.png
curl -I http://TU_SERVIDOR/games/street-fighter-6/street-fighter-6.png
```
Opcional (recomendado): añade cache HTTP (`Cache-Control`, `ETag`) en tu servidor para que el navegador no los vuelva a descargar en cada visita.
3. Cada `manifest.json` debe ser un array con rutas relativas, o con objetos `{ "path", "size", "url" }`. 3. Cada `manifest.json` debe ser un array con rutas relativas, o con objetos `{ "path", "size", "url" }`.
Ejemplo mínimo: Ejemplo mínimo:
@@ -67,4 +98,3 @@ Ejemplo mínimo:
"characters/ryu.png" "characters/ryu.png"
] ]
``` ```
@@ -28,6 +28,7 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
const loadingByTitle = ref<Record<string, boolean>>({}); const loadingByTitle = ref<Record<string, boolean>>({});
const removingByTitle = ref<Record<string, boolean>>({}); const removingByTitle = ref<Record<string, boolean>>({});
const progressByTitle = ref<Record<string, number>>({}); const progressByTitle = ref<Record<string, number>>({});
const assetsBaseUrl = ref('http://localhost');
if (!progressListenerAttached) { if (!progressListenerAttached) {
nodecg.listenFor('scoreko-assets:downloadProgress', (payload: unknown) => { nodecg.listenFor('scoreko-assets:downloadProgress', (payload: unknown) => {
@@ -63,6 +64,10 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
const refreshInstalledGames = async () => { const refreshInstalledGames = async () => {
const response = await sendNodecgMessage<string[]>('scoreko-assets:listInstalled'); const response = await sendNodecgMessage<string[]>('scoreko-assets:listInstalled');
installedGames.value = Array.isArray(response) ? response : []; installedGames.value = Array.isArray(response) ? response : [];
const configResponse = await sendNodecgMessage<{ assetsBaseUrl?: string }>('scoreko-assets:getAssetsBaseUrl');
assetsBaseUrl.value = typeof configResponse?.assetsBaseUrl === 'string' && configResponse.assetsBaseUrl.trim()
? configResponse.assetsBaseUrl
: 'http://localhost';
await refreshCharacterNamesByGame(); await refreshCharacterNamesByGame();
return installedGames.value; return installedGames.value;
}; };
@@ -124,6 +129,7 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
loadingByTitle, loadingByTitle,
removingByTitle, removingByTitle,
progressByTitle, progressByTitle,
assetsBaseUrl,
refreshInstalledGames, refreshInstalledGames,
refreshCharacterNamesByGame, refreshCharacterNamesByGame,
downloadGame, downloadGame,
@@ -8,13 +8,12 @@ const errorMessage = ref('');
const selectedGameSlug = ref<string | null>(null); const selectedGameSlug = ref<string | null>(null);
const search = ref(''); const search = ref('');
const gameLogoModules = import.meta.glob('/src/shared/game-logos/*.png', { const getGameLogoUrl = (repoFolder: string, logoFile: string) => {
eager: true, const cleanBaseUrl = gameAssetsStore.assetsBaseUrl.replace(/\/+$/, '');
import: 'default', const cleanRepoFolder = repoFolder.replace(/^\/+|\/+$/g, '');
query: '?url', const cleanLogoFile = logoFile.replace(/^\/+/, '');
}) as Record<string, string>; return `${cleanBaseUrl}/games/${cleanRepoFolder}/${cleanLogoFile}`;
};
const getGameLogoUrl = (logoFile: string) => gameLogoModules[`/src/shared/game-logos/${logoFile}`] ?? '';
const normalizedSearch = computed(() => search.value.trim().toLowerCase()); const normalizedSearch = computed(() => search.value.trim().toLowerCase());
const filteredGames = computed(() => { const filteredGames = computed(() => {
@@ -109,7 +108,7 @@ onMounted(async () => {
> >
<div class="logo-tile"> <div class="logo-tile">
<QImg <QImg
:src="getGameLogoUrl(game.logoFile)" :src="getGameLogoUrl(game.repoFolder, game.logoFile)"
fit="contain" fit="contain"
height="74px" height="74px"
/> />
@@ -168,7 +167,7 @@ onMounted(async () => {
style="min-width: 360px; max-width: 480px" style="min-width: 360px; max-width: 480px"
> >
<QImg <QImg
:src="getGameLogoUrl(selectedGame.logoFile)" :src="getGameLogoUrl(selectedGame.repoFolder, selectedGame.logoFile)"
fit="contain" fit="contain"
height="80px" height="80px"
class="q-mb-md" class="q-mb-md"
+12
View File
@@ -267,6 +267,18 @@ nodecg.listenFor('scoreko-assets:listCharactersByGame', async (_payload: unknown
} }
}); });
nodecg.listenFor('scoreko-assets:getAssetsBaseUrl', async (_payload: unknown, ack) => {
if (typeof ack !== 'function') {
return;
}
try {
ack(null, { assetsBaseUrl: getConfiguredAssetsBaseUrl() });
} catch (error) {
ack((error as Error).message);
}
});
nodecg.listenFor('scoreko-assets:downloadGame', async (payload: unknown, ack) => { nodecg.listenFor('scoreko-assets:downloadGame', async (payload: unknown, ack) => {
if (typeof ack !== 'function') { if (typeof ack !== 'function') {
return; return;