⚡ perf(vscode): 摘修复用本地合并记录分组,列表改手风琴

This commit is contained in:
cruldra committed 2026-09-01 19:03:56 +08:00
1 parent cfabee08b7
commit 5284fc1730
8 files changed
+183 -221

No files matched your search

@@ -1,5 +1,5 @@
import type { ReleaseCommitGroup } from '../lib/messages'
import { X } from 'lucide-react'
import { ChevronRight, X } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react'
interface Props {
@@ -10,6 +10,10 @@ interface Props {
onConfirm: (shas: string[]) => void
}
function groupKey(g: ReleaseCommitGroup): string {
return g.pr != null ? `pr-${g.pr}` : 'ungrouped'
}
function filterGroups(groups: ReleaseCommitGroup[], query: string): ReleaseCommitGroup[] {
const q = query.trim().toLowerCase()
if (!q)
@@ -33,12 +37,14 @@ function filterGroups(groups: ReleaseCommitGroup[], query: string): ReleaseCommi
export function CherryPickModal({ open, branch, groups, onCancel, onConfirm }: Props) {
const [query, setQuery] = useState('')
const [selected, setSelected] = useState<Set<string>>(() => new Set())
const [expanded, setExpanded] = useState<Set<string>>(() => new Set())
useEffect(() => {
if (!open)
return
setQuery('')
setSelected(new Set())
setExpanded(new Set(groups.filter(g => g.pr == null).map(groupKey)))
}, [open, groups])
useEffect(() => {
@@ -68,7 +74,7 @@ export function CherryPickModal({ open, branch, groups, onCancel, onConfirm }: P
})
}
function toggleGroup(g: ReleaseCommitGroup): void {
function toggleGroupSelected(g: ReleaseCommitGroup): void {
const shas = g.commits.map(c => c.sha)
const allOn = shas.every(s => selected.has(s))
setSelected((prev) => {
@@ -83,6 +89,18 @@ export function CherryPickModal({ open, branch, groups, onCancel, onConfirm }: P
})
}
function toggleExpanded(g: ReleaseCommitGroup): void {
const key = groupKey(g)
setExpanded((prev) => {
const next = new Set(prev)
if (next.has(key))
next.delete(key)
else
next.add(key)
return next
})
}
const count = selected.size
return (
@@ -112,45 +130,69 @@ export function CherryPickModal({ open, branch, groups, onCancel, onConfirm }: P
<div className="min-h-0 flex-1 space-y-2 overflow-y-auto px-4 py-3">
{visible.length === 0
? <div className="py-8 text-center text-xs opacity-70">没有匹配的提交</div>
: visible.map(g => (
<section
key={g.pr ?? `ungrouped-${g.commits[0]?.sha}`}
className="rounded border border-[var(--vscode-panel-border)]"
>
<button
type="button"
onClick={() => toggleGroup(g)}
className="flex w-full items-center gap-2 border-b border-[var(--vscode-panel-border)] px-3 py-2 text-left hover:bg-white/5"
: visible.map((g) => {
const key = groupKey(g)
const openGroup = query.trim() !== '' || expanded.has(key)
const shas = g.commits.map(c => c.sha)
const allOn = shas.length > 0 && shas.every(s => selected.has(s))
const someOn = shas.some(s => selected.has(s))
return (
<section
key={key}
className="rounded border border-[var(--vscode-panel-border)]"
>
<span className="flex-1 truncate font-medium">
{g.pr != null ? `#${g.pr} ${g.title}` : g.title}
</span>
<span className="shrink-0 text-[11px] opacity-60">
{`${g.authors.join(' · ')} · ${g.commits.length} 条`}
</span>
</button>
<ul className="py-1">
{g.commits.map(c => (
<li key={c.sha}>
<label className="flex cursor-pointer items-start gap-2 px-3 py-1.5 hover:bg-white/5">
<input
type="checkbox"
checked={selected.has(c.sha)}
onChange={() => toggleSha(c.sha)}
className="mt-0.5"
/>
<span className="min-w-0 flex-1">
<span className="block truncate">{c.subject}</span>
<span className="block text-[11px] opacity-60">
{`${c.sha.slice(0, 7)} · ${c.author} · ${c.date}`}
</span>
</span>
</label>
</li>
))}
</ul>
</section>
))}
<div className="flex items-center gap-2 px-3 py-2">
<button
type="button"
aria-expanded={openGroup}
onClick={() => toggleExpanded(g)}
className="flex min-w-0 flex-1 items-center gap-2 text-left hover:opacity-90"
>
<ChevronRight className={`size-3.5 shrink-0 opacity-70 transition-transform ${openGroup ? 'rotate-90' : ''}`} />
<span className="min-w-0 flex-1 truncate font-medium">
{g.pr != null ? `#${g.pr} ${g.title}` : g.title}
</span>
<span className="shrink-0 text-[11px] opacity-60">
{`${g.authors.join(' · ')} · ${g.commits.length} 条`}
</span>
</button>
<input
type="checkbox"
checked={allOn}
ref={(el) => {
if (el)
el.indeterminate = someOn && !allOn
}}
onChange={() => toggleGroupSelected(g)}
aria-label="全选本组"
className="shrink-0"
/>
</div>
{openGroup && (
<ul className="border-t border-[var(--vscode-panel-border)] py-1">
{g.commits.map(c => (
<li key={c.sha}>
<label className="flex cursor-pointer items-start gap-2 px-3 py-1.5 hover:bg-white/5">
<input
type="checkbox"
checked={selected.has(c.sha)}
onChange={() => toggleSha(c.sha)}
className="mt-0.5"
/>
<span className="min-w-0 flex-1">
<span className="block truncate">{c.subject}</span>
<span className="block text-[11px] opacity-60">
{`${c.sha.slice(0, 7)} · ${c.author} · ${c.date}`}
</span>
</span>
</label>
</li>
))}
</ul>
)}
</section>
)
})}
</div>
<div className="flex items-center justify-end gap-2 border-t border-[var(--vscode-panel-border)] px-4 py-3">
<span className="mr-auto text-xs opacity-70">