Merge branch 'ui/editor-optimization' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#23)

This commit is contained in:
Tan Nhu 2023-04-06 21:51:25 +00:00 committed by Harness
commit f1ffdadde7
2 changed files with 105 additions and 72 deletions

View File

@ -51,6 +51,10 @@
position: absolute; position: absolute;
width: 100%; width: 100%;
&[data-block-top='-1'] {
display: none;
}
&::before { &::before {
position: absolute; position: absolute;
z-index: 2; z-index: 2;

View File

@ -1,5 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { throttle } from 'lodash-es'
import { Avatar, Container, FontVariation, Layout, StringSubstitute, Text } from '@harness/uicore' import { Avatar, Container, FontVariation, Layout, StringSubstitute, Text } from '@harness/uicore'
import { LanguageDescription } from '@codemirror/language' import { LanguageDescription } from '@codemirror/language'
import { indentWithTab } from '@codemirror/commands' import { indentWithTab } from '@codemirror/commands'
@ -32,7 +31,7 @@ interface BlameBlock {
type BlameBlockRecord = Record<number, BlameBlock> type BlameBlockRecord = Record<number, BlameBlock>
const BLAME_BLOCK_NOT_YET_CALCULATED_TOP_POSITION = -1 const INITIAL_TOP_POSITION = -1
export const GitBlame: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourcePath'>> = ({ export const GitBlame: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourcePath'>> = ({
repoMetadata, repoMetadata,
@ -55,8 +54,8 @@ export const GitBlame: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourcePat
blameBlocks[fromLineNumber] = { blameBlocks[fromLineNumber] = {
fromLineNumber, fromLineNumber,
toLineNumber, toLineNumber,
topPosition: BLAME_BLOCK_NOT_YET_CALCULATED_TOP_POSITION, // Not yet calculated topPosition: INITIAL_TOP_POSITION,
heights: {}, // Not yet calculated heights: {},
commitInfo: commit, commitInfo: commit,
lines: lines, lines: lines,
numberOfLines: lines?.length || 0 numberOfLines: lines?.length || 0
@ -68,6 +67,7 @@ export const GitBlame: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourcePat
setBlameBlocks({ ...blameBlocks }) setBlameBlocks({ ...blameBlocks })
} }
}, [data]) // eslint-disable-line react-hooks/exhaustive-deps }, [data]) // eslint-disable-line react-hooks/exhaustive-deps
const findBlockForLineNumber = useCallback( const findBlockForLineNumber = useCallback(
lineNumber => { lineNumber => {
let startLine = lineNumber let startLine = lineNumber
@ -81,42 +81,55 @@ export const GitBlame: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourcePat
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const onViewUpdate = useCallback( const onViewUpdate = useCallback(
throttle(({ view, geometryChanged }: ViewUpdate) => { ({ view, geometryChanged }: ViewUpdate) => {
if (geometryChanged) { if (geometryChanged) {
view.viewportLineBlocks.forEach(lineBlock => { view.viewportLineBlocks.forEach(lineBlock => {
const { from, top, height } = lineBlock const { from, top, height } = lineBlock
const lineNumber = view.state.doc.lineAt(from).number const lineNumber = view.state.doc.lineAt(from).number
const blameBlockAtLineNumber = findBlockForLineNumber(lineNumber) const blockAtLineNumber = findBlockForLineNumber(lineNumber)
if (!blameBlockAtLineNumber) { if (!blockAtLineNumber) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error('Bad math! Cannot find a block at line', lineNumber) console.error('Bad math! Cannot find a blame block for line', lineNumber)
} else { } else {
if (blameBlockAtLineNumber.topPosition === BLAME_BLOCK_NOT_YET_CALCULATED_TOP_POSITION) { if (blockAtLineNumber.topPosition === INITIAL_TOP_POSITION) {
blameBlockAtLineNumber.topPosition = top blockAtLineNumber.topPosition = top
} }
// CodeMirror reports top position of a block incorrectly sometimes, so we need to normalize it // CodeMirror reports top position of a block incorrectly sometimes, so we need to normalize it
// using the previous block. // using dimensions of the previous block.
if (lineNumber > 1) { if (lineNumber > 1) {
const previousBlock = findBlockForLineNumber(lineNumber - 1) const previousBlock = findBlockForLineNumber(lineNumber - 1)
if (previousBlock.fromLineNumber !== blameBlockAtLineNumber.fromLineNumber) { if (previousBlock.fromLineNumber !== blockAtLineNumber.fromLineNumber) {
const normalizedTop = blockAtLineNumber.topPosition = previousBlock.topPosition + computeHeight(previousBlock.heights)
previousBlock.topPosition + Object.values(previousBlock.heights).reduce((a, b) => a + b, 0)
blameBlockAtLineNumber.topPosition = normalizedTop
} }
} }
blameBlockAtLineNumber.heights[lineNumber] = height blockAtLineNumber.heights[lineNumber] = height
const blockDOM = document.querySelector(
`.${css.blameBox}[data-block-from-line="${blockAtLineNumber.fromLineNumber}"]`
) as HTMLDivElement
if (blockDOM) {
const _height = `${computeHeight(blockAtLineNumber.heights)}px`
const _top = `${blockAtLineNumber.topPosition}px`
if (blockDOM.style.height !== _height || blockDOM.style.top !== _top) {
blockDOM.style.height = _height
blockDOM.style.top = _top
if (blockAtLineNumber.topPosition !== INITIAL_TOP_POSITION) {
blockDOM.removeAttribute('data-block-top')
}
}
}
} }
}) })
setBlameBlocks({ ...blameBlocks })
} }
}, 50), },
[blameBlocks] [] // eslint-disable-line react-hooks/exhaustive-deps
) )
// TODO: Normalize loading and error rendering when implementing new Design layout // TODO: Normalize loading and error rendering when implementing new Design layout
@ -133,42 +146,9 @@ export const GitBlame: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourcePat
<Container className={css.gitBlame}> <Container className={css.gitBlame}>
<Layout.Horizontal className={css.layout}> <Layout.Horizontal className={css.layout}>
<Container className={css.blameColumn}> <Container className={css.blameColumn}>
{Object.values(blameBlocks) {Object.values(blameBlocks).map(blameInfo => (
.filter(({ topPosition }) => topPosition !== BLAME_BLOCK_NOT_YET_CALCULATED_TOP_POSITION) <GitBlameMetaInfo key={blameInfo.fromLineNumber} {...blameInfo} />
.map(({ fromLineNumber, topPosition: top, heights, commitInfo }) => { ))}
const height = Object.values(heights).reduce((a, b) => a + b, 0)
return (
<Container className={css.blameBox} key={fromLineNumber} height={height} style={{ top }}>
<Layout.Horizontal spacing="small" className={css.blameBoxLayout}>
<Container>
<Avatar name={commitInfo?.author?.identity?.name} size="normal" hoverCard={false} />
</Container>
<Container style={{ flexGrow: 1 }}>
<Layout.Vertical spacing="xsmall">
<Text
font={{ variation: FontVariation.BODY }}
lineClamp={2}
tooltipProps={{
portalClassName: css.blameCommitPortalClass
}}>
{commitInfo?.title}
</Text>
<Text font={{ variation: FontVariation.BODY }} lineClamp={1}>
<StringSubstitute
str={getString('blameCommitLine')}
vars={{
author: <strong>{commitInfo?.author?.identity?.name as string}</strong>,
timestamp: <ReactTimeago date={commitInfo?.author?.when as string} />
}}
/>
</Text>
</Layout.Vertical>
</Container>
</Layout.Horizontal>
</Container>
)
})}
</Container> </Container>
<Render when={Object.values(blameBlocks).length}> <Render when={Object.values(blameBlocks).length}>
<GitBlameSourceViewer <GitBlameSourceViewer
@ -210,33 +190,35 @@ interface EditorLinePaddingWidgetSpec extends LineWidgetSpec {
blockLines: number blockLines: number
} }
function GitBlameSourceViewer({ source, filename, onViewUpdate, blameBlocks }: GitBlameSourceViewerProps) { const GitBlameSourceViewer = React.memo(function GitBlameSourceViewer({
const [view, setView] = useState<EditorView>() source,
filename,
onViewUpdate,
blameBlocks
}: GitBlameSourceViewerProps) {
const view = useRef<EditorView>()
const ref = useRef<HTMLDivElement>() const ref = useRef<HTMLDivElement>()
const languageConfig = useMemo(() => new Compartment(), []) const languageConfig = useMemo(() => new Compartment(), [])
const lineWidgetSpec = useMemo(() => {
const spec: EditorLinePaddingWidgetSpec[] = [] useEffect(() => {
const lineWidgetSpec: EditorLinePaddingWidgetSpec[] = []
Object.values(blameBlocks).forEach(block => { Object.values(blameBlocks).forEach(block => {
const blockLines = block.numberOfLines const blockLines = block.numberOfLines
spec.push({ lineWidgetSpec.push({
lineNumber: block.fromLineNumber, lineNumber: block.fromLineNumber,
position: LineWidgetPosition.TOP, position: LineWidgetPosition.TOP,
blockLines blockLines
}) })
spec.push({ lineWidgetSpec.push({
lineNumber: block.toLineNumber, lineNumber: block.toLineNumber,
position: LineWidgetPosition.BOTTOM, position: LineWidgetPosition.BOTTOM,
blockLines blockLines
}) })
}) })
return spec
}, [blameBlocks])
useEffect(() => {
const customLineNumberGutter = gutter({ const customLineNumberGutter = gutter({
lineMarker(_view, line) { lineMarker(_view, line) {
const lineNumber: number = _view.state.doc.lineAt(line.from).number const lineNumber: number = _view.state.doc.lineAt(line.from).number
@ -282,25 +264,25 @@ function GitBlameSourceViewer({ source, filename, onViewUpdate, blameBlocks }: G
parent: ref.current parent: ref.current
}) })
setView(editorView) view.current = editorView
return () => { return () => {
editorView.destroy() editorView.destroy()
} }
}, []) // eslint-disable-line }, []) // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => { useEffect(() => {
if (view && filename) { if (filename) {
languageDescriptionFrom(filename) languageDescriptionFrom(filename)
?.load() ?.load()
.then(languageSupport => { .then(languageSupport => {
view.dispatch({ effects: languageConfig.reconfigure(languageSupport) }) view.current?.dispatch({ effects: languageConfig.reconfigure(languageSupport) })
}) })
} }
}, [filename, view, languageConfig]) }, [filename, view, languageConfig])
return <Container ref={ref} className={css.main} /> return <Container ref={ref} className={css.main} />
} })
function languageDescriptionFrom(filename: string) { function languageDescriptionFrom(filename: string) {
return LanguageDescription.matchFilename(languages, filename) return LanguageDescription.matchFilename(languages, filename)
@ -339,3 +321,50 @@ class EditorLinePaddingWidget extends WidgetType {
return false return false
} }
} }
function computeHeight(heights: Record<number, number>) {
return Object.values(heights).reduce((a, b) => a + b, 0)
}
function GitBlameMetaInfo({ fromLineNumber, toLineNumber, topPosition, heights, commitInfo }: BlameBlock) {
const height = computeHeight(heights)
const { getString } = useStrings()
return (
<Container
className={css.blameBox}
data-block-from-line={`${fromLineNumber}`}
data-block-to-line={`${toLineNumber}`}
data-block-top={`${topPosition}`}
key={`${fromLineNumber}-${height}`}
height={height}
style={{ top: topPosition }}>
<Layout.Horizontal spacing="small" className={css.blameBoxLayout}>
<Container>
<Avatar name={commitInfo?.author?.identity?.name} size="normal" hoverCard={false} />
</Container>
<Container style={{ flexGrow: 1 }}>
<Layout.Vertical spacing="xsmall">
<Text
font={{ variation: FontVariation.BODY }}
lineClamp={2}
tooltipProps={{
portalClassName: css.blameCommitPortalClass
}}>
{commitInfo?.title}
</Text>
<Text font={{ variation: FontVariation.BODY }} lineClamp={1}>
<StringSubstitute
str={getString('blameCommitLine')}
vars={{
author: <strong>{commitInfo?.author?.identity?.name as string}</strong>,
timestamp: <ReactTimeago date={commitInfo?.author?.when as string} />
}}
/>
</Text>
</Layout.Vertical>
</Container>
</Layout.Horizontal>
</Container>
)
}