addons/isl/src/comments/InlineComment.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import * as stylex from '@stylexjs/stylex';
b69ab319import type {ParsedDiff} from 'diff';
b69ab3110import {Icon} from 'isl-components/Icon';
b69ab3111import type {ReactNode} from 'react';
b69ab3112import {forwardRef, lazy, Suspense} from 'react';
b69ab3113import {ComparisonType} from 'shared/Comparison';
b69ab3114import type {DiffViewMode} from '../ComparisonView/SplitDiffView/types';
b69ab3115import {Column, Row} from '../ComponentUtils';
b69ab3116import type {ThemeColor} from '../theme';
b69ab3117import type {CodeChange, DiffComment, SuggestedChange} from '../types';
b69ab3118import {CodePatchSuggestionStatus, SuggestedChangeType} from '../types';
b69ab3119import InlineCommentContent from './InlineCommentContent';
b69ab3120import InlineCommentSuggestionActionBottomBar from './InlineCommentSuggestionActionBottomBar';
b69ab3121
b69ab3122const InlineCommentComparisonView = lazy(() => import('./InlineCommentComparisonView'));
b69ab3123
b69ab3124const styles = stylex.create({
b69ab3125 alignTop: {alignItems: 'flex-start'},
b69ab3126 subheadingsAlignBaseline: {alignItems: 'baseline', gap: '4px'},
b69ab3127 tooltipBar: {width: '100%', justifyContent: 'space-between'},
b69ab3128 boldText: {fontWeight: '700', fontSize: '100%'},
b69ab3129 subtle: {fontSize: '90%', opacity: 0.8},
b69ab3130 headerRow: {
b69ab3131 justifyContent: 'space-between',
b69ab3132 alignItems: 'start',
b69ab3133 wordBreak: 'break-word',
b69ab3134 },
b69ab3135 headerContent: {
b69ab3136 maxWidth: '320px',
b69ab3137 whiteSpace: 'nowrap',
b69ab3138 overflow: 'hidden',
b69ab3139 textOverflow: 'ellipsis',
b69ab3140 },
b69ab3141 headerControl: {
b69ab3142 gap: '4px',
b69ab3143 },
b69ab3144 shortCutTipLink: {
b69ab3145 overflow: 'hidden',
b69ab3146 textOverflow: 'ellipsis',
b69ab3147 fontSize: '12px',
b69ab3148 color: 'var(--vscode-descriptionForeground)',
b69ab3149 cursor: 'pointer',
b69ab3150 },
b69ab3151 underlineOnHoverText: {
b69ab3152 textDecoration: {
b69ab3153 ':hover': 'underline',
b69ab3154 },
b69ab3155 },
b69ab3156});
b69ab3157
b69ab3158export const InlineComment = forwardRef(
b69ab3159 (
b69ab3160 {
b69ab3161 comment,
b69ab3162 versionInfo,
b69ab3163 collapsed,
b69ab3164 diffViewMode,
b69ab3165 onAccept,
b69ab3166 onReject,
b69ab3167 onResolve,
b69ab3168 onUnresolve,
b69ab3169 onClickHeader,
b69ab3170 headerControls,
b69ab3171 bottomControls,
b69ab3172 useThemeHook,
b69ab3173 }: {
b69ab3174 comment: DiffComment;
b69ab3175 versionInfo?: {
b69ab3176 isLatestVersion?: boolean;
b69ab3177 versionAbbr?: string;
b69ab3178 };
b69ab3179 collapsed: boolean;
b69ab3180 diffViewMode: DiffViewMode;
b69ab3181
b69ab3182 onAccept: (codeSuggestion?: SuggestedChange) => unknown;
b69ab3183 onReject: (codeSuggestion?: SuggestedChange) => unknown;
b69ab3184 onResolve: () => unknown;
b69ab3185 onUnresolve: () => unknown;
b69ab3186 onClickHeader: () => unknown;
b69ab3187
b69ab3188 headerControls: ReactNode;
b69ab3189 bottomControls: ReactNode;
b69ab3190
b69ab3191 useThemeHook: () => ThemeColor;
b69ab3192 },
b69ab3193 ref: React.ForwardedRef<HTMLDivElement>,
b69ab3194 ) => {
b69ab3195 const path = comment?.filename ?? '';
b69ab3196 const codeSuggestion = comment?.suggestedChange ?? null;
b69ab3197 const authorName = comment.authorName;
b69ab3198 const codeChange = codeSuggestion?.codeChange;
b69ab3199
b69ab31100 const renderDiffView = (codeChange: CodeChange[]) => {
b69ab31101 const changes = codeChange?.filter(
b69ab31102 (change): change is CodeChange & {patch: ParsedDiff} => change.patch != null,
b69ab31103 );
b69ab31104
b69ab31105 if (changes == null || changes.length === 0) {
b69ab31106 return null;
b69ab31107 }
b69ab31108
b69ab31109 return changes.map((change, i) => {
b69ab31110 return (
b69ab31111 <div key={i}>
b69ab31112 {changes.length === 1 ? null : (
b69ab31113 <div {...stylex.props(styles.boldText)}>Change {i + 1}</div>
b69ab31114 )}
b69ab31115 <InlineCommentComparisonView
b69ab31116 path={path}
b69ab31117 suggestion={change.patch}
b69ab31118 ctx={{
b69ab31119 collapsed: false,
b69ab31120 displayLineNumbers: changes.length > 1, // TODO: currently this line number is not aligned value
b69ab31121 id: {
b69ab31122 comparison: {type: ComparisonType.HeadChanges},
b69ab31123 path,
b69ab31124 },
b69ab31125 setCollapsed: () => null,
b69ab31126 display: diffViewMode,
b69ab31127 useThemeHook,
b69ab31128 }}
b69ab31129 />
b69ab31130 </div>
b69ab31131 );
b69ab31132 });
b69ab31133 };
b69ab31134
b69ab31135 return (
b69ab31136 <div
b69ab31137 ref={ref}
b69ab31138 className="container"
b69ab31139 style={{width: collapsed || diffViewMode === 'unified' ? 600 : 1000}}>
b69ab31140 {collapsed ? (
b69ab31141 <div className="headerRow" onClick={onClickHeader}>
b69ab31142 <div className="headerLeftContent">
b69ab31143 <Icon icon="comment" />
b69ab31144 <div className="headerTitle">
b69ab31145 <span {...stylex.props(styles.boldText)}>{authorName}</span>
b69ab31146 {comment.content != null && comment.content !== '' && (
b69ab31147 <div {...stylex.props(styles.subtle, styles.headerContent)}>
b69ab31148 {comment.content}
b69ab31149 </div>
b69ab31150 )}
b69ab31151 </div>
b69ab31152 </div>
b69ab31153 <Row xstyle={styles.headerControl}>{headerControls}</Row>
b69ab31154 </div>
b69ab31155 ) : (
b69ab31156 <>
b69ab31157 <Row xstyle={styles.headerRow}>
b69ab31158 <Column alignStart style={{marginBlock: '8px'}}>
b69ab31159 <InlineCommentContent
b69ab31160 comment={comment}
b69ab31161 isHeadComment={true}
b69ab31162 isLatestVersion={versionInfo?.isLatestVersion}
b69ab31163 versionAbbr={versionInfo?.versionAbbr}
b69ab31164 />
b69ab31165 {comment.replies.map((reply, i) => (
b69ab31166 <InlineCommentContent comment={reply} key={i} />
b69ab31167 ))}
b69ab31168 </Column>
b69ab31169 <Row xstyle={styles.headerControl}>{headerControls}</Row>
b69ab31170 </Row>
b69ab31171 <Column alignStart style={{marginBlock: '8px'}}>
b69ab31172 {path && codeSuggestion != null && codeChange != null && (
b69ab31173 <>
b69ab31174 {codeSuggestion.type !== SuggestedChangeType.HUMAN_SUGGESTION && (
b69ab31175 <Row xstyle={styles.subheadingsAlignBaseline}>
b69ab31176 <div {...stylex.props(styles.boldText)}>
b69ab31177 {codeSuggestion.type === SuggestedChangeType.METAMATE_SUGGESTION
b69ab31178 ? 'Metamate'
b69ab31179 : 'Signal'}
b69ab31180 </div>
b69ab31181 <div {...stylex.props(styles.subtle)}>suggested changes</div>
b69ab31182 </Row>
b69ab31183 )}
b69ab31184 <Suspense>{renderDiffView(codeChange)}</Suspense>
b69ab31185 </>
b69ab31186 )}
b69ab31187 <Row xstyle={styles.tooltipBar}>
b69ab31188 {codeSuggestion?.status != null ? (
b69ab31189 <InlineCommentSuggestionActionBottomBar
b69ab31190 resolved={codeSuggestion.status === CodePatchSuggestionStatus.Accepted}
b69ab31191 onAccept={() => onAccept(codeSuggestion)}
b69ab31192 onReject={() => onReject(codeSuggestion)}
b69ab31193 />
b69ab31194 ) : (
b69ab31195 <InlineCommentSuggestionActionBottomBar
b69ab31196 resolved={comment.isResolved ?? false}
b69ab31197 onAccept={onResolve}
b69ab31198 onReject={onUnresolve}
b69ab31199 acceptLabel="Resolve"
b69ab31200 rejectLabel="Unresolve"
b69ab31201 isToggle={true}
b69ab31202 />
b69ab31203 )}
b69ab31204 <Row>{bottomControls}</Row>
b69ab31205 </Row>
b69ab31206 </Column>
b69ab31207 </>
b69ab31208 )}
b69ab31209 </div>
b69ab31210 );
b69ab31211 },
b69ab31212);