mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
feat(sidebar): replace the external-worktrees inbox list with one card (#14341)
The Non-Orca worktrees modal already lists every hidden external worktree with search, virtualization, and per-row Show. Its filter is a strict superset of the sidebar inbox's, so the expanded sidebar list was a second, worse copy that grew to ~900px at 24 worktrees. The inbox is now a single clickable card stating the count, which opens that modal. Drops the expand state, nested list, per-row Import, and the Keep hidden / Import all footer.
This commit is contained in:
@@ -16,7 +16,15 @@ vi.mock('@/components/ui/tooltip', () => ({
|
||||
|
||||
const roots: Root[] = []
|
||||
|
||||
async function renderLine(): Promise<HTMLDivElement> {
|
||||
type RenderOverrides = {
|
||||
inboxCount?: number
|
||||
pending?: boolean
|
||||
error?: string | null
|
||||
onReview?: () => void
|
||||
onSuppress?: () => void
|
||||
}
|
||||
|
||||
async function renderLine(overrides: RenderOverrides = {}): Promise<HTMLDivElement> {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
const root = createRoot(container)
|
||||
@@ -26,20 +34,11 @@ async function renderLine(): Promise<HTMLDivElement> {
|
||||
root.render(
|
||||
<NewExternalWorktreesInboxLine
|
||||
repoDisplayName="orca"
|
||||
inboxWorktrees={[
|
||||
{
|
||||
id: 'external-1',
|
||||
displayName: 'payments-refactor',
|
||||
branch: 'refs/heads/payments-refactor',
|
||||
path: '/worktrees/orca/payments-refactor'
|
||||
}
|
||||
]}
|
||||
pending={false}
|
||||
error={null}
|
||||
onImportWorktree={vi.fn()}
|
||||
onKeepHidden={vi.fn()}
|
||||
onImportAll={vi.fn()}
|
||||
onSuppress={vi.fn()}
|
||||
inboxCount={overrides.inboxCount ?? 24}
|
||||
pending={overrides.pending ?? false}
|
||||
error={overrides.error ?? null}
|
||||
onReview={overrides.onReview ?? vi.fn()}
|
||||
onSuppress={overrides.onSuppress ?? vi.fn()}
|
||||
/>
|
||||
)
|
||||
})
|
||||
@@ -47,6 +46,10 @@ async function renderLine(): Promise<HTMLDivElement> {
|
||||
return container
|
||||
}
|
||||
|
||||
function getReviewButton(container: HTMLDivElement): HTMLButtonElement | null {
|
||||
return container.querySelector<HTMLButtonElement>('button[aria-label^="Review "]')
|
||||
}
|
||||
|
||||
describe('NewExternalWorktreesInboxLine', () => {
|
||||
beforeEach(() => {
|
||||
globalThis.IS_REACT_ACT_ENVIRONMENT = true
|
||||
@@ -60,27 +63,83 @@ describe('NewExternalWorktreesInboxLine', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('keeps suppress as a hover-revealed header icon instead of expanded text action', async () => {
|
||||
it('states the count without naming any worktree', async () => {
|
||||
const container = await renderLine()
|
||||
|
||||
expect(container.textContent).toContain('24')
|
||||
expect(container.textContent).toContain('hidden worktrees')
|
||||
// The modal owns the list; the sidebar must not enumerate paths or names.
|
||||
expect(container.querySelectorAll('li')).toHaveLength(0)
|
||||
expect(container.textContent).not.toContain('Import')
|
||||
})
|
||||
|
||||
it('opens review from a single card-wide button', async () => {
|
||||
const onReview = vi.fn()
|
||||
const container = await renderLine({ onReview })
|
||||
|
||||
const review = getReviewButton(container)
|
||||
expect(review).not.toBeNull()
|
||||
expect(review?.getAttribute('aria-label')).toBe('Review 24 hidden worktrees in orca')
|
||||
|
||||
await act(async () => {
|
||||
review?.click()
|
||||
})
|
||||
expect(onReview).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('uses the singular noun for one worktree', async () => {
|
||||
const container = await renderLine({ inboxCount: 1 })
|
||||
|
||||
expect(container.textContent).toContain('hidden worktree')
|
||||
expect(container.textContent).not.toContain('hidden worktrees')
|
||||
expect(getReviewButton(container)?.getAttribute('aria-label')).toBe(
|
||||
'Review 1 hidden worktree in orca'
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps suppress as a hover-revealed control that does not trigger review', async () => {
|
||||
const onReview = vi.fn()
|
||||
const onSuppress = vi.fn()
|
||||
const container = await renderLine({ onReview, onSuppress })
|
||||
|
||||
const suppressButton = container.querySelector<HTMLButtonElement>(
|
||||
'button[aria-label="Hide external worktrees permanently for orca"]'
|
||||
)
|
||||
expect(suppressButton).not.toBeNull()
|
||||
expect(suppressButton?.className).toContain('can-hover:group-hover:opacity-100')
|
||||
expect(container.textContent).toContain("Don't show again")
|
||||
// Nested buttons would make the suppress click ambiguous.
|
||||
expect(getReviewButton(container)?.contains(suppressButton)).toBe(false)
|
||||
|
||||
const expandButton = container.querySelector<HTMLButtonElement>(
|
||||
'button[aria-label="Expand new externally-created worktrees for orca"]'
|
||||
)
|
||||
await act(async () => {
|
||||
expandButton?.click()
|
||||
suppressButton?.click()
|
||||
})
|
||||
expect(onSuppress).toHaveBeenCalledTimes(1)
|
||||
expect(onReview).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
expect(container.textContent).toContain('payments-refactor')
|
||||
const textButtons = [...container.querySelectorAll('button')].filter(
|
||||
(button) => button.textContent === "Don't show again"
|
||||
)
|
||||
expect(textButtons).toHaveLength(0)
|
||||
it('disables both actions while a mutation is pending', async () => {
|
||||
const container = await renderLine({ pending: true })
|
||||
|
||||
expect(getReviewButton(container)?.disabled).toBe(true)
|
||||
expect(
|
||||
container.querySelector<HTMLButtonElement>(
|
||||
'button[aria-label="Hide external worktrees permanently for orca"]'
|
||||
)?.disabled
|
||||
).toBe(true)
|
||||
expect(container.querySelector('section')?.getAttribute('aria-busy')).toBe('true')
|
||||
})
|
||||
|
||||
it('renders nothing when the inbox is empty', async () => {
|
||||
const container = await renderLine({ inboxCount: 0 })
|
||||
|
||||
expect(container.querySelector('section')).toBeNull()
|
||||
})
|
||||
|
||||
it('surfaces the action error as an alert', async () => {
|
||||
const container = await renderLine({ error: 'Could not import external worktrees. Try again.' })
|
||||
|
||||
const alert = container.querySelector('[role="alert"]')
|
||||
expect(alert?.textContent).toBe('Could not import external worktrees. Try again.')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,37 +1,30 @@
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { ChevronRight, X } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { NewExternalWorktreeInboxPreview } from './new-external-worktrees-inbox-candidates'
|
||||
|
||||
type NewExternalWorktreesInboxLineProps = {
|
||||
repoDisplayName: string
|
||||
inboxWorktrees: readonly NewExternalWorktreeInboxPreview[]
|
||||
inboxCount: number
|
||||
pending: boolean
|
||||
error: string | null
|
||||
onImportWorktree?: (worktreeId: string) => void
|
||||
onKeepHidden?: () => void
|
||||
onImportAll?: () => void
|
||||
onReview?: () => void
|
||||
onSuppress?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function NewExternalWorktreesInboxLine({
|
||||
repoDisplayName,
|
||||
inboxWorktrees,
|
||||
inboxCount,
|
||||
pending,
|
||||
error,
|
||||
onImportWorktree,
|
||||
onKeepHidden,
|
||||
onImportAll,
|
||||
onReview,
|
||||
onSuppress,
|
||||
className
|
||||
}: NewExternalWorktreesInboxLineProps): React.JSX.Element | null {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const inboxCount = inboxWorktrees.length
|
||||
const suppressLabel = translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.c3e8a1f4b2',
|
||||
"Don't show again"
|
||||
@@ -41,6 +34,27 @@ export default function NewExternalWorktreesInboxLine({
|
||||
'Hide external worktrees permanently for {{value0}}',
|
||||
{ value0: repoDisplayName }
|
||||
)
|
||||
const isSingular = inboxCount === 1
|
||||
const countLabel = isSingular
|
||||
? translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.2a6f31d8c7',
|
||||
'hidden worktree'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.5b90e4a2f6',
|
||||
'hidden worktrees'
|
||||
)
|
||||
const reviewAriaLabel = isSingular
|
||||
? translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.7f18c5b0d3',
|
||||
'Review {{value0}} hidden worktree in {{value1}}',
|
||||
{ value0: inboxCount, value1: repoDisplayName }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.4e2b7a9c05',
|
||||
'Review {{value0}} hidden worktrees in {{value1}}',
|
||||
{ value0: inboxCount, value1: repoDisplayName }
|
||||
)
|
||||
|
||||
if (inboxCount === 0) {
|
||||
return null
|
||||
@@ -51,151 +65,55 @@ export default function NewExternalWorktreesInboxLine({
|
||||
aria-busy={pending}
|
||||
className={cn('mx-1 my-0.5 ml-3 text-worktree-sidebar-foreground', className)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'group flex min-h-7 min-w-0 items-center gap-1.5 rounded-md px-1.5 text-[11px] leading-none text-muted-foreground transition-colors',
|
||||
'hover:bg-worktree-sidebar-accent hover:text-worktree-sidebar-accent-foreground'
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
<div className="group relative">
|
||||
<button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
disabled={pending}
|
||||
aria-expanded={isExpanded}
|
||||
aria-label={
|
||||
isExpanded
|
||||
? translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.d9f7b2a14c',
|
||||
'Collapse new externally-created worktrees for {{value0}}',
|
||||
{ value0: repoDisplayName }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.e2c4a8d91f',
|
||||
'Expand new externally-created worktrees for {{value0}}',
|
||||
{ value0: repoDisplayName }
|
||||
)
|
||||
}
|
||||
onClick={() => setIsExpanded((value) => !value)}
|
||||
className="shrink-0 rounded-[4px] text-muted-foreground hover:bg-worktree-sidebar-accent hover:text-worktree-sidebar-accent-foreground"
|
||||
>
|
||||
<ChevronRight
|
||||
className={cn('size-3 transition-transform', isExpanded && 'rotate-90')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Button>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.7c4e9b2a81',
|
||||
'New externally-created worktrees'
|
||||
disabled={pending || !onReview}
|
||||
aria-label={reviewAriaLabel}
|
||||
onClick={onReview}
|
||||
className={cn(
|
||||
'flex min-h-8 w-full min-w-0 items-center gap-2 rounded-md border border-worktree-sidebar-border px-2 py-1.5',
|
||||
'text-[11px] leading-none text-muted-foreground transition-colors',
|
||||
'hover:bg-worktree-sidebar-accent hover:text-worktree-sidebar-accent-foreground',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-worktree-sidebar-ring',
|
||||
'disabled:pointer-events-none disabled:opacity-60'
|
||||
)}
|
||||
</span>
|
||||
<span className="relative inline-grid size-6 shrink-0 place-items-center">
|
||||
<span
|
||||
>
|
||||
<span className="inline-flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full border border-border px-1.5 text-[10px] font-medium leading-none tabular-nums">
|
||||
{inboxCount}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate text-left">{countLabel}</span>
|
||||
<ChevronRight
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
'inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full border border-border px-1.5 text-[10px] font-medium leading-none text-muted-foreground transition-opacity',
|
||||
'size-3 shrink-0',
|
||||
// Why: the suppress control takes over this slot on hover.
|
||||
onSuppress &&
|
||||
'can-hover:group-hover:opacity-0 can-hover:group-focus-within:opacity-0 [@media(hover:none)]:opacity-0'
|
||||
)}
|
||||
>
|
||||
{inboxCount}
|
||||
</span>
|
||||
{onSuppress ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
disabled={pending}
|
||||
aria-label={suppressAriaLabel}
|
||||
onClick={onSuppress}
|
||||
className="absolute inset-0 text-muted-foreground hover:bg-worktree-sidebar-accent hover:text-worktree-sidebar-accent-foreground can-hover:pointer-events-none can-hover:opacity-0 can-hover:group-hover:pointer-events-auto can-hover:group-hover:opacity-100 can-hover:group-focus-within:pointer-events-auto can-hover:group-focus-within:opacity-100"
|
||||
>
|
||||
<X className="size-3" aria-hidden="true" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{suppressLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isExpanded ? (
|
||||
<div className="ml-4 mt-0.5 border-l border-worktree-sidebar-border pb-1 pl-2">
|
||||
<p className="px-1.5 py-1 text-[10px] leading-4 text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.4d7a1c9e53',
|
||||
'These worktrees were created outside of Orca.'
|
||||
)}
|
||||
</p>
|
||||
<ul className="grid gap-0.5">
|
||||
{inboxWorktrees.map((worktree) => (
|
||||
<li
|
||||
key={worktree.id ?? worktree.path ?? worktree.displayName}
|
||||
className="flex min-h-7 min-w-0 items-center gap-2 rounded-md px-1.5 py-1 text-xs hover:bg-worktree-sidebar-accent"
|
||||
/>
|
||||
</button>
|
||||
{onSuppress ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
disabled={pending}
|
||||
aria-label={suppressAriaLabel}
|
||||
onClick={onSuppress}
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground hover:bg-worktree-sidebar-accent hover:text-worktree-sidebar-accent-foreground can-hover:pointer-events-none can-hover:opacity-0 can-hover:group-hover:pointer-events-auto can-hover:group-hover:opacity-100 can-hover:group-focus-within:pointer-events-auto can-hover:group-focus-within:opacity-100"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate font-medium">{worktree.displayName}</div>
|
||||
{worktree.path ? (
|
||||
<div className="truncate font-mono text-[10px] text-muted-foreground">
|
||||
{worktree.path}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{onImportWorktree && worktree.id ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
disabled={pending}
|
||||
onClick={() => onImportWorktree(worktree.id!)}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.8b3f2e1d74',
|
||||
'Import'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="grid gap-1 px-1.5 pb-1 pt-1">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{onKeepHidden ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
disabled={pending}
|
||||
onClick={onKeepHidden}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.1c9e7a4b28',
|
||||
'Keep hidden'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{onImportAll ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
size="xs"
|
||||
disabled={pending}
|
||||
onClick={onImportAll}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.sidebar.NewExternalWorktreesInboxLine.6f2d8c1e95',
|
||||
'Import all'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<X className="size-3" aria-hidden="true" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{suppressLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p className="px-1.5 pb-1 pt-0.5 text-[11px] leading-4 text-destructive" role="alert">
|
||||
|
||||
@@ -270,8 +270,6 @@ import {
|
||||
type ImportedWorktreeCardActionState
|
||||
} from './imported-worktrees-card-actions'
|
||||
import {
|
||||
importNewExternalWorktreeInboxPaths,
|
||||
keepNewExternalWorktreeInboxHidden,
|
||||
suppressNewExternalWorktreeInbox,
|
||||
type NewExternalWorktreesInboxActionState
|
||||
} from './new-external-worktrees-inbox-actions'
|
||||
@@ -280,10 +278,7 @@ import {
|
||||
buildImportedWorktreesCardCandidates,
|
||||
getHiddenImportedWorktrees
|
||||
} from './imported-worktrees-card-candidates'
|
||||
import {
|
||||
buildNewExternalWorktreesInboxCandidates,
|
||||
toNewExternalWorktreeInboxPreview
|
||||
} from './new-external-worktrees-inbox-candidates'
|
||||
import { buildNewExternalWorktreesInboxCandidates } from './new-external-worktrees-inbox-candidates'
|
||||
import {
|
||||
WORKTREE_SECTION_HEADER_PADDING_LEFT,
|
||||
LINEAGE_CHILDREN_INLINE_OFFSET,
|
||||
@@ -668,9 +663,6 @@ type VirtualizedWorktreeViewportProps = {
|
||||
handleShowImportedWorktrees: (projectId: string) => void
|
||||
handleKeepImportedWorktreesHidden: (projectId: string) => void
|
||||
importedWorktreeCardActionState: ReadonlyMap<string, ImportedWorktreeCardActionState>
|
||||
handleImportNewExternalWorktree: (projectId: string, worktreeId: string) => void
|
||||
handleImportAllNewExternalWorktrees: (projectId: string) => void
|
||||
handleKeepNewExternalWorktreeInboxHidden: (projectId: string) => void
|
||||
handleOpenSuppressExternalWorktreeInbox: (projectId: string) => void
|
||||
newExternalWorktreeInboxActionState: ReadonlyMap<string, NewExternalWorktreesInboxActionState>
|
||||
handleRemoveProject: (repo: Repo) => void
|
||||
@@ -1335,9 +1327,6 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
||||
handleShowImportedWorktrees,
|
||||
handleKeepImportedWorktreesHidden,
|
||||
importedWorktreeCardActionState,
|
||||
handleImportNewExternalWorktree,
|
||||
handleImportAllNewExternalWorktrees,
|
||||
handleKeepNewExternalWorktreeInboxHidden,
|
||||
handleOpenSuppressExternalWorktreeInbox,
|
||||
newExternalWorktreeInboxActionState,
|
||||
handleRemoveProject,
|
||||
@@ -5058,14 +5047,10 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
||||
>
|
||||
<NewExternalWorktreesInboxLine
|
||||
repoDisplayName={row.repo.displayName}
|
||||
inboxWorktrees={row.inboxWorktrees.map(toNewExternalWorktreeInboxPreview)}
|
||||
inboxCount={row.inboxWorktrees.length}
|
||||
pending={actionState?.pending ?? false}
|
||||
error={actionState?.error ?? null}
|
||||
onImportWorktree={(worktreeId) =>
|
||||
handleImportNewExternalWorktree(row.repo.id, worktreeId)
|
||||
}
|
||||
onKeepHidden={() => handleKeepNewExternalWorktreeInboxHidden(row.repo.id)}
|
||||
onImportAll={() => handleImportAllNewExternalWorktrees(row.repo.id)}
|
||||
onReview={() => handleOpenWorktreeVisibility(row.repo)}
|
||||
onSuppress={() => handleOpenSuppressExternalWorktreeInbox(row.repo.id)}
|
||||
/>
|
||||
</div>
|
||||
@@ -6159,52 +6144,6 @@ const WorktreeList = React.memo(function WorktreeList({
|
||||
[fetchWorktrees, repos, setNewExternalWorktreeInboxState, updateRepo]
|
||||
)
|
||||
|
||||
const handleImportNewExternalWorktree = useCallback(
|
||||
async (projectId: string, worktreeId: string) => {
|
||||
const inboxWorktrees = newExternalWorktreesInboxByRepo.get(projectId)?.inboxWorktrees ?? []
|
||||
const worktree = inboxWorktrees.find((candidate) => candidate.id === worktreeId)
|
||||
if (!worktree) {
|
||||
return
|
||||
}
|
||||
const args = getNewExternalWorktreeInboxActionArgs(projectId, [worktree.path])
|
||||
if (!args) {
|
||||
return
|
||||
}
|
||||
await importNewExternalWorktreeInboxPaths(args)
|
||||
},
|
||||
[getNewExternalWorktreeInboxActionArgs, newExternalWorktreesInboxByRepo]
|
||||
)
|
||||
|
||||
const handleImportAllNewExternalWorktrees = useCallback(
|
||||
async (projectId: string) => {
|
||||
const inboxWorktrees = newExternalWorktreesInboxByRepo.get(projectId)?.inboxWorktrees ?? []
|
||||
const args = getNewExternalWorktreeInboxActionArgs(
|
||||
projectId,
|
||||
inboxWorktrees.map((worktree) => worktree.path)
|
||||
)
|
||||
if (!args) {
|
||||
return
|
||||
}
|
||||
await importNewExternalWorktreeInboxPaths(args)
|
||||
},
|
||||
[getNewExternalWorktreeInboxActionArgs, newExternalWorktreesInboxByRepo]
|
||||
)
|
||||
|
||||
const handleKeepNewExternalWorktreeInboxHidden = useCallback(
|
||||
async (projectId: string) => {
|
||||
const inboxWorktrees = newExternalWorktreesInboxByRepo.get(projectId)?.inboxWorktrees ?? []
|
||||
const args = getNewExternalWorktreeInboxActionArgs(
|
||||
projectId,
|
||||
inboxWorktrees.map((worktree) => worktree.path)
|
||||
)
|
||||
if (!args) {
|
||||
return
|
||||
}
|
||||
await keepNewExternalWorktreeInboxHidden(args)
|
||||
},
|
||||
[getNewExternalWorktreeInboxActionArgs, newExternalWorktreesInboxByRepo]
|
||||
)
|
||||
|
||||
const handleOpenSuppressExternalWorktreeInbox = useCallback((projectId: string) => {
|
||||
setSuppressExternalWorktreeInboxRepoId(projectId)
|
||||
}, [])
|
||||
@@ -6866,9 +6805,6 @@ const WorktreeList = React.memo(function WorktreeList({
|
||||
handleShowImportedWorktrees={handleShowImportedWorktrees}
|
||||
handleKeepImportedWorktreesHidden={handleKeepImportedWorktreesHidden}
|
||||
importedWorktreeCardActionState={importedWorktreeCardActionState}
|
||||
handleImportNewExternalWorktree={handleImportNewExternalWorktree}
|
||||
handleImportAllNewExternalWorktrees={handleImportAllNewExternalWorktrees}
|
||||
handleKeepNewExternalWorktreeInboxHidden={handleKeepNewExternalWorktreeInboxHidden}
|
||||
handleOpenSuppressExternalWorktreeInbox={handleOpenSuppressExternalWorktreeInbox}
|
||||
newExternalWorktreeInboxActionState={newExternalWorktreeInboxActionState}
|
||||
handleRemoveProject={handleRemoveProject}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import type {
|
||||
DetectedWorktree,
|
||||
DetectedWorktreeListResult,
|
||||
Repo,
|
||||
Worktree
|
||||
} from '../../../../shared/types'
|
||||
import type { DetectedWorktreeListResult, Repo, Worktree } from '../../../../shared/types'
|
||||
import { getNewExternalWorktreeInboxWorktrees } from '../../../../shared/external-worktree-inbox'
|
||||
import { isGitRepoKind } from '../../../../shared/repo-kind'
|
||||
import type { NewExternalWorktreesInboxCandidate } from './worktree-list-groups'
|
||||
@@ -39,19 +34,3 @@ export function buildNewExternalWorktreesInboxCandidates(args: {
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
export type NewExternalWorktreeInboxPreview = Pick<
|
||||
DetectedWorktree,
|
||||
'id' | 'displayName' | 'path' | 'branch'
|
||||
>
|
||||
|
||||
export function toNewExternalWorktreeInboxPreview(
|
||||
worktree: DetectedWorktree
|
||||
): NewExternalWorktreeInboxPreview {
|
||||
return {
|
||||
id: worktree.id,
|
||||
displayName: worktree.displayName,
|
||||
path: worktree.path,
|
||||
branch: worktree.branch
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5219,14 +5219,11 @@
|
||||
},
|
||||
"NewExternalWorktreesInboxLine": {
|
||||
"9f2d4c8b17": "Hide external worktrees permanently for {{value0}}",
|
||||
"d9f7b2a14c": "Collapse new externally-created worktrees for {{value0}}",
|
||||
"e2c4a8d91f": "Expand new externally-created worktrees for {{value0}}",
|
||||
"7c4e9b2a81": "New externally-created worktrees",
|
||||
"5e1b8d3f62": "Hide external worktrees permanently",
|
||||
"4d7a1c9e53": "These worktrees were created outside of Orca.",
|
||||
"8b3f2e1d74": "Import",
|
||||
"1c9e7a4b28": "Keep hidden",
|
||||
"6f2d8c1e95": "Import all",
|
||||
"2a6f31d8c7": "hidden worktree",
|
||||
"5b90e4a2f6": "hidden worktrees",
|
||||
"7f18c5b0d3": "Review {{value0}} hidden worktree in {{value1}}",
|
||||
"4e2b7a9c05": "Review {{value0}} hidden worktrees in {{value1}}",
|
||||
"c3e8a1f4b2": "Don't show again"
|
||||
},
|
||||
"newExternalWorktreesInboxActions": {
|
||||
|
||||
@@ -5039,14 +5039,7 @@
|
||||
},
|
||||
"NewExternalWorktreesInboxLine": {
|
||||
"9f2d4c8b17": "Ocultar worktrees externos permanentemente para {{value0}}",
|
||||
"d9f7b2a14c": "Contraer worktrees nuevos creados externamente para {{value0}}",
|
||||
"e2c4a8d91f": "Expandir worktrees nuevos creados externamente para {{value0}}",
|
||||
"7c4e9b2a81": "Nuevos worktrees creados externamente",
|
||||
"5e1b8d3f62": "Ocultar worktrees externos permanentemente",
|
||||
"4d7a1c9e53": "Estos worktrees se crearon fuera de Orca.",
|
||||
"8b3f2e1d74": "Importar",
|
||||
"1c9e7a4b28": "Mantener ocultos",
|
||||
"6f2d8c1e95": "Importar todo",
|
||||
"c3e8a1f4b2": "No volver a mostrar"
|
||||
},
|
||||
"newExternalWorktreesInboxActions": {
|
||||
|
||||
@@ -5039,14 +5039,7 @@
|
||||
},
|
||||
"NewExternalWorktreesInboxLine": {
|
||||
"9f2d4c8b17": "{{value0}} の外部ワークツリーを完全に非表示にする",
|
||||
"d9f7b2a14c": "{{value0}} の外部で作成された新規ワークツリーを折りたたむ",
|
||||
"e2c4a8d91f": "{{value0}} の外部で作成された新規ワークツリーを展開",
|
||||
"7c4e9b2a81": "外部で作成された新規ワークツリー",
|
||||
"5e1b8d3f62": "外部ワークツリーを完全に非表示にする",
|
||||
"4d7a1c9e53": "これらのワークツリーは Orca の外部で作成されました。",
|
||||
"8b3f2e1d74": "インポート",
|
||||
"1c9e7a4b28": "非表示のままにする",
|
||||
"6f2d8c1e95": "すべてインポート",
|
||||
"c3e8a1f4b2": "今後表示しない"
|
||||
},
|
||||
"newExternalWorktreesInboxActions": {
|
||||
|
||||
@@ -5039,14 +5039,7 @@
|
||||
},
|
||||
"NewExternalWorktreesInboxLine": {
|
||||
"9f2d4c8b17": "{{value0}}의 외부 워크트리를 영구적으로 숨기기",
|
||||
"d9f7b2a14c": "{{value0}}의 외부에서 생성된 새 워크트리 접기",
|
||||
"e2c4a8d91f": "{{value0}}의 외부에서 생성된 새 워크트리 펼치기",
|
||||
"7c4e9b2a81": "외부에서 생성된 새 워크트리",
|
||||
"5e1b8d3f62": "외부 워크트리를 영구적으로 숨기기",
|
||||
"4d7a1c9e53": "이 워크트리는 Orca 외부에서 생성되었습니다.",
|
||||
"8b3f2e1d74": "가져오기",
|
||||
"1c9e7a4b28": "숨긴 상태 유지",
|
||||
"6f2d8c1e95": "모두 가져오기",
|
||||
"c3e8a1f4b2": "다시 표시하지 않기"
|
||||
},
|
||||
"newExternalWorktreesInboxActions": {
|
||||
|
||||
@@ -5051,14 +5051,7 @@
|
||||
},
|
||||
"NewExternalWorktreesInboxLine": {
|
||||
"9f2d4c8b17": "永久隐藏 {{value0}} 的外部工作树",
|
||||
"d9f7b2a14c": "折叠 {{value0}} 的外部创建的新工作树",
|
||||
"e2c4a8d91f": "展开 {{value0}} 的外部创建的新工作树",
|
||||
"7c4e9b2a81": "外部创建的新工作树",
|
||||
"5e1b8d3f62": "永久隐藏外部工作树",
|
||||
"4d7a1c9e53": "这些工作树是在 Orca 外部创建的。",
|
||||
"8b3f2e1d74": "导入",
|
||||
"1c9e7a4b28": "保持隐藏",
|
||||
"6f2d8c1e95": "全部导入",
|
||||
"c3e8a1f4b2": "不再显示"
|
||||
},
|
||||
"newExternalWorktreesInboxActions": {
|
||||
|
||||
Reference in New Issue
Block a user