⚡ perf(vscode): 代码阅读预览差异到了不再给每张卡片外面包一层:改动色条画在卡片里、挪到卡片外侧,卡片不拆掉重建
This commit is contained in:
1 parent
0ad4748cc6
commit
b2e998665e
3 files changed
+66
-36
No files matched your search
@@ -277,6 +277,37 @@ describe('一条消息只重画受影响的卡片', () => {
|
|||||||
expect(container.querySelector('[data-offset="30"]')?.getAttribute('data-occurrence')).toBe('normal')
|
expect(container.querySelector('[data-offset="30"]')?.getAttribute('data-occurrence')).toBe('normal')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('差异到了:卡片还是原来那个元素(不拆掉重建),改了的那块标上改动色条,删掉的块放回原处', async () => {
|
||||||
|
await load()
|
||||||
|
await deliver(copy(DOC))
|
||||||
|
const cards = [...container.querySelectorAll('.cr-block')]
|
||||||
|
const removed = { ...block('9', 'gone'), translation: [{ text: 'gone', role: 'plain' as const }] }
|
||||||
|
renders.start()
|
||||||
|
await deliver({
|
||||||
|
type: 'diff',
|
||||||
|
diff: {
|
||||||
|
base: 'HEAD',
|
||||||
|
version: 3,
|
||||||
|
blocks: { save: { change: 'mod', own: true } },
|
||||||
|
removed: [{ parent: '', after: 'save', block: removed, line: 9, kindName: '函数' }],
|
||||||
|
stats: { add: 0, mod: 1, del: 1 },
|
||||||
|
lineCount: 20,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const counts = renders.stop()
|
||||||
|
expect(counts.SegmentText).toBeUndefined()
|
||||||
|
const after = [...container.querySelectorAll('.cr-block:not(.cr-removed)')]
|
||||||
|
expect(after).toHaveLength(3)
|
||||||
|
after.forEach((card, index) => expect(card).toBe(cards[index]))
|
||||||
|
expect(container.querySelector('[data-id="1"] > .cr-change-bar')?.getAttribute('data-change')).toBe('mod')
|
||||||
|
expect(container.querySelector('[data-id="0"] > .cr-change-bar')?.hasAttribute('data-change')).toBe(false)
|
||||||
|
expect(container.querySelector('.cr-level')?.hasAttribute('data-diff')).toBe(true)
|
||||||
|
const gone = container.querySelector('.cr-removed')!
|
||||||
|
expect(gone.querySelector('.cr-change-bar')?.getAttribute('data-change')).toBe('del')
|
||||||
|
expect(cards[1].compareDocumentPosition(gone) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
|
||||||
|
expect(gone.compareDocumentPosition(cards[2]) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it('没展开、没说过的块下面不画问答区;展开一块只重画那一块', async () => {
|
it('没展开、没说过的块下面不画问答区;展开一块只重画那一块', async () => {
|
||||||
await load()
|
await load()
|
||||||
await deliver({ type: 'qa', available: true, threads: [], path: [], subject: 'file', current: [], rewrites: [], rewriteCurrent: [] })
|
await deliver({ type: 'qa', available: true, threads: [], path: [], subject: 'file', current: [], rewrites: [], rewriteCurrent: [] })
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { CSSProperties, ReactElement, ReactNode } from 'react'
|
import type { CSSProperties, ReactElement } from 'react'
|
||||||
import type { RewriteTool } from './BlockTools'
|
import type { RewriteTool } from './BlockTools'
|
||||||
import type { DiffTree, FoldedRuns, PlacedRemoved } from './diffView'
|
import type { DiffTree, FoldedRuns, PlacedRemoved } from './diffView'
|
||||||
import type { GridSlots } from './PropertyGrid'
|
import type { GridSlots } from './PropertyGrid'
|
||||||
@@ -146,28 +146,22 @@ function hasShownChildren(block: BlockView, state: BlockTreeState): boolean {
|
|||||||
|
|
||||||
const CHANGE_NAMES = { add: '新增', mod: '修改', del: '删除' } as const
|
const CHANGE_NAMES = { add: '新增', mod: '修改', del: '删除' } as const
|
||||||
|
|
||||||
/** 标差异时卡片左边外侧的改动色条(没变的留一条透明的,同一层照样对齐);不标差异时原样。 */
|
/**
|
||||||
function ChangeRow({ diff, change, children }: { diff: boolean, change: keyof typeof CHANGE_NAMES | undefined, children: ReactNode }): ReactElement {
|
* 标差异时卡片左边外侧的改动色条(没变的是一条透明的):画在卡片里、由 CSS 挪到卡片左边外侧(同一层的卡片让出它的位置,见 .cr-level[data-diff]),
|
||||||
if (!diff)
|
* 差异到了、走了,卡片都还是原来那个元素,不拆掉重建。
|
||||||
return <>{children}</>
|
*/
|
||||||
return (
|
function ChangeBar({ change }: { change: keyof typeof CHANGE_NAMES | undefined }): ReactElement {
|
||||||
<div className="cr-change-row flex gap-[5px]">
|
return <span className="cr-change-bar" data-change={change} data-tooltip={change && CHANGE_NAMES[change]} />
|
||||||
<span className="cr-change-bar" data-change={change} data-tooltip={change && CHANGE_NAMES[change]} />
|
|
||||||
<div className="min-w-0 flex-1">{children}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 「只看改动」时连着没变的几块折成的一条「… N 块没变」,点开恢复这几块。 */
|
/** 「只看改动」时连着没变的几块折成的一条「… N 块没变」,点开恢复这几块。 */
|
||||||
function UnchangedStrip({ count, onOpen }: { count: number, onOpen: () => void }): ReactElement {
|
function UnchangedStrip({ count, onOpen }: { count: number, onOpen: () => void }): ReactElement {
|
||||||
return (
|
return (
|
||||||
<ChangeRow diff change={undefined}>
|
|
||||||
<button type="button" className="cr-unchanged flex h-[22px] w-full items-center gap-1.5 rounded px-2.5 text-xs" onClick={onOpen}>
|
<button type="button" className="cr-unchanged flex h-[22px] w-full items-center gap-1.5 rounded px-2.5 text-xs" onClick={onOpen}>
|
||||||
<ChevronsUpDown size={12} strokeWidth={1.8} className="shrink-0" aria-hidden />
|
<ChevronsUpDown size={12} strokeWidth={1.8} className="shrink-0" aria-hidden />
|
||||||
{`… ${count} 块没变`}
|
{`… ${count} 块没变`}
|
||||||
<span className="cr-link">点开</span>
|
<span className="cr-link">点开</span>
|
||||||
</button>
|
</button>
|
||||||
</ChangeRow>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,9 +300,7 @@ const BlockItem = memo(({ block }: { block: BlockView }): ReactElement => {
|
|||||||
{shape.run !== undefined && <UnchangedStrip count={shape.run} onOpen={() => tree.onOpenRun(block.id)} />}
|
{shape.run !== undefined && <UnchangedStrip count={shape.run} onOpen={() => tree.onOpenRun(block.id)} />}
|
||||||
{shape.shown && (
|
{shape.shown && (
|
||||||
<>
|
<>
|
||||||
<ChangeRow diff={shape.diff} change={shape.change?.change}>
|
|
||||||
<BlockCard block={block} shape={shape} ask={ask} rewrite={rewriting} />
|
<BlockCard block={block} shape={shape} ask={ask} rewrite={rewriting} />
|
||||||
</ChangeRow>
|
|
||||||
{/* 输入框、展开状态跟着块的记号走,文档刷新块 id 变了也不丢 */}
|
{/* 输入框、展开状态跟着块的记号走,文档刷新块 id 变了也不丢 */}
|
||||||
{qa && ask.chat && (
|
{qa && ask.chat && (
|
||||||
<AskThread
|
<AskThread
|
||||||
@@ -358,7 +350,10 @@ export function BlockLevel({ blocks, owner, slotted = false }: { blocks: BlockVi
|
|||||||
const tree = useBlockTree()
|
const tree = useBlockTree()
|
||||||
// 挂在卡片子行上的子块画在卡片属性表里(slotted 的那一层),这一层不画;删掉的块都放回卡片后面这一层
|
// 挂在卡片子行上的子块画在卡片属性表里(slotted 的那一层),这一层不画;删掉的块都放回卡片后面这一层
|
||||||
const listed = useMemo(() => slotted ? blocks : blocks.filter(block => !block.slot), [blocks, slotted])
|
const listed = useMemo(() => slotted ? blocks : blocks.filter(block => !block.slot), [blocks, slotted])
|
||||||
const removed = useStore(tree.state, state => slotted ? NO_REMOVED : state.diff?.removed.get(owner?.id ?? '') ?? NO_REMOVED)
|
const { removed, diff } = useStore(tree.state, state => ({
|
||||||
|
removed: slotted ? NO_REMOVED : state.diff?.removed.get(owner?.id ?? '') ?? NO_REMOVED,
|
||||||
|
diff: state.diff !== undefined,
|
||||||
|
}), shallowEqual)
|
||||||
const listedIds = new Set(listed.map(block => block.id))
|
const listedIds = new Set(listed.map(block => block.id))
|
||||||
// 删掉的块排在它原来前面那块后面;那块被筛掉了也照排,不跟着消失;那块画在卡片里时排到这一层最后
|
// 删掉的块排在它原来前面那块后面;那块被筛掉了也照排,不跟着消失;那块画在卡片里时排到这一层最后
|
||||||
const removedAfter = (id: string | undefined): ReactElement[] => removed
|
const removedAfter = (id: string | undefined): ReactElement[] => removed
|
||||||
@@ -368,7 +363,7 @@ export function BlockLevel({ blocks, owner, slotted = false }: { blocks: BlockVi
|
|||||||
.filter(item => item.after !== undefined && !listedIds.has(item.after))
|
.filter(item => item.after !== undefined && !listedIds.has(item.after))
|
||||||
.map(item => <RemovedCard key={item.id} placed={item} />)
|
.map(item => <RemovedCard key={item.id} placed={item} />)
|
||||||
return (
|
return (
|
||||||
<div className="cr-level flex gap-2" {...kindProps(owner)} data-owner={owner?.id}>
|
<div className="cr-level flex gap-2" {...kindProps(owner)} data-owner={owner?.id} data-diff={diff ? '' : undefined}>
|
||||||
<div className="flex w-3 shrink-0 justify-center">
|
<div className="flex w-3 shrink-0 justify-center">
|
||||||
<div className="cr-guide w-px" />
|
<div className="cr-guide w-px" />
|
||||||
</div>
|
</div>
|
||||||
@@ -466,6 +461,7 @@ function BlockCard({ block, shape, ask, rewrite }: { block: BlockView, shape: Bl
|
|||||||
data-editing={shape.editing ? '' : undefined}
|
data-editing={shape.editing ? '' : undefined}
|
||||||
data-change={change?.change}
|
data-change={change?.change}
|
||||||
>
|
>
|
||||||
|
{shape.diff && <ChangeBar change={change?.change} />}
|
||||||
{change?.outermost && <span className="cr-added-badge">新增</span>}
|
{change?.outermost && <span className="cr-added-badge">新增</span>}
|
||||||
{shape.hasChildren
|
{shape.hasChildren
|
||||||
? (
|
? (
|
||||||
@@ -529,8 +525,8 @@ function RemovedCard({ placed }: { placed: PlacedRemoved }): ReactElement {
|
|||||||
// 旧块的 id 是旧版本里的位置,换成页面上认删掉的块用的 id,属性行的 id 才不和现在的块撞上
|
// 旧块的 id 是旧版本里的位置,换成页面上认删掉的块用的 id,属性行的 id 才不和现在的块撞上
|
||||||
const old = useMemo(() => ({ ...block, id: placed.id }), [block, placed.id])
|
const old = useMemo(() => ({ ...block, id: placed.id }), [block, placed.id])
|
||||||
return (
|
return (
|
||||||
<ChangeRow diff change="del">
|
<div className="cr-block cr-removed relative flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2" {...kindProps(block)} data-id={placed.id}>
|
||||||
<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}>
|
<ChangeBar change="del" />
|
||||||
<button
|
<button
|
||||||
type="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)]"
|
className="flex h-5 w-3.5 shrink-0 items-center justify-center rounded-sm text-[var(--vscode-descriptionForeground)] hover:text-[var(--vscode-foreground)]"
|
||||||
@@ -554,6 +550,5 @@ function RemovedCard({ placed }: { placed: PlacedRemoved }): ReactElement {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ChangeRow>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -959,25 +959,24 @@ body {
|
|||||||
--cr-diff-del: var(--vscode-editorGutter-deletedBackground, #f85149);
|
--cr-diff-del: var(--vscode-editorGutter-deletedBackground, #f85149);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 卡片左边外侧的改动色条:3px 宽、和卡片等高;没变的卡片留一条透明的,同一层的卡片照样对齐 */
|
/*
|
||||||
|
* 卡片左边外侧的改动色条:3px 宽、和卡片等高,空 5px 再是卡片;没变的卡片留一条透明的,同一层的卡片照样对齐。
|
||||||
|
* 色条画在卡片里(标不标差异卡片都是同一个元素),标差异的那一层(data-diff)卡片、折起来的一段、问答区都往右让出 8px,色条挪进这 8px 里
|
||||||
|
*/
|
||||||
|
.cr-level[data-diff] > div > :is(.cr-block, .cr-unchanged, .cr-qa) {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
.cr-change-bar {
|
.cr-change-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
bottom: -1px;
|
||||||
|
left: -9px;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: 1.5px;
|
border-radius: 1.5px;
|
||||||
}
|
}
|
||||||
.cr-change-bar[data-change="add"] { background: var(--cr-diff-add); }
|
.cr-change-bar[data-change="add"] { background: var(--cr-diff-add); }
|
||||||
.cr-change-bar[data-change="mod"] { background: var(--cr-diff-mod); }
|
.cr-change-bar[data-change="mod"] { background: var(--cr-diff-mod); }
|
||||||
.cr-change-bar[data-change="del"] { background: var(--cr-diff-del); }
|
.cr-change-bar[data-change="del"] { background: var(--cr-diff-del); }
|
||||||
/* 卡片包了一层色条以后不再和子层相邻:悬停卡片时子层竖线换色改按外层认;问答区往右缩到和卡片对齐 */
|
|
||||||
.cr-change-row:has(> div > .cr-block:hover) + .cr-level > * > .cr-guide,
|
|
||||||
.cr-change-row:has(> div > .cr-block:hover) + .cr-qa + .cr-level > * > .cr-guide,
|
|
||||||
.cr-change-row:has(> div > .cr-block:hover) + .cr-qa + .cr-qa + .cr-level > * > .cr-guide {
|
|
||||||
background: var(--kind);
|
|
||||||
}
|
|
||||||
.cr-change-row + .cr-qa,
|
|
||||||
.cr-change-row + .cr-qa + .cr-qa {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 新增的卡片(连同它的子卡片):底色淡绿;最外面那张左上角压着「新增」小标 */
|
/* 新增的卡片(连同它的子卡片):底色淡绿;最外面那张左上角压着「新增」小标 */
|
||||||
.cr-block[data-change="add"] {
|
.cr-block[data-change="add"] {
|
||||||
@@ -1001,14 +1000,19 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 删掉的块:红色虚线边、淡红底、半透明,没有编辑、问 AI 按钮;展开后下面是旧内容,整张划掉 */
|
/* 删掉的块:红色虚线边、淡红底、半透明,没有编辑、问 AI 按钮;展开后下面是旧内容,整张划掉 */
|
||||||
|
/* 半透明落在边框、底色和里面的内容上,不落在改动色条上(色条画在卡片里,同别的卡片的一样亮) */
|
||||||
.cr-removed {
|
.cr-removed {
|
||||||
border: 1px dashed color-mix(in srgb, var(--cr-diff-del) 80%, transparent);
|
border: 1px dashed color-mix(in srgb, var(--cr-diff-del) 48%, transparent);
|
||||||
background: color-mix(in srgb, var(--cr-diff-del) 6%, transparent);
|
background: color-mix(in srgb, var(--cr-diff-del) 3.6%, transparent);
|
||||||
|
}
|
||||||
|
.cr-removed > :not(.cr-change-bar) {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
.cr-block.cr-removed:hover {
|
.cr-block.cr-removed:hover {
|
||||||
border-color: var(--cr-diff-del);
|
border-color: color-mix(in srgb, var(--cr-diff-del) 80%, transparent);
|
||||||
background: color-mix(in srgb, var(--cr-diff-del) 10%, transparent);
|
background: color-mix(in srgb, var(--cr-diff-del) 8%, transparent);
|
||||||
|
}
|
||||||
|
.cr-block.cr-removed:hover > :not(.cr-change-bar) {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
.cr-removed-head {
|
.cr-removed-head {
|
||||||
|
|||||||
Reference in new issue
Block a user