diff options
| author | 2026-03-29 21:35:34 +0800 | |
|---|---|---|
| committer | 2026-03-29 21:35:34 +0800 | |
| commit | 70348cefb7de8c1e044800296a99177309c5a81e (patch) | |
| tree | eb0fdfbcc880574e9b386a3f2fc9b3a89489e5b5 /packages/ui/src/models/game.ts | |
| parent | f2f5383a1b615a7493316d558dc55271198e772a (diff) | |
| parent | 1c115141cc7b676e6a07786594155c3ac293fe34 (diff) | |
| download | DropOut-70348cefb7de8c1e044800296a99177309c5a81e.tar.gz DropOut-70348cefb7de8c1e044800296a99177309c5a81e.zip | |
refactor(ui): full rewrite instance and code struct (#129)
## Summary by Sourcery
Refactor the UI to modernize effect handling, routing, and legacy APIs
while adding a reusable alert dialog component and cleaning up obsolete
stores.
New Features:
- Introduce a shared SaturnEffect context via ParticleBackground so
pages can access the effect without relying on global window APIs.
- Add a Base UI–powered alert dialog component for consistent
confirmation and warning flows across the app.
- Define a central router configuration module with instance routes to
standardize page wiring.
Bug Fixes:
- Ensure SaturnEffect nullish checks are handled safely when forwarding
pointer and touch events from the home view.
Enhancements:
- Rewrite ParticleBackground to manage its own SaturnEffect lifecycle
via React state and context instead of global accessors.
- Update the home view to use the SaturnEffect hook, simplify
pointer/touch handlers, and remove legacy game and release store usage.
- Refine layout and accessibility attributes for various form field and
label components, including field grouping and error rendering keys.
- Simplify sidebar navigation and adjust the user dropdown trigger to
work with the updated dropdown menu API.
- Wrap the root outlet for the home route with ParticleBackground only
on the index path to limit the effect to the intended view.
- Clean up imports and code style in radio group and other UI primitives
for consistency.
Chores:
- Remove deprecated UI stores and utility modules that are no longer
used with the new architecture.
- Add changeset entries documenting the Saturn effect refactor,
ParticleBackground rewrite, and removal of legacy store code.
Diffstat (limited to 'packages/ui/src/models/game.ts')
| -rw-r--r-- | packages/ui/src/models/game.ts | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/packages/ui/src/models/game.ts b/packages/ui/src/models/game.ts new file mode 100644 index 0000000..5342078 --- /dev/null +++ b/packages/ui/src/models/game.ts @@ -0,0 +1,113 @@ +import { listen, type UnlistenFn } from "@tauri-apps/api/event"; +import { toast } from "sonner"; +import { create } from "zustand"; +import { + startGame as startGameCommand, + stopGame as stopGameCommand, +} from "@/client"; +import type { GameExitedEvent } from "@/types/bindings/core"; + +interface GameState { + runningInstanceId: string | null; + runningVersionId: string | null; + launchingInstanceId: string | null; + stoppingInstanceId: string | null; + lifecycleUnlisten: UnlistenFn | null; + + isGameRunning: boolean; + startGame: (instanceId: string, versionId: string) => Promise<string | null>; + stopGame: (instanceId?: string | null) => Promise<string | null>; +} + +export const useGameStore = create<GameState>((set, get) => ({ + runningInstanceId: null, + runningVersionId: null, + launchingInstanceId: null, + stoppingInstanceId: null, + lifecycleUnlisten: null, + + get isGameRunning() { + return get().runningInstanceId !== null; + }, + + startGame: async (instanceId, versionId) => { + const { isGameRunning, lifecycleUnlisten } = get(); + + if (isGameRunning) { + toast.info("A game is already running"); + return null; + } else { + lifecycleUnlisten?.(); + } + + set({ + launchingInstanceId: instanceId, + }); + toast.info(`Preparing to launch ${versionId}...`); + + const unlisten = await listen<GameExitedEvent>("game-exited", (event) => { + const { instanceId, versionId, wasStopped, exitCode } = event.payload; + + set({ + runningInstanceId: null, + runningVersionId: null, + launchingInstanceId: null, + stoppingInstanceId: null, + }); + + if (wasStopped) { + toast.success( + `Stopped Minecraft ${versionId} for instance ${instanceId}`, + ); + } else { + toast.info( + `Minecraft ${versionId} exited with code ${exitCode} for instance ${instanceId}`, + ); + } + }); + + set({ lifecycleUnlisten: unlisten }); + + try { + const message = await startGameCommand(instanceId, versionId); + set({ + launchingInstanceId: null, + runningInstanceId: instanceId, + runningVersionId: versionId, + }); + toast.success(message); + return message; + } catch (e) { + console.error(e); + set({ launchingInstanceId: null }); + toast.error(`Error: ${e}`); + return null; + } + }, + + stopGame: async (instanceId) => { + const { runningInstanceId } = get(); + + if (!runningInstanceId) { + toast.info("No running game found"); + return null; + } + + if (instanceId !== runningInstanceId) { + toast.info("That instance is not the one currently running"); + return null; + } + + set({ stoppingInstanceId: runningInstanceId }); + + try { + return await stopGameCommand(); + } catch (e) { + console.error("Failed to stop game:", e); + toast.error(`Failed to stop game: ${e}`); + return null; + } finally { + set({ stoppingInstanceId: null }); + } + }, +})); |