From 3b080c801b12354910401b08e4e1e35f43e246be Mon Sep 17 00:00:00 2001 From: xintaofei Date: Fri, 27 Mar 2026 15:08:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E5=99=A8=E7=9A=84=E5=88=9B=E5=BB=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/commands/project_boot.rs | 53 ++++ src-tauri/src/lib.rs | 1 + .../shadcn/create-project-dialog.tsx | 253 ++++++++++++++---- .../shadcn/shadcn-config-panel.tsx | 3 - src/components/ui/radio-group.tsx | 44 +++ src/components/ui/tabs.tsx | 2 +- src/i18n/messages/ar.json | 6 + src/i18n/messages/de.json | 6 + src/i18n/messages/en.json | 6 + src/i18n/messages/es.json | 6 + src/i18n/messages/fr.json | 6 + src/i18n/messages/ja.json | 6 + src/i18n/messages/ko.json | 6 + src/i18n/messages/pt.json | 6 + src/i18n/messages/zh-CN.json | 6 + src/i18n/messages/zh-TW.json | 6 + src/lib/api.ts | 7 + src/lib/types.ts | 6 + 18 files changed, 374 insertions(+), 55 deletions(-) create mode 100644 src/components/ui/radio-group.tsx diff --git a/src-tauri/src/commands/project_boot.rs b/src-tauri/src/commands/project_boot.rs index 20bd145..d0efda3 100644 --- a/src-tauri/src/commands/project_boot.rs +++ b/src-tauri/src/commands/project_boot.rs @@ -1,7 +1,60 @@ +use serde::Serialize; use std::path::PathBuf; use crate::app_error::AppCommandError; +// --------------------------------------------------------------------------- +// Package manager detection +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Serialize)] +pub struct PackageManagerInfo { + pub name: String, + pub installed: bool, + pub version: Option, +} + +async fn detect_one(name: &str) -> PackageManagerInfo { + let program = match name { + "bun" => "bun", + "pnpm" => "pnpm", + "yarn" => "yarn", + _ => "npm", + }; + + let result = crate::process::tokio_command(program) + .arg("--version") + .output() + .await; + + match result { + Ok(output) if output.status.success() => { + let version = String::from_utf8_lossy(&output.stdout) + .trim() + .to_string(); + PackageManagerInfo { + name: name.to_string(), + installed: true, + version: Some(version), + } + } + _ => PackageManagerInfo { + name: name.to_string(), + installed: false, + version: None, + }, + } +} + +#[tauri::command] +pub async fn detect_package_manager(name: String) -> PackageManagerInfo { + detect_one(&name).await +} + +// --------------------------------------------------------------------------- +// Project creation +// --------------------------------------------------------------------------- + #[tauri::command] pub async fn create_shadcn_project( project_name: String, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 8cc204c..5d40719 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -270,6 +270,7 @@ pub fn run() { windows::open_stash_window, windows::open_push_window, windows::open_project_boot_window, + project_boot::detect_package_manager, project_boot::create_shadcn_project, system_settings::get_system_proxy_settings, system_settings::update_system_proxy_settings, diff --git a/src/components/project-boot/shadcn/create-project-dialog.tsx b/src/components/project-boot/shadcn/create-project-dialog.tsx index b5fd662..b598a92 100644 --- a/src/components/project-boot/shadcn/create-project-dialog.tsx +++ b/src/components/project-boot/shadcn/create-project-dialog.tsx @@ -1,19 +1,37 @@ "use client" -import { useState } from "react" +import { useState, useEffect, useCallback } from "react" import { useTranslations } from "next-intl" -import { Loader2, FolderOpen } from "lucide-react" +import { + Loader2, + FolderOpen, + ChevronsUpDown, + CircleCheck, + CircleX, +} from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" +import { Switch } from "@/components/ui/switch" import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@/components/ui/tabs" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" +import { + Field, + FieldContent, + FieldLabel, + FieldTitle, +} from "@/components/ui/field" +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible" import { Dialog, DialogContent, @@ -22,9 +40,17 @@ import { DialogTitle, } from "@/components/ui/dialog" import { openFileDialog } from "@/lib/platform" -import { createShadcnProject, openFolderWindow } from "@/lib/api" +import { + createShadcnProject, + openFolderWindow, + detectPackageManager, +} from "@/lib/api" import { toErrorMessage } from "@/lib/app-error" -import { FRAMEWORK_OPTIONS, PACKAGE_MANAGER_OPTIONS } from "./constants" +import { + BASE_OPTIONS, + FRAMEWORK_OPTIONS, + PACKAGE_MANAGER_OPTIONS, +} from "./constants" interface CreateProjectDialogProps { open: boolean @@ -42,9 +68,38 @@ export function CreateProjectDialog({ const [framework, setFramework] = useState("next") const [packageManager, setPackageManager] = useState("pnpm") const [saveDirectory, setSaveDirectory] = useState("") + const [base, setBase] = useState("radix") + const [rtl, setRtl] = useState(false) + const [advancedOpen, setAdvancedOpen] = useState(false) const [creating, setCreating] = useState(false) const [error, setError] = useState(null) + const [pmVersion, setPmVersion] = useState(null) + const [pmInstalled, setPmInstalled] = useState(null) + const [pmChecking, setPmChecking] = useState(false) + + const checkPackageManager = useCallback(async (name: string) => { + setPmChecking(true) + setPmInstalled(null) + setPmVersion(null) + try { + const info = await detectPackageManager(name) + setPmInstalled(info.installed) + setPmVersion(info.version ?? null) + } catch { + setPmInstalled(false) + setPmVersion(null) + } finally { + setPmChecking(false) + } + }, []) + + useEffect(() => { + if (open) { + checkPackageManager(packageManager) + } + }, [open, packageManager, checkPackageManager]) + const handleBrowse = async () => { const result = await openFileDialog({ directory: true, multiple: false }) if (!result) return @@ -81,11 +136,18 @@ export function CreateProjectDialog({ setFramework("next") setPackageManager("pnpm") setSaveDirectory("") + setBase("radix") + setRtl(false) + setAdvancedOpen(false) setError(null) + setPmVersion(null) + setPmInstalled(null) } const canCreate = - projectName.trim().length > 0 && saveDirectory.trim().length > 0 + projectName.trim().length > 0 && + saveDirectory.trim().length > 0 && + pmInstalled === true return ( -
- - -
- -
- - -
-
@@ -173,6 +195,135 @@ export function CreateProjectDialog({
+
+ + + + {PACKAGE_MANAGER_OPTIONS.map((opt) => ( + + {opt.label} + + ))} + + {PACKAGE_MANAGER_OPTIONS.map((opt) => ( + +
+ {pmChecking ? ( + <> + + + {t("createDialog.pmChecking")} + + + ) : pmInstalled ? ( + <> + + + {opt.label} v{pmVersion} + + + ) : ( + <> + + + {t("createDialog.pmNotInstalled")} + + + )} +
+
+ ))} +
+
+ + + + + + +
+ + + {FRAMEWORK_OPTIONS.map((opt) => ( + + + + {opt.label} + + + + + ))} + +
+ +
+ + + {BASE_OPTIONS.map((opt) => ( + + + + {opt.label} + + + + + ))} + +
+ + +
+
+ {error && (
{error} diff --git a/src/components/project-boot/shadcn/shadcn-config-panel.tsx b/src/components/project-boot/shadcn/shadcn-config-panel.tsx index f168273..62a8e9e 100644 --- a/src/components/project-boot/shadcn/shadcn-config-panel.tsx +++ b/src/components/project-boot/shadcn/shadcn-config-panel.tsx @@ -13,7 +13,6 @@ import { } from "@/components/ui/select" import { ScrollArea } from "@/components/ui/scroll-area" import { - BASE_OPTIONS, STYLE_OPTIONS, BASE_COLOR_OPTIONS, THEME_OPTIONS, @@ -35,7 +34,6 @@ interface ShadcnConfigPanelProps { } type ConfigI18nKey = - | "config.base" | "config.style" | "config.baseColor" | "config.theme" @@ -53,7 +51,6 @@ const CONFIG_FIELDS: { i18nKey: ConfigI18nKey options: { value: string; label: string }[] }[] = [ - { key: "base", i18nKey: "config.base", options: BASE_OPTIONS }, { key: "style", i18nKey: "config.style", options: STYLE_OPTIONS }, { key: "baseColor", i18nKey: "config.baseColor", options: BASE_COLOR_OPTIONS }, { key: "theme", i18nKey: "config.theme", options: THEME_OPTIONS }, diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx new file mode 100644 index 0000000..da46bc3 --- /dev/null +++ b/src/components/ui/radio-group.tsx @@ -0,0 +1,44 @@ +"use client" + +import * as React from "react" +import { RadioGroup as RadioGroupPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function RadioGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function RadioGroupItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { RadioGroup, RadioGroupItem } diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index 11aff07..7c34f86 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -63,7 +63,7 @@ function TabsTrigger({ { window.open("/project-boot", "project-boot") } +export async function detectPackageManager( + name: string +): Promise { + return getTransport().call("detect_package_manager", { name }) +} + export async function createShadcnProject(params: { projectName: string template: string diff --git a/src/lib/types.ts b/src/lib/types.ts index 3dc8603..b6cd983 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -536,6 +536,12 @@ export interface GitDetectResult { path: string | null } +export interface PackageManagerInfo { + name: string + installed: boolean + version: string | null +} + export interface GitSettings { custom_path: string | null }