diff --git a/.woodpecker.yaml b/.woodpecker.yaml index ccc6cca..2d1cb02 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -27,14 +27,9 @@ steps: - npm run typecheck - npm run lint # The window test wants a display and a store on the machine; that check belongs - # where there is one. The bridge check is worth running here: it exercises the - # registry and the message bundles. - - | - if command -v python3 >/dev/null 2>&1; then - npm run smoke - else - echo "no python3 in the image — the smoke test needs it, skipping" - fi + # where there is one. The bridge check runs here unconditionally — it needs nothing + # but Node, now that the store engine is part of the application. + - npm run smoke # A quarter of a gigabyte of packages is not worth building on every push, so the # two builds run when a release is being cut — or when asked for by hand. diff --git a/Makefile b/Makefile index 997c741..c8d6ba8 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,10 @@ SHELL := /bin/sh SCRIPTS := scripts NODE_MIN := 22 -# The version is package.json's, so the release tag never drifts from the app. -VERSION := $(shell python3 -c 'import json; print(json.load(open("package.json"))["version"])') +# The version is package.json's, so the release tag never drifts from the app. Read with +# node, which this project already requires — nothing here needs an interpreter the app +# itself no longer depends on. +VERSION := $(shell node -p 'require("./package.json").version') TAG ?= v$(VERSION) # Which site's store registry a packaged build reads. Empty means the default in @@ -116,6 +118,5 @@ version: ## Show the versions involved @printf "typescript "; npx tsc --version 2>/dev/null || echo "missing" @printf "eslint "; npx eslint --version 2>/dev/null || echo "missing" @printf "tea "; tea --version 2>/dev/null | head -1 || echo "missing — devarea: make tea" - @printf "python3 "; python3 --version 2>/dev/null || echo "missing" - @printf "registry "; python3 -c 'import json; print(json.load(open("package.json"))["warpEngine"]["registryUrl"])' + @printf "registry "; node -p 'require("./package.json").warpEngine.registryUrl' @if [ -n "$(STORES_API)" ]; then printf " build override: %s\n" "$(STORES_API)"; fi diff --git a/README.md b/README.md index 5677813..2db166a 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ # warp-engine-client — the WarpEngine Client app -The graphical client for a WarpEngine store — the app is called **WarpEngine -Store** — driving -[`warp-engine-desktop-store`](https://git.teletypegames.org/stores/warp-engine-desktop-store) -underneath: the catalog as a grid of cards, one click to install a title into your -own application menu, one to play it, one to remove it. Linux, macOS and Windows. +The client for a WarpEngine store: the catalog as a grid of cards, one click to install +a title into your own application menu, one to play it, one to remove it. Linux, macOS +and Windows. -The CLI stays the product; this is its front door. Every action here runs -`desktop_store.py`, so there is one catalog logic, one state file and one delete -guard — the window never touches the filesystem itself. +**The store engine is part of this application.** Reading the catalog, choosing which +release fits this machine, unpacking it, writing the menu entry and remembering what +went where all happen in process — there is no interpreter to find and no child process +to parse. What lands on disk has not changed: `config.json` and `state.json` keep the +shape the shell engine wrote, so a machine whose library was installed by the CLI keeps +it, and the engine's own defaults still decide everything a store does not configure. -It is also **the Windows install path**. The store's own installer is -`curl … | sh`, which Windows does not have; this app downloads the store engine -itself, into the same folder the shell installer would use. +That also makes this the only install path that needs nothing of the machine. The shell +store's installer was `curl … | sh`, which Windows does not have. Which store it installs is not baked in: the client asks a registry — `GET /api/stores` on the site — and each record says what the store is called, which @@ -20,10 +20,8 @@ catalog it serves and where its configuration lives. ## What it needs -- **Python 3** on the machine, because the store is a Python program. The app - looks for `python3`, `python` and `py -3`, and says so plainly if none answer. -- Nothing else at runtime. No Node, no package manager, no admin rights: the - store installs under your own user account. +- **Nothing.** No interpreter, no package manager, no admin rights: the app carries its + own runtime and the store installs under your own user account. To *develop* it you also need **Node 22 or newer** — see below — and nothing else: the toolchain (TypeScript, ESLint, esbuild, electron-builder) installs with `make setup`. @@ -48,8 +46,8 @@ If macOS offers *Open Anyway* under **System Settings ▸ Privacy & Security** a a blocked attempt, that works as well. Notarisation is the only thing that removes the step entirely, and it needs a paid Apple Developer ID. -Nothing the store itself downloads is affected: Python fetches those files, and -Python does not set the quarantine flag. +Nothing the store itself downloads is affected: the app fetches those files over its +own HTTP client, which does not set the quarantine flag. **v1.0.0 could not be opened at all** — it reported *"is damaged"*. The bundle had never been signed; only its main executable carried the linker's ad-hoc signature, @@ -311,9 +309,9 @@ is the map** — the layers, every pattern in use, and the naming rules. The sho | Layer | What lives there | |---|---| | `src/shared/` | the IPC channel table, the bridge contract, the DTOs, the two message bundles | -| `src/domain/` | models, ports and errors — no Electron, no Node, no Python | +| `src/domain/` | models, ports and errors — no Electron, no Node | | `src/application/` | services and the domain → DTO mappers | -| `src/infrastructure/` | the adapters: the Python CLI, HTTP, the filesystem, Electron itself | +| `src/infrastructure/` | the adapters: the store engine, HTTP, the archive reader, the filesystem, Electron itself | | `src/main/` | the window, the IPC controllers, the composition root, the self-test | | `src/preload/` | the bridge, bundled into one file — a sandboxed preload cannot require modules | | `src/renderer/` | the state store, the views and the renderer controllers | @@ -334,14 +332,47 @@ Two properties are worth stating because they are what the layers buy: CSP that allows only its own script and stylesheet plus images over HTTPS. Links open in the real browser; the window itself never navigates. -`PythonStoreCatalogGateway` is the only class that knows the store is a Python -program. It talks to the CLI through `--json`, which puts data on stdout and the -human-readable log on stderr. That flag arrived with engine **1.1.0**, and the client -checks: an older store is met with an offer to refresh it rather than a failed call. +`NativeStoreCatalogGateway` is the engine behind the `StoreCatalogGateway` port, and +`src/infrastructure/engine/` is the engine itself: the catalog client, the release +picker, the host match, the payload installer, the three launcher writers and the state +file. Above the port nothing knows any of that exists, which is the point — a second +host would be a second gateway, not a second code path. -`STORE_ENGINES` has one entry today. The RetroArch store has the same command shape, -so a second entry is the whole change needed to drive it too — that is why the table is -there. +`STORE_ENGINES` has one entry today. A RetroArch store writes playlists rather than +menu entries, so it would be an entry there and a gateway of its own. + +### Reading a zip without a dependency + +Node has no zip reader and this application has **no runtime dependencies**, so +`src/infrastructure/archive/ZipArchive.ts` is one over `node:zlib` — about 150 lines that +walk the central directory, inflate `stored` and `deflate` entries, and restore the +executable bit from each entry's external attributes. That last part is not a detail: the +archive records it, and without it nothing the store installs can start. + +It reads what our own release pipeline produces and refuses the rest: a zip64 archive, an +unknown compression method and a path that would escape the destination are all errors +rather than best guesses. + +### Which WarpEngine served the catalog + +Every WarpEngine API response carries a `WarpEngine-Version` header, so the client knows +the engine's age without asking. `SUPPORTED_WARP_ENGINE_VERSIONS` lists the versions this +client is written against, and `selectCatalogDialect` maps each one to the `CatalogDialect` +that reads its catalog shape. + +The switch over that list is exhaustive, which is the whole mechanism: adding a version to +the array stops the build — in the type checker *and* in the linter — until somebody says +what it reads like. A new engine version cannot arrive silently. + +| What the header says | What happens | +|---|---| +| a supported version | its dialect reads the catalog, and the log names it | +| nothing at all | read as the oldest supported version, which is what an engine older than 0.4.0 is | +| older than anything supported | the same, and the log says so | +| newer than anything supported | the newest dialect is tried anyway, with a warning that titles may be missed | + +Three versions share one dialect today, because the catalog's shape has not changed +across them. One class serving three versions is the honest way to say that. ## Verified, and not diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ca83ed7..3ece61b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,37 +1,57 @@ -# WarpEngine Client 1.5.0 +# WarpEngine Client 2.0.0 -**The app is called WarpEngine Client.** It was WarpEngine Store, which named the thing -it opens rather than the thing you run — and the store is a catalog on a site, not a -window on your machine. The window title, the packages, the bundle and the menu entry all -say Client now; the repository already did. The store the window is driving is named in -the side menu, so the bar no longer repeats it as a badge. +**The store engine is part of the app. Nothing has to be installed on the machine any +more.** Reading the catalog, choosing which release fits your computer, downloading and +unpacking it, writing the menu entry and remembering what went where all happen inside +the application now. There is no Python to find, no child process, and no JSON protocol +between the two halves — which is why this is a major version rather than a feature. -**Every title in the catalog is listed, including the ones this machine cannot install.** -A C64 cartridge on a desktop, or a game with no build for your operating system, used to -be silently absent — leaving you to wonder whether the catalog is small or your machine -is unusual. Those cards are now there, dimmed, with an *unsupported platform* or *no -build for this machine* badge and the engine's own sentence underneath, and with nothing -to press. They have a category of their own, *Not for this machine*, and they stay out of -the native/hosted counts: a title with no build has no mode to be counted under. +What that changes for a person: on Windows and on a fresh Mac the app simply works. +Before it looked for `python3`, `python` and `py -3`, and where none answered it drew a +screen with a link to python.org instead of a catalog. That screen is gone, along with +the one that offered to refresh a store engine too old to drive. -This needs store engine **1.2.0** (desktop) on **warpstore 1.4.0**, which report what -they had to leave out and why. An older engine still works — its listing is simply all -installable, as it was before. +**Your existing library is kept.** `config.json` and `state.json` on disk are unchanged — +the same field names, the same `:` keys, the same file modes — so a machine +whose games were installed by the shell store keeps them. Opening this version against +such a store lists them as installed, offers no needless update, and a sync reports +*already up to date*. A `version: 1` state file is still migrated on first read. -**A build can be pointed at another site's registry.** +The two Python files an earlier install left in the store folder are removed the next +time that store is set up. Nothing reads them, and a folder that still looks like it +holds the engine invites someone to run it against a state file this app is also writing. -```sh -make dist STORES_API=https://games.example.org/api/stores -``` +**The client knows which WarpEngine served a catalog.** Every WarpEngine API response +carries a `WarpEngine-Version` header, and the client now reads it, names the version in +the log, and picks the catalog dialect for it. `SUPPORTED_WARP_ENGINE_VERSIONS` lists what +this build was written against — 0.2, 0.3 and 0.4 — and the four cases are all handled: -The address is written into the packaged app's own `package.json`, so a client built for -somebody else's catalog needs no source change and nothing set on the user's machine. A -runtime `STORES_API` still wins, which is for trying something out rather than shipping. +| The header says | What the client does | +|---|---| +| a supported version | reads the catalog with that version's dialect | +| nothing at all | reads it as the oldest supported version — an engine before 0.4.0 sent no header | +| something older | the same, and says so in the log | +| something newer | tries the newest dialect anyway, warning that titles may be missed | + +Adding a version to that list fails the build until somebody says what it reads like, in +the type checker and in the linter both. A new engine version cannot arrive unnoticed. + +**Refresh and the language picker are icons.** They sit together at the foot of the side +menu, and the *Actions* heading that used to head a section of one button is gone. Both +carry their name as a tooltip and to a screen reader, and the language picker is still a +real ` - + +
+ + + +
diff --git a/src/renderer/state/AppStore.ts b/src/renderer/state/AppStore.ts index 9f9b48a..c178889 100644 --- a/src/renderer/state/AppStore.ts +++ b/src/renderer/state/AppStore.ts @@ -1,5 +1,4 @@ import type { AppStateDto } from '../../shared/contracts/dto/AppStateDto' -import type { EngineVersionDto } from '../../shared/contracts/dto/EngineVersionDto' import type { GameDto } from '../../shared/contracts/dto/GameDto' import type { InstalledStoreDto } from '../../shared/contracts/dto/InstalledStoreDto' import type { StorePathsDto } from '../../shared/contracts/dto/StorePathsDto' @@ -21,11 +20,8 @@ export interface AppState { readonly locales: readonly Locale[] readonly messages: MessageBundle readonly navigationOpen: boolean - readonly pythonVersion: string | null readonly stores: readonly InstalledStoreDto[] readonly currentStore: InstalledStoreDto | null - readonly engine: EngineVersionDto | null - readonly minimumEngineVersion: string readonly registryUrl: string readonly defaultStoreRoot: string readonly games: readonly GameDto[] @@ -42,11 +38,8 @@ const INITIAL_STATE: AppState = { locales: ['en'], messages: ENGLISH_MESSAGES, navigationOpen: true, - pythonVersion: null, stores: [], currentStore: null, - engine: null, - minimumEngineVersion: '', registryUrl: '', defaultStoreRoot: '', games: [], @@ -85,11 +78,8 @@ export class AppStore { locales: dto.locales, messages: dto.messages, navigationOpen: dto.navigationOpen, - pythonVersion: dto.pythonVersion, stores: dto.stores, currentStore: dto.currentStore, - engine: dto.engine, - minimumEngineVersion: dto.minimumEngineVersion, registryUrl: dto.registryUrl, defaultStoreRoot: dto.defaultStoreRoot } @@ -111,8 +101,8 @@ export class AppStore { this.notify() } - public applySelectedStore (store: InstalledStoreDto, engine: EngineVersionDto | null): void { - this.state = { ...this.state, currentStore: store, engine, games: [], paths: null, filter: ALL_CATEGORIES } + public applySelectedStore (store: InstalledStoreDto): void { + this.state = { ...this.state, currentStore: store, games: [], paths: null, filter: ALL_CATEGORIES } this.notify() } diff --git a/src/renderer/style.css b/src/renderer/style.css index ea120bc..70c6f6d 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -85,7 +85,11 @@ body.nav-closed .side { margin-left: calc(-1 * var(--side-width)); } padding-top: 12px; border-top: 1px solid var(--line); gap: 8px; + flex-direction: row; + align-items: center; } +/* The icons sit at the end of the footer row, away from the log toggle. */ +.side-tools { display: flex; align-items: center; gap: 4px; margin-left: auto; } .side-head { font-size: 11px; font-weight: 700; @@ -167,8 +171,51 @@ body.nav-closed .side { margin-left: calc(-1 * var(--side-width)); } padding: 8px 10px 2px; } -.side-lang { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-dim); } -.side-lang .select { margin-left: auto; } +/* + * An icon-only control: a square the size of its glyph, quiet until pointed at. + * `position: relative` is what lets the language picker put its , stretched over the icon and invisible. It keeps the native + * dropdown — and its keyboard and screen-reader behaviour — while only the glyph shows. + */ +.locale-select { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + border: 0; + opacity: 0; + cursor: pointer; + appearance: none; + font: inherit; +} /* --- top bar ------------------------------------------------------------ */ .bar { @@ -223,7 +270,7 @@ body.nav-closed .side { margin-left: calc(-1 * var(--side-width)); } } .link { color: var(--accent); cursor: pointer; text-decoration: underline; } -/* --- gate (no python, or no store yet) ---------------------------------- */ +/* --- gate (no store yet) ---------------------------------- */ .gate { margin: auto; max-width: 520px; diff --git a/src/renderer/views/SideMenuView.ts b/src/renderer/views/SideMenuView.ts index ba1a37e..edba07e 100644 --- a/src/renderer/views/SideMenuView.ts +++ b/src/renderer/views/SideMenuView.ts @@ -21,14 +21,14 @@ export interface SideMenuViewCallbacks { */ export class SideMenuView { private readonly storesHead = requireElement('head-stores', HTMLElement) - private readonly actionsHead = requireElement('head-actions', HTMLElement) private readonly categoriesHead = requireElement('head-cats', HTMLElement) - private readonly languageHead = requireElement('head-lang', HTMLElement) private readonly storeList = requireElement('store-list', HTMLElement) private readonly addStore = requireElement('add-store', HTMLButtonElement) private readonly refresh = requireElement('refresh', HTMLButtonElement) private readonly categories = requireElement('cats', HTMLElement) private readonly locale = requireElement('locale', HTMLSelectElement) + /** The square around the select: it is what a pointer hovers, so the tooltip is its. */ + private readonly localeControl = requireElement('locale-control', HTMLElement) public constructor (private readonly callbacks: SideMenuViewCallbacks) { this.addStore.addEventListener('click', callbacks.onAddStore) @@ -38,11 +38,15 @@ export class SideMenuView { public render (state: AppState): void { setText(this.storesHead, state.messages.stores) - setText(this.actionsHead, state.messages.actions) setText(this.categoriesHead, state.messages.categories) - setText(this.languageHead, state.messages.language) setText(this.addStore, state.messages.addStore) - setText(this.refresh, state.messages.refresh) + // Refresh and the language picker are icons: naming them is all the view does, and + // writing text into them would replace the glyph. + describeControl(this.refresh, state.messages.refresh) + // The picker is two elements: the invisible select takes the focus and the reader's + // name, the square around it takes the hover and the tooltip. + describeControl(this.locale, state.messages.language) + this.localeControl.title = state.messages.language this.renderStores(state) this.renderCategories(state) @@ -117,3 +121,14 @@ export class SideMenuView { for (const row of this.storeList.querySelectorAll('button')) row.disabled = state.busy } } + +/** + * Name an icon-only control. + * + * `title` is the tooltip a mouse finds and `aria-label` is what a screen reader reads; + * an icon button needs both, and they are the same sentence. + */ +function describeControl (element: HTMLElement, name: string): void { + element.title = name + element.setAttribute('aria-label', name) +} diff --git a/src/scripts/SmokeTest.ts b/src/scripts/SmokeTest.ts index 8919b49..b13ee5e 100644 --- a/src/scripts/SmokeTest.ts +++ b/src/scripts/SmokeTest.ts @@ -6,12 +6,10 @@ import type { InstalledStore } from '../domain/models/InstalledStore' import type { RegistryStore } from '../domain/models/RegistryStore' import { DESKTOP_STORE_ENGINE } from '../domain/models/StoreEngine' import { deriveStoreId } from '../domain/models/StoreIdentity' +import { NativeStoreCatalogGateway } from '../infrastructure/engine/NativeStoreCatalogGateway' import { HttpTextClient } from '../infrastructure/http/HttpTextClient' -import { PythonEngineProcessRunner } from '../infrastructure/process/PythonEngineProcessRunner' -import { SystemPythonRuntimeLocator } from '../infrastructure/process/SystemPythonRuntimeLocator' import { FileSystemInstalledStoreRepository } from '../infrastructure/repositories/FileSystemInstalledStoreRepository' import { HttpStoreRegistryRepository } from '../infrastructure/repositories/HttpStoreRegistryRepository' -import { PythonStoreCatalogGateway } from '../infrastructure/repositories/PythonStoreCatalogGateway' import type { GameDto } from '../shared/contracts/dto/GameDto' import { ENGLISH_MESSAGES } from '../shared/i18n/EnglishMessages' import { HUNGARIAN_MESSAGES } from '../shared/i18n/HungarianMessages' @@ -20,6 +18,9 @@ import { LOCALES } from '../shared/i18n/MessageBundle' /** * Drives the store with no window and no Electron at all. * + * Since the engine is part of this application, this exercises the real thing rather + * than a child process: a bad release choice or a broken menu entry fails here. + * * This is the second composition root, and the reason the layers are worth having: * the same services the window uses are assembled here against the same ports, so an * integration mistake shows up in a terminal rather than in a screenshot. @@ -31,10 +32,7 @@ class SmokeTest { private failed = false private readonly stores = new FileSystemInstalledStoreRepository() - private readonly pythonLocator = new SystemPythonRuntimeLocator() - private readonly catalogGateway = new PythonStoreCatalogGateway( - new PythonEngineProcessRunner(this.pythonLocator) - ) + private readonly catalogGateway = new NativeStoreCatalogGateway() private readonly httpClient = new HttpTextClient() private readonly registry = new HttpStoreRegistryRepository(this.httpClient) private readonly gameMapper = new GameDtoMapper() @@ -42,7 +40,6 @@ class SmokeTest { public async run (): Promise { console.log('warp-engine-client smoke test') - if (!this.checkPython()) return 1 this.checkMessages() await this.checkRegistry() @@ -53,16 +50,6 @@ class SmokeTest { return this.failed ? 1 : 0 } - private checkPython (): boolean { - const runtime = this.pythonLocator.findRuntime() - if (runtime === null) { - this.reportBad('python', 'not found — the store cannot run') - return false - } - this.reportOk('python', runtime.version) - return true - } - /** The bundles are typed against one key set, so this only counts them. */ private checkMessages (): void { const english = Object.keys(ENGLISH_MESSAGES).length @@ -115,15 +102,12 @@ class SmokeTest { id: path.basename(home).replace(DESKTOP_STORE_ENGINE.homeSuffix, ''), name: path.basename(home), home, - scriptPath: path.join(home, DESKTOP_STORE_ENGINE.scriptFileName), configPath: path.join(home, 'config.json'), engine: DESKTOP_STORE_ENGINE.id } - for (const file of [store.scriptPath, store.configPath]) { - if (!fs.existsSync(file)) { - this.reportBad('SMOKE_HOME', `${file} is missing`) - return null - } + if (!fs.existsSync(store.configPath)) { + this.reportBad('SMOKE_HOME', `${store.configPath} is missing`) + return null } this.reportOk('store (SMOKE_HOME)', store.home) return store @@ -143,11 +127,6 @@ class SmokeTest { const logLines: string[] = [] const progress = { onLog: (line: string): void => { logLines.push(line) } } - const engine = this.catalogGateway.readEngineVersion(store) - if (engine === null) this.reportBad('engine', 'the store did not answer --version') - else if (!engine.supported) this.reportBad('engine', `${engine.text} is too old for this client`) - else this.reportOk('engine', engine.text) - const paths = await this.catalogGateway.readPaths(store, progress) if (paths.operatingSystem.length > 0 && paths.storeFolder.length > 0) { this.reportOk('paths', `${paths.operatingSystem} → ${paths.storeFolder}`) diff --git a/src/shared/contracts/dto/AppStateDto.ts b/src/shared/contracts/dto/AppStateDto.ts index 82ae727..7a852ab 100644 --- a/src/shared/contracts/dto/AppStateDto.ts +++ b/src/shared/contracts/dto/AppStateDto.ts @@ -1,6 +1,5 @@ import type { Locale } from '../../i18n/MessageBundle' import type { MessageBundle } from '../../i18n/MessageBundle' -import type { EngineVersionDto } from './EngineVersionDto' import type { InstalledStoreDto } from './InstalledStoreDto' /** Everything the window needs before it can paint anything. */ @@ -9,12 +8,8 @@ export interface AppStateDto { readonly locales: readonly Locale[] readonly messages: MessageBundle readonly navigationOpen: boolean - /** The Python 3 version string, or null when there is none to drive the store. */ - readonly pythonVersion: string | null readonly currentStore: InstalledStoreDto | null readonly stores: readonly InstalledStoreDto[] - readonly engine: EngineVersionDto | null - readonly minimumEngineVersion: string readonly registryUrl: string readonly defaultStoreRoot: string readonly appVersion: string diff --git a/src/shared/contracts/dto/EngineVersionDto.ts b/src/shared/contracts/dto/EngineVersionDto.ts deleted file mode 100644 index 75dd2e7..0000000 --- a/src/shared/contracts/dto/EngineVersionDto.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** The installed engine's version, and whether this client can drive it. */ -export interface EngineVersionDto { - readonly text: string - readonly supported: boolean -} diff --git a/src/shared/contracts/dto/StoreSelectionDto.ts b/src/shared/contracts/dto/StoreSelectionDto.ts index c66ea38..38f7c80 100644 --- a/src/shared/contracts/dto/StoreSelectionDto.ts +++ b/src/shared/contracts/dto/StoreSelectionDto.ts @@ -1,8 +1,6 @@ -import type { EngineVersionDto } from './EngineVersionDto' import type { InstalledStoreDto } from './InstalledStoreDto' -/** The answer to a store switch: which store is open, and can it be driven. */ +/** The answer to a store switch: which store is now open. */ export interface StoreSelectionDto { readonly store: InstalledStoreDto - readonly engine: EngineVersionDto | null } diff --git a/src/shared/i18n/EnglishMessages.ts b/src/shared/i18n/EnglishMessages.ts index 30851c2..0550e87 100644 --- a/src/shared/i18n/EnglishMessages.ts +++ b/src/shared/i18n/EnglishMessages.ts @@ -25,7 +25,6 @@ export const ENGLISH_MESSAGES = { stores: 'Stores', addStore: 'Add a store…', switchFailed: 'That store could not be opened', - actions: 'Actions', categories: 'Categories', catAll: 'Everything', catInstalled: 'Installed', @@ -45,12 +44,6 @@ export const ENGLISH_MESSAGES = { registryFailed: 'The list of stores could not be fetched', registryEmpty: 'The list of stores came back empty. Nothing to install from yet.', registryRetry: 'Try again', - oldEngineTitle: 'The store needs refreshing', - oldEngineBody: 'The store engine on this machine is older than this client can drive. Refreshing it downloads the current engine and keeps your settings and installed games.', - oldEngineAction: 'Refresh the store', - noPythonTitle: 'Python 3 is required', - noPythonBody: 'The store is a Python program, so Python 3 has to be installed. Install it, then reopen this window.', - pythonLink: 'python.org/downloads', paths: 'Where things go', openStoreFolder: 'Open the store folder', openMenuFolder: 'Open the menu folder', diff --git a/src/shared/i18n/HungarianMessages.ts b/src/shared/i18n/HungarianMessages.ts index 69d9dff..0abcbc8 100644 --- a/src/shared/i18n/HungarianMessages.ts +++ b/src/shared/i18n/HungarianMessages.ts @@ -25,7 +25,6 @@ export const HUNGARIAN_MESSAGES: MessageBundle = { stores: 'Store-ok', addStore: 'Store hozzáadása…', switchFailed: 'Ez a store nem nyitható meg', - actions: 'Műveletek', categories: 'Kategóriák', catAll: 'Minden', catInstalled: 'Telepítve', @@ -45,12 +44,6 @@ export const HUNGARIAN_MESSAGES: MessageBundle = { registryFailed: 'A store-ok listája nem érhető el', registryEmpty: 'A store-ok listája üresen jött vissza. Egyelőre nincs miből telepíteni.', registryRetry: 'Újra', - oldEngineTitle: 'A store frissítésre vár', - oldEngineBody: 'A gépen lévő store-motor régebbi, mint amit ez a kliens vezérelni tud. A frissítés letölti a mostani motort, a beállításaid és a telepített játékok pedig megmaradnak.', - oldEngineAction: 'Store frissítése', - noPythonTitle: 'Python 3 kell hozzá', - noPythonBody: 'A store egy Python program, tehát Python 3 kell a gépre. Telepítsd, majd nyisd meg újra ezt az ablakot.', - pythonLink: 'python.org/downloads', paths: 'Hova kerül', openStoreFolder: 'Store könyvtár megnyitása', openMenuFolder: 'Menü könyvtár megnyitása',