Skip to content

Commit

Permalink
Merge pull request #96 from danvk/pdiff
Browse files Browse the repository at this point in the history
Show perceptual diffs
  • Loading branch information
danvk committed Apr 11, 2015
2 parents 1efe6bd + 64da4d1 commit e55e89e
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 92 deletions.
3 changes: 1 addition & 2 deletions bower.json
Expand Up @@ -19,7 +19,6 @@
"highlightjs": "~8.0.0",
"underscore": "~1.6.0",
"react": "~0.12.2",
"react-router": "~0.11.6",
"resemblejs": "1.2.1"
"react-router": "~0.11.6"
}
}
4 changes: 4 additions & 0 deletions tests/pair_test.py
Expand Up @@ -38,14 +38,18 @@ def test_pairing_with_move():
testdir = 'testdata/renamedfile'
diff = util.find_diff('%s/left/dir' % testdir, '%s/right/dir' % testdir)
eq_([{'a': 'file.json',
'a_path': 'testdata/renamedfile/left/dir/file.json',
'path': 'file.json',
'b': 'renamed.json',
'b_path': 'testdata/renamedfile/right/dir/renamed.json',
'type': 'move',
'no_changes': True,
'idx': 0},
{'a': 'file.json',
'a_path': 'testdata/renamedfile/left/dir/file.json',
'path': 'file.json',
'b': None,
'b_path': None,
'type': 'delete',
'no_changes': False,
'idx': 1}], diff)
Expand Down
1 change: 0 additions & 1 deletion tests/runner.html
Expand Up @@ -18,7 +18,6 @@
<script src="../webdiff/static/components/react/react.js"></script>
<script src="../webdiff/static/components/react/JSXTransformer.js"></script>
<script src="../webdiff/static/components/react-router/dist/react-router.js"></script>
<script src="../webdiff/static/components/resemblejs/resemble.js"></script>

<script src="../webdiff/static/codediff.js/difflib.js"></script>
<script src="../webdiff/static/codediff.js/codediff.js"></script>
Expand Down
31 changes: 30 additions & 1 deletion webdiff/app.py
Expand Up @@ -14,7 +14,7 @@
import time
import webbrowser

