优化项目启动器的创建功能
This commit is contained in:
@@ -1,7 +1,60 @@
|
|||||||
|
use serde::Serialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::app_error::AppCommandError;
|
use crate::app_error::AppCommandError;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Package manager detection
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct PackageManagerInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub installed: bool,
|
||||||
|
pub version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
#[tauri::command]
|
||||||
pub async fn create_shadcn_project(
|
pub async fn create_shadcn_project(
|
||||||
project_name: String,
|
project_name: String,
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ pub fn run() {
|
|||||||
windows::open_stash_window,
|
windows::open_stash_window,
|
||||||
windows::open_push_window,
|
windows::open_push_window,
|
||||||
windows::open_project_boot_window,
|
windows::open_project_boot_window,
|
||||||
|
project_boot::detect_package_manager,
|
||||||
project_boot::create_shadcn_project,
|
project_boot::create_shadcn_project,
|
||||||
system_settings::get_system_proxy_settings,
|
system_settings::get_system_proxy_settings,
|
||||||
system_settings::update_system_proxy_settings,
|
system_settings::update_system_proxy_settings,
|
||||||
|
|||||||
@@ -1,19 +1,37 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState, useEffect, useCallback } from "react"
|
||||||
import { useTranslations } from "next-intl"
|
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 { toast } from "sonner"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Switch } from "@/components/ui/switch"
|
||||||
import {
|
import {
|
||||||
Select,
|
Tabs,
|
||||||
SelectContent,
|
TabsList,
|
||||||
SelectItem,
|
TabsTrigger,
|
||||||
SelectTrigger,
|
TabsContent,
|
||||||
SelectValue,
|
} from "@/components/ui/tabs"
|
||||||
} from "@/components/ui/select"
|
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 {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -22,9 +40,17 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { openFileDialog } from "@/lib/platform"
|
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 { 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 {
|
interface CreateProjectDialogProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -42,9 +68,38 @@ export function CreateProjectDialog({
|
|||||||
const [framework, setFramework] = useState("next")
|
const [framework, setFramework] = useState("next")
|
||||||
const [packageManager, setPackageManager] = useState("pnpm")
|
const [packageManager, setPackageManager] = useState("pnpm")
|
||||||
const [saveDirectory, setSaveDirectory] = useState("")
|
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 [creating, setCreating] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const [pmVersion, setPmVersion] = useState<string | null>(null)
|
||||||
|
const [pmInstalled, setPmInstalled] = useState<boolean | null>(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 handleBrowse = async () => {
|
||||||
const result = await openFileDialog({ directory: true, multiple: false })
|
const result = await openFileDialog({ directory: true, multiple: false })
|
||||||
if (!result) return
|
if (!result) return
|
||||||
@@ -81,11 +136,18 @@ export function CreateProjectDialog({
|
|||||||
setFramework("next")
|
setFramework("next")
|
||||||
setPackageManager("pnpm")
|
setPackageManager("pnpm")
|
||||||
setSaveDirectory("")
|
setSaveDirectory("")
|
||||||
|
setBase("radix")
|
||||||
|
setRtl(false)
|
||||||
|
setAdvancedOpen(false)
|
||||||
setError(null)
|
setError(null)
|
||||||
|
setPmVersion(null)
|
||||||
|
setPmInstalled(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const canCreate =
|
const canCreate =
|
||||||
projectName.trim().length > 0 && saveDirectory.trim().length > 0
|
projectName.trim().length > 0 &&
|
||||||
|
saveDirectory.trim().length > 0 &&
|
||||||
|
pmInstalled === true
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -111,46 +173,6 @@ export function CreateProjectDialog({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<Label>{t("createDialog.frameworkTemplate")}</Label>
|
|
||||||
<Select
|
|
||||||
value={framework}
|
|
||||||
onValueChange={setFramework}
|
|
||||||
disabled={creating}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{FRAMEWORK_OPTIONS.map((opt) => (
|
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<Label>{t("createDialog.packageManager")}</Label>
|
|
||||||
<Select
|
|
||||||
value={packageManager}
|
|
||||||
onValueChange={setPackageManager}
|
|
||||||
disabled={creating}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{PACKAGE_MANAGER_OPTIONS.map((opt) => (
|
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>{t("createDialog.saveDirectory")}</Label>
|
<Label>{t("createDialog.saveDirectory")}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -173,6 +195,135 @@ export function CreateProjectDialog({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label>{t("createDialog.packageManager")}</Label>
|
||||||
|
<Tabs
|
||||||
|
value={packageManager}
|
||||||
|
onValueChange={setPackageManager}
|
||||||
|
className="gap-0"
|
||||||
|
>
|
||||||
|
<TabsList className="w-full">
|
||||||
|
{PACKAGE_MANAGER_OPTIONS.map((opt) => (
|
||||||
|
<TabsTrigger
|
||||||
|
key={opt.value}
|
||||||
|
value={opt.value}
|
||||||
|
className="flex-1"
|
||||||
|
disabled={creating}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
{PACKAGE_MANAGER_OPTIONS.map((opt) => (
|
||||||
|
<TabsContent key={opt.value} value={opt.value}>
|
||||||
|
<div className="flex h-8 items-center gap-1.5 text-sm">
|
||||||
|
{pmChecking ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="size-3.5 animate-spin text-muted-foreground" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("createDialog.pmChecking")}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : pmInstalled ? (
|
||||||
|
<>
|
||||||
|
<CircleCheck className="size-3.5 text-emerald-500" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{opt.label} v{pmVersion}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CircleX className="size-3.5 text-destructive" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("createDialog.pmNotInstalled")}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-auto gap-1 px-0 text-xs text-muted-foreground"
|
||||||
|
disabled={creating}
|
||||||
|
>
|
||||||
|
<ChevronsUpDown className="size-3.5" />
|
||||||
|
{t("createDialog.advancedOptions")}
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent className="space-y-4 pt-2">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label>{t("createDialog.frameworkTemplate")}</Label>
|
||||||
|
<RadioGroup
|
||||||
|
value={framework}
|
||||||
|
onValueChange={setFramework}
|
||||||
|
disabled={creating}
|
||||||
|
className="grid grid-cols-2 gap-2"
|
||||||
|
>
|
||||||
|
{FRAMEWORK_OPTIONS.map((opt) => (
|
||||||
|
<FieldLabel key={opt.value} htmlFor={`fw-${opt.value}`}>
|
||||||
|
<Field orientation="horizontal">
|
||||||
|
<FieldContent>
|
||||||
|
<FieldTitle>{opt.label}</FieldTitle>
|
||||||
|
</FieldContent>
|
||||||
|
<RadioGroupItem
|
||||||
|
value={opt.value}
|
||||||
|
id={`fw-${opt.value}`}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</FieldLabel>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label>{t("createDialog.base")}</Label>
|
||||||
|
<RadioGroup
|
||||||
|
value={base}
|
||||||
|
onValueChange={setBase}
|
||||||
|
disabled={creating}
|
||||||
|
className="grid grid-cols-2 gap-2"
|
||||||
|
>
|
||||||
|
{BASE_OPTIONS.map((opt) => (
|
||||||
|
<FieldLabel key={opt.value} htmlFor={`base-${opt.value}`}>
|
||||||
|
<Field orientation="horizontal">
|
||||||
|
<FieldContent>
|
||||||
|
<FieldTitle>{opt.label}</FieldTitle>
|
||||||
|
</FieldContent>
|
||||||
|
<RadioGroupItem
|
||||||
|
value={opt.value}
|
||||||
|
id={`base-${opt.value}`}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</FieldLabel>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="flex cursor-pointer items-center gap-3 rounded-lg border p-3">
|
||||||
|
<Switch
|
||||||
|
checked={rtl}
|
||||||
|
onCheckedChange={setRtl}
|
||||||
|
disabled={creating}
|
||||||
|
/>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<div className="text-sm font-medium">
|
||||||
|
{t("createDialog.enableRtl")}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
{t("createDialog.enableRtlDescription")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
{error}
|
{error}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import {
|
import {
|
||||||
BASE_OPTIONS,
|
|
||||||
STYLE_OPTIONS,
|
STYLE_OPTIONS,
|
||||||
BASE_COLOR_OPTIONS,
|
BASE_COLOR_OPTIONS,
|
||||||
THEME_OPTIONS,
|
THEME_OPTIONS,
|
||||||
@@ -35,7 +34,6 @@ interface ShadcnConfigPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ConfigI18nKey =
|
type ConfigI18nKey =
|
||||||
| "config.base"
|
|
||||||
| "config.style"
|
| "config.style"
|
||||||
| "config.baseColor"
|
| "config.baseColor"
|
||||||
| "config.theme"
|
| "config.theme"
|
||||||
@@ -53,7 +51,6 @@ const CONFIG_FIELDS: {
|
|||||||
i18nKey: ConfigI18nKey
|
i18nKey: ConfigI18nKey
|
||||||
options: { value: string; label: string }[]
|
options: { value: string; label: string }[]
|
||||||
}[] = [
|
}[] = [
|
||||||
{ key: "base", i18nKey: "config.base", options: BASE_OPTIONS },
|
|
||||||
{ key: "style", i18nKey: "config.style", options: STYLE_OPTIONS },
|
{ key: "style", i18nKey: "config.style", options: STYLE_OPTIONS },
|
||||||
{ key: "baseColor", i18nKey: "config.baseColor", options: BASE_COLOR_OPTIONS },
|
{ key: "baseColor", i18nKey: "config.baseColor", options: BASE_COLOR_OPTIONS },
|
||||||
{ key: "theme", i18nKey: "config.theme", options: THEME_OPTIONS },
|
{ key: "theme", i18nKey: "config.theme", options: THEME_OPTIONS },
|
||||||
|
|||||||
44
src/components/ui/radio-group.tsx
Normal file
44
src/components/ui/radio-group.tsx
Normal file
@@ -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<typeof RadioGroupPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Root
|
||||||
|
data-slot="radio-group"
|
||||||
|
className={cn("grid w-full gap-3", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RadioGroupItem({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
data-slot="radio-group-item"
|
||||||
|
className={cn(
|
||||||
|
"group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<RadioGroupPrimitive.Indicator
|
||||||
|
data-slot="radio-group-indicator"
|
||||||
|
className="flex size-4 items-center justify-center"
|
||||||
|
>
|
||||||
|
<span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" />
|
||||||
|
</RadioGroupPrimitive.Indicator>
|
||||||
|
</RadioGroupPrimitive.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { RadioGroup, RadioGroupItem }
|
||||||
@@ -63,7 +63,7 @@ function TabsTrigger({
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
data-slot="tabs-trigger"
|
data-slot="tabs-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"gap-1.5 rounded-xl border border-transparent px-2 py-1 text-sm font-medium group-data-vertical/tabs:px-2.5 group-data-vertical/tabs:py-1.5 [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"gap-1.5 rounded-xl border border-transparent px-2 py-1 text-sm font-medium group-data-vertical/tabs:px-2.5 group-data-vertical/tabs:py-1.5 [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-full flex-1 items-center justify-center whitespace-nowrap transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
||||||
"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
|
"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
|
||||||
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "دليل الحفظ",
|
"saveDirectory": "دليل الحفظ",
|
||||||
"saveDirectoryPlaceholder": "اختر الدليل...",
|
"saveDirectoryPlaceholder": "اختر الدليل...",
|
||||||
"browseDirectory": "تصفح",
|
"browseDirectory": "تصفح",
|
||||||
|
"advancedOptions": "خيارات متقدمة",
|
||||||
|
"base": "المكتبة الأساسية",
|
||||||
|
"enableRtl": "تفعيل دعم RTL",
|
||||||
|
"enableRtlDescription": "تفعيل دعم التخطيط للغات التي تُكتب من اليمين إلى اليسار (مثل العربية والعبرية)",
|
||||||
|
"pmChecking": "جارٍ التحقق...",
|
||||||
|
"pmNotInstalled": "غير مثبت",
|
||||||
"cancel": "إلغاء",
|
"cancel": "إلغاء",
|
||||||
"create": "إنشاء",
|
"create": "إنشاء",
|
||||||
"creating": "جاري إنشاء المشروع..."
|
"creating": "جاري إنشاء المشروع..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "Speicherverzeichnis",
|
"saveDirectory": "Speicherverzeichnis",
|
||||||
"saveDirectoryPlaceholder": "Verzeichnis auswählen...",
|
"saveDirectoryPlaceholder": "Verzeichnis auswählen...",
|
||||||
"browseDirectory": "Durchsuchen",
|
"browseDirectory": "Durchsuchen",
|
||||||
|
"advancedOptions": "Erweiterte Optionen",
|
||||||
|
"base": "Basisbibliothek",
|
||||||
|
"enableRtl": "RTL-Unterstützung aktivieren",
|
||||||
|
"enableRtlDescription": "Layout-Unterstützung für Rechts-nach-links-Sprachen (z.B. Arabisch, Hebräisch) aktivieren",
|
||||||
|
"pmChecking": "Wird überprüft...",
|
||||||
|
"pmNotInstalled": "Nicht installiert",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"create": "Erstellen",
|
"create": "Erstellen",
|
||||||
"creating": "Projekt wird erstellt..."
|
"creating": "Projekt wird erstellt..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "Save Directory",
|
"saveDirectory": "Save Directory",
|
||||||
"saveDirectoryPlaceholder": "Select directory...",
|
"saveDirectoryPlaceholder": "Select directory...",
|
||||||
"browseDirectory": "Browse",
|
"browseDirectory": "Browse",
|
||||||
|
"advancedOptions": "Advanced Options",
|
||||||
|
"base": "Base Library",
|
||||||
|
"enableRtl": "Enable RTL Support",
|
||||||
|
"enableRtlDescription": "Enable layout support for right-to-left languages (e.g. Arabic, Hebrew)",
|
||||||
|
"pmChecking": "Checking...",
|
||||||
|
"pmNotInstalled": "Not installed",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"creating": "Creating project..."
|
"creating": "Creating project..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "Directorio de guardado",
|
"saveDirectory": "Directorio de guardado",
|
||||||
"saveDirectoryPlaceholder": "Seleccionar directorio...",
|
"saveDirectoryPlaceholder": "Seleccionar directorio...",
|
||||||
"browseDirectory": "Explorar",
|
"browseDirectory": "Explorar",
|
||||||
|
"advancedOptions": "Opciones Avanzadas",
|
||||||
|
"base": "Biblioteca Base",
|
||||||
|
"enableRtl": "Habilitar Soporte RTL",
|
||||||
|
"enableRtlDescription": "Habilitar soporte de diseño para idiomas de derecha a izquierda (ej. árabe, hebreo)",
|
||||||
|
"pmChecking": "Verificando...",
|
||||||
|
"pmNotInstalled": "No instalado",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"create": "Crear",
|
"create": "Crear",
|
||||||
"creating": "Creando proyecto..."
|
"creating": "Creando proyecto..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "Répertoire de sauvegarde",
|
"saveDirectory": "Répertoire de sauvegarde",
|
||||||
"saveDirectoryPlaceholder": "Sélectionner un répertoire...",
|
"saveDirectoryPlaceholder": "Sélectionner un répertoire...",
|
||||||
"browseDirectory": "Parcourir",
|
"browseDirectory": "Parcourir",
|
||||||
|
"advancedOptions": "Options avancées",
|
||||||
|
"base": "Bibliothèque de base",
|
||||||
|
"enableRtl": "Activer le support RTL",
|
||||||
|
"enableRtlDescription": "Activer la prise en charge de la mise en page pour les langues de droite à gauche (ex. arabe, hébreu)",
|
||||||
|
"pmChecking": "Vérification...",
|
||||||
|
"pmNotInstalled": "Non installé",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"create": "Créer",
|
"create": "Créer",
|
||||||
"creating": "Création du projet..."
|
"creating": "Création du projet..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "保存先ディレクトリ",
|
"saveDirectory": "保存先ディレクトリ",
|
||||||
"saveDirectoryPlaceholder": "ディレクトリを選択...",
|
"saveDirectoryPlaceholder": "ディレクトリを選択...",
|
||||||
"browseDirectory": "参照",
|
"browseDirectory": "参照",
|
||||||
|
"advancedOptions": "詳細オプション",
|
||||||
|
"base": "基盤ライブラリ",
|
||||||
|
"enableRtl": "RTL サポートを有効にする",
|
||||||
|
"enableRtlDescription": "右から左に書く言語(アラビア語、ヘブライ語など)のレイアウトサポートを有効にする",
|
||||||
|
"pmChecking": "確認中...",
|
||||||
|
"pmNotInstalled": "未インストール",
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
"create": "作成",
|
"create": "作成",
|
||||||
"creating": "プロジェクトを作成中..."
|
"creating": "プロジェクトを作成中..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "저장 디렉토리",
|
"saveDirectory": "저장 디렉토리",
|
||||||
"saveDirectoryPlaceholder": "디렉토리 선택...",
|
"saveDirectoryPlaceholder": "디렉토리 선택...",
|
||||||
"browseDirectory": "찾아보기",
|
"browseDirectory": "찾아보기",
|
||||||
|
"advancedOptions": "고급 옵션",
|
||||||
|
"base": "기본 라이브러리",
|
||||||
|
"enableRtl": "RTL 지원 활성화",
|
||||||
|
"enableRtlDescription": "오른쪽에서 왼쪽으로 쓰는 언어(예: 아랍어, 히브리어)의 레이아웃 지원 활성화",
|
||||||
|
"pmChecking": "확인 중...",
|
||||||
|
"pmNotInstalled": "설치되지 않음",
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"create": "만들기",
|
"create": "만들기",
|
||||||
"creating": "프로젝트 생성 중..."
|
"creating": "프로젝트 생성 중..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "Diretório de salvamento",
|
"saveDirectory": "Diretório de salvamento",
|
||||||
"saveDirectoryPlaceholder": "Selecionar diretório...",
|
"saveDirectoryPlaceholder": "Selecionar diretório...",
|
||||||
"browseDirectory": "Procurar",
|
"browseDirectory": "Procurar",
|
||||||
|
"advancedOptions": "Opções Avançadas",
|
||||||
|
"base": "Biblioteca Base",
|
||||||
|
"enableRtl": "Ativar Suporte RTL",
|
||||||
|
"enableRtlDescription": "Ativar suporte de layout para idiomas da direita para a esquerda (ex.: árabe, hebraico)",
|
||||||
|
"pmChecking": "Verificando...",
|
||||||
|
"pmNotInstalled": "Não instalado",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"create": "Criar",
|
"create": "Criar",
|
||||||
"creating": "Criando projeto..."
|
"creating": "Criando projeto..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "保存目录",
|
"saveDirectory": "保存目录",
|
||||||
"saveDirectoryPlaceholder": "选择目录...",
|
"saveDirectoryPlaceholder": "选择目录...",
|
||||||
"browseDirectory": "浏览",
|
"browseDirectory": "浏览",
|
||||||
|
"advancedOptions": "高级选项",
|
||||||
|
"base": "基础库",
|
||||||
|
"enableRtl": "启用 RTL 支持",
|
||||||
|
"enableRtlDescription": "为从右到左书写的语言(如阿拉伯语、希伯来语)启用布局支持",
|
||||||
|
"pmChecking": "正在检测...",
|
||||||
|
"pmNotInstalled": "未安装",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"create": "创建",
|
"create": "创建",
|
||||||
"creating": "正在创建项目..."
|
"creating": "正在创建项目..."
|
||||||
|
|||||||
@@ -1606,6 +1606,12 @@
|
|||||||
"saveDirectory": "儲存目錄",
|
"saveDirectory": "儲存目錄",
|
||||||
"saveDirectoryPlaceholder": "選擇目錄...",
|
"saveDirectoryPlaceholder": "選擇目錄...",
|
||||||
"browseDirectory": "瀏覽",
|
"browseDirectory": "瀏覽",
|
||||||
|
"advancedOptions": "進階選項",
|
||||||
|
"base": "基礎庫",
|
||||||
|
"enableRtl": "啟用 RTL 支援",
|
||||||
|
"enableRtlDescription": "為從右到左書寫的語言(如阿拉伯語、希伯來語)啟用佈局支援",
|
||||||
|
"pmChecking": "正在檢測...",
|
||||||
|
"pmNotInstalled": "未安裝",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"create": "建立",
|
"create": "建立",
|
||||||
"creating": "正在建立專案..."
|
"creating": "正在建立專案..."
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import type {
|
|||||||
SystemProxySettings,
|
SystemProxySettings,
|
||||||
GitCredentials,
|
GitCredentials,
|
||||||
GitDetectResult,
|
GitDetectResult,
|
||||||
|
PackageManagerInfo,
|
||||||
GitSettings,
|
GitSettings,
|
||||||
GitHubAccountsSettings,
|
GitHubAccountsSettings,
|
||||||
GitHubTokenValidation,
|
GitHubTokenValidation,
|
||||||
@@ -958,6 +959,12 @@ export async function openProjectBootWindow(): Promise<void> {
|
|||||||
window.open("/project-boot", "project-boot")
|
window.open("/project-boot", "project-boot")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function detectPackageManager(
|
||||||
|
name: string
|
||||||
|
): Promise<PackageManagerInfo> {
|
||||||
|
return getTransport().call("detect_package_manager", { name })
|
||||||
|
}
|
||||||
|
|
||||||
export async function createShadcnProject(params: {
|
export async function createShadcnProject(params: {
|
||||||
projectName: string
|
projectName: string
|
||||||
template: string
|
template: string
|
||||||
|
|||||||
@@ -536,6 +536,12 @@ export interface GitDetectResult {
|
|||||||
path: string | null
|
path: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PackageManagerInfo {
|
||||||
|
name: string
|
||||||
|
installed: boolean
|
||||||
|
version: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export interface GitSettings {
|
export interface GitSettings {
|
||||||
custom_path: string | null
|
custom_path: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user