mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
Refine workspace lineage child chip
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -3,10 +3,12 @@ import React, { useEffect, useMemo, useCallback, useState } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useAppStore } from '@/store'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
||||
import {
|
||||
AlertTriangle,
|
||||
Bell,
|
||||
ChevronDown,
|
||||
GitMerge,
|
||||
LoaderCircle,
|
||||
CircleCheck,
|
||||
@@ -55,6 +57,9 @@ type WorktreeCardProps = {
|
||||
hideRepoBadge?: boolean
|
||||
parentLabel?: string
|
||||
lineageState?: 'valid' | 'missing'
|
||||
lineageChildCount?: number
|
||||
lineageCollapsed?: boolean
|
||||
onLineageToggle?: (event: React.MouseEvent<HTMLButtonElement>) => void
|
||||
onSelectionGesture?: (event: React.MouseEvent<HTMLDivElement>, worktreeId: string) => boolean
|
||||
onContextMenuSelect?: (event: React.MouseEvent<HTMLDivElement>) => readonly Worktree[]
|
||||
}
|
||||
@@ -74,7 +79,10 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
onContextMenuSelect,
|
||||
hideRepoBadge,
|
||||
parentLabel,
|
||||
lineageState
|
||||
lineageState,
|
||||
lineageChildCount = 0,
|
||||
lineageCollapsed = false,
|
||||
onLineageToggle
|
||||
}: WorktreeCardProps) {
|
||||
const openModal = useAppStore((s) => s.openModal)
|
||||
const updateWorktreeMeta = useAppStore((s) => s.updateWorktreeMeta)
|
||||
@@ -432,6 +440,13 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
)
|
||||
|
||||
const unreadTooltip = worktree.isUnread ? 'Mark read' : 'Mark unread'
|
||||
const childWorkspaceLabel = `${lineageChildCount} child ${
|
||||
lineageChildCount === 1 ? 'workspace' : 'workspaces'
|
||||
}`
|
||||
const childWorkspaceShortLabel = `${lineageChildCount} ${
|
||||
lineageChildCount === 1 ? 'child' : 'children'
|
||||
}`
|
||||
const showLineageChildChip = lineageChildCount > 0 && onLineageToggle !== undefined
|
||||
|
||||
// Why: the 'unread' card property is the user's opt-out. When off, we render
|
||||
// as if the workspace is read so bold emphasis never appears. The persisted
|
||||
@@ -652,6 +667,34 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
|
||||
<CacheTimer worktreeId={worktree.id} />
|
||||
|
||||
{showLineageChildChip && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="h-[18px] max-w-[7.5rem] shrink-0 gap-1 rounded-md border border-sidebar-border bg-sidebar-accent/55 px-1.5 text-[10px] font-medium leading-none text-muted-foreground shadow-none hover:bg-sidebar-accent hover:text-foreground focus-visible:ring-1 focus-visible:ring-sidebar-ring"
|
||||
aria-label={`${lineageCollapsed ? 'Show' : 'Hide'} ${childWorkspaceLabel}`}
|
||||
aria-expanded={!lineageCollapsed}
|
||||
onClick={onLineageToggle}
|
||||
>
|
||||
<Workflow className="size-2.5" />
|
||||
<span className="truncate">{childWorkspaceShortLabel}</span>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'size-2.5 transition-transform',
|
||||
lineageCollapsed && '-rotate-90'
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
{lineageCollapsed ? 'Show child workspaces' : 'Hide child workspaces'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{parentLabel && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable max-lines */
|
||||
import React, { useMemo, useCallback, useRef, useState, useEffect, useLayoutEffect } from 'react'
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
import { ChevronDown, ChevronRight, CircleX, Plus } from 'lucide-react'
|
||||
import { ChevronDown, CircleX, Plus } from 'lucide-react'
|
||||
import { useAppStore } from '@/store'
|
||||
import {
|
||||
getAllWorktreesFromState,
|
||||
@@ -94,20 +94,20 @@ function LineageGuides({ row }: { row: Extract<Row, { type: 'item' }> }) {
|
||||
continues ? (
|
||||
<span
|
||||
key={index}
|
||||
className="absolute top-0 bottom-0 w-px bg-current/80"
|
||||
className="absolute top-0 bottom-0 w-[2px] rounded-full bg-current/90"
|
||||
style={{ left: `${index * step + lineX}px` }}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
<span
|
||||
className="absolute top-0 w-px bg-current/80"
|
||||
className="absolute top-0 w-[2px] rounded-full bg-current/90"
|
||||
style={{
|
||||
left: `${currentX}px`,
|
||||
height: row.isLastLineageChild ? '50%' : '100%'
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
className="absolute h-px bg-current/80"
|
||||
className="absolute h-[2px] rounded-full bg-current/90"
|
||||
style={{
|
||||
left: `${currentX}px`,
|
||||
top: '50%',
|
||||
@@ -118,45 +118,6 @@ function LineageGuides({ row }: { row: Extract<Row, { type: 'item' }> }) {
|
||||
)
|
||||
}
|
||||
|
||||
function LineageDisclosure({
|
||||
row,
|
||||
onToggle
|
||||
}: {
|
||||
row: Extract<Row, { type: 'item' }>
|
||||
onToggle: (event: React.MouseEvent<HTMLButtonElement>) => void
|
||||
}) {
|
||||
if (!row.lineageGroupKey || row.lineageChildCount <= 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const label = `${row.lineageCollapsed ? 'Show' : 'Hide'} ${row.lineageChildCount} child ${
|
||||
row.lineageChildCount === 1 ? 'workspace' : 'workspaces'
|
||||
}`
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="absolute right-2 top-2 z-10 size-5 rounded-md bg-sidebar/80 text-muted-foreground opacity-80 shadow-sm ring-1 ring-sidebar-border/70 backdrop-blur-sm transition-colors hover:bg-sidebar-accent hover:text-foreground hover:opacity-100 focus-visible:opacity-100"
|
||||
aria-label={label}
|
||||
aria-expanded={!row.lineageCollapsed}
|
||||
onClick={onToggle}
|
||||
>
|
||||
<ChevronRight
|
||||
className={cn('size-3 transition-transform', !row.lineageCollapsed && 'rotate-90')}
|
||||
/>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
{row.lineageCollapsed ? 'Show child workspaces' : 'Hide child workspaces'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
type VirtualizedWorktreeViewportProps = {
|
||||
rows: Row[]
|
||||
activeWorktreeId: string | null
|
||||
@@ -654,6 +615,8 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
||||
)
|
||||
}
|
||||
|
||||
const lineageToggleGroupKey = row.lineageGroupKey
|
||||
|
||||
return (
|
||||
<div
|
||||
key={vItem.key}
|
||||
@@ -684,16 +647,17 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
||||
row.depth > 0 && row.lineageState === 'valid' ? undefined : row.parentLabel
|
||||
}
|
||||
lineageState={row.lineageState}
|
||||
/>
|
||||
<LineageDisclosure
|
||||
row={row}
|
||||
onToggle={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (row.lineageGroupKey) {
|
||||
toggleGroup(row.lineageGroupKey)
|
||||
}
|
||||
}}
|
||||
lineageChildCount={row.lineageChildCount}
|
||||
lineageCollapsed={row.lineageCollapsed}
|
||||
onLineageToggle={
|
||||
lineageToggleGroupKey
|
||||
? (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
toggleGroup(lineageToggleGroupKey)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user