Skip to content

Commit

Permalink
feat(component): Creating a DiffView Component (patternfly#1019)
Browse files Browse the repository at this point in the history
  • Loading branch information
glekner authored and jeff-phillips-18 committed Dec 19, 2018
1 parent 4ad14bc commit f38c10a
Show file tree
Hide file tree
Showing 13 changed files with 433 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@import '~react-diff-view/index.css';

@diff-light-green-200: #cdffd8;
@diff-light-green-100: #e6ffed;
@diff-green: #acf2bd;
@diff-light-red: #ffeef0;
@diff-red: #fdb8c0;

.diff-pf.diff {
border: 1px solid @color-pf-black-300;
max-height: 400px;
overflow: auto;
clear: both;

.diff-line {
line-height: 1.7;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
}

.diff-gutter {
border-left: 1px solid @color-pf-black-300;
}

.diff-gutter.diff-gutter-insert {
background-color: @diff-light-green-200;
}

.diff-hunk-header {
border: 1px solid @color-pf-black-300;
}

.diff-code-insert {
background-color: @diff-light-green-100;

mark.diff-code-edit {
background-color: @diff-green;
}
}

.diff-code-delete {
background-color: @diff-light-red;

mark.diff-code-edit {
background-color: @diff-red;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
@import 'properties-side-panel';
@import 'table-grid';
@import 'vertical-tabs';
@import 'diff-view';
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
"patternfly": "^3.58.0",
"patternfly-react": "^2.28.2",
"react-bootstrap": "^0.32.1",
"react-virtualized": "9.x"
"react-diff-view": "^1.8.1",
"react-virtualized": "9.x",
"unidiff": "^1.0.1"
},
"peerDependencies": {
"prop-types": "^15.6.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@import '~react-diff-view/index.css';

$diff-light-green-200: #cdffd8;
$diff-light-green-100: #e6ffed;
$diff-green: #acf2bd;
$diff-light-red: #ffeef0;
$diff-red: #fdb8c0;

.diff-pf.diff {
border: 1px solid $color-pf-black-300;
max-height: 400px;
overflow: auto;
clear: both;

.diff-line {
line-height: 1.7;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
}

.diff-gutter {
border-left: 1px solid $color-pf-black-300;
}

.diff-gutter.diff-gutter-insert {
background-color: $diff-light-green-200;
}

.diff-hunk-header {
border: 1px solid $color-pf-black-300;
}

.diff-code-insert {
background-color: $diff-light-green-100;

mark.diff-code-edit {
background-color: $diff-green;
}
}

.diff-code-delete {
background-color: $diff-light-red;

mark.diff-code-edit {
background-color: $diff-red;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
@import 'properties-side-panel';
@import 'table-grid';
@import 'vertical-tabs';
@import 'diffview';
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { parseDiff, Diff, markCharacterEdits, markWordEdits } from 'react-diff-view';
import { formatLines, diffLines } from 'unidiff';

const getDiff = (oldText, newText) => {
const diffText = formatLines(diffLines(oldText, newText));
// these two lines are faked to mock git diff output
const header = ['diff --git a/a b/b', 'index 0000000..1111111 100644'];
return `${header.join('\n')}\n${diffText}`;
};

const DiffView = ({
oldText,
newText,
className,
viewType,
patch,
markEditsByWord,
emptyState,
markThreshold,
markLongDistanceDiff,
...props
}) => {
const markEditProps = { markThreshold, markLongDistanceDiff };
const markEdits = markEditsByWord ? markWordEdits(markEditProps) : markCharacterEdits(markEditProps);
const classes = classNames('diff-pf', className);

// Old, New Text
if (!patch) {
const gitDiff = getDiff(oldText, newText);
const files = parseDiff(gitDiff);
const hunk = files[0].hunks;

if (hunk.length === 0) return emptyState;
return hunk && <Diff className={classes} hunks={hunk} markEdits={markEdits} viewType={viewType} {...props} />;
}

// Patch
const files = parseDiff(patch);
// eslint-disable-next-line react/prop-types
const renderFile = ({ oldRevision, newRevision, type, hunks }) => (
<Diff
className={classes}
key={`${oldRevision}-${newRevision}`}
viewType={viewType}
diffType={type}
hunks={hunks}
markEdits={markEdits}
{...props}
/>
);

if (patch === '') return emptyState;
return <div>{files.map(renderFile)}</div>;
};

DiffView.propTypes = {
/** className */
className: PropTypes.string,
/** oldText to compare */
oldText: PropTypes.string,
/** newText to compare */
newText: PropTypes.string,
/** viewType of the DiffView (Unified, Split) */
viewType: PropTypes.string.isRequired,
/** git diff output */
patch: PropTypes.string,
/** mark Diff by Words instead of Characters */
markEditsByWord: PropTypes.bool,
/** emptyState node when there is no diff */
emptyState: PropTypes.node.isRequired,
/** The maximum string distance when this function should try to mark edits */
markThreshold: PropTypes.number,
/** If true, two strings with distance greater than threshold will create an edit containing the whole string */
markLongDistanceDiff: PropTypes.bool
};

DiffView.defaultProps = {
oldText: null,
newText: null,
patch: null,
markEditsByWord: false,
markThreshold: 30,
markLongDistanceDiff: true,
className: ''
};

export default DiffView;
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info/dist/index';
import { defaultTemplate } from 'storybook/decorators/storyTemplates';
import { storybookPackageName, STORYBOOK_CATEGORY } from 'storybook/constants/siteConstants';

import { DiffView } from './index';

import { name } from '../../../package.json';
import { boolean, text, select, withKnobs } from '@storybook/addon-knobs';

const stories = storiesOf(`${storybookPackageName(name)}/${STORYBOOK_CATEGORY.CONTENT_VIEWS}/DiffView`, module);
const emptyState = <h1>No Diff</h1>;

stories.addDecorator(
defaultTemplate({
title: 'Diff View',
description: (
<div>
The Diff View is based on the <a href="https://www.npmjs.com/package/react-diff-view">react-diff-view</a>{' '}
package. This Component supports both git diff Patch and Old/New text inputs and displays the diff in Unified or
Split View.
</div>
)
})
);

stories.addDecorator(withKnobs);
stories.add(
'DiffView',
withInfo({
source: true,
propTables: [DiffView]
})(() => {
const viewType = select('View Type', { split: 'split', unified: 'unified' }, 'split');

return (
<div style={{ width: '600px', padding: '30' }}>
<DiffView
oldText={text('Old Text', 'Old Text')}
newText={text('New Text', 'New Text')}
viewType={viewType}
emptyState={emptyState}
markEditsByWord={boolean('Mark by Word', false)}
/>
</div>
);
})
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';

import { DiffView } from './index';
import { diffMock, patchMock, emptyState } from './_mocks_/DiffViewMocks';

test('Diff View renders w/Patch properly', () => {
const component = shallow(<DiffView {...patchMock} />);
expect(toJson(component.render())).toMatchSnapshot();
});

test('Diff View renders w/Text properly', () => {
const component = shallow(<DiffView {...diffMock} />);
expect(toJson(component.render())).toMatchSnapshot();
});

test('Diff View renders emptyState', () => {
const component = shallow(<DiffView {...emptyState} />);
expect(toJson(component.render())).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Diff View renders emptyState 1`] = `
<div>
empty
</div>
`;

exports[`Diff View renders w/Patch properly 1`] = `
<div>
<table
class="diff diff-pf"
>
<colgroup>
<col
class="diff-gutter-col"
/>
<col
class="diff-gutter-col"
/>
<col />
</colgroup>
</table>
</div>
`;

exports[`Diff View renders w/Text properly 1`] = `
<table
class="diff diff-pf"
>
<colgroup>
<col
class="diff-gutter-col"
/>
<col />
<col
class="diff-gutter-col"
/>
<col />
</colgroup>
<tbody
class="diff-hunk"
>
<tr
class="diff-hunk-header"
>
<td
class="diff-hunk-header-gutter"
/>
<td
class="diff-hunk-header-content"
colspan="3"
>
@@ -1 +1 @@
</td>
</tr>
<tr
class="diff-line diff-line-compare"
>
<td
class="diff-gutter diff-gutter-delete"
data-line-number="1"
/>
<td
class="diff-code diff-code-delete"
>
<span
class="diff-code-text"
>
hello friend
</span>
</td>
<td
class="diff-gutter diff-gutter-insert"
data-line-number="1"
/>
<td
class="diff-code diff-code-insert"
>
<span
class="diff-code-text"
>
hello
<mark
class="diff-code-edit"
>
there
</mark>
friend
</span>
</td>
</tr>
</tbody>
</table>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';

export const diffMock = {
oldText: 'hello friend',
newText: 'hello there friend',
viewType: 'split',
emptyState: <div>empty</div>
};

export const patchMock = {
viewType: 'unified',
patch: '---',
emptyState: <div>empty</div>
};

export const emptyState = {
oldText: 'hello friend',
newText: 'hello friend',
viewType: 'split',
emptyState: <div>empty</div>
};
Loading

0 comments on commit f38c10a

Please sign in to comment.