import React from 'react'; import { translate } from 'react-i18next'; import classNames from 'classnames'; import styled from 'styled-components'; import { Change, Diff as DiffComponent, DiffObjectProps, File, getChangeKey, Hunk, } from 'react-diff-view'; import { Button, ButtonGroup } from '../buttons'; import Tag from '../Tag'; import Icon from '../Icon'; type Props = DiffObjectProps & { file: File; defaultCollapse: boolean; // context props t: (p: string) => string; }; type State = { collapsed: boolean; sideBySide: boolean; }; const DiffFilePanel = styled.div` /* remove bottom border for collapsed panels */ ${props => (props.collapsed ? 'border-bottom: none;' : '')}; `; const FlexWrapLevel = styled.div` /* breaks into a second row when buttons and title become too long */ flex-wrap: wrap; `; const FullWidthTitleHeader = styled.div` max-width: 100%; `; const TitleWrapper = styled.span` margin-left: 0.25rem; `; const ButtonWrapper = styled.div` /* align child to right */ margin-left: auto; `; const HunkDivider = styled.hr` margin: 0.5rem 0; `; const ChangeTypeTag = styled(Tag)` margin-left: 0.75rem; `; const ModifiedDiffComponent = styled(DiffComponent)` /* column sizing */ > colgroup .diff-gutter-col { width: 3.25rem; } /* prevent following content from moving down */ > .diff-gutter:empty:hover::after { font-size: 0.7rem; } /* smaller font size for code */ & .diff-line { font-size: 0.75rem; } /* comment padding for sidebyside view */ &.split .diff-widget-content .is-indented-line { padding-left: 3.25rem; } /* comment padding for combined view */ &.unified .diff-widget-content .is-indented-line { padding-left: 6.5rem; } `; class DiffFile extends React.Component { static defaultProps = { defaultCollapse: false, }; constructor(props: Props) { super(props); this.state = { collapsed: this.props.defaultCollapse, sideBySide: false, }; } // collapse diff by clicking collapseDiffs button componentDidUpdate(prevProps) { const { defaultCollapse } = this.props; if (prevProps.defaultCollapse !== defaultCollapse) { this.setState({ collapsed: defaultCollapse, }); } } toggleCollapse = () => { const { file } = this.props; if (file && !file.isBinary) { this.setState(state => ({ collapsed: !state.collapsed, })); } }; toggleSideBySide = () => { this.setState(state => ({ sideBySide: !state.sideBySide, })); }; setCollapse = (collapsed: boolean) => { this.setState({ collapsed, }); }; createHunkHeader = (hunk: Hunk, i: number) => { if (i > 0) { return ; } return null; }; collectHunkAnnotations = (hunk: Hunk) => { const { annotationFactory, file } = this.props; if (annotationFactory) { return annotationFactory({ hunk, file, }); } }; handleClickEvent = (change: Change, hunk: Hunk) => { const { file, onClick } = this.props; const context = { changeId: getChangeKey(change), change, hunk, file, }; if (onClick) { onClick(context); } }; createCustomEvents = (hunk: Hunk) => { const { onClick } = this.props; if (onClick) { return { gutter: { onClick: (change: Change) => { this.handleClickEvent(change, hunk); }, }, }; } }; renderHunk = (hunk: Hunk, i: number) => { return ( ); }; renderFileTitle = (file: any) => { if ( file.oldPath !== file.newPath && (file.type === 'copy' || file.type === 'rename') ) { return ( <> {file.oldPath} {' '} {file.newPath} ); } else if (file.type === 'delete') { return file.oldPath; } return file.newPath; }; hoverFileTitle = (file: any) => { if ( file.oldPath !== file.newPath && (file.type === 'copy' || file.type === 'rename') ) { return ( <> {file.oldPath} > {file.newPath} ); } else if (file.type === 'delete') { return file.oldPath; } return file.newPath; }; renderChangeTag = (file: any) => { const { t } = this.props; if (!file.type) { return; } const key = 'diff.changes.' + file.type; let value = t(key); if (key === value) { value = file.type; } const color = value === 'added' ? 'success is-outlined' : value === 'deleted' ? 'danger is-outlined' : 'info is-outlined'; return ( ); }; render() { const { file, fileControlFactory, fileAnnotationFactory, t } = this.props; const { collapsed, sideBySide } = this.state; const viewType = sideBySide ? 'split' : 'unified'; let body = null; let icon = 'angle-right'; if (!collapsed) { const fileAnnotations = fileAnnotationFactory ? fileAnnotationFactory(file) : null; icon = 'angle-down'; body = (
{fileAnnotations} {file.hunks.map(this.renderHunk)}
); } const collapseIcon = file && !file.isBinary ? : null; const fileControls = fileControlFactory ? fileControlFactory(file, this.setCollapse) : null; return (
{collapseIcon} {this.renderFileTitle(file)} {this.renderChangeTag(file)}
{body}
); } } export default translate('repos')(DiffFile);