from flask import (Flask, render_template, send_from_directory,
from flask import (Flask, render_template, send_from_directory, send_file,
request, jsonify, Response)

import util
Expand Down Expand Up @@ -136,6 +136,34 @@ def get_image(side, path):
return response


@app.route("/pdiff/<int:idx>")
def get_pdiff(idx):
idx = int(idx)
pair = DIFF[idx]
try:
_, pdiff_image = util.generate_pdiff_image(pair['a_path'], pair['b_path'])
dilated_image = util.generate_dilated_pdiff_image(pdiff_image)
except util.ImageMagickNotAvailableError:
return 'ImageMagick is not available', 501
except util.ImageMagickError as e:
return 'ImageMagick error %s' % e, 501
return send_file(dilated_image)


@app.route("/pdiffbbox/<int:idx>")
def get_pdiff_bbox(idx):
idx = int(idx)
pair = DIFF[idx]
try:
_, pdiff_image = util.generate_pdiff_image(pair['a_path'], pair['b_path'])
bbox = util.get_pdiff_bbox(pdiff_image)
except util.ImageMagickNotAvailableError:
return 'ImageMagick is not available', 501
except util.ImageMagickError as e:
return 'ImageMagick error %s' % e, 501
return jsonify(bbox)


# Show the first diff by default
@app.route("/")
def index():
Expand All @@ -147,6 +175,7 @@ def file_diff(idx):
idx = int(idx)
return render_template('file_diff.html',
idx=idx,
has_magick=util.is_imagemagick_available(),
pairs=DIFF)


Expand Down
11 changes: 11 additions & 0 deletions webdiff/static/css/style.css
Expand Up @@ -142,10 +142,21 @@ table .side-a, table .side-b {
}
.perceptual-diff {
position: absolute;
}
.perceptual-diff.bbox {
border: 2px solid hotpink;
box-shadow: 0 0 5px 0 rgba(50, 50, 50, 0.75);
}
.perceptual-diff.pixels {
opacity: 0.5;
}

.diff-box-disabled {
color: gray;
}
.pdiff-options {
margin-left: 10px;
}
.magick {
font-style: italic;
}
54 changes: 33 additions & 21 deletions webdiff/static/js/components.jsx
Expand Up @@ -4,6 +4,13 @@
*/
'use strict';

// Perceptual diffing mode
var PDIFF_MODE = {
OFF: 0,
BBOX: 1,
PIXELS: 2
};

// Webdiff application root.
var makeRoot = function(filePairs, initiallySelectedIndex) {
return React.createClass({
Expand All @@ -15,7 +22,7 @@ var makeRoot = function(filePairs, initiallySelectedIndex) {
mixins: [ReactRouter.Navigation, ReactRouter.State],
getInitialState: () => ({
imageDiffMode: 'side-by-side',
showPerceptualDiffBox: false
pdiffMode: PDIFF_MODE.OFF
}),
getDefaultProps: function() {
return {filePairs, initiallySelectedIndex};
Expand All @@ -31,26 +38,28 @@ var makeRoot = function(filePairs, initiallySelectedIndex) {
changeImageDiffModeHandler: function(mode) {
this.setState({imageDiffMode: mode});
},
changeShowPerceptualDiffBox: function(shouldShow) {
this.setState({showPerceptualDiffBox: shouldShow});
changePdiffMode: function(pdiffMode) {
this.setState({pdiffMode});
},
computePerceptualDiff: function() {
computePerceptualDiffBox: function() {
var fp = this.props.filePairs[this.getIndex()];
computePerceptualDiff('/a/image/' + fp.a, '/b/image/' + fp.b)
.then((diffData) => {
fp.diffData = diffData;
this.forceUpdate(); // tell react about this change
})
.catch(function(reason) {
console.error(reason);
});
if (!fp.is_image_diff || !isSameSizeImagePair(fp)) return;
$.getJSON(`/pdiffbbox/${this.getIndex()}`)
.done(bbox => {
if (!fp.diffData) fp.diffData = {};
fp.diffData.diffBounds = bbox;
this.forceUpdate(); // tell react about this change
}).fail(error => {
console.error(error);
});
},
render: function() {
var idx = this.getIndex(),
filePair = this.props.filePairs[idx];

if (this.state.showPerceptualDiffBox && !filePair.diffData) {
this.computePerceptualDiff();
if (this.state.pdiffMode == PDIFF_MODE.BBOX && !filePair.diffData) {
// XXX this might shoot off unnecessary XHRs--use a Promise!
this.computePerceptualDiffBox();
}

return (
Expand All @@ -60,9 +69,9 @@ var makeRoot = function(filePairs, initiallySelectedIndex) {
fileChangeHandler={this.selectIndex} />
<DiffView filePair={filePair}
imageDiffMode={this.state.imageDiffMode}
showPerceptualDiffBox={this.state.showPerceptualDiffBox}
pdiffMode={this.state.pdiffMode}
changeImageDiffModeHandler={this.changeImageDiffModeHandler}
changeShowPerceptualDiffBox={this.changeShowPerceptualDiffBox} />
changePdiffMode={this.changePdiffMode} />
</div>
);
},
Expand All @@ -84,7 +93,7 @@ var makeRoot = function(filePairs, initiallySelectedIndex) {
this.setState({imageDiffMode: 'blink'});
} else if (e.keyCode == 80) { // p
this.setState({
showPerceptualDiffBox: !this.state.showPerceptualDiffBox
pdiffMode: (this.state.pdiffMode + 1) % 3
});
}
});
Expand Down Expand Up @@ -230,9 +239,9 @@ var DiffView = React.createClass({
propTypes: {
filePair: React.PropTypes.object.isRequired,
imageDiffMode: React.PropTypes.oneOf(IMAGE_DIFF_MODES).isRequired,
showPerceptualDiffBox: React.PropTypes.bool,
pdiffMode: React.PropTypes.number,
changeImageDiffModeHandler: React.PropTypes.func.isRequired,
changeShowPerceptualDiffBox: React.PropTypes.func.isRequired
changePdiffMode: React.PropTypes.func.isRequired
},
render: function() {
if (this.props.filePair.is_image_diff) {
Expand All @@ -249,8 +258,11 @@ var NoChanges = React.createClass({
filePair: React.PropTypes.object.isRequired
},
render: function() {
if (this.props.filePair.no_changes) {
return <div className="no-changes">(No Changes)</div>;
var fp = this.props.filePair;
if (fp.no_changes) {
return <div className="no-changes">(File content is identical)</div>;
} else if (fp.is_image_diff && fp.are_same_pixels) {
return <div className="no-changes">Pixels are the same, though file content differs (perhaps the headers are different?)</div>;
} else {
return null;
}
Expand Down

0 comments on commit e55e89e

Please sign in to comment.