From 3eedabce43038eb6faca92a2f38a52f9f653a570 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Tue, 18 Aug 2026 08:33:19 +0200 Subject: [PATCH] One /stores page for both stores, with a device chooser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Batocera store had /batocera to itself. A RetroArch store now serves the same cartridges on every other machine, and copying the page would have duplicated everything the two have in common — the framing, the platform table, the engine links — so they share one page and a Batocera/RetroArch chooser. Only the install command, the CLI and the uninstall line change with the tab. /batocera and /retroarch both redirect here with the matching tab preselected, so old links keep working and the name someone guesses after reading "RetroArch store" lands somewhere useful. `?device=` makes a link to either half shareable. The page also documents uninstalling, which it never did, and the five command blocks share one CommandBlock component instead of repeating the copy button. The i18n `batocera` block becomes `stores` in both locales; every key the page uses was checked to exist in both. Co-Authored-By: Claude Opus 5 (1M context) --- apps/frontend/src/i18n/locales/en.ts | 34 ++- apps/frontend/src/i18n/locales/hu.ts | 34 ++- .../src/page/batocera/BatoceraIndexPage.vue | 201 ------------- .../src/page/catalog/CatalogIndexPage.vue | 4 +- .../frontend/src/page/stores/CommandBlock.vue | 41 +++ .../src/page/stores/StoresIndexPage.vue | 277 ++++++++++++++++++ apps/frontend/src/router/batocera.router.ts | 5 - apps/frontend/src/router/index.router.ts | 4 +- apps/frontend/src/router/stores.router.ts | 10 + 9 files changed, 378 insertions(+), 232 deletions(-) delete mode 100644 apps/frontend/src/page/batocera/BatoceraIndexPage.vue create mode 100644 apps/frontend/src/page/stores/CommandBlock.vue create mode 100644 apps/frontend/src/page/stores/StoresIndexPage.vue delete mode 100644 apps/frontend/src/router/batocera.router.ts create mode 100644 apps/frontend/src/router/stores.router.ts diff --git a/apps/frontend/src/i18n/locales/en.ts b/apps/frontend/src/i18n/locales/en.ts index 7a4755c..3715c83 100644 --- a/apps/frontend/src/i18n/locales/en.ts +++ b/apps/frontend/src/i18n/locales/en.ts @@ -198,23 +198,35 @@ export default { title: 'Build Matrix', subtitle: 'Which engines build for which platforms.', }, - batocera: { - title: 'Batocera Store', - subtitle: 'Our catalog, straight on your retro box.', - lead: 'ttg-batocera-store puts our catalog on Batocera, the plug-and-play retro gaming distribution. It pulls our games into the box\'s ROM folders together with box art and EmulationStation metadata, and can be re-run any time from the Ports menu to pick up new releases. Python 3 standard library only — nothing to install alongside it.', + stores: { + title: 'Stores', + subtitle: 'Our games on your own machine, kept up to date.', + lead: 'Two stores put our catalog on the two things that play it: a Batocera box, and RetroArch on anything else — desktop Linux, Windows, macOS, Android, a Steam Deck or a handheld. Both download the games straight from this site with box art, add them to the device\'s own library, and can be re-run any time to pick up new releases. Python 3 standard library only — nothing to install alongside them.', + chooseTitle: 'Pick your device', repo: 'Git repository', docs: 'Documentation', - batoceraProject: 'Batocera project', installTitle: 'Install', - installDesc: 'Run this over SSH on the Batocera box. It installs the client, writes the config, creates the Ports entry and runs the first sync.', - installNote: 'Then restart EmulationStation so the games show up:', useTitle: 'Use it', - useDesc: 'On the device: Ports ▸ "Teletype Games Store". Over SSH the client has a small CLI:', - platformsTitle: 'What it installs', - platformsDesc: 'A catalog platform is installable when its release asset boots directly on a Batocera system. Other platforms ship web and desktop builds instead, so they are not mapped.', + uninstallTitle: 'Remove it', + uninstallDesc: 'One line takes it all back out: the games, the metadata, the store itself.', + uninstallNote: 'Your own games are never touched. Anything you put in the store\'s folder keeps its place, your play counts and favourites survive, and the backups the store made of files it found are listed rather than deleted.', + platformsTitle: 'What they install', + platformsDesc: 'A catalog platform is installable when its release file boots directly in an emulator — the same cartridges either way. Our other platforms ship web and desktop builds, which no emulator loads; on a Batocera box those can be installed as Ports instead.', engineTitle: 'Run your own store', - engineDesc: 'Nothing here is specific to us. Our store is just a config file on top of warp-engine-batocera-store, an open engine that works with any WarpEngine-based site: point it at your own catalog, give it a name and a ROM subfolder of its own, and your games land on a Batocera box the same way. Several stores can live side by side on one machine without touching each other\'s games.', + engineDesc: 'Nothing here is specific to us. Each store is just a config file on top of an open engine that works with any WarpEngine-based site: point it at your own catalog, give it a name and a folder of its own, and your games land on a device the same way. The two engines share warpstore, which does the catalog side for both, so a third kind of device is an adapter and a config rather than a new program. Several stores can live side by side on one machine without touching each other\'s games.', copy: 'Copy', + batocera: { + tagline: 'The plug-and-play retro distribution', + installDesc: 'Run this over SSH on the Batocera box. It installs the engine, writes the config, creates the Ports entry and runs the first sync.', + installNote: 'Then restart EmulationStation so the games show up:', + useDesc: 'On the device: Ports ▸ "Teletype Games Store". Over SSH the store has a small CLI:', + }, + retroarch: { + tagline: 'Desktop, Android, Steam Deck, handhelds', + installDesc: 'Run this on the machine RetroArch is installed on. It reads your retroarch.cfg to find where your playlists, thumbnails and cores live, then writes one playlist per platform with box art.', + installNote: 'Restart RetroArch afterwards — it builds its menu at startup. If the VICE x64 and TIC-80 cores are missing it still works, and RetroArch asks which core to use the first time.', + useDesc: 'The store has a small CLI. Close RetroArch before syncing: it writes its playlists back when it exits, so a sync underneath a running instance can be undone.', + }, }, code: { title: 'Codebase', diff --git a/apps/frontend/src/i18n/locales/hu.ts b/apps/frontend/src/i18n/locales/hu.ts index 115bd0c..39fdc84 100644 --- a/apps/frontend/src/i18n/locales/hu.ts +++ b/apps/frontend/src/i18n/locales/hu.ts @@ -198,23 +198,35 @@ export default { title: 'Build mátrix', subtitle: 'Melyik engine melyik platformra fordít.', }, - batocera: { - title: 'Batocera Store', - subtitle: 'A katalógusunk közvetlenül a retró gépeden.', - lead: 'A ttg-batocera-store a katalógusunkat teszi fel a Batocerára, a plug-and-play retró gamer disztribúcióra. A játékainkat a gép ROM mappáiba tölti le, borítóképpel és EmulationStation metaadatokkal együtt, és a Ports menüből bármikor újrafuttatható az új kiadásokért. Csak a Python 3 alapkönyvtárát használja — nem kell mellé semmit telepíteni.', + stores: { + title: 'Store-ok', + subtitle: 'A játékaink a saját gépeden, mindig frissen.', + lead: 'Két store teszi fel a katalógusunkat arra a kettőre, ami le is játssza: egy Batocera gépre, és RetroArch-ra minden máson — asztali Linuxon, Windowson, macOS-en, Androidon, Steam Decken vagy egy kézikonzolon. Mindkettő közvetlenül erről az oldalról tölti le a játékokat borítóképpel, felveszi őket a gép saját könyvtárába, és bármikor újrafuttatható az új kiadásokért. Csak a Python 3 alapkönyvtárát használják — nem kell mellé semmit telepíteni.', + chooseTitle: 'Válaszd ki a gépedet', repo: 'Git tároló', docs: 'Dokumentáció', - batoceraProject: 'Batocera projekt', installTitle: 'Telepítés', - installDesc: 'Futtasd ezt SSH-n a Batocera gépen. Telepíti a klienst, kiírja a konfigurációt, létrehozza a Ports bejegyzést és lefuttatja az első szinkront.', - installNote: 'Utána indítsd újra az EmulationStationt, hogy megjelenjenek a játékok:', useTitle: 'Használat', - useDesc: 'A gépen: Ports ▸ „Teletype Games Store”. SSH-n keresztül egy egyszerű CLI áll rendelkezésre:', - platformsTitle: 'Mit telepít', - platformsDesc: 'Egy katalógus-platform akkor telepíthető, ha a kiadás fájlja közvetlenül elindul egy Batocera rendszeren. A többi platform webes és asztali buildeket ad, ezért nincsenek leképezve.', + uninstallTitle: 'Eltávolítás', + uninstallDesc: 'Egy sor mindent visszabont: a játékokat, a metaadatokat és magát a store-t.', + uninstallNote: 'A saját játékaidhoz nem nyúl. Ami a store mappájába tőled került, a helyén marad, a játékszámláló és a kedvencek megmaradnak, a store által készített biztonsági másolatokat pedig felsorolja, nem törli.', + platformsTitle: 'Mit telepítenek', + platformsDesc: 'Egy katalógus-platform akkor telepíthető, ha a kiadás fájlja közvetlenül elindul egy emulátorban — mindkét úton ugyanezek. A többi platformunk webes és asztali buildet ad, amit emulátor nem tölt be; Batocera gépen azok Ports-ként telepíthetők.', engineTitle: 'Csinálj saját store-t', - engineDesc: 'Itt semmi sem ránk van szabva. A mi store-unk csak egy konfigurációs fájl a warp-engine-batocera-store fölött, ami bármilyen WarpEngine alapú oldallal működik: állítsd a saját katalógusodra, adj neki nevet és saját ROM almappát, és a játékaid ugyanígy kerülnek fel egy Batocera gépre. Egy gépen több store is megfér egymás mellett anélkül, hogy egymás játékaihoz nyúlnának.', + engineDesc: 'Itt semmi sem ránk van szabva. Mindkét store csak egy konfigurációs fájl egy nyílt motor fölött, ami bármilyen WarpEngine alapú oldallal működik: állítsd a saját katalógusodra, adj neki nevet és saját mappát, és a játékaid ugyanígy kerülnek fel egy gépre. A két motor a warpstore-on osztozik, ami mindkettőnek a katalógus-oldalt viszi, tehát egy harmadik fajta gép már csak egy adapter és egy konfiguráció, nem új program. Egy gépen több store is megfér egymás mellett anélkül, hogy egymás játékaihoz nyúlnának.', copy: 'Másolás', + batocera: { + tagline: 'A plug-and-play retró disztribúció', + installDesc: 'Futtasd ezt SSH-n a Batocera gépen. Telepíti a motort, kiírja a konfigurációt, létrehozza a Ports bejegyzést és lefuttatja az első szinkront.', + installNote: 'Utána indítsd újra az EmulationStationt, hogy megjelenjenek a játékok:', + useDesc: 'A gépen: Ports ▸ „Teletype Games Store”. SSH-n keresztül egy egyszerű CLI áll rendelkezésre:', + }, + retroarch: { + tagline: 'Asztali gép, Android, Steam Deck, kézikonzol', + installDesc: 'Futtasd azon a gépen, amin a RetroArch van. A retroarch.cfg-ből olvassa ki, hol vannak a playlistjeid, a borítóképek és a magok, majd platformonként egy playlistet ír borítóképpel.', + installNote: 'Utána indítsd újra a RetroArch-ot — a menüt indulásnál építi. Ha a VICE x64 és a TIC-80 mag nincs telepítve, akkor is működik: a RetroArch az első indításnál rákérdez, melyik maggal futtassa.', + useDesc: 'A store-nak van egy egyszerű CLI-je. Szinkron előtt zárd be a RetroArch-ot: kilépéskor visszaírja a playlistjeit, tehát a futó példány alatti szinkron elveszhet.', + }, }, code: { title: 'Kódbázis', diff --git a/apps/frontend/src/page/batocera/BatoceraIndexPage.vue b/apps/frontend/src/page/batocera/BatoceraIndexPage.vue deleted file mode 100644 index cf4f3ff..0000000 --- a/apps/frontend/src/page/batocera/BatoceraIndexPage.vue +++ /dev/null @@ -1,201 +0,0 @@ - - - - - diff --git a/apps/frontend/src/page/catalog/CatalogIndexPage.vue b/apps/frontend/src/page/catalog/CatalogIndexPage.vue index 1813d42..aaf31e8 100644 --- a/apps/frontend/src/page/catalog/CatalogIndexPage.vue +++ b/apps/frontend/src/page/catalog/CatalogIndexPage.vue @@ -67,8 +67,8 @@ {{ t('builds.title') }} | - - {{ t('batocera.title') }} + + {{ t('stores.title') }} diff --git a/apps/frontend/src/page/stores/CommandBlock.vue b/apps/frontend/src/page/stores/CommandBlock.vue new file mode 100644 index 0000000..d888dce --- /dev/null +++ b/apps/frontend/src/page/stores/CommandBlock.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/apps/frontend/src/page/stores/StoresIndexPage.vue b/apps/frontend/src/page/stores/StoresIndexPage.vue new file mode 100644 index 0000000..e76a829 --- /dev/null +++ b/apps/frontend/src/page/stores/StoresIndexPage.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/apps/frontend/src/router/batocera.router.ts b/apps/frontend/src/router/batocera.router.ts deleted file mode 100644 index 6e3477f..0000000 --- a/apps/frontend/src/router/batocera.router.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router' - -export const batoceraRouter: RouteRecordRaw[] = [ - { path: '/batocera', name: 'batoceraIndex', component: () => import('../page/batocera/BatoceraIndexPage.vue') }, -] diff --git a/apps/frontend/src/router/index.router.ts b/apps/frontend/src/router/index.router.ts index 1292c22..808a1eb 100644 --- a/apps/frontend/src/router/index.router.ts +++ b/apps/frontend/src/router/index.router.ts @@ -8,7 +8,7 @@ import { enginesRouter } from './engines.router' import { howtosRouter } from './howtos.router' import { teamRouter } from './team.router' import { buildsRouter } from './builds.router' -import { batoceraRouter } from './batocera.router' +import { storesRouter } from './stores.router' export const router = createRouter({ history: createWebHistory(), @@ -22,7 +22,7 @@ export const router = createRouter({ ...howtosRouter, ...teamRouter, ...buildsRouter, - ...batoceraRouter, + ...storesRouter, ], scrollBehavior() { return { top: 0 } diff --git a/apps/frontend/src/router/stores.router.ts b/apps/frontend/src/router/stores.router.ts new file mode 100644 index 0000000..88ddb7b --- /dev/null +++ b/apps/frontend/src/router/stores.router.ts @@ -0,0 +1,10 @@ +import type { RouteRecordRaw } from 'vue-router' + +export const storesRouter: RouteRecordRaw[] = [ + { path: '/stores', name: 'storesIndex', component: () => import('../page/stores/StoresIndexPage.vue') }, + // The Batocera store had a page to itself until the RetroArch store joined it. + // Both device names keep working as URLs — the old links, and the guess someone + // makes after reading "RetroArch store" — and each lands on its own tab. + { path: '/batocera', redirect: { name: 'storesIndex', query: { device: 'batocera' } } }, + { path: '/retroarch', redirect: { name: 'storesIndex', query: { device: 'retroarch' } } }, +]