sourcegraph/client/web/src/components/diff/FileDiffNode.tsx
GitStart-SourceGraph da154b5a93
[SG-33302] Upgrade to the latest react version and migrate to the new root API (#36045)
* feat: react v18 and new root api with some errors fixes

* web: repo page integration test flake fix (#37592)

* web: fix `blog-viewer` integration test flake (#38069)

* fix: build-ts

* fix: new failing unit tests

* fix: unit test

* web: fix search integration test

* feat: applied request changes

* fix: new failing test because of global mockReactVisibilitySensor

* fix: unexpected onLayoutChange call on SmartInsightsViewGrid

* fix: make ForwardReferenceComponent support custom children

* fix: more new ts lint issue

* fix: remaining issue BatchSpec

* fix: SmartInsightsViewGrid issue with useLayoutEffect

Co-authored-by: gitstart-sourcegraph <gitstart@users.noreply.github.com>
Co-authored-by: Valery Bugakov <skymk1@gmail.com>
2022-07-15 10:58:08 +07:00

189 lines
7.9 KiB
TypeScript

import React, { useState, useCallback } from 'react'
import { mdiChevronDown, mdiChevronRight } from '@mdi/js'
import classNames from 'classnames'
import * as H from 'history'
import prettyBytes from 'pretty-bytes'
import { Observable } from 'rxjs'
import { ViewerId } from '@sourcegraph/shared/src/api/viewerTypes'
import { ThemeProps } from '@sourcegraph/shared/src/theme'
import { Button, Badge, Link, Icon, Text, createLinkUrl, Tooltip } from '@sourcegraph/wildcard'
import { FileDiffFields } from '../../graphql-operations'
import { DiffMode } from '../../repo/commit/RepositoryCommitPage'
import { dirname } from '../../util/path'
import { DiffStat, DiffStatSquares } from './DiffStat'
import { ExtensionInfo } from './FileDiffConnection'
import { FileDiffHunks } from './FileDiffHunks'
import styles from './FileDiffNode.module.scss'
export interface FileDiffNodeProps extends ThemeProps {
node: FileDiffFields
lineNumbers: boolean
className?: string
location: H.Location
history: H.History
extensionInfo?: ExtensionInfo<{
observeViewerId?: (uri: string) => Observable<ViewerId | undefined>
}>
/** Reflect selected line in url */
persistLines?: boolean
diffMode?: DiffMode
}
/** A file diff. */
export const FileDiffNode: React.FunctionComponent<React.PropsWithChildren<FileDiffNodeProps>> = ({
history,
isLightTheme,
lineNumbers,
location,
node,
className,
extensionInfo,
persistLines,
diffMode = 'unified',
}) => {
const [expanded, setExpanded] = useState<boolean>(true)
const [renderDeleted, setRenderDeleted] = useState<boolean>(false)
const toggleExpand = useCallback((): void => {
setExpanded(!expanded)
}, [expanded])
const onClickToViewDeleted = useCallback((): void => {
setRenderDeleted(true)
}, [])
let path: React.ReactNode
if (node.newPath && (node.newPath === node.oldPath || !node.oldPath)) {
path = <span title={node.newPath}>{node.newPath}</span>
} else if (node.newPath && node.oldPath && node.newPath !== node.oldPath) {
path = (
<span title={`${node.oldPath}${node.newPath}`}>
{node.oldPath} {node.newPath}
</span>
)
} else {
// By process of elimination (that TypeScript is unfortunately unable to infer, except
// by reorganizing this code in a way that's much more complex to humans), node.oldPath
// is non-null.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
path = <span title={node.oldPath!}>{node.oldPath}</span>
}
let stat: React.ReactNode
// If one of the files was binary, display file size change instead of DiffStat.
if (node.oldFile?.binary || node.newFile?.binary) {
const sizeChange = (node.newFile?.byteSize ?? 0) - (node.oldFile?.byteSize ?? 0)
const className = sizeChange >= 0 ? 'text-success' : 'text-danger'
stat = <strong className={classNames(className, 'code')}>{prettyBytes(sizeChange)}</strong>
} else {
stat = (
<>
<DiffStat className="mr-1" {...node.stat} />
<DiffStatSquares {...node.stat} />
</>
)
}
const anchor = `diff-${node.internalID}`
return (
<>
{/* The empty <a> tag is to allow users to anchor links to the top of this file diff node */}
<Link to="" id={anchor} aria-hidden={true} tabIndex={-1} />
<li className={classNames('test-file-diff-node', styles.fileDiffNode, className)}>
<div className={styles.header}>
<Button
aria-label={expanded ? 'Hide file diff' : 'Show file diff'}
variant="icon"
className="mr-2"
onClick={toggleExpand}
size="sm"
>
<Icon svgPath={expanded ? mdiChevronDown : mdiChevronRight} aria-hidden={true} />
</Button>
<div className={classNames('align-items-baseline', styles.headerPathStat)}>
{!node.oldPath && (
<Badge variant="success" className="text-uppercase mr-2">
Added
</Badge>
)}
{!node.newPath && (
<Badge variant="danger" className="text-uppercase mr-2">
Deleted
</Badge>
)}
{node.newPath && node.oldPath && node.newPath !== node.oldPath && (
<Badge variant="warning" className="text-uppercase mr-2">
{dirname(node.newPath) !== dirname(node.oldPath) ? 'Moved' : 'Renamed'}
</Badge>
)}
{stat}
{node.mostRelevantFile.__typename === 'GitBlob' ? (
<Tooltip content="View file at revision">
<Link to={node.mostRelevantFile.url} className="mr-0 ml-2 fw-bold">
<strong>{path}</strong>
</Link>
</Tooltip>
) : (
<span className="ml-2">{path}</span>
)}
<Tooltip content="Pin diff">
<Link
to={createLinkUrl({ ...location, hash: anchor })}
className={classNames('ml-2', styles.headerPath)}
>
#
</Link>
</Tooltip>
</div>
</div>
{expanded &&
(node.oldFile?.binary || node.newFile?.binary ? (
<div className="text-muted m-2">Binary files can't be rendered.</div>
) : !node.newPath && !renderDeleted ? (
<div className="text-muted m-2">
<Text className="mb-0">Deleted files aren't rendered by default.</Text>
<Button className="m-0 p-0" onClick={onClickToViewDeleted} variant="link">
Click here to view.
</Button>
</div>
) : (
<FileDiffHunks
className={styles.hunks}
fileDiffAnchor={anchor}
history={history}
isLightTheme={isLightTheme}
location={location}
persistLines={persistLines}
extensionInfo={
extensionInfo && {
extensionsController: extensionInfo.extensionsController,
observeViewerId: extensionInfo.observeViewerId,
hoverifier: extensionInfo.hoverifier,
base: {
...extensionInfo.base,
filePath: node.oldPath,
},
head: {
...extensionInfo.head,
filePath: node.newPath,
},
}
}
hunks={node.hunks}
lineNumbers={lineNumbers}
diffMode={diffMode}
/>
))}
</li>
</>
)
}