7.0 KB213 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 * as stylex from '@stylexjs/stylex';
9import type {ParsedDiff} from 'diff';
10import {Icon} from 'isl-components/Icon';
11import type {ReactNode} from 'react';
12import {forwardRef, lazy, Suspense} from 'react';
13import {ComparisonType} from 'shared/Comparison';
14import type {DiffViewMode} from '../ComparisonView/SplitDiffView/types';
15import {Column, Row} from '../ComponentUtils';
16import type {ThemeColor} from '../theme';
17import type {CodeChange, DiffComment, SuggestedChange} from '../types';
18import {CodePatchSuggestionStatus, SuggestedChangeType} from '../types';
19import InlineCommentContent from './InlineCommentContent';
20import InlineCommentSuggestionActionBottomBar from './InlineCommentSuggestionActionBottomBar';
21
22const InlineCommentComparisonView = lazy(() => import('./InlineCommentComparisonView'));
23
24const styles = stylex.create({
25 alignTop: {alignItems: 'flex-start'},
26 subheadingsAlignBaseline: {alignItems: 'baseline', gap: '4px'},
27 tooltipBar: {width: '100%', justifyContent: 'space-between'},
28 boldText: {fontWeight: '700', fontSize: '100%'},
29 subtle: {fontSize: '90%', opacity: 0.8},
30 headerRow: {
31 justifyContent: 'space-between',
32 alignItems: 'start',
33 wordBreak: 'break-word',
34 },
35 headerContent: {
36 maxWidth: '320px',
37 whiteSpace: 'nowrap',
38 overflow: 'hidden',
39 textOverflow: 'ellipsis',
40 },
41 headerControl: {
42 gap: '4px',
43 },
44 shortCutTipLink: {
45 overflow: 'hidden',
46 textOverflow: 'ellipsis',
47 fontSize: '12px',
48 color: 'var(--vscode-descriptionForeground)',
49 cursor: 'pointer',
50 },
51 underlineOnHoverText: {
52 textDecoration: {
53 ':hover': 'underline',
54 },
55 },
56});
57
58export const InlineComment = forwardRef(
59 (
60 {
61 comment,
62 versionInfo,
63 collapsed,
64 diffViewMode,
65 onAccept,
66 onReject,
67 onResolve,
68 onUnresolve,
69 onClickHeader,
70 headerControls,
71 bottomControls,
72 useThemeHook,
73 }: {
74 comment: DiffComment;
75 versionInfo?: {
76 isLatestVersion?: boolean;
77 versionAbbr?: string;
78 };
79 collapsed: boolean;
80 diffViewMode: DiffViewMode;
81
82 onAccept: (codeSuggestion?: SuggestedChange) => unknown;
83 onReject: (codeSuggestion?: SuggestedChange) => unknown;
84 onResolve: () => unknown;
85 onUnresolve: () => unknown;
86 onClickHeader: () => unknown;
87
88 headerControls: ReactNode;
89 bottomControls: ReactNode;
90
91 useThemeHook: () => ThemeColor;
92 },
93 ref: React.ForwardedRef<HTMLDivElement>,
94 ) => {
95 const path = comment?.filename ?? '';
96 const codeSuggestion = comment?.suggestedChange ?? null;
97 const authorName = comment.authorName;
98 const codeChange = codeSuggestion?.codeChange;
99
100 const renderDiffView = (codeChange: CodeChange[]) => {
101 const changes = codeChange?.filter(
102 (change): change is CodeChange & {patch: ParsedDiff} => change.patch != null,
103 );
104
105 if (changes == null || changes.length === 0) {
106 return null;
107 }
108
109 return changes.map((change, i) => {
110 return (
111 <div key={i}>
112 {changes.length === 1 ? null : (
113 <div {...stylex.props(styles.boldText)}>Change {i + 1}</div>
114 )}
115 <InlineCommentComparisonView
116 path={path}
117 suggestion={change.patch}
118 ctx={{
119 collapsed: false,
120 displayLineNumbers: changes.length > 1, // TODO: currently this line number is not aligned value
121 id: {
122 comparison: {type: ComparisonType.HeadChanges},
123 path,
124 },
125 setCollapsed: () => null,
126 display: diffViewMode,
127 useThemeHook,
128 }}
129 />
130 </div>
131 );
132 });
133 };
134
135 return (
136 <div
137 ref={ref}
138 className="container"
139 style={{width: collapsed || diffViewMode === 'unified' ? 600 : 1000}}>
140 {collapsed ? (
141 <div className="headerRow" onClick={onClickHeader}>
142 <div className="headerLeftContent">
143 <Icon icon="comment" />
144 <div className="headerTitle">
145 <span {...stylex.props(styles.boldText)}>{authorName}</span>
146 {comment.content != null && comment.content !== '' && (
147 <div {...stylex.props(styles.subtle, styles.headerContent)}>
148 {comment.content}
149 </div>
150 )}
151 </div>
152 </div>
153 <Row xstyle={styles.headerControl}>{headerControls}</Row>
154 </div>
155 ) : (
156 <>
157 <Row xstyle={styles.headerRow}>
158 <Column alignStart style={{marginBlock: '8px'}}>
159 <InlineCommentContent
160 comment={comment}
161 isHeadComment={true}
162 isLatestVersion={versionInfo?.isLatestVersion}
163 versionAbbr={versionInfo?.versionAbbr}
164 />
165 {comment.replies.map((reply, i) => (
166 <InlineCommentContent comment={reply} key={i} />
167 ))}
168 </Column>
169 <Row xstyle={styles.headerControl}>{headerControls}</Row>
170 </Row>
171 <Column alignStart style={{marginBlock: '8px'}}>
172 {path && codeSuggestion != null && codeChange != null && (
173 <>
174 {codeSuggestion.type !== SuggestedChangeType.HUMAN_SUGGESTION && (
175 <Row xstyle={styles.subheadingsAlignBaseline}>
176 <div {...stylex.props(styles.boldText)}>
177 {codeSuggestion.type === SuggestedChangeType.METAMATE_SUGGESTION
178 ? 'Metamate'
179 : 'Signal'}
180 </div>
181 <div {...stylex.props(styles.subtle)}>suggested changes</div>
182 </Row>
183 )}
184 <Suspense>{renderDiffView(codeChange)}</Suspense>
185 </>
186 )}
187 <Row xstyle={styles.tooltipBar}>
188 {codeSuggestion?.status != null ? (
189 <InlineCommentSuggestionActionBottomBar
190 resolved={codeSuggestion.status === CodePatchSuggestionStatus.Accepted}
191 onAccept={() => onAccept(codeSuggestion)}
192 onReject={() => onReject(codeSuggestion)}
193 />
194 ) : (
195 <InlineCommentSuggestionActionBottomBar
196 resolved={comment.isResolved ?? false}
197 onAccept={onResolve}
198 onReject={onUnresolve}
199 acceptLabel="Resolve"
200 rejectLabel="Unresolve"
201 isToggle={true}
202 />
203 )}
204 <Row>{bottomControls}</Row>
205 </Row>
206 </Column>
207 </>
208 )}
209 </div>
210 );
211 },
212);
213