aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui/src/components/bottom-bar.tsx
diff options
context:
space:
mode:
author苏向夜 <fu050409@163.com>2026-02-25 01:32:51 +0800
committer苏向夜 <fu050409@163.com>2026-02-25 01:32:51 +0800
commit66668d85d603c5841d755a6023aa1925559fc6d4 (patch)
tree485464148c76b0021efb55b7d2afd1c3004ceee0 /packages/ui/src/components/bottom-bar.tsx
parenta6773bd092db654360c599ca6b0108ea0e456e8c (diff)
downloadDropOut-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.tsx231
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>
+ );
+}