mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Update create project dialog design (#5231)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -3,17 +3,15 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { Dialog } from '@/components/ui/dialog'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { CreateStep } from './AddRepoCreateStep'
|
||||
import type { GitAvailability, RepoKind } from './create-project-defaults'
|
||||
import type { GitAvailability } from './create-project-defaults'
|
||||
|
||||
function renderCreateStep({
|
||||
createName = '',
|
||||
createKind = 'git',
|
||||
gitAvailability = 'available',
|
||||
createParent = '/Users/alice/orca/projects',
|
||||
parentDefaultPending = false
|
||||
}: {
|
||||
createName?: string
|
||||
createKind?: RepoKind
|
||||
gitAvailability?: GitAvailability
|
||||
createParent?: string
|
||||
parentDefaultPending?: boolean
|
||||
@@ -24,7 +22,6 @@ function renderCreateStep({
|
||||
<CreateStep
|
||||
createName={createName}
|
||||
createParent={createParent}
|
||||
createKind={createKind}
|
||||
createError={null}
|
||||
isCreating={false}
|
||||
defaultParent="/Users/alice/orca/projects"
|
||||
@@ -33,7 +30,6 @@ function renderCreateStep({
|
||||
parentDefaultPending={parentDefaultPending}
|
||||
onNameChange={vi.fn()}
|
||||
onParentChange={vi.fn()}
|
||||
onKindChange={vi.fn()}
|
||||
onPickParent={vi.fn()}
|
||||
onCreate={vi.fn()}
|
||||
/>
|
||||
@@ -43,24 +39,31 @@ function renderCreateStep({
|
||||
}
|
||||
|
||||
describe('CreateStep', () => {
|
||||
it('renders the name-first create UI with advanced controls collapsed', () => {
|
||||
it('renders the conductor-style Git project form without templates or kind selection', () => {
|
||||
const html = renderCreateStep()
|
||||
|
||||
expect(html).toContain('Create a new project')
|
||||
expect(html).toContain('Name')
|
||||
expect(html).toContain('Git repository in ~/orca/projects')
|
||||
// The summary card itself is the collapsed disclosure for the uncommon settings.
|
||||
expect(html).toContain('aria-expanded="false"')
|
||||
expect(html).toContain('Create project')
|
||||
expect(html).toContain('Project name')
|
||||
expect(html).not.toContain('Git repo:')
|
||||
expect(html).not.toContain('>project-name</span>')
|
||||
expect(html).toContain('Parent folder')
|
||||
expect(html).toContain('Browse')
|
||||
expect(html).not.toContain('Template')
|
||||
expect(html).not.toContain('Project kind')
|
||||
expect(html).not.toContain('Location</span>')
|
||||
expect(html).not.toContain('aria-label="Browse server filesystem"')
|
||||
})
|
||||
|
||||
it('shows the Git fallback explanation in the collapsed summary', () => {
|
||||
const html = renderCreateStep({ createKind: 'folder', gitAvailability: 'unavailable' })
|
||||
it('shows the repo name in the helper only after a project name is entered', () => {
|
||||
const html = renderCreateStep({ createName: 'demo-project' })
|
||||
|
||||
expect(html).toContain('Folder in ~/orca/projects')
|
||||
expect(html).toContain('Git isn't installed, so a plain folder is the default.')
|
||||
expect(html).toContain('Git repo:')
|
||||
expect(html).toContain('demo-project')
|
||||
})
|
||||
|
||||
it('requires Git instead of falling back to folder creation', () => {
|
||||
const html = renderCreateStep({ gitAvailability: 'unavailable' })
|
||||
|
||||
expect(html).toContain('Git is required to create a project.')
|
||||
expect(html).toContain('disabled=""')
|
||||
})
|
||||
|
||||
it('disables create while an auto-filled parent belongs to a previous target', () => {
|
||||
|
||||
@@ -1,30 +1,17 @@
|
||||
// Step for AddRepoDialog (orca#763), split out so create-project state stays scoped.
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { ChevronDown, Folder, GitBranch, Loader2 } from 'lucide-react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { CornerDownLeft, FolderOpen, Loader2 } from 'lucide-react'
|
||||
import { DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
CreateProjectLocationField,
|
||||
CreateProjectParentBrowser
|
||||
} from './CreateProjectLocationField'
|
||||
import { CreateProjectParentBrowser } from './CreateProjectLocationField'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import {
|
||||
formatCreateProjectParentSummary,
|
||||
joinCreateProjectPath,
|
||||
type GitAvailability,
|
||||
type RepoKind
|
||||
} from './create-project-defaults'
|
||||
|
||||
// ── UI helpers ───────────────────────────────────────────────────────
|
||||
|
||||
const CREATE_PROJECT_NAME_PLACEHOLDER = 'project-name'
|
||||
import { getScreenSubmitModifierLabel } from '@/lib/screen-submit-shortcut'
|
||||
import { formatCreateProjectParentSummary, type GitAvailability } from './create-project-defaults'
|
||||
|
||||
type CreateStepProps = {
|
||||
createName: string
|
||||
createParent: string
|
||||
createKind: RepoKind
|
||||
createError: string | null
|
||||
isCreating: boolean
|
||||
defaultParent?: string
|
||||
@@ -35,7 +22,6 @@ type CreateStepProps = {
|
||||
runtimeEnvironmentId?: string | null
|
||||
onNameChange: (value: string) => void
|
||||
onParentChange: (value: string) => void
|
||||
onKindChange: (kind: RepoKind) => void
|
||||
onPickParent: () => void
|
||||
onCreate: () => void
|
||||
}
|
||||
@@ -43,7 +29,6 @@ type CreateStepProps = {
|
||||
export function CreateStep({
|
||||
createName,
|
||||
createParent,
|
||||
createKind,
|
||||
createError,
|
||||
isCreating,
|
||||
defaultParent = '',
|
||||
@@ -54,52 +39,16 @@ export function CreateStep({
|
||||
runtimeEnvironmentId,
|
||||
onNameChange,
|
||||
onParentChange,
|
||||
onKindChange,
|
||||
onPickParent,
|
||||
onCreate
|
||||
}: CreateStepProps): React.JSX.Element {
|
||||
const radioGroupRef = useRef<HTMLDivElement>(null)
|
||||
const radioFocusFrameRef = useRef<number | null>(null)
|
||||
const [browsingParent, setBrowsingParent] = useState(false)
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false)
|
||||
|
||||
const cancelRadioFocusFrame = useCallback((): void => {
|
||||
if (radioFocusFrameRef.current === null) {
|
||||
return
|
||||
}
|
||||
cancelAnimationFrame(radioFocusFrameRef.current)
|
||||
radioFocusFrameRef.current = null
|
||||
}, [])
|
||||
|
||||
const setRadioGroupNode = useCallback(
|
||||
(node: HTMLDivElement | null): void => {
|
||||
// Why: the queued arrow-key focus is only valid while this radiogroup is mounted.
|
||||
if (!node) {
|
||||
cancelRadioFocusFrame()
|
||||
}
|
||||
radioGroupRef.current = node
|
||||
},
|
||||
[cancelRadioFocusFrame]
|
||||
)
|
||||
|
||||
// Arrow keys cycle selection within the radiogroup (WAI-ARIA radio pattern).
|
||||
const cycleKind = useCallback(() => {
|
||||
const next = createKind === 'git' ? 'folder' : 'git'
|
||||
onKindChange(next)
|
||||
cancelRadioFocusFrame()
|
||||
radioFocusFrameRef.current = requestAnimationFrame(() => {
|
||||
radioFocusFrameRef.current = null
|
||||
const nextEl = radioGroupRef.current?.querySelector<HTMLButtonElement>(
|
||||
`[data-kind="${next}"]`
|
||||
)
|
||||
nextEl?.focus()
|
||||
})
|
||||
}, [cancelRadioFocusFrame, createKind, onKindChange])
|
||||
|
||||
const canSubmit =
|
||||
createName.trim().length > 0 &&
|
||||
createParent.trim().length > 0 &&
|
||||
gitAvailability !== 'checking' &&
|
||||
gitAvailability !== 'unavailable' &&
|
||||
!parentDefaultPending &&
|
||||
!isCreating
|
||||
const missingLocationLabel = translate(
|
||||
@@ -111,7 +60,7 @@ export function CreateStep({
|
||||
'server folder not selected'
|
||||
)
|
||||
|
||||
const summaryParent = useMemo(
|
||||
const parentSummary = useMemo(
|
||||
() =>
|
||||
formatCreateProjectParentSummary({
|
||||
parent: createParent,
|
||||
@@ -128,14 +77,8 @@ export function CreateStep({
|
||||
runtimeEnvironmentId
|
||||
]
|
||||
)
|
||||
const targetPathPreview = useMemo(() => {
|
||||
const name = createName.trim() || CREATE_PROJECT_NAME_PLACEHOLDER
|
||||
return createParent.trim() ? joinCreateProjectPath(createParent, name) : ''
|
||||
}, [createName, createParent])
|
||||
const kindLabel =
|
||||
createKind === 'git'
|
||||
? translate('auto.components.sidebar.AddRepoCreateStep.11fd2a7db8', 'Git repository')
|
||||
: translate('auto.components.sidebar.AddRepoCreateStep.038729c107', 'Folder')
|
||||
const repoNamePreview = createName.trim()
|
||||
const submitShortcutModifierLabel = getScreenSubmitModifierLabel()
|
||||
const showGitFallback = gitAvailability === 'unavailable'
|
||||
const showGitChecking = gitAvailability === 'checking'
|
||||
const showRuntimeMissingParent =
|
||||
@@ -157,14 +100,14 @@ export function CreateStep({
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.c7b9f94456',
|
||||
'Create a new project'
|
||||
'auto.components.sidebar.AddRepoCreateStep.createProjectTitle',
|
||||
'Create project'
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.b100311784',
|
||||
'Name it and Orca will create a real project with sensible defaults.'
|
||||
'auto.components.sidebar.AddRepoCreateStep.createProjectDescription',
|
||||
'Create a local Git repo and first workspace.'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -173,14 +116,16 @@ export function CreateStep({
|
||||
(= content size), so a long path inside the Location row would blow out
|
||||
the dialog width even with flex + truncate on the row itself. min-w-0
|
||||
here caps the grid track at the dialog's max-width. */}
|
||||
<div className="space-y-3.5 pt-1 min-w-0">
|
||||
{/* Name. Monospaced because it ends up as a directory name. */}
|
||||
<div className="space-y-1">
|
||||
<div className="min-w-0 space-y-5 pt-1">
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="create-project-name"
|
||||
className="text-[11px] font-medium text-muted-foreground block"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
{translate('auto.components.sidebar.AddRepoCreateStep.a8149a3a5a', 'Name')}
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.projectNameLabel',
|
||||
'Project name'
|
||||
)}
|
||||
</label>
|
||||
<Input
|
||||
id="create-project-name"
|
||||
@@ -190,194 +135,110 @@ export function CreateStep({
|
||||
'auto.components.sidebar.AddRepoCreateStep.0ae45b8238',
|
||||
'my-project'
|
||||
)}
|
||||
className="h-11 text-sm font-mono"
|
||||
className="h-11 text-sm"
|
||||
disabled={isCreating}
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{repoNamePreview ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.createsGitRepoHelp',
|
||||
'Git repo:'
|
||||
)}{' '}
|
||||
<span className="rounded-md bg-muted px-1.5 py-0.5 font-mono">{repoNamePreview}</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Summary card doubles as the disclosure for the uncommon settings, so the
|
||||
defaults and the controls to change them live in one place. */}
|
||||
<div className="min-w-0 rounded-md border border-border bg-muted/30">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAdvancedOpen((open) => !open)}
|
||||
aria-expanded={advancedOpen}
|
||||
className="flex w-full min-w-0 items-start gap-2.5 rounded-md px-3 py-2.5 text-left transition-colors cursor-pointer hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="create-project-parent"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
>
|
||||
<span className="mt-0.5 inline-flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-background/60 text-muted-foreground">
|
||||
{createKind === 'git' ? (
|
||||
<GitBranch className="size-3.5" />
|
||||
) : (
|
||||
<Folder className="size-3.5" />
|
||||
)}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.685b5eefe1',
|
||||
'{{kind}} in {{parent}}',
|
||||
{
|
||||
kind: kindLabel,
|
||||
parent: summaryParent
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
{showGitChecking ? (
|
||||
<p className="mt-0.5 flex items-center gap-1.5 text-[11px] text-muted-foreground">
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.2a762f3b19',
|
||||
'Checking Git on this host...'
|
||||
)}
|
||||
</p>
|
||||
) : showGitFallback ? (
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.fe1e616c5b',
|
||||
"Git isn't installed, so a plain folder is the default."
|
||||
)}
|
||||
</p>
|
||||
) : showRuntimeMissingParent ? (
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.c234df77f7',
|
||||
'Choose or enter a server parent folder before creating.'
|
||||
)}
|
||||
</p>
|
||||
) : targetPathPreview ? (
|
||||
<p
|
||||
className="mt-0.5 truncate font-mono text-[11px] text-muted-foreground"
|
||||
title={targetPathPreview}
|
||||
>
|
||||
{targetPathPreview}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'size-4 shrink-0 self-center text-muted-foreground transition-transform',
|
||||
advancedOpen && 'rotate-180'
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.parentFolderLabel',
|
||||
'Parent folder'
|
||||
)}
|
||||
</label>
|
||||
<div className="flex min-w-0 gap-2">
|
||||
<Input
|
||||
id="create-project-parent"
|
||||
value={createParent}
|
||||
onChange={(e) => onParentChange(e.target.value)}
|
||||
placeholder={translate(
|
||||
'auto.components.sidebar.CreateProjectLocationField.2a20a603a3',
|
||||
'/home/user/projects'
|
||||
)}
|
||||
className="h-11 min-w-0 flex-1 font-mono text-sm"
|
||||
disabled={isCreating}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{advancedOpen && (
|
||||
<div className="space-y-3 border-t border-border px-3 py-3">
|
||||
{/* Real radiogroup so screen readers announce the segmented choice. */}
|
||||
<div className="space-y-1.5">
|
||||
<span className="text-[11px] font-medium text-muted-foreground block">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.180e9b5e48',
|
||||
'Project kind'
|
||||
)}
|
||||
</span>
|
||||
<div
|
||||
ref={setRadioGroupNode}
|
||||
role="radiogroup"
|
||||
aria-label={translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.180e9b5e48',
|
||||
'Project kind'
|
||||
)}
|
||||
className="grid grid-cols-2 rounded-md border border-border bg-muted/30 p-0.5"
|
||||
>
|
||||
{(['git', 'folder'] as const).map((kind) => {
|
||||
const selected = createKind === kind
|
||||
const label =
|
||||
kind === 'git'
|
||||
? translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.11fd2a7db8',
|
||||
'Git repository'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.038729c107',
|
||||
'Folder'
|
||||
)
|
||||
const Icon = kind === 'git' ? GitBranch : Folder
|
||||
return (
|
||||
<button
|
||||
key={kind}
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={selected}
|
||||
tabIndex={selected ? 0 : -1}
|
||||
onClick={() => onKindChange(kind)}
|
||||
onKeyDown={(e) => {
|
||||
// Why: keep keyboard radio navigation intact inside the compact segmented control.
|
||||
if (
|
||||
e.key === 'ArrowLeft' ||
|
||||
e.key === 'ArrowRight' ||
|
||||
e.key === 'ArrowUp' ||
|
||||
e.key === 'ArrowDown'
|
||||
) {
|
||||
e.preventDefault()
|
||||
cycleKind()
|
||||
} else if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
onKindChange(kind)
|
||||
}
|
||||
}}
|
||||
disabled={isCreating}
|
||||
data-kind={kind}
|
||||
className={cn(
|
||||
'inline-flex min-w-0 items-center justify-center gap-1.5 rounded-sm border px-2.5 py-2 text-xs font-medium outline-none transition-colors',
|
||||
// Why: the segment sits on a muted card, so bg-background alone
|
||||
// is too subtle; the border makes the selected state legible.
|
||||
selected
|
||||
? 'border-border bg-background text-foreground shadow-xs'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground',
|
||||
'focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-60'
|
||||
)}
|
||||
>
|
||||
<Icon className="size-3.5 shrink-0" />
|
||||
<span className="truncate">{label}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{showGitFallback && (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.fe1e616c5b',
|
||||
"Git isn't installed, so a plain folder is the default."
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* The local picker returns client paths; runtime servers browse host paths via RPC. */}
|
||||
<CreateProjectLocationField
|
||||
createParent={createParent}
|
||||
isCreating={isCreating}
|
||||
manualParentEntry={manualParentEntry}
|
||||
runtimeEnvironmentId={runtimeEnvironmentId}
|
||||
onParentChange={onParentChange}
|
||||
onPickParent={onPickParent}
|
||||
onBrowseServer={() => setBrowsingParent(true)}
|
||||
/>
|
||||
|
||||
{targetPathPreview && (
|
||||
<p className="min-w-0 break-all rounded-md border border-border bg-background/40 px-2.5 py-2 font-mono text-[11px] text-muted-foreground">
|
||||
{targetPathPreview}
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (runtimeEnvironmentId) {
|
||||
setBrowsingParent(true)
|
||||
} else {
|
||||
onPickParent()
|
||||
}
|
||||
}}
|
||||
disabled={isCreating || (manualParentEntry && !runtimeEnvironmentId)}
|
||||
size="sm"
|
||||
className="h-11 shrink-0 gap-1.5 px-3"
|
||||
>
|
||||
<FolderOpen className="size-3.5" />
|
||||
{translate('auto.components.sidebar.AddRepoCreateStep.browseParentFolder', 'Browse')}
|
||||
</Button>
|
||||
</div>
|
||||
{showGitChecking ? (
|
||||
<p className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.2a762f3b19',
|
||||
'Checking Git on this host...'
|
||||
)}
|
||||
</div>
|
||||
</p>
|
||||
) : showGitFallback ? (
|
||||
<p className="text-sm text-destructive" role="alert">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.gitRequiredError',
|
||||
'Git is required to create a project.'
|
||||
)}
|
||||
</p>
|
||||
) : showRuntimeMissingParent ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoCreateStep.c234df77f7',
|
||||
'Choose or enter a server parent folder before creating.'
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
<p className="truncate text-sm text-muted-foreground" title={parentSummary}>
|
||||
{parentSummary}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{createError && (
|
||||
<p className="text-[11px] text-destructive" role="alert">
|
||||
<p className="mt-6 text-sm text-destructive" role="alert">
|
||||
{createError}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Button onClick={onCreate} disabled={!canSubmit} size="lg" className="w-full">
|
||||
{isCreating
|
||||
? translate('auto.components.sidebar.AddRepoCreateStep.85085d74d2', 'Creating…')
|
||||
: translate('auto.components.sidebar.AddRepoCreateStep.45b7c26034', 'Create project')}
|
||||
</Button>
|
||||
<div className="flex justify-end pt-2">
|
||||
<Button onClick={onCreate} disabled={!canSubmit} size="lg">
|
||||
{isCreating
|
||||
? translate('auto.components.sidebar.AddRepoCreateStep.85085d74d2', 'Creating…')
|
||||
: translate('auto.components.sidebar.AddRepoCreateStep.createAction', 'Create')}
|
||||
<span className="ml-1 inline-flex items-center gap-0.5 rounded border border-primary-foreground/25 bg-primary-foreground/10 px-1.5 py-0.5 text-[10px] font-medium leading-none text-primary-foreground/75">
|
||||
<span>{submitShortcutModifierLabel}</span>
|
||||
<CornerDownLeft className="size-3" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -133,12 +133,10 @@ const AddRepoDialog = React.memo(function AddRepoDialog() {
|
||||
const {
|
||||
createName,
|
||||
createParent,
|
||||
createKind,
|
||||
createError,
|
||||
isCreating,
|
||||
setCreateName,
|
||||
setCreateParent,
|
||||
setCreateKind,
|
||||
setCreateError,
|
||||
resetCreateState,
|
||||
handlePickParent,
|
||||
@@ -153,14 +151,12 @@ const AddRepoDialog = React.memo(function AddRepoDialog() {
|
||||
createRuntimeParentStatus,
|
||||
createParentDefaultPending,
|
||||
resetCreateDefaultState,
|
||||
markCreateParentTouched,
|
||||
markCreateKindTouched
|
||||
markCreateParentTouched
|
||||
} = useCreateProjectDefaults({
|
||||
step,
|
||||
activeRuntimeEnvironmentId: settings?.activeRuntimeEnvironmentId,
|
||||
createParent,
|
||||
setCreateParent,
|
||||
setCreateKind
|
||||
setCreateParent
|
||||
})
|
||||
|
||||
const {
|
||||
@@ -332,7 +328,6 @@ const AddRepoDialog = React.memo(function AddRepoDialog() {
|
||||
nestedGroupName={nestedGroupName}
|
||||
createName={createName}
|
||||
createParent={createParent}
|
||||
createKind={createKind}
|
||||
createError={createError}
|
||||
isCreating={isCreating}
|
||||
createDefaultParent={createDefaultParent}
|
||||
@@ -390,11 +385,6 @@ const AddRepoDialog = React.memo(function AddRepoDialog() {
|
||||
setCreateParent(value)
|
||||
setCreateError(null)
|
||||
}}
|
||||
onCreateKindChange={(kind) => {
|
||||
markCreateKindTouched()
|
||||
setCreateKind(kind)
|
||||
setCreateError(null)
|
||||
}}
|
||||
onPickCreateParent={() => {
|
||||
void handlePickParent().then((dir) => {
|
||||
if (dir) {
|
||||
|
||||
@@ -53,7 +53,6 @@ function renderStepContent(overrides: Partial<StepContentProps>): string {
|
||||
nestedGroupName: 'platform',
|
||||
createName: '',
|
||||
createParent: '',
|
||||
createKind: 'git',
|
||||
createError: null,
|
||||
isCreating: false,
|
||||
createDefaultParent: '',
|
||||
@@ -82,7 +81,6 @@ function renderStepContent(overrides: Partial<StepContentProps>): string {
|
||||
onImportNestedRepos: vi.fn(),
|
||||
onCreateNameChange: vi.fn(),
|
||||
onCreateParentChange: vi.fn(),
|
||||
onCreateKindChange: vi.fn(),
|
||||
onPickCreateParent: vi.fn(),
|
||||
onCreate: vi.fn(),
|
||||
...overrides
|
||||
@@ -129,8 +127,9 @@ describe('AddRepoDialogStepContent nested imports', () => {
|
||||
activeRuntimeEnvironmentId: 'env-1'
|
||||
})
|
||||
|
||||
expect(html).toContain('Create a new project')
|
||||
expect(html).toContain('server folder not selected')
|
||||
expect(html).toContain('Create project')
|
||||
expect(html).toContain('Choose or enter a server parent folder before creating.')
|
||||
expect(html).toContain('Browse')
|
||||
})
|
||||
|
||||
it('offers server browsing for remote clone destinations', () => {
|
||||
|
||||
@@ -38,7 +38,6 @@ type AddRepoDialogStepContentProps = {
|
||||
nestedGroupName: string
|
||||
createName: string
|
||||
createParent: string
|
||||
createKind: 'git' | 'folder'
|
||||
createError: string | null
|
||||
isCreating: boolean
|
||||
createDefaultParent: string
|
||||
@@ -67,7 +66,6 @@ type AddRepoDialogStepContentProps = {
|
||||
onImportNestedRepos: (mode: 'group' | 'separate') => void
|
||||
onCreateNameChange: (name: string) => void
|
||||
onCreateParentChange: (parent: string) => void
|
||||
onCreateKindChange: (kind: 'git' | 'folder') => void
|
||||
onPickCreateParent: () => void
|
||||
onCreate: () => void
|
||||
}
|
||||
@@ -100,7 +98,6 @@ export function AddRepoDialogStepContent({
|
||||
nestedGroupName,
|
||||
createName,
|
||||
createParent,
|
||||
createKind,
|
||||
createError,
|
||||
isCreating,
|
||||
createDefaultParent,
|
||||
@@ -129,7 +126,6 @@ export function AddRepoDialogStepContent({
|
||||
onImportNestedRepos,
|
||||
onCreateNameChange,
|
||||
onCreateParentChange,
|
||||
onCreateKindChange,
|
||||
onPickCreateParent,
|
||||
onCreate
|
||||
}: AddRepoDialogStepContentProps): React.JSX.Element | null {
|
||||
@@ -224,7 +220,6 @@ export function AddRepoDialogStepContent({
|
||||
<CreateStep
|
||||
createName={createName}
|
||||
createParent={createParent}
|
||||
createKind={createKind}
|
||||
createError={createError}
|
||||
isCreating={isCreating}
|
||||
defaultParent={createDefaultParent}
|
||||
@@ -235,7 +230,6 @@ export function AddRepoDialogStepContent({
|
||||
runtimeEnvironmentId={activeRuntimeEnvironmentId}
|
||||
onNameChange={onCreateNameChange}
|
||||
onParentChange={onCreateParentChange}
|
||||
onKindChange={onCreateKindChange}
|
||||
onPickParent={onPickCreateParent}
|
||||
onCreate={onCreate}
|
||||
/>
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('AddRepoLocalStartStep', () => {
|
||||
expect(markup).toContain('Browse folder')
|
||||
expect(markup).toContain('Clone from URL')
|
||||
expect(markup).toContain('Remote project')
|
||||
expect(markup).toContain('Create new project')
|
||||
expect(markup).toContain('Create project')
|
||||
expect(markup).toContain('Other ways to add')
|
||||
expect(markup).not.toContain('More options')
|
||||
})
|
||||
@@ -140,7 +140,7 @@ describe('AddRepoLocalStartStep', () => {
|
||||
const titles = getActionTitles(false)
|
||||
|
||||
expect(titles.primary).toBe('Browse folder')
|
||||
expect(titles.secondary).toEqual(['Clone from URL', 'Remote project', 'Create new project'])
|
||||
expect(titles.secondary).toEqual(['Clone from URL', 'Remote project', 'Create project'])
|
||||
})
|
||||
|
||||
it('keeps Browse folder primary for SSH-likely users', () => {
|
||||
@@ -149,14 +149,14 @@ describe('AddRepoLocalStartStep', () => {
|
||||
expect(markup).toContain('Browse folder')
|
||||
expect(markup).toContain('Remote project')
|
||||
expect(markup).toContain('Clone from URL')
|
||||
expect(markup).toContain('Create new project')
|
||||
expect(markup).toContain('Create project')
|
||||
})
|
||||
|
||||
it('orders secondary actions remote-first for SSH-likely users', () => {
|
||||
const titles = getActionTitles(true)
|
||||
|
||||
expect(titles.primary).toBe('Browse folder')
|
||||
expect(titles.secondary).toEqual(['Remote project', 'Clone from URL', 'Create new project'])
|
||||
expect(titles.secondary).toEqual(['Remote project', 'Clone from URL', 'Create project'])
|
||||
})
|
||||
|
||||
it('focuses Browse folder when the default Add Project step opens', async () => {
|
||||
@@ -188,7 +188,7 @@ describe('AddRepoLocalStartStep', () => {
|
||||
|
||||
expect(findButton(container, 'Clone from URL').disabled).toBe(false)
|
||||
expect(findButton(container, 'Remote project').disabled).toBe(false)
|
||||
expect(findButton(container, 'Create new project').disabled).toBe(false)
|
||||
expect(findButton(container, 'Create project').disabled).toBe(false)
|
||||
|
||||
await act(async () => {
|
||||
root.unmount()
|
||||
|
||||
@@ -71,12 +71,12 @@ export function getAddRepoLocalStartActions({
|
||||
kind: 'create' as const,
|
||||
icon: Plus,
|
||||
title: translate(
|
||||
'auto.components.sidebar.add.repo.local.start.actions.c709860596',
|
||||
'Create new project'
|
||||
'auto.components.sidebar.add.repo.local.start.actions.createProjectTitle',
|
||||
'Create project'
|
||||
),
|
||||
description: translate(
|
||||
'auto.components.sidebar.add.repo.local.start.actions.d72789705e',
|
||||
'Start from an empty folder'
|
||||
'auto.components.sidebar.add.repo.local.start.actions.createGitProjectDescription',
|
||||
'Create a local Git repository'
|
||||
),
|
||||
onClick: onOpenCreateStep
|
||||
}
|
||||
|
||||
@@ -63,16 +63,14 @@ function useHarness(overrides: Partial<Parameters<typeof useCreateProjectDefault
|
||||
mocks.stateIndex = 0
|
||||
mocks.refIndex = 0
|
||||
const setCreateParent = vi.fn()
|
||||
const setCreateKind = vi.fn()
|
||||
const result = useCreateProjectDefaults({
|
||||
step: 'create',
|
||||
activeRuntimeEnvironmentId: null,
|
||||
createParent: '',
|
||||
setCreateParent,
|
||||
setCreateKind,
|
||||
...overrides
|
||||
})
|
||||
return { result, setCreateParent, setCreateKind }
|
||||
return { result, setCreateParent }
|
||||
}
|
||||
|
||||
describe('useCreateProjectDefaults', () => {
|
||||
@@ -93,16 +91,15 @@ describe('useCreateProjectDefaults', () => {
|
||||
mocks.getDefaultCreateProjectParent.mockResolvedValue('/Users/alice/orca/projects')
|
||||
})
|
||||
|
||||
it('auto-fills the local default parent and defaults to git when available', async () => {
|
||||
it('auto-fills the local default parent and records Git availability', async () => {
|
||||
mocks.isGitAvailable.mockResolvedValue(true)
|
||||
|
||||
const { setCreateParent, setCreateKind } = useHarness()
|
||||
const { setCreateParent } = useHarness()
|
||||
await flushAsync()
|
||||
|
||||
expect(setCreateParent).toHaveBeenCalledWith('/Users/alice/orca/projects')
|
||||
expect(mocks.stateValues[DEFAULT_PARENT_STATE]).toBe('/Users/alice/orca/projects')
|
||||
expect(mocks.stateValues[GIT_AVAILABILITY_STATE]).toBe('available')
|
||||
expect(setCreateKind).toHaveBeenCalledWith('git')
|
||||
expect(mocks.getDefaultCreateProjectParent).toHaveBeenCalled()
|
||||
expect(mocks.callRuntimeRpc).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -128,24 +125,22 @@ describe('useCreateProjectDefaults', () => {
|
||||
expect(mocks.stateValues[DEFAULT_PARENT_STATE]).toBe('/Users/alice/orca/projects')
|
||||
})
|
||||
|
||||
it('defaults to folder with a visible fallback when Git is unavailable', async () => {
|
||||
it('records unavailable Git without changing project kind', async () => {
|
||||
mocks.isGitAvailable.mockResolvedValue(false)
|
||||
|
||||
const { setCreateKind } = useHarness()
|
||||
useHarness()
|
||||
await flushAsync()
|
||||
|
||||
expect(mocks.stateValues[GIT_AVAILABILITY_STATE]).toBe('unavailable')
|
||||
expect(setCreateKind).toHaveBeenCalledWith('folder')
|
||||
})
|
||||
|
||||
it('reports unknown availability and keeps the kind when the Git probe fails', async () => {
|
||||
it('reports unknown availability when the Git probe fails', async () => {
|
||||
mocks.isGitAvailable.mockRejectedValue(new Error('probe failed'))
|
||||
|
||||
const { setCreateKind } = useHarness()
|
||||
useHarness()
|
||||
await flushAsync()
|
||||
|
||||
expect(mocks.stateValues[GIT_AVAILABILITY_STATE]).toBe('unknown')
|
||||
expect(setCreateKind).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not overwrite a parent the user already chose', async () => {
|
||||
@@ -161,7 +156,7 @@ describe('useCreateProjectDefaults', () => {
|
||||
mocks.browseRuntimeServerDirectory.mockResolvedValue({ resolvedPath: '/home/alice' })
|
||||
mocks.callRuntimeRpc.mockResolvedValue({ available: true })
|
||||
|
||||
const { setCreateParent, setCreateKind } = useHarness({ activeRuntimeEnvironmentId: 'env-1' })
|
||||
const { setCreateParent } = useHarness({ activeRuntimeEnvironmentId: 'env-1' })
|
||||
await flushAsync()
|
||||
|
||||
expect(mocks.browseRuntimeServerDirectory).toHaveBeenCalledWith('env-1', '~')
|
||||
@@ -176,7 +171,6 @@ describe('useCreateProjectDefaults', () => {
|
||||
{ timeoutMs: 3000 }
|
||||
)
|
||||
expect(mocks.isGitAvailable).not.toHaveBeenCalled()
|
||||
expect(setCreateKind).toHaveBeenCalledWith('git')
|
||||
})
|
||||
|
||||
it('replaces an untouched local default when switching to a runtime target', async () => {
|
||||
@@ -249,11 +243,10 @@ describe('useCreateProjectDefaults', () => {
|
||||
})
|
||||
|
||||
it('does nothing outside the create step', async () => {
|
||||
const { setCreateParent, setCreateKind } = useHarness({ step: 'add' })
|
||||
const { setCreateParent } = useHarness({ step: 'add' })
|
||||
await flushAsync()
|
||||
|
||||
expect(setCreateParent).not.toHaveBeenCalled()
|
||||
expect(setCreateKind).not.toHaveBeenCalled()
|
||||
expect(mocks.isGitAvailable).not.toHaveBeenCalled()
|
||||
expect(mocks.browseRuntimeServerDirectory).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -5,11 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { browseRuntimeServerDirectory } from '@/runtime/runtime-server-directory-browser'
|
||||
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
|
||||
import type { AddRepoDialogStep } from './add-repo-dialog-types'
|
||||
import {
|
||||
getDefaultCreateProjectParent,
|
||||
type GitAvailability,
|
||||
type RepoKind
|
||||
} from './create-project-defaults'
|
||||
import { getDefaultCreateProjectParent, type GitAvailability } from './create-project-defaults'
|
||||
|
||||
const LOCAL_GIT_AVAILABILITY_TIMEOUT_MS = 1500
|
||||
const RUNTIME_GIT_AVAILABILITY_TIMEOUT_MS = 3000
|
||||
@@ -51,14 +47,12 @@ export function useCreateProjectDefaults({
|
||||
step,
|
||||
activeRuntimeEnvironmentId,
|
||||
createParent,
|
||||
setCreateParent,
|
||||
setCreateKind
|
||||
setCreateParent
|
||||
}: {
|
||||
step: AddRepoDialogStep
|
||||
activeRuntimeEnvironmentId: string | null | undefined
|
||||
createParent: string
|
||||
setCreateParent: (value: string) => void
|
||||
setCreateKind: (kind: RepoKind) => void
|
||||
}): {
|
||||
createDefaultParent: string
|
||||
createGitAvailability: GitAvailability
|
||||
@@ -66,7 +60,6 @@ export function useCreateProjectDefaults({
|
||||
createParentDefaultPending: boolean
|
||||
resetCreateDefaultState: () => void
|
||||
markCreateParentTouched: (value?: string) => void
|
||||
markCreateKindTouched: () => void
|
||||
} {
|
||||
const [createDefaultParent, setCreateDefaultParent] = useState('')
|
||||
const [createGitAvailability, setCreateGitAvailability] = useState<GitAvailability>('unknown')
|
||||
@@ -76,7 +69,6 @@ export function useCreateProjectDefaults({
|
||||
const autoFilledCreateParentRef = useRef<AutoFilledCreateParent | null>(null)
|
||||
const createParentProvenanceRef = useRef<CreateParentProvenance | null>(null)
|
||||
const createParentTouchedRef = useRef(false)
|
||||
const createKindTouchedRef = useRef(false)
|
||||
const createParentDefaultGenRef = useRef(0)
|
||||
const createGitProbeGenRef = useRef(0)
|
||||
const activeCreateParentRuntimeEnvironmentId = activeRuntimeEnvironmentId?.trim() || null
|
||||
@@ -96,7 +88,6 @@ export function useCreateProjectDefaults({
|
||||
autoFilledCreateParentRef.current = null
|
||||
createParentProvenanceRef.current = null
|
||||
createParentTouchedRef.current = false
|
||||
createKindTouchedRef.current = false
|
||||
setCreateDefaultParent('')
|
||||
setCreateGitAvailability('unknown')
|
||||
setCreateRuntimeParentStatus('idle')
|
||||
@@ -114,9 +105,6 @@ export function useCreateProjectDefaults({
|
||||
},
|
||||
[activeCreateParentRuntimeEnvironmentId, createParent]
|
||||
)
|
||||
const markCreateKindTouched = useCallback(() => {
|
||||
createKindTouchedRef.current = true
|
||||
}, [])
|
||||
|
||||
const createParentDefaultPending =
|
||||
step === 'create' &&
|
||||
@@ -282,10 +270,6 @@ export function useCreateProjectDefaults({
|
||||
return
|
||||
}
|
||||
setCreateGitAvailability(available ? 'available' : 'unavailable')
|
||||
if (createKindTouchedRef.current) {
|
||||
return
|
||||
}
|
||||
setCreateKind(available ? 'git' : 'folder')
|
||||
})
|
||||
.catch(() => {
|
||||
if (gen !== createGitProbeGenRef.current) {
|
||||
@@ -293,7 +277,7 @@ export function useCreateProjectDefaults({
|
||||
}
|
||||
setCreateGitAvailability('unknown')
|
||||
})
|
||||
}, [activeRuntimeEnvironmentId, setCreateKind, step])
|
||||
}, [activeRuntimeEnvironmentId, step])
|
||||
|
||||
return {
|
||||
createDefaultParent,
|
||||
@@ -301,7 +285,6 @@ export function useCreateProjectDefaults({
|
||||
createRuntimeParentStatus,
|
||||
createParentDefaultPending: createParentPending,
|
||||
resetCreateDefaultState,
|
||||
markCreateParentTouched,
|
||||
markCreateKindTouched
|
||||
markCreateParentTouched
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('useCreateRepo default-checkout handoff', () => {
|
||||
vi.clearAllMocks()
|
||||
mocks.stateIndex = 0
|
||||
mocks.stateSetters = []
|
||||
mocks.stateValues = ['created', '/projects', 'git', null, false]
|
||||
mocks.stateValues = ['created', '/projects', null, false]
|
||||
mocks.storeState.repos = []
|
||||
mocks.storeState.worktreesByRepo = {}
|
||||
mocks.storeState.settings.activeRuntimeEnvironmentId = null
|
||||
@@ -163,11 +163,11 @@ describe('useCreateRepo default-checkout handoff', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('marks onboarding folder progress when a created folder project opens', async () => {
|
||||
it('uses the folder completion path if IPC returns a folder project', async () => {
|
||||
const repo = makeRepo({ kind: 'folder' })
|
||||
const worktree = { id: `${repo.id}::/projects/created` }
|
||||
const closeModal = vi.fn()
|
||||
mocks.stateValues = ['created', '/projects', 'folder', null, false]
|
||||
mocks.stateValues = ['created', '/projects', null, false]
|
||||
mocks.createRepo.mockResolvedValue({ repo })
|
||||
mocks.fetchWorktrees.mockImplementation(async (repoId: string) => {
|
||||
mocks.storeState.worktreesByRepo = { [repoId]: [worktree] }
|
||||
@@ -181,7 +181,7 @@ describe('useCreateRepo default-checkout handoff', () => {
|
||||
expect(mocks.createRepo).toHaveBeenCalledWith({
|
||||
parentPath: '/projects',
|
||||
name: 'created',
|
||||
kind: 'folder'
|
||||
kind: 'git'
|
||||
})
|
||||
expect(mocks.fetchWorktrees).toHaveBeenCalledWith(repo.id)
|
||||
expect(mocks.activateAndRevealWorktree).toHaveBeenCalledWith(worktree.id, {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-cl
|
||||
import { isGitRepoKind } from '../../../../shared/repo-kind'
|
||||
import type { Repo } from '../../../../shared/types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { RepoKind } from './create-project-defaults'
|
||||
|
||||
export function useCreateRepo(
|
||||
fetchWorktrees: (
|
||||
@@ -22,7 +21,6 @@ export function useCreateRepo(
|
||||
) {
|
||||
const [createName, setCreateName] = useState('')
|
||||
const [createParent, setCreateParent] = useState('')
|
||||
const [createKind, setCreateKind] = useState<RepoKind>('git')
|
||||
const [createError, setCreateError] = useState<string | null>(null)
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
const mountedRef = useMountedRef()
|
||||
@@ -36,7 +34,6 @@ export function useCreateRepo(
|
||||
createGenRef.current++
|
||||
setCreateName('')
|
||||
setCreateParent('')
|
||||
setCreateKind('git')
|
||||
setCreateError(null)
|
||||
setIsCreating(false)
|
||||
}, [])
|
||||
@@ -82,14 +79,14 @@ export function useCreateRepo(
|
||||
{
|
||||
parentPath,
|
||||
name,
|
||||
kind: createKind
|
||||
kind: 'git'
|
||||
},
|
||||
{ timeoutMs: 60_000 }
|
||||
)
|
||||
: await window.api.repos.create({
|
||||
parentPath,
|
||||
name,
|
||||
kind: createKind
|
||||
kind: 'git'
|
||||
})
|
||||
// Why: if the user closed the dialog or clicked Back mid-create,
|
||||
// createGenRef was bumped by resetCreateState. Ignore stale results.
|
||||
@@ -170,17 +167,15 @@ export function useCreateRepo(
|
||||
setIsCreating(false)
|
||||
}
|
||||
}
|
||||
}, [createName, createParent, createKind, fetchWorktrees, mountedRef, closeModal, onGitRepoReady])
|
||||
}, [createName, createParent, fetchWorktrees, mountedRef, closeModal, onGitRepoReady])
|
||||
|
||||
return {
|
||||
createName,
|
||||
createParent,
|
||||
createKind,
|
||||
createError,
|
||||
isCreating,
|
||||
setCreateName,
|
||||
setCreateParent,
|
||||
setCreateKind,
|
||||
setCreateError,
|
||||
resetCreateState,
|
||||
handlePickParent,
|
||||
|
||||
@@ -3062,7 +3062,15 @@
|
||||
"2c12db1511": "Project already added",
|
||||
"875dda0995": "Enter a server parent path.",
|
||||
"45b7c26034": "Create project",
|
||||
"85085d74d2": "Creating…"
|
||||
"85085d74d2": "Creating…",
|
||||
"createProjectTitle": "Create project",
|
||||
"createProjectDescription": "Create a local Git repo and first workspace.",
|
||||
"projectNameLabel": "Project name",
|
||||
"createsGitRepoHelp": "Git repo:",
|
||||
"parentFolderLabel": "Parent folder",
|
||||
"browseParentFolder": "Browse",
|
||||
"gitRequiredError": "Git is required to create a project.",
|
||||
"createAction": "Create"
|
||||
},
|
||||
"AddRepoNestedImportStep": {
|
||||
"496f68cf8c": "Scanning repositories. Click to stop.",
|
||||
@@ -3663,7 +3671,9 @@
|
||||
"a6c20dca96": "Open a project from an SSH target",
|
||||
"3d162cc76f": "Remote project",
|
||||
"fb4fc5380e": "Local project, Git repo, or folder with many repos",
|
||||
"2281fdc8c7": "Browse folder"
|
||||
"2281fdc8c7": "Browse folder",
|
||||
"createProjectTitle": "Create project",
|
||||
"createGitProjectDescription": "Create a local Git repository"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3062,7 +3062,15 @@
|
||||
"fe1e616c5b": "Git no está instalado, así que una carpeta simple será el valor predeterminado.",
|
||||
"c234df77f7": "Elige o introduce una carpeta principal del servidor antes de crear.",
|
||||
"3a13f6e88b": "ubicación no seleccionada",
|
||||
"6ed14c0281": "carpeta del servidor no seleccionada"
|
||||
"6ed14c0281": "carpeta del servidor no seleccionada",
|
||||
"createProjectTitle": "Crear proyecto",
|
||||
"createProjectDescription": "Crea un repositorio Git local y el primer espacio de trabajo.",
|
||||
"projectNameLabel": "Nombre del proyecto",
|
||||
"createsGitRepoHelp": "Repositorio Git:",
|
||||
"parentFolderLabel": "Carpeta principal",
|
||||
"browseParentFolder": "Explorar",
|
||||
"gitRequiredError": "Git es necesario para crear un proyecto.",
|
||||
"createAction": "Crear"
|
||||
},
|
||||
"AddRepoNestedImportStep": {
|
||||
"496f68cf8c": "Escaneo de repositorios. Haga clic para detener.",
|
||||
@@ -3663,7 +3671,9 @@
|
||||
"a6c20dca96": "Abrir un proyecto desde un destino SSH",
|
||||
"3d162cc76f": "Proyecto remoto",
|
||||
"fb4fc5380e": "Proyecto local, repo de Git o carpeta con muchos repos",
|
||||
"2281fdc8c7": "Explorar carpeta"
|
||||
"2281fdc8c7": "Explorar carpeta",
|
||||
"createProjectTitle": "Crear proyecto",
|
||||
"createGitProjectDescription": "Crear un repositorio Git local"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3043,7 +3043,15 @@
|
||||
"fe1e616c5b": "Git がインストールされていないため、通常のフォルダが既定になります。",
|
||||
"c234df77f7": "作成する前に、サーバーの親フォルダを選択または入力してください。",
|
||||
"3a13f6e88b": "場所が選択されていません",
|
||||
"6ed14c0281": "サーバーフォルダが選択されていません"
|
||||
"6ed14c0281": "サーバーフォルダが選択されていません",
|
||||
"createProjectTitle": "プロジェクトの作成",
|
||||
"createProjectDescription": "ローカルの Git リポジトリと最初のワークスペースを作成します。",
|
||||
"projectNameLabel": "プロジェクト名",
|
||||
"createsGitRepoHelp": "Git リポジトリ:",
|
||||
"parentFolderLabel": "親フォルダ",
|
||||
"browseParentFolder": "参照",
|
||||
"gitRequiredError": "プロジェクトを作成するには Git が必要です。",
|
||||
"createAction": "作成"
|
||||
},
|
||||
"AddRepoNestedImportStep": {
|
||||
"496f68cf8c": "リポジトリをスキャンしています。クリックして停止します。",
|
||||
@@ -3644,7 +3652,9 @@
|
||||
"a6c20dca96": "SSH ターゲットからプロジェクトを開く",
|
||||
"3d162cc76f": "リモートプロジェクト",
|
||||
"fb4fc5380e": "ローカルプロジェクト、Git repo、または多数の repos を含むフォルダー",
|
||||
"2281fdc8c7": "フォルダを参照する"
|
||||
"2281fdc8c7": "フォルダを参照する",
|
||||
"createProjectTitle": "プロジェクトの作成",
|
||||
"createGitProjectDescription": "ローカルの Git リポジトリを作成"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3043,7 +3043,15 @@
|
||||
"fe1e616c5b": "Git이 설치되어 있지 않아 일반 폴더가 기본값입니다.",
|
||||
"c234df77f7": "만들기 전에 서버 상위 폴더를 선택하거나 입력하세요.",
|
||||
"3a13f6e88b": "위치가 선택되지 않음",
|
||||
"6ed14c0281": "서버 폴더가 선택되지 않음"
|
||||
"6ed14c0281": "서버 폴더가 선택되지 않음",
|
||||
"createProjectTitle": "프로젝트 생성",
|
||||
"createProjectDescription": "로컬 Git 저장소와 첫 번째 작업 공간을 만듭니다.",
|
||||
"projectNameLabel": "프로젝트 이름",
|
||||
"createsGitRepoHelp": "Git 저장소:",
|
||||
"parentFolderLabel": "상위 폴더",
|
||||
"browseParentFolder": "찾아보기",
|
||||
"gitRequiredError": "프로젝트를 만들려면 Git이 필요합니다.",
|
||||
"createAction": "생성"
|
||||
},
|
||||
"AddRepoNestedImportStep": {
|
||||
"496f68cf8c": "저장소를 스캔하는 중입니다. 중지하려면 클릭하세요.",
|
||||
@@ -3644,7 +3652,9 @@
|
||||
"a6c20dca96": "SSH 대상에서 프로젝트 열기",
|
||||
"3d162cc76f": "원격 프로젝트",
|
||||
"fb4fc5380e": "로컬 프로젝트, Git repo 또는 repos가 많은 폴더",
|
||||
"2281fdc8c7": "폴더 찾아보기"
|
||||
"2281fdc8c7": "폴더 찾아보기",
|
||||
"createProjectTitle": "프로젝트 생성",
|
||||
"createGitProjectDescription": "로컬 Git 저장소 생성"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3043,7 +3043,15 @@
|
||||
"fe1e616c5b": "未安装 Git,因此默认创建普通文件夹。",
|
||||
"c234df77f7": "创建前请选择或输入服务器父文件夹。",
|
||||
"3a13f6e88b": "未选择位置",
|
||||
"6ed14c0281": "未选择服务器文件夹"
|
||||
"6ed14c0281": "未选择服务器文件夹",
|
||||
"createProjectTitle": "创建项目",
|
||||
"createProjectDescription": "创建本地 Git 存储库和第一个工作区。",
|
||||
"projectNameLabel": "项目名称",
|
||||
"createsGitRepoHelp": "Git 存储库:",
|
||||
"parentFolderLabel": "父文件夹",
|
||||
"browseParentFolder": "浏览",
|
||||
"gitRequiredError": "创建项目需要 Git。",
|
||||
"createAction": "创建"
|
||||
},
|
||||
"AddRepoNestedImportStep": {
|
||||
"496f68cf8c": "扫描存储库。单击停止。",
|
||||
@@ -3644,7 +3652,9 @@
|
||||
"a6c20dca96": "从 SSH 目标打开项目",
|
||||
"3d162cc76f": "远程项目",
|
||||
"fb4fc5380e": "本地项目、Git repo 或包含多个 repos 的文件夹",
|
||||
"2281fdc8c7": "浏览文件夹"
|
||||
"2281fdc8c7": "浏览文件夹",
|
||||
"createProjectTitle": "创建项目",
|
||||
"createGitProjectDescription": "创建本地 Git 存储库"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user