refactor(workspace): remove welcome page and render conversation panel directly
This commit is contained in:
@@ -1,15 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { ConversationDetailPanel } from "@/components/conversations/conversation-detail-panel"
|
import { ConversationDetailPanel } from "@/components/conversations/conversation-detail-panel"
|
||||||
import { useAppWorkspace } from "@/contexts/app-workspace-context"
|
|
||||||
import { WorkspaceEmpty } from "@/components/workspace-empty/workspace-empty"
|
|
||||||
|
|
||||||
export default function WorkspacePage() {
|
export default function WorkspacePage() {
|
||||||
const { folders, foldersHydrated } = useAppWorkspace()
|
|
||||||
|
|
||||||
if (foldersHydrated && folders.length === 0) {
|
|
||||||
return <WorkspaceEmpty />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ConversationDetailPanel />
|
return <ConversationDetailPanel />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1389,7 +1389,6 @@ export function ConversationDetailPanel() {
|
|||||||
|
|
||||||
const canTile = isTileMode && tabs.length > 1
|
const canTile = isTileMode && tabs.length > 1
|
||||||
|
|
||||||
// Empty state: no tabs at all — show full-screen welcome
|
|
||||||
if (hasNoTabs) {
|
if (hasNoTabs) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useState, useMemo } from "react"
|
|
||||||
import { useTranslations } from "next-intl"
|
|
||||||
import { toast } from "sonner"
|
|
||||||
import { cloneRepository } from "@/lib/api"
|
|
||||||
import { isDesktop, openFileDialog } from "@/lib/platform"
|
|
||||||
import { useAppWorkspace } from "@/contexts/app-workspace-context"
|
|
||||||
import { useGitCredential } from "@/contexts/git-credential-context"
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogFooter,
|
|
||||||
} from "@/components/ui/dialog"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
import { FolderOpen, Loader2 } from "lucide-react"
|
|
||||||
import { resolveCloneError } from "@/components/workspace-empty/error-utils"
|
|
||||||
import { DirectoryBrowserDialog } from "@/components/shared/directory-browser-dialog"
|
|
||||||
|
|
||||||
interface CloneDialogProps {
|
|
||||||
open: boolean
|
|
||||||
onOpenChange: (open: boolean) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CloneDialog({ open: isOpen, onOpenChange }: CloneDialogProps) {
|
|
||||||
const t = useTranslations("WelcomePage")
|
|
||||||
const { withCredentialRetry } = useGitCredential()
|
|
||||||
const { openFolder } = useAppWorkspace()
|
|
||||||
const [url, setUrl] = useState("")
|
|
||||||
const [targetDir, setTargetDir] = useState("")
|
|
||||||
const [cloning, setCloning] = useState(false)
|
|
||||||
const [browserOpen, setBrowserOpen] = useState(false)
|
|
||||||
const [error, setError] = useState<{
|
|
||||||
message: string
|
|
||||||
detail: string | null
|
|
||||||
} | null>(null)
|
|
||||||
|
|
||||||
const repoName = useMemo(
|
|
||||||
() =>
|
|
||||||
url
|
|
||||||
.replace(/\.git$/, "")
|
|
||||||
.split("/")
|
|
||||||
.pop() ?? "repo",
|
|
||||||
[url]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleBrowse = async () => {
|
|
||||||
if (isDesktop()) {
|
|
||||||
const selected = await openFileDialog({
|
|
||||||
directory: true,
|
|
||||||
multiple: false,
|
|
||||||
})
|
|
||||||
if (selected) {
|
|
||||||
setTargetDir(Array.isArray(selected) ? selected[0] : selected)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setBrowserOpen(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClone = async () => {
|
|
||||||
if (!url || !targetDir) return
|
|
||||||
|
|
||||||
const fullPath = `${targetDir}/${repoName}`
|
|
||||||
|
|
||||||
setCloning(true)
|
|
||||||
setError(null)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await withCredentialRetry(
|
|
||||||
(creds) => cloneRepository(url, fullPath, creds),
|
|
||||||
{ remoteUrl: url }
|
|
||||||
)
|
|
||||||
await openFolder(fullPath)
|
|
||||||
onOpenChange(false)
|
|
||||||
resetForm()
|
|
||||||
} catch (err) {
|
|
||||||
const resolvedError = resolveCloneError(err)
|
|
||||||
setError({
|
|
||||||
message: t(resolvedError.key),
|
|
||||||
detail: resolvedError.detail ?? null,
|
|
||||||
})
|
|
||||||
toast.error(t("toasts.cloneFailed"), {
|
|
||||||
description: resolvedError.detail ?? t(resolvedError.key),
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
setCloning(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
setUrl("")
|
|
||||||
setTargetDir("")
|
|
||||||
setError(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Dialog
|
|
||||||
open={isOpen}
|
|
||||||
onOpenChange={(v) => {
|
|
||||||
onOpenChange(v)
|
|
||||||
if (!v) resetForm()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>{t("cloneDialog.title")}</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="space-y-4 py-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="repo-url">{t("cloneDialog.repositoryUrl")}</Label>
|
|
||||||
<Input
|
|
||||||
id="repo-url"
|
|
||||||
placeholder={t("cloneDialog.repositoryUrlPlaceholder")}
|
|
||||||
value={url}
|
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
|
||||||
disabled={cloning}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="target-dir">{t("cloneDialog.directory")}</Label>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Input
|
|
||||||
id="target-dir"
|
|
||||||
placeholder={t("cloneDialog.directoryPlaceholder")}
|
|
||||||
value={targetDir}
|
|
||||||
onChange={(e) => setTargetDir(e.target.value)}
|
|
||||||
disabled={cloning}
|
|
||||||
className="flex-1"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
onClick={handleBrowse}
|
|
||||||
disabled={cloning}
|
|
||||||
title={t("cloneDialog.browseDirectory")}
|
|
||||||
aria-label={t("cloneDialog.browseDirectory")}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<FolderOpen className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{targetDir && url && (
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{t("cloneDialog.clonePath", {
|
|
||||||
path: `${targetDir}/${repoName}`,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-sm text-destructive">{error.message}</p>
|
|
||||||
{error.detail && (
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{error.detail}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => onOpenChange(false)}
|
|
||||||
disabled={cloning}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{t("cloneDialog.cancel")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleClone}
|
|
||||||
disabled={!url || !targetDir || cloning}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{cloning && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
|
||||||
{t("cloneDialog.clone")}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<DirectoryBrowserDialog
|
|
||||||
open={browserOpen}
|
|
||||||
onOpenChange={setBrowserOpen}
|
|
||||||
onSelect={(path) => setTargetDir(path)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
import { extractAppCommandError, toErrorMessage } from "@/lib/app-error"
|
|
||||||
|
|
||||||
export type WelcomeErrorKey =
|
|
||||||
| "errors.unknown"
|
|
||||||
| "errors.invalidInput"
|
|
||||||
| "errors.notFound"
|
|
||||||
| "errors.alreadyExists"
|
|
||||||
| "errors.dependencyMissing"
|
|
||||||
| "errors.databaseError"
|
|
||||||
| "errors.ioError"
|
|
||||||
| "errors.externalCommandFailed"
|
|
||||||
| "errors.windowOperationFailed"
|
|
||||||
| "errors.gitNotInstalled"
|
|
||||||
| "errors.targetDirectoryNotEmpty"
|
|
||||||
| "errors.repositoryNotFound"
|
|
||||||
| "errors.networkUnavailable"
|
|
||||||
| "errors.authenticationFailed"
|
|
||||||
| "errors.permissionDenied"
|
|
||||||
|
|
||||||
export interface WelcomeErrorResult {
|
|
||||||
key: WelcomeErrorKey
|
|
||||||
detail?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeErrorMessage(error: unknown): string {
|
|
||||||
return toErrorMessage(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripClonePrefix(message: string): string {
|
|
||||||
return message.replace(/^clone failed:\s*/i, "").trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapCommonCodeToKey(code: string): WelcomeErrorKey {
|
|
||||||
switch (code) {
|
|
||||||
case "invalid_input":
|
|
||||||
case "configuration_missing":
|
|
||||||
case "configuration_invalid":
|
|
||||||
return "errors.invalidInput"
|
|
||||||
case "not_found":
|
|
||||||
return "errors.notFound"
|
|
||||||
case "already_exists":
|
|
||||||
return "errors.alreadyExists"
|
|
||||||
case "permission_denied":
|
|
||||||
return "errors.permissionDenied"
|
|
||||||
case "dependency_missing":
|
|
||||||
return "errors.dependencyMissing"
|
|
||||||
case "network_error":
|
|
||||||
return "errors.networkUnavailable"
|
|
||||||
case "authentication_failed":
|
|
||||||
return "errors.authenticationFailed"
|
|
||||||
case "database_error":
|
|
||||||
return "errors.databaseError"
|
|
||||||
case "io_error":
|
|
||||||
return "errors.ioError"
|
|
||||||
case "external_command_failed":
|
|
||||||
return "errors.externalCommandFailed"
|
|
||||||
case "window_operation_failed":
|
|
||||||
return "errors.windowOperationFailed"
|
|
||||||
case "task_execution_failed":
|
|
||||||
return "errors.unknown"
|
|
||||||
default:
|
|
||||||
return "errors.unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveWelcomeError(error: unknown): WelcomeErrorResult {
|
|
||||||
const appError = extractAppCommandError(error)
|
|
||||||
if (appError) {
|
|
||||||
const key = mapCommonCodeToKey(appError.code)
|
|
||||||
const detail =
|
|
||||||
key === "errors.unknown" ? appError.detail || appError.message : undefined
|
|
||||||
|
|
||||||
return detail ? { key, detail } : { key }
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: "errors.unknown",
|
|
||||||
detail: normalizeErrorMessage(error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveCloneError(error: unknown): WelcomeErrorResult {
|
|
||||||
const appError = extractAppCommandError(error)
|
|
||||||
if (appError) {
|
|
||||||
switch (appError.code) {
|
|
||||||
case "dependency_missing":
|
|
||||||
return { key: "errors.gitNotInstalled" }
|
|
||||||
case "already_exists":
|
|
||||||
return { key: "errors.targetDirectoryNotEmpty" }
|
|
||||||
case "not_found":
|
|
||||||
return { key: "errors.repositoryNotFound" }
|
|
||||||
case "network_error":
|
|
||||||
return { key: "errors.networkUnavailable" }
|
|
||||||
case "authentication_failed":
|
|
||||||
return { key: "errors.authenticationFailed" }
|
|
||||||
case "permission_denied":
|
|
||||||
return { key: "errors.permissionDenied" }
|
|
||||||
default: {
|
|
||||||
const key = mapCommonCodeToKey(appError.code)
|
|
||||||
const detail =
|
|
||||||
key === "errors.unknown"
|
|
||||||
? appError.detail || appError.message
|
|
||||||
: undefined
|
|
||||||
return detail ? { key, detail } : { key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawMessage = normalizeErrorMessage(error)
|
|
||||||
const message = stripClonePrefix(rawMessage)
|
|
||||||
const normalized = message.toLowerCase()
|
|
||||||
|
|
||||||
if (normalized.includes("git is not installed")) {
|
|
||||||
return { key: "errors.gitNotInstalled" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalized.includes("already exists and is not an empty directory")) {
|
|
||||||
return { key: "errors.targetDirectoryNotEmpty" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalized.includes("repository not found")) {
|
|
||||||
return { key: "errors.repositoryNotFound" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
normalized.includes("could not resolve host") ||
|
|
||||||
normalized.includes("network is unreachable") ||
|
|
||||||
normalized.includes("connection timed out") ||
|
|
||||||
normalized.includes("failed to connect")
|
|
||||||
) {
|
|
||||||
return { key: "errors.networkUnavailable" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
normalized.includes("authentication failed") ||
|
|
||||||
normalized.includes("could not read username") ||
|
|
||||||
normalized.includes("permission denied (publickey)")
|
|
||||||
) {
|
|
||||||
return { key: "errors.authenticationFailed" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalized.includes("permission denied")) {
|
|
||||||
return { key: "errors.permissionDenied" }
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: "errors.unknown",
|
|
||||||
detail: message || rawMessage || undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useState } from "react"
|
|
||||||
import { FolderOpen, GitBranch, Rocket } from "lucide-react"
|
|
||||||
import { useTranslations } from "next-intl"
|
|
||||||
import { toast } from "sonner"
|
|
||||||
import { useAppWorkspace } from "@/contexts/app-workspace-context"
|
|
||||||
import { openProjectBootWindow } from "@/lib/api"
|
|
||||||
import { isDesktop, openFileDialog } from "@/lib/platform"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { CloneDialog } from "./clone-dialog"
|
|
||||||
import { resolveWelcomeError } from "@/components/workspace-empty/error-utils"
|
|
||||||
import { DirectoryBrowserDialog } from "@/components/shared/directory-browser-dialog"
|
|
||||||
|
|
||||||
export function FolderActions() {
|
|
||||||
const t = useTranslations("WelcomePage")
|
|
||||||
const { openFolder } = useAppWorkspace()
|
|
||||||
const [cloneOpen, setCloneOpen] = useState(false)
|
|
||||||
const [browserOpen, setBrowserOpen] = useState(false)
|
|
||||||
|
|
||||||
const handleOpen = async () => {
|
|
||||||
if (isDesktop()) {
|
|
||||||
const result = await openFileDialog({
|
|
||||||
directory: true,
|
|
||||||
multiple: false,
|
|
||||||
})
|
|
||||||
if (!result) return
|
|
||||||
const selected = Array.isArray(result) ? result[0] : result
|
|
||||||
try {
|
|
||||||
await openFolder(selected)
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[FolderActions] failed to open folder:", err)
|
|
||||||
const resolvedError = resolveWelcomeError(err)
|
|
||||||
toast.error(t("toasts.openFolderFailed"), {
|
|
||||||
description: resolvedError.detail ?? t(resolvedError.key),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setBrowserOpen(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBrowserSelect = async (path: string) => {
|
|
||||||
try {
|
|
||||||
await openFolder(path)
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[FolderActions] failed to open folder:", err)
|
|
||||||
const resolvedError = resolveWelcomeError(err)
|
|
||||||
toast.error(t("toasts.openFolderFailed"), {
|
|
||||||
description: resolvedError.detail ?? t(resolvedError.key),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full flex flex-col gap-1 px-3">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="justify-start gap-2 h-9"
|
|
||||||
onClick={handleOpen}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<FolderOpen className="h-4 w-4" />
|
|
||||||
{t("openFolder")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="justify-start gap-2 h-9"
|
|
||||||
onClick={() => setCloneOpen(true)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<GitBranch className="h-4 w-4" />
|
|
||||||
{t("cloneRepository")}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="justify-start gap-2 h-9"
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await openProjectBootWindow("welcome")
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[FolderActions] failed to open project boot:", err)
|
|
||||||
toast.error(t("toasts.openProjectBootFailed"))
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<Rocket className="h-4 w-4" />
|
|
||||||
{t("projectBoot")}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<CloneDialog open={cloneOpen} onOpenChange={setCloneOpen} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DirectoryBrowserDialog
|
|
||||||
open={browserOpen}
|
|
||||||
onOpenChange={setBrowserOpen}
|
|
||||||
onSelect={handleBrowserSelect}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useMemo, useState } from "react"
|
|
||||||
import { Search, X, FolderOpen } from "lucide-react"
|
|
||||||
import { formatDistanceToNow } from "date-fns"
|
|
||||||
import { enUS, zhCN, zhTW } from "date-fns/locale"
|
|
||||||
import { useLocale, useTranslations } from "next-intl"
|
|
||||||
import { toast } from "sonner"
|
|
||||||
import { useAppWorkspace } from "@/contexts/app-workspace-context"
|
|
||||||
import { removeFolderFromHistory } from "@/lib/api"
|
|
||||||
import type { FolderHistoryEntry } from "@/lib/types"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { resolveWelcomeError } from "@/components/workspace-empty/error-utils"
|
|
||||||
|
|
||||||
interface FolderListProps {
|
|
||||||
history: FolderHistoryEntry[]
|
|
||||||
loading: boolean
|
|
||||||
onRefresh: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FolderList({ history, loading, onRefresh }: FolderListProps) {
|
|
||||||
const t = useTranslations("WelcomePage")
|
|
||||||
const locale = useLocale()
|
|
||||||
const { openFolder } = useAppWorkspace()
|
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
const dateFnsLocale =
|
|
||||||
locale === "zh-CN" ? zhCN : locale === "zh-TW" ? zhTW : enUS
|
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
|
||||||
if (!search) return history
|
|
||||||
const lowerCaseSearch = search.toLowerCase()
|
|
||||||
|
|
||||||
return history.filter(
|
|
||||||
(h) =>
|
|
||||||
h.name.toLowerCase().includes(lowerCaseSearch) ||
|
|
||||||
h.path.toLowerCase().includes(lowerCaseSearch)
|
|
||||||
)
|
|
||||||
}, [history, search])
|
|
||||||
|
|
||||||
const handleOpen = async (path: string) => {
|
|
||||||
try {
|
|
||||||
await openFolder(path)
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to open folder:", err)
|
|
||||||
const resolvedError = resolveWelcomeError(err)
|
|
||||||
toast.error(t("toasts.openFolderFailed"), {
|
|
||||||
description: resolvedError.detail ?? t(resolvedError.key),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRemove = async (e: React.MouseEvent, path: string) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
try {
|
|
||||||
await removeFolderFromHistory(path)
|
|
||||||
onRefresh()
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to remove folder:", err)
|
|
||||||
const resolvedError = resolveWelcomeError(err)
|
|
||||||
toast.error(t("toasts.removeFromHistoryFailed"), {
|
|
||||||
description: resolvedError.detail ?? t(resolvedError.key),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex-1 flex flex-col min-w-0 p-8">
|
|
||||||
<div className="relative mb-4">
|
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
||||||
<Input
|
|
||||||
placeholder={t("searchPlaceholder")}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
|
||||||
className="pl-9 h-9"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
|
||||||
{loading ? (
|
|
||||||
<div className="flex items-center justify-center h-32 text-sm text-muted-foreground">
|
|
||||||
{t("loading")}
|
|
||||||
</div>
|
|
||||||
) : filtered.length === 0 ? (
|
|
||||||
<div className="flex flex-col items-center justify-center h-32 text-sm text-muted-foreground gap-2">
|
|
||||||
<FolderOpen className="h-8 w-8" />
|
|
||||||
<span>{t("emptyFolders")}</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
filtered.map((entry) => (
|
|
||||||
<div
|
|
||||||
key={entry.id}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className="w-full text-left p-3 rounded-lg hover:bg-accent/50 transition-colors group flex items-start gap-3 cursor-pointer"
|
|
||||||
onClick={() => handleOpen(entry.path)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
e.preventDefault()
|
|
||||||
handleOpen(entry.path)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm font-medium truncate">
|
|
||||||
{entry.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-muted-foreground truncate mt-0.5">
|
|
||||||
{entry.path}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 mt-0.5">
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{formatDistanceToNow(new Date(entry.last_opened_at), {
|
|
||||||
addSuffix: true,
|
|
||||||
locale: dateFnsLocale,
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-destructive/10 rounded shrink-0 mt-0.5"
|
|
||||||
onClick={(e) => handleRemove(e, entry.path)}
|
|
||||||
title={t("removeFromHistory")}
|
|
||||||
aria-label={t("removeFromHistory")}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground hover:text-destructive" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { AppIcon } from "@/components/app-icon"
|
|
||||||
import { useTranslations } from "next-intl"
|
|
||||||
import { getCurrentAppVersion } from "@/lib/updater"
|
|
||||||
|
|
||||||
export function SoftwareInfo() {
|
|
||||||
const t = useTranslations("WelcomePage")
|
|
||||||
const [version, setVersion] = useState<string>("")
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getCurrentAppVersion()
|
|
||||||
.then(setVersion)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("[Welcome] get app version failed:", err)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full flex items-center gap-4 px-6 py-8">
|
|
||||||
<AppIcon className="size-12" />
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="text-base">Codeg</span>
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{version
|
|
||||||
? t("softwareVersion", { version })
|
|
||||||
: t("softwareVersion", { version: "..." })}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useEffect, useState, useCallback } from "react"
|
|
||||||
import { loadFolderHistory } from "@/lib/api"
|
|
||||||
import type { FolderHistoryEntry } from "@/lib/types"
|
|
||||||
import { FolderActions } from "./folder-actions"
|
|
||||||
import { FolderList } from "./folder-list"
|
|
||||||
import { SoftwareInfo } from "./software-info"
|
|
||||||
|
|
||||||
export function WorkspaceEmpty() {
|
|
||||||
const [history, setHistory] = useState<FolderHistoryEntry[]>([])
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await loadFolderHistory()
|
|
||||||
setHistory(result)
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[WorkspaceEmpty] loadFolderHistory failed:", err)
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void refresh()
|
|
||||||
}, [refresh])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-full w-full overflow-hidden">
|
|
||||||
<aside className="flex w-72 shrink-0 flex-col border-r">
|
|
||||||
<SoftwareInfo />
|
|
||||||
<FolderActions />
|
|
||||||
</aside>
|
|
||||||
<main className="flex-1 min-w-0">
|
|
||||||
<FolderList history={history} loading={loading} onRefresh={refresh} />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "البرتغالية",
|
"portuguese": "البرتغالية",
|
||||||
"arabic": "العربية"
|
"arabic": "العربية"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "مرحبًا بك في Codeg",
|
|
||||||
"openSettings": "فتح الإعدادات",
|
|
||||||
"searchPlaceholder": "ابحث في المجلدات...",
|
|
||||||
"loading": "جارٍ التحميل...",
|
|
||||||
"emptyFolders": "لا توجد مجلدات بعد",
|
|
||||||
"removeFromHistory": "إزالة من السجل",
|
|
||||||
"openFolder": "فتح مجلد",
|
|
||||||
"cloneRepository": "استنساخ المستودع",
|
|
||||||
"projectBoot": "مُنشئ المشروع",
|
|
||||||
"softwareVersion": "الإصدار {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "فشل تحميل سجل المجلدات",
|
|
||||||
"openFolderFailed": "فشل فتح المجلد",
|
|
||||||
"removeFromHistoryFailed": "فشل إزالة المجلد من السجل",
|
|
||||||
"openSettingsFailed": "فشل فتح الإعدادات",
|
|
||||||
"cloneFailed": "فشل استنساخ المستودع",
|
|
||||||
"openProjectBootFailed": "فشل فتح مُنشئ المشروع"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "حدث خطأ غير متوقع",
|
|
||||||
"invalidInput": "إدخال غير صالح.",
|
|
||||||
"notFound": "المورد غير موجود.",
|
|
||||||
"alreadyExists": "المورد موجود بالفعل.",
|
|
||||||
"dependencyMissing": "هناك اعتماد مطلوب مفقود.",
|
|
||||||
"databaseError": "فشلت عملية قاعدة البيانات.",
|
|
||||||
"ioError": "فشلت عملية الملف.",
|
|
||||||
"externalCommandFailed": "فشل تنفيذ الأمر الخارجي.",
|
|
||||||
"windowOperationFailed": "فشلت عملية النافذة.",
|
|
||||||
"gitNotInstalled": "Git غير مثبت. يرجى تثبيت Git أولًا.",
|
|
||||||
"targetDirectoryNotEmpty": "مجلد الهدف موجود بالفعل وليس فارغًا.",
|
|
||||||
"repositoryNotFound": "المستودع غير موجود. تحقق من الرابط وصلاحيات الوصول.",
|
|
||||||
"networkUnavailable": "الشبكة غير متاحة. تحقق من الاتصال وحاول مرة أخرى.",
|
|
||||||
"authenticationFailed": "فشلت المصادقة. تحقق من بيانات الاعتماد أو مفتاح SSH.",
|
|
||||||
"permissionDenied": "تم رفض الإذن. تحقق من صلاحيات المجلد."
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "استنساخ مستودع",
|
|
||||||
"repositoryUrl": "رابط المستودع",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "المجلد",
|
|
||||||
"directoryPlaceholder": "اختر مجلد الهدف...",
|
|
||||||
"browseDirectory": "تصفح المجلد",
|
|
||||||
"cancel": "إلغاء",
|
|
||||||
"clone": "استنساخ",
|
|
||||||
"clonePath": "مسار الاستنساخ: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "المصادقة مطلوبة",
|
"title": "المصادقة مطلوبة",
|
||||||
"description": "يتطلب الخادم البعيد بيانات اعتماد. أدخل اسم المستخدم وكلمة المرور (أو رمز الوصول الشخصي).",
|
"description": "يتطلب الخادم البعيد بيانات اعتماد. أدخل اسم المستخدم وكلمة المرور (أو رمز الوصول الشخصي).",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "Portugiesisch",
|
"portuguese": "Portugiesisch",
|
||||||
"arabic": "Arabisch"
|
"arabic": "Arabisch"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "Willkommen bei Codeg",
|
|
||||||
"openSettings": "Einstellungen öffnen",
|
|
||||||
"searchPlaceholder": "Ordner suchen...",
|
|
||||||
"loading": "Wird geladen...",
|
|
||||||
"emptyFolders": "Noch keine Ordner",
|
|
||||||
"removeFromHistory": "Aus Verlauf entfernen",
|
|
||||||
"openFolder": "Ordner öffnen",
|
|
||||||
"cloneRepository": "Repository klonen",
|
|
||||||
"projectBoot": "Projekt-Starter",
|
|
||||||
"softwareVersion": "Version {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "Ordnerverlauf konnte nicht geladen werden",
|
|
||||||
"openFolderFailed": "Ordner konnte nicht geöffnet werden",
|
|
||||||
"removeFromHistoryFailed": "Ordner konnte nicht entfernt werden",
|
|
||||||
"openSettingsFailed": "Einstellungen konnten nicht geöffnet werden",
|
|
||||||
"cloneFailed": "Repository konnte nicht geklont werden",
|
|
||||||
"openProjectBootFailed": "Projekt-Starter konnte nicht geöffnet werden"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "Unerwarteter Fehler",
|
|
||||||
"invalidInput": "Ungültige Eingabe.",
|
|
||||||
"notFound": "Ressource nicht gefunden.",
|
|
||||||
"alreadyExists": "Ressource existiert bereits.",
|
|
||||||
"dependencyMissing": "Erforderliche Abhängigkeit fehlt.",
|
|
||||||
"databaseError": "Datenbankoperation fehlgeschlagen.",
|
|
||||||
"ioError": "Dateioperation fehlgeschlagen.",
|
|
||||||
"externalCommandFailed": "Externer Befehl fehlgeschlagen.",
|
|
||||||
"windowOperationFailed": "Fensteroperation fehlgeschlagen.",
|
|
||||||
"gitNotInstalled": "Git ist nicht installiert. Bitte installiere zuerst Git.",
|
|
||||||
"targetDirectoryNotEmpty": "Das Zielverzeichnis existiert bereits und ist nicht leer.",
|
|
||||||
"repositoryNotFound": "Repository nicht gefunden. Prüfe URL und Zugriffsrechte.",
|
|
||||||
"networkUnavailable": "Netzwerk ist nicht verfügbar. Prüfe die Verbindung und versuche es erneut.",
|
|
||||||
"authenticationFailed": "Authentifizierung fehlgeschlagen. Prüfe Zugangsdaten oder SSH-Schlüssel.",
|
|
||||||
"permissionDenied": "Zugriff verweigert. Prüfe die Verzeichnisrechte."
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "Repository klonen",
|
|
||||||
"repositoryUrl": "Repository-URL",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "Verzeichnis",
|
|
||||||
"directoryPlaceholder": "Zielverzeichnis auswählen...",
|
|
||||||
"browseDirectory": "Verzeichnis durchsuchen",
|
|
||||||
"cancel": "Abbrechen",
|
|
||||||
"clone": "Klonen",
|
|
||||||
"clonePath": "Klonpfad: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "Authentifizierung erforderlich",
|
"title": "Authentifizierung erforderlich",
|
||||||
"description": "Der Remote-Server erfordert Anmeldedaten. Geben Sie Ihren Benutzernamen und Ihr Passwort (oder persönliches Zugriffstoken) ein.",
|
"description": "Der Remote-Server erfordert Anmeldedaten. Geben Sie Ihren Benutzernamen und Ihr Passwort (oder persönliches Zugriffstoken) ein.",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "Portuguese",
|
"portuguese": "Portuguese",
|
||||||
"arabic": "Arabic"
|
"arabic": "Arabic"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "Welcome to Codeg",
|
|
||||||
"openSettings": "Open Settings",
|
|
||||||
"searchPlaceholder": "Search folders...",
|
|
||||||
"loading": "Loading...",
|
|
||||||
"emptyFolders": "No folders yet",
|
|
||||||
"removeFromHistory": "Remove from history",
|
|
||||||
"openFolder": "Open Folder",
|
|
||||||
"cloneRepository": "Clone Repository",
|
|
||||||
"projectBoot": "Project Boot",
|
|
||||||
"softwareVersion": "version {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "Failed to load folder history",
|
|
||||||
"openFolderFailed": "Failed to open folder",
|
|
||||||
"removeFromHistoryFailed": "Failed to remove folder",
|
|
||||||
"openSettingsFailed": "Failed to open settings",
|
|
||||||
"cloneFailed": "Failed to clone repository",
|
|
||||||
"openProjectBootFailed": "Failed to open Project Boot"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "Unexpected error",
|
|
||||||
"invalidInput": "Invalid input.",
|
|
||||||
"notFound": "Resource not found.",
|
|
||||||
"alreadyExists": "Resource already exists.",
|
|
||||||
"dependencyMissing": "Required dependency is missing.",
|
|
||||||
"databaseError": "Database operation failed.",
|
|
||||||
"ioError": "File operation failed.",
|
|
||||||
"externalCommandFailed": "External command failed.",
|
|
||||||
"windowOperationFailed": "Window operation failed.",
|
|
||||||
"gitNotInstalled": "Git is not installed. Please install Git first.",
|
|
||||||
"targetDirectoryNotEmpty": "Target directory already exists and is not empty.",
|
|
||||||
"repositoryNotFound": "Repository not found. Check URL and access permissions.",
|
|
||||||
"networkUnavailable": "Network is unavailable. Check your connection and try again.",
|
|
||||||
"authenticationFailed": "Authentication failed. Check credentials or SSH key.",
|
|
||||||
"permissionDenied": "Permission denied. Check directory permissions."
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "Clone Repository",
|
|
||||||
"repositoryUrl": "Repository URL",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "Directory",
|
|
||||||
"directoryPlaceholder": "Select target directory...",
|
|
||||||
"browseDirectory": "Browse directory",
|
|
||||||
"cancel": "Cancel",
|
|
||||||
"clone": "Clone",
|
|
||||||
"clonePath": "Clone path: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "Authentication Required",
|
"title": "Authentication Required",
|
||||||
"description": "The remote server requires credentials. Enter your username and password (or personal access token).",
|
"description": "The remote server requires credentials. Enter your username and password (or personal access token).",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "Portugués",
|
"portuguese": "Portugués",
|
||||||
"arabic": "Árabe"
|
"arabic": "Árabe"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "Bienvenido a Codeg",
|
|
||||||
"openSettings": "Abrir configuración",
|
|
||||||
"searchPlaceholder": "Buscar carpetas...",
|
|
||||||
"loading": "Cargando...",
|
|
||||||
"emptyFolders": "Aún no hay carpetas",
|
|
||||||
"removeFromHistory": "Quitar del historial",
|
|
||||||
"openFolder": "Abrir carpeta",
|
|
||||||
"cloneRepository": "Clonar repositorio",
|
|
||||||
"projectBoot": "Inicio de Proyecto",
|
|
||||||
"softwareVersion": "versión {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "No se pudo cargar el historial de carpetas",
|
|
||||||
"openFolderFailed": "No se pudo abrir la carpeta",
|
|
||||||
"removeFromHistoryFailed": "No se pudo quitar la carpeta",
|
|
||||||
"openSettingsFailed": "No se pudo abrir la configuración",
|
|
||||||
"cloneFailed": "No se pudo clonar el repositorio",
|
|
||||||
"openProjectBootFailed": "Error al abrir el inicio de proyecto"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "Error inesperado",
|
|
||||||
"invalidInput": "Entrada no válida.",
|
|
||||||
"notFound": "Recurso no encontrado.",
|
|
||||||
"alreadyExists": "El recurso ya existe.",
|
|
||||||
"dependencyMissing": "Falta una dependencia requerida.",
|
|
||||||
"databaseError": "Falló la operación de base de datos.",
|
|
||||||
"ioError": "Falló la operación de archivos.",
|
|
||||||
"externalCommandFailed": "Falló el comando externo.",
|
|
||||||
"windowOperationFailed": "Falló la operación de ventana.",
|
|
||||||
"gitNotInstalled": "Git no está instalado. Instala Git primero.",
|
|
||||||
"targetDirectoryNotEmpty": "El directorio de destino ya existe y no está vacío.",
|
|
||||||
"repositoryNotFound": "Repositorio no encontrado. Revisa la URL y los permisos de acceso.",
|
|
||||||
"networkUnavailable": "La red no está disponible. Revisa tu conexión e inténtalo de nuevo.",
|
|
||||||
"authenticationFailed": "Autenticación fallida. Revisa credenciales o clave SSH.",
|
|
||||||
"permissionDenied": "Permiso denegado. Revisa los permisos del directorio."
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "Clonar repositorio",
|
|
||||||
"repositoryUrl": "URL del repositorio",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "Directorio",
|
|
||||||
"directoryPlaceholder": "Selecciona el directorio de destino...",
|
|
||||||
"browseDirectory": "Explorar directorio",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"clone": "Clonar",
|
|
||||||
"clonePath": "Ruta de clonación: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "Autenticación requerida",
|
"title": "Autenticación requerida",
|
||||||
"description": "El servidor remoto requiere credenciales. Introduce tu nombre de usuario y contraseña (o token de acceso personal).",
|
"description": "El servidor remoto requiere credenciales. Introduce tu nombre de usuario y contraseña (o token de acceso personal).",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "Portugais",
|
"portuguese": "Portugais",
|
||||||
"arabic": "Arabe"
|
"arabic": "Arabe"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "Bienvenue sur Codeg",
|
|
||||||
"openSettings": "Ouvrir les paramètres",
|
|
||||||
"searchPlaceholder": "Rechercher des dossiers...",
|
|
||||||
"loading": "Chargement...",
|
|
||||||
"emptyFolders": "Aucun dossier pour le moment",
|
|
||||||
"removeFromHistory": "Retirer de l’historique",
|
|
||||||
"openFolder": "Ouvrir un dossier",
|
|
||||||
"cloneRepository": "Cloner un dépôt",
|
|
||||||
"projectBoot": "Lanceur de projet",
|
|
||||||
"softwareVersion": "Version {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "Échec du chargement de l’historique des dossiers",
|
|
||||||
"openFolderFailed": "Échec de l’ouverture du dossier",
|
|
||||||
"removeFromHistoryFailed": "Échec de la suppression du dossier",
|
|
||||||
"openSettingsFailed": "Échec de l’ouverture des paramètres",
|
|
||||||
"cloneFailed": "Échec du clonage du dépôt",
|
|
||||||
"openProjectBootFailed": "Impossible d'ouvrir le lanceur de projet"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "Erreur inattendue",
|
|
||||||
"invalidInput": "Entrée invalide.",
|
|
||||||
"notFound": "Ressource introuvable.",
|
|
||||||
"alreadyExists": "La ressource existe déjà.",
|
|
||||||
"dependencyMissing": "Une dépendance requise est manquante.",
|
|
||||||
"databaseError": "Échec de l’opération de base de données.",
|
|
||||||
"ioError": "Échec de l’opération de fichier.",
|
|
||||||
"externalCommandFailed": "La commande externe a échoué.",
|
|
||||||
"windowOperationFailed": "Échec de l’opération de fenêtre.",
|
|
||||||
"gitNotInstalled": "Git n’est pas installé. Veuillez installer Git d’abord.",
|
|
||||||
"targetDirectoryNotEmpty": "Le répertoire cible existe déjà et n’est pas vide.",
|
|
||||||
"repositoryNotFound": "Dépôt introuvable. Vérifiez l’URL et les autorisations d’accès.",
|
|
||||||
"networkUnavailable": "Le réseau est indisponible. Vérifiez votre connexion et réessayez.",
|
|
||||||
"authenticationFailed": "Échec de l’authentification. Vérifiez les identifiants ou la clé SSH.",
|
|
||||||
"permissionDenied": "Permission refusée. Vérifiez les autorisations du répertoire."
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "Cloner un dépôt",
|
|
||||||
"repositoryUrl": "URL du dépôt",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "Répertoire",
|
|
||||||
"directoryPlaceholder": "Sélectionnez le répertoire cible...",
|
|
||||||
"browseDirectory": "Parcourir le répertoire",
|
|
||||||
"cancel": "Annuler",
|
|
||||||
"clone": "Cloner",
|
|
||||||
"clonePath": "Chemin de clonage : {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "Authentification requise",
|
"title": "Authentification requise",
|
||||||
"description": "Le serveur distant nécessite des identifiants. Entrez votre nom d'utilisateur et votre mot de passe (ou jeton d'accès personnel).",
|
"description": "Le serveur distant nécessite des identifiants. Entrez votre nom d'utilisateur et votre mot de passe (ou jeton d'accès personnel).",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "ポルトガル語",
|
"portuguese": "ポルトガル語",
|
||||||
"arabic": "アラビア語"
|
"arabic": "アラビア語"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "Codegへようこそ",
|
|
||||||
"openSettings": "設定を開く",
|
|
||||||
"searchPlaceholder": "フォルダを検索...",
|
|
||||||
"loading": "読み込み中...",
|
|
||||||
"emptyFolders": "フォルダがありません",
|
|
||||||
"removeFromHistory": "履歴から削除",
|
|
||||||
"openFolder": "フォルダを開く",
|
|
||||||
"cloneRepository": "リポジトリをクローン",
|
|
||||||
"projectBoot": "プロジェクトブート",
|
|
||||||
"softwareVersion": "バージョン {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "フォルダ履歴の読み込みに失敗しました",
|
|
||||||
"openFolderFailed": "フォルダを開けませんでした",
|
|
||||||
"removeFromHistoryFailed": "履歴からの削除に失敗しました",
|
|
||||||
"openSettingsFailed": "設定を開けませんでした",
|
|
||||||
"cloneFailed": "リポジトリのクローンに失敗しました",
|
|
||||||
"openProjectBootFailed": "プロジェクトブートを開けませんでした"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "予期しないエラーが発生しました",
|
|
||||||
"invalidInput": "入力が無効です。",
|
|
||||||
"notFound": "リソースが見つかりません。",
|
|
||||||
"alreadyExists": "リソースは既に存在します。",
|
|
||||||
"dependencyMissing": "必要な依存関係が不足しています。",
|
|
||||||
"databaseError": "データベース操作に失敗しました。",
|
|
||||||
"ioError": "ファイル操作に失敗しました。",
|
|
||||||
"externalCommandFailed": "外部コマンドの実行に失敗しました。",
|
|
||||||
"windowOperationFailed": "ウィンドウ操作に失敗しました。",
|
|
||||||
"gitNotInstalled": "Git がインストールされていません。先に Git をインストールしてください。",
|
|
||||||
"targetDirectoryNotEmpty": "対象ディレクトリは既に存在し、空ではありません。",
|
|
||||||
"repositoryNotFound": "リポジトリが見つかりません。URL とアクセス権を確認してください。",
|
|
||||||
"networkUnavailable": "ネットワークに接続できません。接続を確認して再試行してください。",
|
|
||||||
"authenticationFailed": "認証に失敗しました。認証情報または SSH キーを確認してください。",
|
|
||||||
"permissionDenied": "権限がありません。ディレクトリ権限を確認してください。"
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "リポジトリをクローン",
|
|
||||||
"repositoryUrl": "リポジトリ URL",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "ディレクトリ",
|
|
||||||
"directoryPlaceholder": "保存先ディレクトリを選択...",
|
|
||||||
"browseDirectory": "ディレクトリを参照",
|
|
||||||
"cancel": "キャンセル",
|
|
||||||
"clone": "クローン",
|
|
||||||
"clonePath": "クローンパス: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "認証が必要です",
|
"title": "認証が必要です",
|
||||||
"description": "リモートサーバーが認証情報を要求しています。ユーザー名とパスワード(またはアクセストークン)を入力してください。",
|
"description": "リモートサーバーが認証情報を要求しています。ユーザー名とパスワード(またはアクセストークン)を入力してください。",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "포르투갈어",
|
"portuguese": "포르투갈어",
|
||||||
"arabic": "아랍어"
|
"arabic": "아랍어"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "Codeg에 오신 것을 환영합니다",
|
|
||||||
"openSettings": "설정 열기",
|
|
||||||
"searchPlaceholder": "폴더 검색...",
|
|
||||||
"loading": "로딩 중...",
|
|
||||||
"emptyFolders": "폴더가 없습니다",
|
|
||||||
"removeFromHistory": "기록에서 제거",
|
|
||||||
"openFolder": "폴더 열기",
|
|
||||||
"cloneRepository": "저장소 클론",
|
|
||||||
"projectBoot": "프로젝트 부트",
|
|
||||||
"softwareVersion": "버전 {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "폴더 기록을 불러오지 못했습니다",
|
|
||||||
"openFolderFailed": "폴더를 열지 못했습니다",
|
|
||||||
"removeFromHistoryFailed": "기록에서 제거하지 못했습니다",
|
|
||||||
"openSettingsFailed": "설정을 열지 못했습니다",
|
|
||||||
"cloneFailed": "저장소 클론에 실패했습니다",
|
|
||||||
"openProjectBootFailed": "프로젝트 부트를 열지 못했습니다"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "예기치 않은 오류가 발생했습니다",
|
|
||||||
"invalidInput": "잘못된 입력입니다.",
|
|
||||||
"notFound": "리소스를 찾을 수 없습니다.",
|
|
||||||
"alreadyExists": "리소스가 이미 존재합니다.",
|
|
||||||
"dependencyMissing": "필수 의존성이 누락되었습니다.",
|
|
||||||
"databaseError": "데이터베이스 작업에 실패했습니다.",
|
|
||||||
"ioError": "파일 작업에 실패했습니다.",
|
|
||||||
"externalCommandFailed": "외부 명령 실행에 실패했습니다.",
|
|
||||||
"windowOperationFailed": "창 작업에 실패했습니다.",
|
|
||||||
"gitNotInstalled": "Git이 설치되어 있지 않습니다. 먼저 Git을 설치하세요.",
|
|
||||||
"targetDirectoryNotEmpty": "대상 디렉터리가 이미 존재하며 비어 있지 않습니다.",
|
|
||||||
"repositoryNotFound": "저장소를 찾을 수 없습니다. URL 및 접근 권한을 확인하세요.",
|
|
||||||
"networkUnavailable": "네트워크를 사용할 수 없습니다. 연결을 확인한 뒤 다시 시도하세요.",
|
|
||||||
"authenticationFailed": "인증에 실패했습니다. 자격 증명 또는 SSH 키를 확인하세요.",
|
|
||||||
"permissionDenied": "권한이 없습니다. 디렉터리 권한을 확인하세요."
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "저장소 클론",
|
|
||||||
"repositoryUrl": "저장소 URL",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "디렉터리",
|
|
||||||
"directoryPlaceholder": "대상 디렉터리 선택...",
|
|
||||||
"browseDirectory": "디렉터리 찾아보기",
|
|
||||||
"cancel": "취소",
|
|
||||||
"clone": "클론",
|
|
||||||
"clonePath": "클론 경로: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "인증 필요",
|
"title": "인증 필요",
|
||||||
"description": "원격 서버에서 자격 증명을 요구합니다. 사용자 이름과 비밀번호(또는 개인 액세스 토큰)를 입력하세요.",
|
"description": "원격 서버에서 자격 증명을 요구합니다. 사용자 이름과 비밀번호(또는 개인 액세스 토큰)를 입력하세요.",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "Português",
|
"portuguese": "Português",
|
||||||
"arabic": "Árabe"
|
"arabic": "Árabe"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "Bem-vindo ao Codeg",
|
|
||||||
"openSettings": "Abrir configurações",
|
|
||||||
"searchPlaceholder": "Pesquisar pastas...",
|
|
||||||
"loading": "Carregando...",
|
|
||||||
"emptyFolders": "Ainda não há pastas",
|
|
||||||
"removeFromHistory": "Remover do histórico",
|
|
||||||
"openFolder": "Abrir pasta",
|
|
||||||
"cloneRepository": "Clonar repositório",
|
|
||||||
"projectBoot": "Inicializador de Projeto",
|
|
||||||
"softwareVersion": "versão {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "Falha ao carregar o histórico de pastas",
|
|
||||||
"openFolderFailed": "Falha ao abrir a pasta",
|
|
||||||
"removeFromHistoryFailed": "Falha ao remover a pasta",
|
|
||||||
"openSettingsFailed": "Falha ao abrir as configurações",
|
|
||||||
"cloneFailed": "Falha ao clonar o repositório",
|
|
||||||
"openProjectBootFailed": "Falha ao abrir o inicializador de projeto"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "Erro inesperado",
|
|
||||||
"invalidInput": "Entrada inválida.",
|
|
||||||
"notFound": "Recurso não encontrado.",
|
|
||||||
"alreadyExists": "O recurso já existe.",
|
|
||||||
"dependencyMissing": "Dependência obrigatória ausente.",
|
|
||||||
"databaseError": "Falha na operação de banco de dados.",
|
|
||||||
"ioError": "Falha na operação de arquivo.",
|
|
||||||
"externalCommandFailed": "Falha no comando externo.",
|
|
||||||
"windowOperationFailed": "Falha na operação da janela.",
|
|
||||||
"gitNotInstalled": "O Git não está instalado. Instale o Git primeiro.",
|
|
||||||
"targetDirectoryNotEmpty": "O diretório de destino já existe e não está vazio.",
|
|
||||||
"repositoryNotFound": "Repositório não encontrado. Verifique a URL e as permissões de acesso.",
|
|
||||||
"networkUnavailable": "A rede está indisponível. Verifique sua conexão e tente novamente.",
|
|
||||||
"authenticationFailed": "Falha na autenticação. Verifique as credenciais ou a chave SSH.",
|
|
||||||
"permissionDenied": "Permissão negada. Verifique as permissões do diretório."
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "Clonar repositório",
|
|
||||||
"repositoryUrl": "URL do repositório",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "Diretório",
|
|
||||||
"directoryPlaceholder": "Selecione o diretório de destino...",
|
|
||||||
"browseDirectory": "Procurar diretório",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"clone": "Clonar",
|
|
||||||
"clonePath": "Caminho de clonagem: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "Autenticação necessária",
|
"title": "Autenticação necessária",
|
||||||
"description": "O servidor remoto requer credenciais. Insira seu nome de usuário e senha (ou token de acesso pessoal).",
|
"description": "O servidor remoto requer credenciais. Insira seu nome de usuário e senha (ou token de acesso pessoal).",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "葡萄牙语",
|
"portuguese": "葡萄牙语",
|
||||||
"arabic": "阿拉伯语"
|
"arabic": "阿拉伯语"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "欢迎使用Codeg",
|
|
||||||
"openSettings": "打开设置",
|
|
||||||
"searchPlaceholder": "搜索文件夹...",
|
|
||||||
"loading": "加载中...",
|
|
||||||
"emptyFolders": "暂无文件夹",
|
|
||||||
"removeFromHistory": "从历史中移除",
|
|
||||||
"openFolder": "打开文件夹",
|
|
||||||
"cloneRepository": "克隆仓库",
|
|
||||||
"projectBoot": "项目启动器",
|
|
||||||
"softwareVersion": "版本 {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "加载文件夹历史失败",
|
|
||||||
"openFolderFailed": "打开文件夹失败",
|
|
||||||
"removeFromHistoryFailed": "移除历史记录失败",
|
|
||||||
"openSettingsFailed": "打开设置失败",
|
|
||||||
"cloneFailed": "克隆仓库失败",
|
|
||||||
"openProjectBootFailed": "打开项目启动器失败"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "发生未知错误",
|
|
||||||
"invalidInput": "输入无效。",
|
|
||||||
"notFound": "资源不存在。",
|
|
||||||
"alreadyExists": "资源已存在。",
|
|
||||||
"dependencyMissing": "缺少必要依赖。",
|
|
||||||
"databaseError": "数据库操作失败。",
|
|
||||||
"ioError": "文件操作失败。",
|
|
||||||
"externalCommandFailed": "外部命令执行失败。",
|
|
||||||
"windowOperationFailed": "窗口操作失败。",
|
|
||||||
"gitNotInstalled": "未检测到 Git,请先安装 Git。",
|
|
||||||
"targetDirectoryNotEmpty": "目标目录已存在且不为空。",
|
|
||||||
"repositoryNotFound": "仓库不存在,请检查地址和访问权限。",
|
|
||||||
"networkUnavailable": "网络不可用,请检查网络连接后重试。",
|
|
||||||
"authenticationFailed": "认证失败,请检查凭据或 SSH Key。",
|
|
||||||
"permissionDenied": "权限不足,请检查目录权限。"
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "克隆仓库",
|
|
||||||
"repositoryUrl": "仓库地址",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "目录",
|
|
||||||
"directoryPlaceholder": "选择目标目录...",
|
|
||||||
"browseDirectory": "浏览目录",
|
|
||||||
"cancel": "取消",
|
|
||||||
"clone": "克隆",
|
|
||||||
"clonePath": "克隆路径: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "需要身份验证",
|
"title": "需要身份验证",
|
||||||
"description": "远程服务器要求输入凭据。请输入用户名和密码(或个人访问令牌)。",
|
"description": "远程服务器要求输入凭据。请输入用户名和密码(或个人访问令牌)。",
|
||||||
|
|||||||
@@ -12,54 +12,6 @@
|
|||||||
"portuguese": "葡萄牙語",
|
"portuguese": "葡萄牙語",
|
||||||
"arabic": "阿拉伯語"
|
"arabic": "阿拉伯語"
|
||||||
},
|
},
|
||||||
"WelcomePage": {
|
|
||||||
"title": "歡迎使用Codeg",
|
|
||||||
"openSettings": "打開設定",
|
|
||||||
"searchPlaceholder": "搜尋資料夾...",
|
|
||||||
"loading": "載入中...",
|
|
||||||
"emptyFolders": "暫無資料夾",
|
|
||||||
"removeFromHistory": "從歷史中移除",
|
|
||||||
"openFolder": "打開資料夾",
|
|
||||||
"cloneRepository": "複製倉庫",
|
|
||||||
"projectBoot": "專案啟動器",
|
|
||||||
"softwareVersion": "版本 {version}",
|
|
||||||
"toasts": {
|
|
||||||
"loadFolderHistoryFailed": "載入資料夾歷史失敗",
|
|
||||||
"openFolderFailed": "打開資料夾失敗",
|
|
||||||
"removeFromHistoryFailed": "移除歷史記錄失敗",
|
|
||||||
"openSettingsFailed": "打開設定失敗",
|
|
||||||
"cloneFailed": "複製倉庫失敗",
|
|
||||||
"openProjectBootFailed": "開啟專案啟動器失敗"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"unknown": "發生未知錯誤",
|
|
||||||
"invalidInput": "輸入無效。",
|
|
||||||
"notFound": "資源不存在。",
|
|
||||||
"alreadyExists": "資源已存在。",
|
|
||||||
"dependencyMissing": "缺少必要依賴。",
|
|
||||||
"databaseError": "資料庫操作失敗。",
|
|
||||||
"ioError": "檔案操作失敗。",
|
|
||||||
"externalCommandFailed": "外部指令執行失敗。",
|
|
||||||
"windowOperationFailed": "視窗操作失敗。",
|
|
||||||
"gitNotInstalled": "未檢測到 Git,請先安裝 Git。",
|
|
||||||
"targetDirectoryNotEmpty": "目標目錄已存在且不為空。",
|
|
||||||
"repositoryNotFound": "倉庫不存在,請檢查地址與存取權限。",
|
|
||||||
"networkUnavailable": "網路不可用,請檢查網路連線後重試。",
|
|
||||||
"authenticationFailed": "驗證失敗,請檢查憑證或 SSH Key。",
|
|
||||||
"permissionDenied": "權限不足,請檢查目錄權限。"
|
|
||||||
},
|
|
||||||
"cloneDialog": {
|
|
||||||
"title": "複製倉庫",
|
|
||||||
"repositoryUrl": "倉庫地址",
|
|
||||||
"repositoryUrlPlaceholder": "https://github.com/user/repo.git",
|
|
||||||
"directory": "目錄",
|
|
||||||
"directoryPlaceholder": "選擇目標目錄...",
|
|
||||||
"browseDirectory": "瀏覽目錄",
|
|
||||||
"cancel": "取消",
|
|
||||||
"clone": "複製",
|
|
||||||
"clonePath": "克隆路徑: {path}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GitCredentialDialog": {
|
"GitCredentialDialog": {
|
||||||
"title": "需要身份驗證",
|
"title": "需要身份驗證",
|
||||||
"description": "遠端伺服器要求輸入憑據。請輸入使用者名稱和密碼(或個人存取權杖)。",
|
"description": "遠端伺服器要求輸入憑據。請輸入使用者名稱和密碼(或個人存取權杖)。",
|
||||||
|
|||||||
Reference in New Issue
Block a user