Skip to content

Commit e55e89e

Browse files
committed
Merge pull request #96 from danvk/pdiff
Show perceptual diffs
2 parents 1efe6bd + 64da4d1 commit e55e89e

File tree

11 files changed

+295
-92
lines changed

11 files changed

+295
-92
lines changed

bower.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
"highlightjs": "~8.0.0",
2020
"underscore": "~1.6.0",
2121
"react": "~0.12.2",
22-
"react-router": "~0.11.6",
23-
"resemblejs": "1.2.1"
22+
"react-router": "~0.11.6"
2423
}
2524
}

tests/pair_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,18 @@ def test_pairing_with_move():
3838
testdir = 'testdata/renamedfile'
3939
diff = util.find_diff('%s/left/dir' % testdir, '%s/right/dir' % testdir)
4040
eq_([{'a': 'file.json',
41+
'a_path': 'testdata/renamedfile/left/dir/file.json',
4142
'path': 'file.json',
4243
'b': 'renamed.json',
44+
'b_path': 'testdata/renamedfile/right/dir/renamed.json',
4345
'type': 'move',
4446
'no_changes': True,
4547
'idx': 0},
4648
{'a': 'file.json',
49+
'a_path': 'testdata/renamedfile/left/dir/file.json',
4750
'path': 'file.json',
4851
'b': None,
52+
'b_path': None,
4953
'type': 'delete',
5054
'no_changes': False,
5155
'idx': 1}], diff)

tests/runner.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
<script src="../webdiff/static/components/react/react.js"></script>
1919
<script src="../webdiff/static/components/react/JSXTransformer.js"></script>
2020
<script src="../webdiff/static/components/react-router/dist/react-router.js"></script>
21-
<script src="../webdiff/static/components/resemblejs/resemble.js"></script>
2221

2322
<script src="../webdiff/static/codediff.js/difflib.js"></script>
2423
<script src="../webdiff/static/codediff.js/codediff.js"></script>

webdiff/app.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import time
1515
import webbrowser
1616

