✨ feat(vscode): 代码阅读预览标出新增 / 修改 / 删除的卡片(左侧改动色条、新增淡绿底挂小标、删掉的块原位收成一行可展开),筛选栏第二行写对比基准和改动统计,新文件顶部横幅
This commit is contained in:
1 parent
2a5806c6b5
commit
3b7ce69842
10 files changed
+491
-20
No files matched your search
@@ -1,12 +1,14 @@
|
||||
import type { CSSProperties, ReactElement } from 'react'
|
||||
import type { CSSProperties, ReactElement, ReactNode } from 'react'
|
||||
import type { DiffTree, PlacedRemoved } from './diffView'
|
||||
import type { QaState } from './qa'
|
||||
import type { PendingImage } from './qaImages'
|
||||
import type { BlockView } from './types'
|
||||
import { ChevronDown, ChevronRight, EyeOff } from 'lucide-react'
|
||||
import { Fragment } from 'react'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { AskThread } from './AskThread'
|
||||
import { BlockTools } from './BlockTools'
|
||||
import { CodeEditor } from './CodeEditor'
|
||||
import { removedTitle } from './diffView'
|
||||
import { KindIcon } from './kindIcons'
|
||||
import { PropertyGrid, PropertyTable } from './PropertyGrid'
|
||||
import { asksOf, historyOf } from './qa'
|
||||
@@ -33,6 +35,8 @@ export interface BlockTreeState {
|
||||
onToggleFold: (id: string) => void
|
||||
/** 问 AI;文件不在工作区文件夹里(问答没地方存)时没有 */
|
||||
qa?: QaTree
|
||||
/** 代码差异:新增、修改的块和删掉的块;侧栏开着「未提交的更改」或审查工单、基准里有这个文件时才有 */
|
||||
diff?: DiffTree
|
||||
}
|
||||
|
||||
/** 问 AI 的状态和动作,块按自己的记号(anchor)取 */
|
||||
@@ -48,9 +52,23 @@ export interface QaTree {
|
||||
onSelectProfile: (id: string) => void
|
||||
}
|
||||
|
||||
/** 块下面有没有画出来的子块:子块都被筛掉、被盖住时不留空的一层、不给折叠箭头。 */
|
||||
/** 块下面有没有画出来的子块(删掉的子块也算):子块都被筛掉、被盖住时不留空的一层、不给折叠箭头。 */
|
||||
function hasShownChildren(block: BlockView, tree: BlockTreeState): boolean {
|
||||
return block.children.some(child => shown(child, tree))
|
||||
return block.children.some(child => shown(child, tree)) || (!tree.covered.has(block.id) && tree.diff?.removed.has(block.id) === true)
|
||||
}
|
||||
|
||||
const CHANGE_NAMES = { add: '新增', mod: '修改', del: '删除' } as const
|
||||
|
||||
/** 标差异时卡片左边外侧的改动色条(没变的留一条透明的,同一层照样对齐);不标差异时原样。 */
|
||||
function ChangeRow({ tree, change, children }: { tree: BlockTreeState, change: keyof typeof CHANGE_NAMES | undefined, children: ReactNode }): ReactElement {
|
||||
if (!tree.diff)
|
||||
return <>{children}</>
|
||||
return (
|
||||
<div className="cr-change-row flex gap-[5px]">
|
||||
<span className="cr-change-bar" data-change={change} data-tooltip={change && CHANGE_NAMES[change]} />
|
||||
<div className="min-w-0 flex-1">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** 块画不画:被筛掉的、被正在编辑的祖先盖住的都不画。 */
|
||||
@@ -74,21 +92,35 @@ function kindProps(block: BlockView | undefined): { 'data-kind'?: string, 'data-
|
||||
/**
|
||||
* 一层代码块:左边一条竖线标出这一层,每块的子块紧跟在它下面、再往右缩进一层。被筛掉、被盖住的块不画,不留空位,竖线跟着变短。
|
||||
* owner 是这一层所属的块(顶层没有),悬停它时竖线换成它的种类色。
|
||||
* 标差异时每张卡片左边外侧一条改动色条,删掉的块收成一行放回原来的位置(见 RemovedCard)。
|
||||
*/
|
||||
export function BlockLevel({ blocks, owner, tree }: { blocks: BlockView[], owner?: BlockView, tree: BlockTreeState }): ReactElement {
|
||||
const removed = tree.diff?.removed.get(owner?.id ?? '') ?? []
|
||||
// 删掉的块排在它原来前面那块后面;那块被筛掉了也照排,不跟着消失
|
||||
const removedAfter = (id: string | undefined): ReactElement[] => removed
|
||||
.filter(item => item.after === id)
|
||||
.map(item => <RemovedCard key={item.id} placed={item} tree={tree} />)
|
||||
return (
|
||||
<div className="cr-level flex gap-2" {...kindProps(owner)}>
|
||||
<div className="flex w-3 shrink-0 justify-center">
|
||||
<div className="cr-guide w-px" />
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
||||
{blocks.filter(block => shown(block, tree)).map(block => (
|
||||
{removedAfter(undefined)}
|
||||
{blocks.map(block => (
|
||||
<Fragment key={block.id}>
|
||||
<BlockCard block={block} tree={tree} />
|
||||
{tree.qa && <BlockQa key={block.anchor} anchor={block.anchor} qa={tree.qa} />}
|
||||
{hasShownChildren(block, tree) && !tree.collapsed.has(block.id) && (
|
||||
<BlockLevel blocks={block.children} owner={block} tree={tree} />
|
||||
{shown(block, tree) && (
|
||||
<>
|
||||
<ChangeRow tree={tree} change={tree.diff?.changes.get(block.id)?.change}>
|
||||
<BlockCard block={block} tree={tree} />
|
||||
</ChangeRow>
|
||||
{tree.qa && <BlockQa key={block.anchor} anchor={block.anchor} qa={tree.qa} />}
|
||||
{hasShownChildren(block, tree) && !tree.collapsed.has(block.id) && (
|
||||
<BlockLevel blocks={block.children} owner={block} tree={tree} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{removedAfter(block.id)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
@@ -139,14 +171,17 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }):
|
||||
const collapsed = tree.collapsed.has(block.id)
|
||||
const hasChildren = hasShownChildren(block, tree)
|
||||
const Chevron = collapsed ? ChevronRight : ChevronDown
|
||||
const change = tree.diff?.changes.get(block.id)
|
||||
return (
|
||||
<div
|
||||
className="cr-block flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2"
|
||||
className="cr-block relative flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2"
|
||||
{...kindProps(block)}
|
||||
data-id={block.id}
|
||||
data-line={block.startLine}
|
||||
data-editing={editing ? '' : undefined}
|
||||
data-change={change?.change}
|
||||
>
|
||||
{change?.outermost && <span className="cr-added-badge">新增</span>}
|
||||
{hasChildren
|
||||
? (
|
||||
<button
|
||||
@@ -189,3 +224,44 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }):
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删掉的一块,放在原来的位置:红色虚线边、半透明,没有编辑、问 AI 按钮。默认收成一行「已删除 · 种类 名字」,
|
||||
* 点箭头或这一行展开,下面是旧内容(整张划掉,名字不能跳定义);子块不画。
|
||||
*/
|
||||
function RemovedCard({ placed, tree }: { placed: PlacedRemoved, tree: BlockTreeState }): ReactElement {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { block, kindName } = placed.removed
|
||||
const title = removedTitle(block)
|
||||
const Chevron = open ? ChevronDown : ChevronRight
|
||||
const toggle = (): void => setOpen(value => !value)
|
||||
return (
|
||||
<ChangeRow tree={tree} change="del">
|
||||
<div className="cr-block cr-removed flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2" {...kindProps(block)} data-id={placed.id}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-5 w-3.5 shrink-0 items-center justify-center rounded-sm text-[var(--vscode-descriptionForeground)] hover:text-[var(--vscode-foreground)]"
|
||||
aria-label={open ? '收起删掉的内容' : '展开删掉的内容'}
|
||||
aria-expanded={open}
|
||||
onClick={toggle}
|
||||
>
|
||||
<Chevron size={14} strokeWidth={1.8} />
|
||||
</button>
|
||||
<KindIcon block={block} name={kindName} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="cr-removed-head cursor-pointer truncate text-[12.5px] leading-5" onClick={toggle}>
|
||||
<span className="cr-removed-label">已删除 · </span>
|
||||
{kindName}
|
||||
{title && <span className="cr-removed-title">{` ${title}`}</span>}
|
||||
</div>
|
||||
{open && (
|
||||
<div className="cr-removed-old mt-1">
|
||||
{/* 旧块的 id 是旧版本里的位置,换成页面上认删掉的块用的 id,属性行的 id 才不和现在的块撞上 */}
|
||||
<BlockPreview block={{ ...block, id: placed.id }} tree={tree} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ChangeRow>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user