diff options
| author | 2026-02-25 01:32:51 +0800 | |
|---|---|---|
| committer | 2026-02-25 01:32:51 +0800 | |
| commit | 66668d85d603c5841d755a6023aa1925559fc6d4 (patch) | |
| tree | 485464148c76b0021efb55b7d2afd1c3004ceee0 /packages/ui/src/components/bottom-bar.tsx | |
| parent | a6773bd092db654360c599ca6b0108ea0e456e8c (diff) | |
| download | DropOut-66668d85d603c5841d755a6023aa1925559fc6d4.tar.gz DropOut-66668d85d603c5841d755a6023aa1925559fc6d4.zip | |
chore(workspace): replace legacy codes
Diffstat (limited to 'packages/ui/src/components/bottom-bar.tsx')
| -rw-r--r-- | packages/ui/src/components/bottom-bar.tsx | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/packages/ui/src/components/bottom-bar.tsx b/packages/ui/src/components/bottom-bar.tsx new file mode 100644 index 0000000..32eb852 --- /dev/null +++ b/packages/ui/src/components/bottom-bar.tsx @@ -0,0 +1,231 @@ +import { listen, type UnlistenFn } from "@tauri-apps/api/event"; +import { Play, User } from "lucide-react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { toast } from "sonner"; +import { listInstalledVersions, startGame } from "@/client"; +import { cn } from "@/lib/utils"; +import { useAuthStore } from "@/models/auth"; +import { useInstancesStore } from "@/models/instances"; +import { useGameStore } from "@/stores/game-store"; +import { LoginModal } from "./login-modal"; +import { Button } from "./ui/button"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "./ui/select"; + +interface InstalledVersion { + id: string; + type: string; +} + +export function BottomBar() { + const authStore = useAuthStore(); + const gameStore = useGameStore(); + const instancesStore = useInstancesStore(); + + const [selectedVersion, setSelectedVersion] = useState<string | null>(null); + const [installedVersions, setInstalledVersions] = useState< + InstalledVersion[] + >([]); + const [isLoadingVersions, setIsLoadingVersions] = useState(true); + const [showLoginModal, setShowLoginModal] = useState(false); + + const loadInstalledVersions = useCallback(async () => { + if (!instancesStore.activeInstance) { + setInstalledVersions([]); + setIsLoadingVersions(false); + return; + } + + setIsLoadingVersions(true); + try { + const versions = await listInstalledVersions( + instancesStore.activeInstance.id, + ); + + const installed = versions || []; + setInstalledVersions(installed); + + // If no version is selected but we have installed versions, select the first one + if (!gameStore.selectedVersion && installed.length > 0) { + gameStore.setSelectedVersion(installed[0].id); + } + } catch (error) { + console.error("Failed to load installed versions:", error); + } finally { + setIsLoadingVersions(false); + } + }, [ + instancesStore.activeInstance, + gameStore.selectedVersion, + gameStore.setSelectedVersion, + ]); + + useEffect(() => { + loadInstalledVersions(); + + // Listen for backend events that should refresh installed versions. + let unlistenDownload: UnlistenFn | null = null; + let unlistenVersionDeleted: UnlistenFn | null = null; + + (async () => { + try { + unlistenDownload = await listen("download-complete", () => { + loadInstalledVersions(); + }); + } catch (err) { + // best-effort: do not break UI if listening fails + // eslint-disable-next-line no-console + console.warn("Failed to attach download-complete listener:", err); + } + + try { + unlistenVersionDeleted = await listen("version-deleted", () => { + loadInstalledVersions(); + }); + } catch (err) { + // eslint-disable-next-line no-console + console.warn("Failed to attach version-deleted listener:", err); + } + })(); + + return () => { + try { + if (unlistenDownload) unlistenDownload(); + } catch { + // ignore + } + try { + if (unlistenVersionDeleted) unlistenVersionDeleted(); + } catch { + // ignore + } + }; + }, [loadInstalledVersions]); + + const handleStartGame = async () => { + if (!selectedVersion) { + toast.info("Please select a version!"); + return; + } + + if (!instancesStore.activeInstance) { + toast.info("Please select an instance first!"); + return; + } + // await gameStore.startGame( + // authStore.currentAccount, + // authStore.openLoginModal, + // instancesStore.activeInstanceId, + // uiStore.setView, + // ); + await startGame(instancesStore.activeInstance?.id, selectedVersion); + }; + + const getVersionTypeColor = (type: string) => { + switch (type) { + case "release": + return "bg-emerald-500"; + case "snapshot": + return "bg-amber-500"; + case "old_beta": + return "bg-rose-500"; + case "old_alpha": + return "bg-violet-500"; + default: + return "bg-gray-500"; + } + }; + + const versionOptions = useMemo( + () => + installedVersions.map((v) => ({ + label: `${v.id}${v.type !== "release" ? ` (${v.type})` : ""}`, + value: v.id, + type: v.type, + })), + [installedVersions], + ); + + return ( + <div className="absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/30 via-transparent to-transparent p-4 z-10"> + <div className="max-w-7xl mx-auto"> + <div className="flex items-center justify-between bg-white/5 dark:bg-black/20 backdrop-blur-xl border border-white/10 dark:border-white/5 p-3 shadow-lg"> + <div className="flex items-center gap-4"> + <div className="flex flex-col"> + <span className="text-xs font-mono text-zinc-400 uppercase tracking-wider"> + Active Instance + </span> + <span className="text-sm font-medium text-white"> + {instancesStore.activeInstance?.name || "No instance selected"} + </span> + </div> + + <Select + items={versionOptions} + onValueChange={setSelectedVersion} + disabled={isLoadingVersions} + > + <SelectTrigger className="max-w-48"> + <SelectValue + placeholder={ + isLoadingVersions + ? "Loading versions..." + : "Please select a version" + } + /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + {versionOptions.map((item) => ( + <SelectItem + key={item.value} + value={item.value} + className={getVersionTypeColor(item.type)} + > + {item.label} + </SelectItem> + ))} + </SelectGroup> + </SelectContent> + </Select> + </div> + + <div className="flex items-center gap-3"> + {authStore.account ? ( + <Button + className={cn( + "px-4 py-2 shadow-xl", + "bg-emerald-600! hover:bg-emerald-500!", + )} + size="lg" + onClick={handleStartGame} + > + <Play /> + Start + </Button> + ) : ( + <Button + className="px-4 py-2" + size="lg" + onClick={() => setShowLoginModal(true)} + > + <User /> Login + </Button> + )} + </div> + </div> + </div> + + <LoginModal + open={showLoginModal} + onOpenChange={() => setShowLoginModal(false)} + /> + </div> + ); +} |