17-
from flask import (Flask, render_template, send_from_directory,
17+
from flask import (Flask, render_template, send_from_directory, send_file,
1818
request, jsonify, Response)
1919

2020
import util
@@ -136,6 +136,34 @@ def get_image(side, path):
136136
return response
137137

138138

139+
@app.route("/pdiff/<int:idx>")
140+
def get_pdiff(idx):
141+
idx = int(idx)
142+
pair = DIFF[idx]
143+
try:
144+
_, pdiff_image = util.generate_pdiff_image(pair['a_path'], pair['b_path'])
145+
dilated_image = util.generate_dilated_pdiff_image(pdiff_image)
146+
except util.ImageMagickNotAvailableError:
147+
return 'ImageMagick is not available', 501
148+
except util.ImageMagickError as e:
149+
return 'ImageMagick error %s' % e, 501
150+
return send_file(dilated_image)
151+
152+
153+
@app.route("/pdiffbbox/<int:idx>")
154+
def get_pdiff_bbox(idx):
155+
idx = int(idx)
156+
pair = DIFF[idx]
157+
try:
158+
_, pdiff_image = util.generate_pdiff_image(pair['a_path'], pair['b_path'])
159+
bbox = util.get_pdiff_bbox(pdiff_image)
160+
except util.ImageMagickNotAvailableError:
161+
return 'ImageMagick is not available', 501
162+
except util.ImageMagickError as e:
163+
return 'ImageMagick error %s' % e, 501
164+
return jsonify(bbox)
165+
166+
139167
# Show the first diff by default
140168
@app.route("/")
141169
def index():
@@ -147,6 +175,7 @@ def file_diff(idx):
147175
idx = int(idx)
148176
return render_template('file_diff.html',
149177
idx=idx,
178+
has_magick=util.is_imagemagick_available(),
150179
pairs=DIFF)
151180

152181

webdiff/static/css/style.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,21 @@ table .side-a, table .side-b {
142142
}
143143
.perceptual-diff {
144144
position: absolute;
145+
}
146+
.perceptual-diff.bbox {
145147
border: 2px solid hotpink;
146148
box-shadow: 0 0 5px 0 rgba(50, 50, 50, 0.75);
147149
}
150+
.perceptual-diff.pixels {
151+
opacity: 0.5;
152+
}
148153

149154
.diff-box-disabled {
150155
color: gray;
151156
}
157+
.pdiff-options {
158+
margin-left: 10px;
159+
}
160+
.magick {
161+
font-style: italic;
162+
}

webdiff/static/js/components.jsx

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
*/
55
'use strict';
66

7+
// Perceptual diffing mode
8+
var PDIFF_MODE = {
9+
OFF: 0,
10+
BBOX: 1,
11+
PIXELS: 2
12+
};
13+
714
// Webdiff application root.
815
var makeRoot = function(filePairs, initiallySelectedIndex) {
916
return React.createClass({
@@ -15,7 +22,7 @@ var makeRoot = function(filePairs, initiallySelectedIndex) {
1522
mixins: [ReactRouter.Navigation, ReactRouter.State],
1623
getInitialState: () => ({
1724
imageDiffMode: 'side-by-side',
18-
showPerceptualDiffBox: false
25+
pdiffMode: PDIFF_MODE.OFF
1926
}),
2027
getDefaultProps: function() {
2128
return {filePairs, initiallySelectedIndex};
@@ -31,26 +38,28 @@ var makeRoot = function(filePairs, initiallySelectedIndex) {
3138
changeImageDiffModeHandler: function(mode) {
3239
this.setState({imageDiffMode: mode});
3340
},
34-
changeShowPerceptualDiffBox: function(shouldShow) {
35-
this.setState({showPerceptualDiffBox: shouldShow});
41+
changePdiffMode: function(pdiffMode) {
42+
this.setState({pdiffMode});
3643
},
37-
computePerceptualDiff: function() {
44+
computePerceptualDiffBox: function() {
3845
var fp = this.props.filePairs[this.getIndex()];
39-
computePerceptualDiff('/a/image/' + fp.a, '/b/image/' + fp.b)
40-
.then((diffData) => {
41-
fp.diffData = diffData;
42-
this.forceUpdate(); // tell react about this change
43-
})
44-
.catch(function(reason) {
45-
console.error(reason);
46-
});
46+
if (!fp.is_image_diff || !isSameSizeImagePair(fp)) return;
47+
$.getJSON(`/pdiffbbox/${this.getIndex()}`)
48+
.done(bbox => {
49+
if (!fp.diffData) fp.diffData = {};
50+
fp.diffData.diffBounds = bbox;
51+
this.forceUpdate(); // tell react about this change
52+
}).fail(error => {
53+
console.error(error);
54+
});
4755
},
4856
render: function() {
4957
var idx = this.getIndex(),
5058
filePair = this.props.filePairs[idx];
5159

52-
if (this.state.showPerceptualDiffBox && !filePair.diffData) {
53-
this.computePerceptualDiff();
60+
if (this.state.pdiffMode == PDIFF_MODE.BBOX && !filePair.diffData) {
61+
// XXX this might shoot off unnecessary XHRs--use a Promise!
62+
this.computePerceptualDiffBox();
5463
}
5564

5665
return (
@@ -60,9 +69,9 @@ var makeRoot = function(filePairs, initiallySelectedIndex) {
6069
fileChangeHandler={this.selectIndex} />
6170
<DiffView filePair={filePair}
6271
imageDiffMode={this.state.imageDiffMode}
63-
showPerceptualDiffBox={this.state.showPerceptualDiffBox}
72+
pdiffMode={this.state.pdiffMode}
6473
changeImageDiffModeHandler={this.changeImageDiffModeHandler}
65-
changeShowPerceptualDiffBox={this.changeShowPerceptualDiffBox} />
74+
changePdiffMode={this.changePdiffMode} />
6675
</div>
6776
);
6877
},
@@ -84,7 +93,7 @@ var makeRoot = function(filePairs, initiallySelectedIndex) {
8493
this.setState({imageDiffMode: 'blink'});
8594
} else if (e.keyCode == 80) { // p
8695
this.setState({
87-
showPerceptualDiffBox: !this.state.showPerceptualDiffBox
96+
pdiffMode: (this.state.pdiffMode + 1) % 3
8897
});
8998
}
9099
});
@@ -230,9 +239,9 @@ var DiffView = React.createClass({
230239
propTypes: {
231240
filePair: React.PropTypes.object.isRequired,
232241
imageDiffMode: React.PropTypes.oneOf(IMAGE_DIFF_MODES).isRequired,
233-
showPerceptualDiffBox: React.PropTypes.bool,
242+
pdiffMode: React.PropTypes.number,
234243
changeImageDiffModeHandler: React.PropTypes.func.isRequired,
235-
changeShowPerceptualDiffBox: React.PropTypes.func.isRequired
244+
changePdiffMode: React.PropTypes.func.isRequired
236245
},
237246
render: function() {
238247
if (this.props.filePair.is_image_diff) {
@@ -249,8 +258,11 @@ var NoChanges = React.createClass({
249258
filePair: React.PropTypes.object.isRequired
250259
},
251260
render: function() {
252-
if (this.props.filePair.no_changes) {
253-
return <div className="no-changes">(No Changes)</div>;
261+
var fp = this.props.filePair;
262+
if (fp.no_changes) {
263+
return <div className="no-changes">(File content is identical)</div>;
264+
} else if (fp.is_image_diff && fp.are_same_pixels) {
265+
return <div className="no-changes">Pixels are the same, though file content differs (perhaps the headers are different?)</div>;
254266
} else {
255267
return null;
256268
}

0 commit comments

Comments
 (0)