| b69ab31 | | | 1 | /** |
| b69ab31 | | | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| b69ab31 | | | 3 | * |
| b69ab31 | | | 4 | * This source code is licensed under the MIT license found in the |
| b69ab31 | | | 5 | * LICENSE file in the root directory of this source tree. |
| b69ab31 | | | 6 | */ |
| b69ab31 | | | 7 | |
| b69ab31 | | | 8 | import './SplitDiffHunk.css'; |
| b69ab31 | | | 9 | |
| b69ab31 | | | 10 | import {guessIsSubmodule} from 'shared/patch/parse'; |
| b69ab31 | | | 11 | import {type ParsedDiff} from 'shared/patch/types'; |
| b69ab31 | | | 12 | import type {Context} from './types'; |
| b69ab31 | | | 13 | |
| b69ab31 | | | 14 | import {Button} from 'isl-components/Button'; |
| b69ab31 | | | 15 | import {Icon} from 'isl-components/Icon'; |
| b69ab31 | | | 16 | import {Tooltip} from 'isl-components/Tooltip'; |
| b69ab31 | | | 17 | import {useState} from 'react'; |
| b69ab31 | | | 18 | import {generatedStatusDescription} from '../../GeneratedFile'; |
| b69ab31 | | | 19 | import {T, t} from '../../i18n'; |
| b69ab31 | | | 20 | import platform from '../../platform'; |
| b69ab31 | | | 21 | import {GeneratedStatus} from '../../types'; |
| b69ab31 | | | 22 | import {FileHeader, diffTypeToIconType} from './SplitDiffFileHeader'; |
| b69ab31 | | | 23 | import {SplitDiffTable} from './SplitDiffHunk'; |
| b69ab31 | | | 24 | |
| b69ab31 | | | 25 | export function SplitDiffView({ |
| b69ab31 | | | 26 | ctx, |
| b69ab31 | | | 27 | path, |
| b69ab31 | | | 28 | patch, |
| b69ab31 | | | 29 | generatedStatus, |
| b69ab31 | | | 30 | }: { |
| b69ab31 | | | 31 | ctx: Context; |
| b69ab31 | | | 32 | path: string; |
| b69ab31 | | | 33 | patch: ParsedDiff; |
| b69ab31 | | | 34 | generatedStatus?: GeneratedStatus; |
| b69ab31 | | | 35 | }) { |
| b69ab31 | | | 36 | const fileName = patch.newFileName ?? patch.oldFileName ?? '/dev/null'; |
| b69ab31 | | | 37 | // whether the file is manually or automatically collapsed by the chevron |
| b69ab31 | | | 38 | const collapsed = ctx.collapsed; |
| b69ab31 | | | 39 | |
| b69ab31 | | | 40 | const isGenerated = generatedStatus != null && generatedStatus === GeneratedStatus.Generated; |
| b69ab31 | | | 41 | // whether the file content is collapsed due to being generated |
| b69ab31 | | | 42 | const [isContentCollapsed, setIsContentCollapsed] = useState(isGenerated); |
| b69ab31 | | | 43 | |
| b69ab31 | | | 44 | const preamble = []; |
| b69ab31 | | | 45 | if (generatedStatus != null && generatedStatus !== GeneratedStatus.Manual) { |
| b69ab31 | | | 46 | preamble.push( |
| b69ab31 | | | 47 | <FileStatusBanner key="generated" color={'modified'}> |
| b69ab31 | | | 48 | <div>{generatedStatusDescription(generatedStatus)}</div> |
| b69ab31 | | | 49 | {isContentCollapsed ? ( |
| b69ab31 | | | 50 | <Button icon onClick={() => setIsContentCollapsed(false)}> |
| b69ab31 | | | 51 | <T>Show anyway</T> |
| b69ab31 | | | 52 | </Button> |
| b69ab31 | | | 53 | ) : null} |
| b69ab31 | | | 54 | </FileStatusBanner>, |
| b69ab31 | | | 55 | ); |
| b69ab31 | | | 56 | } |
| b69ab31 | | | 57 | |
| b69ab31 | | | 58 | const isSubmodule = guessIsSubmodule(patch); |
| b69ab31 | | | 59 | const fileActions = ( |
| b69ab31 | | | 60 | <> |
| b69ab31 | | | 61 | {platform.openDiff == null ? null : ( |
| b69ab31 | | | 62 | <Tooltip title={t('Open diff view for file')} placement={'bottom'}> |
| b69ab31 | | | 63 | <Button |
| b69ab31 | | | 64 | icon |
| b69ab31 | | | 65 | className="split-diff-view-file-header-open-diff-button" |
| b69ab31 | | | 66 | onClick={() => { |
| b69ab31 | | | 67 | platform.openDiff?.(path, ctx.id.comparison); |
| b69ab31 | | | 68 | }}> |
| b69ab31 | | | 69 | <Icon icon="git-pull-request-go-to-changes" /> |
| b69ab31 | | | 70 | </Button> |
| b69ab31 | | | 71 | </Tooltip> |
| b69ab31 | | | 72 | )} |
| b69ab31 | | | 73 | {!isSubmodule && ( |
| b69ab31 | | | 74 | <Tooltip title={t('Open file')} placement={'bottom'}> |
| b69ab31 | | | 75 | <Button |
| b69ab31 | | | 76 | icon |
| b69ab31 | | | 77 | className="split-diff-view-file-header-open-button" |
| b69ab31 | | | 78 | onClick={() => { |
| b69ab31 | | | 79 | platform.openFile(path); |
| b69ab31 | | | 80 | }}> |
| b69ab31 | | | 81 | <Icon icon="go-to-file" /> |
| b69ab31 | | | 82 | </Button> |
| b69ab31 | | | 83 | </Tooltip> |
| b69ab31 | | | 84 | )} |
| b69ab31 | | | 85 | </> |
| b69ab31 | | | 86 | ); |
| b69ab31 | | | 87 | |
| b69ab31 | | | 88 | const copyFrom = patch.oldFileName === fileName ? undefined : patch.oldFileName; |
| b69ab31 | | | 89 | const iconType = diffTypeToIconType(patch.type); |
| b69ab31 | | | 90 | return ( |
| b69ab31 | | | 91 | <div className="split-diff-view"> |
| b69ab31 | | | 92 | <FileHeader |
| b69ab31 | | | 93 | path={fileName} |
| b69ab31 | | | 94 | copyFrom={copyFrom} |
| b69ab31 | | | 95 | iconType={iconType} |
| b69ab31 | | | 96 | open={!collapsed} |
| b69ab31 | | | 97 | onChangeOpen={open => ctx.setCollapsed(!open)} |
| b69ab31 | | | 98 | fileActions={fileActions} |
| b69ab31 | | | 99 | /> |
| b69ab31 | | | 100 | {!collapsed && preamble && <div className="split-diff-view-file-preamble">{preamble}</div>} |
| b69ab31 | | | 101 | {!collapsed && !isContentCollapsed && <SplitDiffTable ctx={ctx} path={path} patch={patch} />} |
| b69ab31 | | | 102 | </div> |
| b69ab31 | | | 103 | ); |
| b69ab31 | | | 104 | } |
| b69ab31 | | | 105 | |
| b69ab31 | | | 106 | function FileStatusBanner({ |
| b69ab31 | | | 107 | children, |
| b69ab31 | | | 108 | color, |
| b69ab31 | | | 109 | }: { |
| b69ab31 | | | 110 | children: React.ReactNode; |
| b69ab31 | | | 111 | color: 'added' | 'removed' | 'modified'; |
| b69ab31 | | | 112 | }): React.ReactElement { |
| b69ab31 | | | 113 | return ( |
| b69ab31 | | | 114 | <div className={`split-diff-view-file-status-banner split-diff-view-banner-${color}`}> |
| b69ab31 | | | 115 | {children} |
| b69ab31 | | | 116 | </div> |
| b69ab31 | | | 117 | ); |
| b69ab31 | | | 118 | } |