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