Skip to content

Commit e47cbe1

Browse files
committed
feat: add summary element and fold expansion/folding (fixes #22, #21)
1 parent a6222c7 commit e47cbe1

File tree

6 files changed

+132
-35
lines changed

6 files changed

+132
-35
lines changed

examples/src/index.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface ExampleState {
1818
language?: string;
1919
theme: 'dark' | 'light';
2020
enableSyntaxHighlighting?: boolean;
21+
columnHeaders: boolean;
2122
compareMethod?: DiffMethod;
2223
customGutter?: boolean;
2324
}
@@ -31,6 +32,7 @@ class Example extends Component<{}, ExampleState> {
3132
highlightLine: [],
3233
theme: 'dark',
3334
splitView: true,
35+
columnHeaders: true,
3436
customGutter: true,
3537
enableSyntaxHighlighting: true,
3638
compareMethod: DiffMethod.CHARS
@@ -145,6 +147,22 @@ class Example extends Component<{}, ExampleState> {
145147
</label>
146148
<span>Syntax highlighting</span>
147149
</div>
150+
<div>
151+
<label className={'switch'}>
152+
<input
153+
type="checkbox"
154+
checked={this.state.columnHeaders}
155+
onChange={() => {
156+
this.setState({
157+
columnHeaders:
158+
!this.state.columnHeaders,
159+
});
160+
}}
161+
/>
162+
<span className="slider round"></span>
163+
</label>
164+
<span>Column Headers</span>
165+
</div>
148166
<div>
149167
<label className={'switch'}>
150168
<input
@@ -225,8 +243,9 @@ class Example extends Component<{}, ExampleState> {
225243
: undefined
226244
}
227245
useDarkTheme={this.state.theme === 'dark'}
228-
leftTitle={`${this.state.compareMethod === DiffMethod.JSON ? 'package.json' : 'webpack.config.js'} master@2178133 - pushed 2 hours ago.`}
229-
rightTitle={`${this.state.compareMethod === DiffMethod.JSON ? 'package.json' : 'webpack.config.js'} master@64207ee - pushed 13 hours ago.`}
246+
summary={this.state.compareMethod === DiffMethod.JSON ? 'package.json' : 'webpack.config.js'}
247+
leftTitle={this.state.columnHeaders ? `master@2178133 - pushed 2 hours ago.` : undefined}
248+
rightTitle={this.state.columnHeaders ? `master@64207ee - pushed 13 hours ago.` : undefined}
230249
/>
231250
</div>
232251
<footer>

src/compute-hidden-blocks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {DiffType, LineInformation} from "./compute-lines";
22
import {ReactElement} from "react";
33

4-
interface Block {
4+
export interface Block {
55
index: number
66
startLine: number
77
endLine: number

src/expand.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function Expand() {
2+
return <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="m8.177.677 2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25a.75.75 0 0 1-1.5 0V4H5.104a.25.25 0 0 1-.177-.427L7.823.677a.25.25 0 0 1 .354 0ZM7.25 10.75a.75.75 0 0 1 1.5 0V12h2.146a.25.25 0 0 1 .177.427l-2.896 2.896a.25.25 0 0 1-.354 0l-2.896-2.896A.25.25 0 0 1 5.104 12H7.25v-1.25Zm-5-2a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z"></path></svg>
3+
}

src/fold.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function Fold() {
2+
return <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M10.896 2H8.75V.75a.75.75 0 0 0-1.5 0V2H5.104a.25.25 0 0 0-.177.427l2.896 2.896a.25.25 0 0 0 .354 0l2.896-2.896A.25.25 0 0 0 10.896 2ZM8.75 15.25a.75.75 0 0 1-1.5 0V14H5.104a.25.25 0 0 1-.177-.427l2.896-2.896a.25.25 0 0 1 .354 0l2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25Zm-6.5-6.5a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z"></path></svg>
3+
}

src/index.tsx

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import * as React from 'react';
2-
import * as PropTypes from 'prop-types';
2+
import {ReactElement} from 'react';
33
import cn from 'classnames';
44

55
import {computeLineInformation, DiffInformation, DiffMethod, DiffType, LineInformation,} from './compute-lines';
66
import computeStyles, {ReactDiffViewerStyles, ReactDiffViewerStylesOverride,} from './styles';
7-
import {ReactElement} from "react";
8-
import {computeHiddenBlocks} from "./compute-hidden-blocks";
7+
import {Block, computeHiddenBlocks} from "./compute-hidden-blocks";
98
import IntrinsicElements = React.JSX.IntrinsicElements;
9+
import {Expand} from "./expand";
10+
import {Fold} from "./fold";
1011

1112
const m = require('memoize-one');
1213

@@ -69,6 +70,10 @@ export interface ReactDiffViewerProps {
6970
styles?: ReactDiffViewerStylesOverride;
7071
// Use dark theme.
7172
useDarkTheme?: boolean;
73+
/**
74+
* Used to describe the thing being diffed
75+
*/
76+
summary?: string | ReactElement;
7277
// Title for left column
7378
leftTitle?: string | ReactElement;
7479
// Title for left column
@@ -519,7 +524,7 @@ class DiffViewer extends React.Component<
519524
/**
520525
* Generates the entire diff view.
521526
*/
522-
private renderDiff = (): {leftLines: ReactElement[], rightLines: ReactElement[]} => {
527+
private renderDiff = (): {leftLines: ReactElement[], rightLines: ReactElement[], lineInformation: LineInformation[], blocks: Block[] } => {
523528
const {
524529
oldValue,
525530
newValue,
@@ -577,7 +582,7 @@ class DiffViewer extends React.Component<
577582
rightLines.push(...right)
578583
},
579584
);
580-
return {leftLines, rightLines};
585+
return {leftLines, rightLines, lineInformation, blocks};
581586
};
582587

583588
public render = (): ReactElement => {
@@ -600,36 +605,67 @@ class DiffViewer extends React.Component<
600605

601606
this.styles = this.computeStyles(this.props.styles, useDarkTheme, nonce);
602607
const nodes = this.renderDiff();
603-
const colSpanOnSplitView = hideLineNumbers ? 2 : 3;
604-
const colSpanOnInlineView = hideLineNumbers ? 2 : 4;
605-
let columnExtension = this.props.renderGutter ? 1 : 0;
606-
607-
console.log(nodes.rightLines)
608+
let deletions = 0, additions = 0
609+
nodes.lineInformation.forEach((l) => {
610+
if (l.left.type === DiffType.ADDED) {
611+
additions++
612+
}
613+
if (l.right.type === DiffType.ADDED) {
614+
additions++
615+
}
616+
if (l.left.type === DiffType.REMOVED) {
617+
deletions++
618+
}
619+
if (l.right.type === DiffType.REMOVED) {
620+
deletions++
621+
}
622+
})
623+
const totalChanges = deletions + additions
624+
625+
const percentageAddition = Math.round((additions / totalChanges) * 100)
626+
const blocks: ReactElement[] = []
627+
for(let i = 0; i < 5; i++) {
628+
if (percentageAddition > i * 20) {
629+
blocks.push(<span key={i} className={cn(this.styles.block, this.styles.blockAddition)} />)
630+
} else {
631+
blocks.push(<span key={i} className={cn(this.styles.block, this.styles.blockDeletion)} />)
632+
}
633+
}
634+
const allExpanded = this.state.expandedBlocks.length === nodes.blocks.length
608635

609636
return (
610-
<div
611-
className={cn(this.styles.diffContainer, {
612-
[this.styles.splitView]: splitView,
613-
})}
614-
>
615-
<div className={this.styles.column} role={'table'} title={`Diff information for ${leftTitle}`}>
616-
<div
617-
className={cn(this.styles.titleBlock, this.styles.column)}
618-
role={'columnheader'}
619-
>
620-
<pre className={this.styles.contentText}>{leftTitle}</pre>
621-
</div>
622-
{nodes.leftLines}
637+
<div>
638+
<div className={this.styles.summary} role={'banner'}>
639+
<a style={{ cursor: 'pointer'}} onClick={() => {
640+
this.setState({
641+
expandedBlocks: allExpanded ? [] : nodes.blocks.map(b => b.index)
642+
})
643+
}}>{allExpanded ? <Fold /> : <Expand />}</a> {totalChanges} <div style={{ display: 'flex', gap: '1px'}}>{blocks}</div> {this.props.summary ? <span>{this.props.summary}</span> : null}
623644
</div>
624-
{nodes.rightLines.length > 0 ? <div className={this.styles.column} role={'table'} title={`Diff information for ${rightTitle}`}>
625-
<div
626-
className={cn(this.styles.titleBlock, this.styles.column)}
627-
role={'columnheader'}
628-
>
629-
<pre className={this.styles.contentText}>{rightTitle}</pre>
645+
<div
646+
className={cn(this.styles.diffContainer, {
647+
[this.styles.splitView]: splitView,
648+
})}
649+
>
650+
<div className={this.styles.column} role={'table'} title={`Diff information for ${leftTitle}`}>
651+
{leftTitle ? <div
652+
className={cn(this.styles.titleBlock, this.styles.column)}
653+
role={'columnheader'}
654+
>
655+
<pre className={this.styles.contentText}>{leftTitle}</pre>
656+
</div> : null }
657+
{nodes.leftLines}
630658
</div>
631-
{nodes.rightLines}
632-
</div> : null}
659+
{nodes.rightLines.length > 0 ? <div className={this.styles.column} role={'table'} title={`Diff information for ${rightTitle}`}>
660+
{rightTitle ? <div
661+
className={cn(this.styles.titleBlock, this.styles.column)}
662+
role={'columnheader'}
663+
>
664+
<pre className={this.styles.contentText}>{rightTitle}</pre>
665+
</div> : null}
666+
{nodes.rightLines}
667+
</div> : null}
668+
</div>
633669
</div>
634670
);
635671
};

src/styles.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface ReactDiffViewerStyles {
1818
wordAdded?: string;
1919
wordRemoved?: string;
2020
codeFoldGutter?: string;
21+
summary?: string;
2122
codeFoldContentContainer?: string;
2223
emptyGutter?: string;
2324
emptyLine?: string;
@@ -151,7 +152,7 @@ export default (
151152
removedGutterColor: '#8c8c8c',
152153
codeFoldContentColor: '#656a8b',
153154
diffViewerTitleBackground: '#2f323e',
154-
diffViewerTitleColor: '#555a7b',
155+
diffViewerTitleColor: '#757a9b',
155156
diffViewerTitleBorderColor: '#353846',
156157
},
157158
...(overrideVariables.dark || {}),
@@ -172,6 +173,17 @@ export default (
172173
label: 'split-view',
173174
});
174175

176+
const summary = css({
177+
background: variables.diffViewerTitleBackground,
178+
color: variables.diffViewerTitleColor,
179+
padding: '0.5em 1em',
180+
display: 'flex',
181+
alignItems: 'center',
182+
gap: '0.5em',
183+
fontFamily: "monospace",
184+
fill: variables.diffViewerTitleColor,
185+
})
186+
175187
const diffContainer = css({
176188
width: '100%',
177189
minWidth: '1000px',
@@ -261,11 +273,13 @@ export default (
261273

262274
const wordAdded = css({
263275
background: variables.wordAddedBackground,
276+
textDecoration: 'none',
264277
label: 'word-added',
265278
});
266279

267280
const wordRemoved = css({
268281
background: variables.wordRemovedBackground,
282+
textDecoration: 'none',
269283
label: 'word-removed',
270284
});
271285

@@ -284,6 +298,24 @@ export default (
284298
label: 'code-fold-content',
285299
});
286300

301+
const block = css({
302+
display: 'block',
303+
width: '10px',
304+
height: '10px',
305+
backgroundColor: '#ddd',
306+
borderWidth: '1px',
307+
borderStyle: 'solid',
308+
borderColor: variables.diffViewerTitleBorderColor
309+
});
310+
311+
const blockAddition = css({
312+
backgroundColor: variables.wordAddedBackground
313+
})
314+
315+
const blockDeletion = css({
316+
backgroundColor: variables.wordRemovedBackground
317+
})
318+
287319
const codeFold = css({
288320
backgroundColor: variables.codeFoldBackground,
289321
height: 40,
@@ -404,6 +436,10 @@ export default (
404436
lineContent,
405437
wordDiff,
406438
wordAdded,
439+
summary,
440+
block,
441+
blockAddition,
442+
blockDeletion,
407443
wordRemoved,
408444
codeFoldGutter,
409445
codeFoldContentContainer,

0 commit comments

Comments
 (0)