mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
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:
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user