mirror of https://github.com/harness/drone.git
Render off-screen diffs in a single pre tag to boost performance
parent
263e8647aa
commit
e6dc4c4fc9
|
@ -31,7 +31,7 @@ export default {
|
|||
PULL_REQUEST_DIFF_RENDERING_BLOCK_SIZE: 10,
|
||||
|
||||
/** Detection margin for on-screen / off-screen rendering optimization. In pixels. */
|
||||
IN_VIEWPORT_DETECTION_MARGIN: 5_000,
|
||||
IN_VIEWPORT_DETECTION_MARGIN: 256_000,
|
||||
|
||||
/** Limit for the secret input in bytes */
|
||||
SECRET_LIMIT_IN_BYTES: 5_242_880
|
||||
|
|
|
@ -363,8 +363,6 @@ const CommentsThread = <T = unknown,>({
|
|||
let commentRow = annotatedRow.nextElementSibling as HTMLElement
|
||||
|
||||
while (commentRow?.dataset?.annotatedLine) {
|
||||
toggleHidden(commentRow)
|
||||
|
||||
// Toggle opposite place-holder as well
|
||||
const diffParent = commentRow.closest('.d2h-code-wrapper')?.parentElement
|
||||
const oppositeDiv = diffParent?.classList.contains('right')
|
||||
|
@ -376,6 +374,7 @@ const CommentsThread = <T = unknown,>({
|
|||
|
||||
oppositePlaceHolders?.forEach(dom => toggleHidden(dom))
|
||||
|
||||
toggleHidden(commentRow)
|
||||
commentRow = commentRow.nextElementSibling as HTMLElement
|
||||
}
|
||||
show.current = !show.current
|
||||
|
@ -388,6 +387,7 @@ const CommentsThread = <T = unknown,>({
|
|||
|
||||
button.classList.add(css.toggleComment)
|
||||
button.title = getString('pr.toggleComments')
|
||||
button.dataset.toggleComment = 'true'
|
||||
|
||||
button.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') toggleComments(e)
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg fill="none" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><mask id="a" height="20" maskUnits="userSpaceOnUse" width="20" x="0" y="0"><path clip-rule="evenodd" d="m10 20c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10z" fill="#fff" fill-rule="evenodd"/></mask><mask id="b" fill="#fff"><path clip-rule="evenodd" d="m7.33325 7.91797c-.27614 0-.5.22386-.5.5s.22386.5.5.5h5.33335c.2761 0 .5-.22386.5-.5s-.2239-.5-.5-.5zm0 1.83333c-.27614 0-.5.22386-.5.5 0 .2761.22386.5.5.5h4.66665c.2762 0 .5-.2239.5-.5 0-.27614-.2238-.5-.5-.5z" fill="#fff" fill-rule="evenodd"/></mask><g opacity=".9"><path clip-rule="evenodd" d="m10 20c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10z" fill="#d8d8d8" fill-rule="evenodd"/><g mask="url(#a)"><path d="m0 0h20v20h-20z" fill="#ff661a"/><path d="m7.33341 5.33203h5.33329c1.4728 0 2.6667 1.19391 2.6667 2.66667v2.6667c0 1.4727-1.1939 2.6666-2.6667 2.6666h-2.6666l-2.66669 2v-2c-1.47275 0-2.66666-1.1939-2.66666-2.6666v-2.6667c0-1.47276 1.19391-2.66667 2.66666-2.66667z" stroke="#f3f3fa" stroke-linecap="round" stroke-linejoin="round"/><path d="m7.83325 8.41797c0 .27614-.22386.5-.5.5v-2c-.82843 0-1.5.67157-1.5 1.5zm-.5-.5c.27614 0 .5.22386.5.5h-2c0 .82843.67157 1.5 1.5 1.5zm5.33335 0h-5.33335v2h5.33335zm-.5.5c0-.27614.2238-.5.5-.5v2c.8284 0 1.5-.67157 1.5-1.5zm.5.5c-.2762 0-.5-.22386-.5-.5h2c0-.82843-.6716-1.5-1.5-1.5zm-5.33335 0h5.33335v-2h-5.33335zm.5 1.33333c0 .2761-.22386.5-.5.5v-2c-.82843 0-1.5.67157-1.5 1.5zm-.5-.5c.27614 0 .5.22386.5.5h-2c0 .8284.67157 1.5 1.5 1.5zm4.66665 0h-4.66665v2h4.66665zm-.5.5c0-.27614.2239-.5.5-.5v2c.8284 0 1.5-.6716 1.5-1.5zm.5.5c-.2761 0-.5-.2239-.5-.5h2c0-.82843-.6716-1.5-1.5-1.5zm-4.66665 0h4.66665v-2h-4.66665z" fill="#f3f3fa" mask="url(#b)" opacity=".5"/></g></g></svg>
|
||||
<svg fill="none" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><mask id="a" height="20" maskUnits="userSpaceOnUse" width="20" x="0" y="0"><path clip-rule="evenodd" d="m10 20c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10z" fill="#fff" fill-rule="evenodd"/></mask><mask id="b" fill="#fff"><path clip-rule="evenodd" d="m7.33325 7.91797c-.27614 0-.5.22386-.5.5s.22386.5.5.5h5.33335c.2761 0 .5-.22386.5-.5s-.2239-.5-.5-.5zm0 1.83333c-.27614 0-.5.22386-.5.5 0 .2761.22386.5.5.5h4.66665c.2762 0 .5-.2239.5-.5 0-.27614-.2238-.5-.5-.5z" fill="#fff" fill-rule="evenodd"/></mask><g opacity=".9"><path clip-rule="evenodd" d="m10 20c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10z" fill="#d8d8d8" fill-rule="evenodd"/><g mask="url(#a)"><path d="m0 0h20v20h-20z" fill="#1b841d"/><path d="m7.33341 5.33203h5.33329c1.4728 0 2.6667 1.19391 2.6667 2.66667v2.6667c0 1.4727-1.1939 2.6666-2.6667 2.6666h-2.6666l-2.66669 2v-2c-1.47275 0-2.66666-1.1939-2.66666-2.6666v-2.6667c0-1.47276 1.19391-2.66667 2.66666-2.66667z" stroke="#f3f3fa" stroke-linecap="round" stroke-linejoin="round"/><path d="m7.83325 8.41797c0 .27614-.22386.5-.5.5v-2c-.82843 0-1.5.67157-1.5 1.5zm-.5-.5c.27614 0 .5.22386.5.5h-2c0 .82843.67157 1.5 1.5 1.5zm5.33335 0h-5.33335v2h5.33335zm-.5.5c0-.27614.2238-.5.5-.5v2c.8284 0 1.5-.67157 1.5-1.5zm.5.5c-.2762 0-.5-.22386-.5-.5h2c0-.82843-.6716-1.5-1.5-1.5zm-5.33335 0h5.33335v-2h-5.33335zm.5 1.33333c0 .2761-.22386.5-.5.5v-2c-.82843 0-1.5.67157-1.5 1.5zm-.5-.5c.27614 0 .5.22386.5.5h-2c0 .8284.67157 1.5 1.5 1.5zm4.66665 0h-4.66665v2h4.66665zm-.5.5c0-.27614.2239-.5.5-.5v2c.8284 0 1.5-.6716 1.5-1.5zm.5.5c-.2761 0-.5-.2239-.5-.5h2c0-.82843-.6716-1.5-1.5-1.5zm-4.66665 0h4.66665v-2h-4.66665z" fill="#f3f3fa" mask="url(#b)" opacity=".5"/></g></g></svg>
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
@ -465,14 +465,12 @@
|
|||
border-bottom-right-radius: 4px;
|
||||
max-width: calc(var(--page-container-width) - 48px);
|
||||
|
||||
&.hidden {
|
||||
height: var(--block-height);
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: auto var(--line-height);
|
||||
|
||||
* {
|
||||
display: none !important;
|
||||
}
|
||||
.offscreenText {
|
||||
font-size: 12px;
|
||||
white-space: normal;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ export declare const expandCollapseDiffBtn: string
|
|||
export declare const fileChanged: string
|
||||
export declare const fname: string
|
||||
export declare const fnamePopover: string
|
||||
export declare const hidden: string
|
||||
export declare const main: string
|
||||
export declare const offscreenText: string
|
||||
export declare const popover: string
|
||||
export declare const readOnly: string
|
||||
export declare const selectoSelection: string
|
||||
|
|
|
@ -46,10 +46,10 @@ import { CopyButton } from 'components/CopyButton/CopyButton'
|
|||
import { NavigationCheck } from 'components/NavigationCheck/NavigationCheck'
|
||||
import type { UseGetPullRequestInfoResult } from 'pages/PullRequest/useGetPullRequestInfo'
|
||||
import { useQueryParams } from 'hooks/useQueryParams'
|
||||
import { useCustomEventListener } from 'hooks/useEventListener'
|
||||
import { useCustomEventListener, useEventListener } from 'hooks/useEventListener'
|
||||
import { useShowRequestError } from 'hooks/useShowRequestError'
|
||||
import { getErrorMessage, isInViewport } from 'utils/Utils'
|
||||
import { createRequestIdleCallbackTaskPool } from 'utils/Task'
|
||||
import { createRequestAnimationFrameTaskPool } from 'utils/Task'
|
||||
import { useResizeObserver } from 'hooks/useResizeObserver'
|
||||
import { useFindGitBranch } from 'hooks/useFindGitBranch'
|
||||
import Config from 'Config'
|
||||
|
@ -165,6 +165,7 @@ const DiffViewerInternal: React.FC<DiffViewerProps> = ({
|
|||
},
|
||||
[ref]
|
||||
)
|
||||
const contentHTML = useRef<string | null>(null)
|
||||
|
||||
useResizeObserver(
|
||||
contentRef,
|
||||
|
@ -178,20 +179,6 @@ const DiffViewerInternal: React.FC<DiffViewerProps> = ({
|
|||
)
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let taskId = 0
|
||||
if (inView) {
|
||||
taskId = scheduleLowPriorityTask(() => {
|
||||
if (isMounted.current && contentRef.current) contentRef.current.classList.remove(css.hidden)
|
||||
})
|
||||
} else {
|
||||
taskId = scheduleLowPriorityTask(() => {
|
||||
if (isMounted.current && contentRef.current) contentRef.current.classList.add(css.hidden)
|
||||
})
|
||||
}
|
||||
return () => cancelTask(taskId)
|
||||
}, [inView, isMounted])
|
||||
|
||||
//
|
||||
// Handling custom events sent to DiffViewer from external components/features
|
||||
// such as "jump to file", "jump to comment", etc...
|
||||
|
@ -290,7 +277,7 @@ const DiffViewerInternal: React.FC<DiffViewerProps> = ({
|
|||
if (isInViewport(containerRef.current as Element, 1000)) {
|
||||
renderDiffAndComments()
|
||||
} else {
|
||||
taskId = scheduleLowPriorityTask(renderDiffAndComments)
|
||||
taskId = scheduleTask(renderDiffAndComments)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,6 +303,67 @@ const DiffViewerInternal: React.FC<DiffViewerProps> = ({
|
|||
|
||||
const branchInfo = useFindGitBranch(pullReqMetadata?.source_branch)
|
||||
|
||||
useEffect(
|
||||
function serializeDeserializeContent() {
|
||||
const dom = contentRef.current
|
||||
|
||||
if (inView) {
|
||||
if (isMounted.current && dom && contentHTML.current) {
|
||||
dom.innerHTML = contentHTML.current
|
||||
contentHTML.current = null
|
||||
|
||||
// Remove all signs from the raw HTML that CommentBox was mounted so
|
||||
// it can be mounted/re-rendered again freshly
|
||||
dom.querySelectorAll('tr[data-source-line-number]').forEach(row => {
|
||||
row.removeAttribute('data-source-line-number')
|
||||
row.removeAttribute('data-comment-ids')
|
||||
row.querySelector('button[data-toggle-comment="true"]')?.remove?.()
|
||||
})
|
||||
dom.querySelectorAll('tr[data-annotated-line],tr[data-place-holder-for-line]').forEach(row => {
|
||||
row.remove?.()
|
||||
})
|
||||
|
||||
// Attach comments again
|
||||
commentsHook.current.attachAllCommentThreads()
|
||||
}
|
||||
} else {
|
||||
if (isMounted.current && dom && !contentHTML.current) {
|
||||
const { clientHeight, textContent, innerHTML } = dom
|
||||
|
||||
// Detach comments since they are no longer in sync in DOM as
|
||||
// all DOMs are removed
|
||||
commentsHook.current.detachAllCommentThreads()
|
||||
|
||||
// Save current innerHTML
|
||||
contentHTML.current = innerHTML
|
||||
|
||||
// TODO: Might be good to clean textContent a bit to not include
|
||||
// diff header info, line numbers, hunk headers, etc...
|
||||
|
||||
// Set innerHTML to a pre tag with the same height to avoid reflow
|
||||
// The pre textContent allows Cmd/Ctrl-F to work
|
||||
dom.innerHTML = `<pre style="height: ${clientHeight + 'px'}" class="${
|
||||
css.offscreenText
|
||||
}">${textContent}</pre>`
|
||||
}
|
||||
}
|
||||
},
|
||||
[inView, isMounted, commentsHook]
|
||||
)
|
||||
|
||||
// Add click event listener from contentRef to handle click event on "Show Diff" button
|
||||
// This can't be done from the button itself because it got serialized / deserialized from
|
||||
// text during off-screen optimization (handler will be gone/destroyed)
|
||||
useEventListener(
|
||||
'click',
|
||||
useCallback(function showDiff(event) {
|
||||
if (((event.target as HTMLElement)?.closest('button') as HTMLElement)?.dataset?.action === ACTION_SHOW_DIFF) {
|
||||
setRenderCustomContent(false)
|
||||
}
|
||||
}, []),
|
||||
contentRef.current as HTMLDivElement
|
||||
)
|
||||
|
||||
useShowRequestError(fullDiffError, 0)
|
||||
|
||||
useEffect(
|
||||
|
@ -505,28 +553,32 @@ const DiffViewerInternal: React.FC<DiffViewerProps> = ({
|
|||
</Container>
|
||||
|
||||
<Container id={diff.contentId} data-path={diff.filePath} className={css.diffContent} ref={contentRef}>
|
||||
<Render when={renderCustomContent && !collapsed}>
|
||||
<Container height={200} flex={{ align: 'center-center' }}>
|
||||
<Layout.Vertical padding="xlarge" style={{ alignItems: 'center' }}>
|
||||
<Render when={fileDeleted || isDiffTooLarge || diffHasVeryLongLine}>
|
||||
<Button variation={ButtonVariation.LINK} onClick={() => setRenderCustomContent(false)}>
|
||||
{getString('pr.showDiff')}
|
||||
</Button>
|
||||
</Render>
|
||||
<Text>
|
||||
{getString(
|
||||
fileDeleted
|
||||
? 'pr.fileDeleted'
|
||||
: isDiffTooLarge || diffHasVeryLongLine
|
||||
? 'pr.diffTooLarge'
|
||||
: isBinary
|
||||
? 'pr.fileBinary'
|
||||
: 'pr.fileUnchanged'
|
||||
)}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Render>
|
||||
{/* Note: This parent container is needed to make sure "Show Diff" work correctly
|
||||
with content converted between textContent and innerHTML */}
|
||||
<Container>
|
||||
<Render when={renderCustomContent && !collapsed}>
|
||||
<Container height={200} flex={{ align: 'center-center' }}>
|
||||
<Layout.Vertical padding="xlarge" style={{ alignItems: 'center' }}>
|
||||
<Render when={fileDeleted || isDiffTooLarge || diffHasVeryLongLine}>
|
||||
<Button variation={ButtonVariation.LINK} onClick={() => setRenderCustomContent(false)}>
|
||||
{getString('pr.showDiff')}
|
||||
</Button>
|
||||
</Render>
|
||||
<Text>
|
||||
{getString(
|
||||
fileDeleted
|
||||
? 'pr.fileDeleted'
|
||||
: isDiffTooLarge || diffHasVeryLongLine
|
||||
? 'pr.diffTooLarge'
|
||||
: isBinary
|
||||
? 'pr.fileBinary'
|
||||
: 'pr.fileUnchanged'
|
||||
)}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Render>
|
||||
</Container>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
<NavigationCheck when={dirty} />
|
||||
|
@ -535,6 +587,7 @@ const DiffViewerInternal: React.FC<DiffViewerProps> = ({
|
|||
}
|
||||
|
||||
const BLOCK_HEIGHT = '--block-height'
|
||||
const ACTION_SHOW_DIFF = 'showDiff'
|
||||
|
||||
export enum DiffViewerEvent {
|
||||
SCROLL_INTO_VIEW = 'scrollIntoView'
|
||||
|
@ -551,6 +604,6 @@ export interface DiffViewerExchangeState {
|
|||
fullDiff?: DiffFileEntry
|
||||
}
|
||||
|
||||
const { scheduleTask: scheduleLowPriorityTask, cancelTask } = createRequestIdleCallbackTaskPool()
|
||||
const { scheduleTask, cancelTask } = createRequestAnimationFrameTaskPool()
|
||||
|
||||
export const DiffViewer = React.memo(DiffViewerInternal)
|
||||
|
|
Loading…
Reference in New Issue