gb.diffRenamedFile = File was renamed from {0} | gb.diffRenamedFile = File was renamed from {0} | ||||
gb.diffCopiedFile = File was copied from {0} | gb.diffCopiedFile = File was copied from {0} | ||||
gb.diffTruncated = Diff truncated after the above file | gb.diffTruncated = Diff truncated after the above file | ||||
gb.opacityAdjust = Adjust opacity | |||||
gb.blinkComparator = Blink comparator | |||||
gb.imgdiffSubtract = Subtract (black = identical) |
gb.diffRenamedFile = Datei umbenannt von {0} | gb.diffRenamedFile = Datei umbenannt von {0} | ||||
gb.diffCopiedFile = Datei kopiert von {0} | gb.diffCopiedFile = Datei kopiert von {0} | ||||
gb.diffTruncated = Diff nach obiger Datei abgeschnitten | gb.diffTruncated = Diff nach obiger Datei abgeschnitten | ||||
gb.opacityAdjust = Transparenz | |||||
gb.blinkComparator = Blinkkomparator | |||||
gb.imgdiffSubtract = Pixeldifferenz (schwarz = identisch) |
gb.diffRenamedFile = Fichier renomm\u00e9 de {0} | gb.diffRenamedFile = Fichier renomm\u00e9 de {0} | ||||
gb.diffCopiedFile = Fichier copi\u00e9 de {0} | gb.diffCopiedFile = Fichier copi\u00e9 de {0} | ||||
gb.diffTruncated = Affichage de diff\u00e9rences supprim\u00e9e apr\u00e8s le fichier ci-dessus | gb.diffTruncated = Affichage de diff\u00e9rences supprim\u00e9e apr\u00e8s le fichier ci-dessus | ||||
gb.opacityAdjust = ajuster l'opacit\u00e9 | |||||
gb.blinkComparator = Comparateur \u00e0 clignotement | |||||
gb.imgdiffSubtract = Diff\u00e9rence (noir = identique) |
if (StringUtils.isEmpty(baseObjectId)) { | if (StringUtils.isEmpty(baseObjectId)) { | ||||
// use first parent | // use first parent | ||||
RevCommit parent = commit.getParentCount() == 0 ? null : commit.getParent(0); | RevCommit parent = commit.getParentCount() == 0 ? null : commit.getParent(0); | ||||
ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName, | |||||
ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName, | |||||
parent.getName(), commit.getName(), imageExtensions); | parent.getName(), commit.getName(), imageExtensions); | ||||
diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML, handler).content; | diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML, handler).content; | ||||
if (handler.getImgDiffCount() > 0) { | if (handler.getImgDiffCount() > 0) { | ||||
} else { | } else { | ||||
// base commit specified | // base commit specified | ||||
RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId); | RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId); | ||||
ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName, | |||||
ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName, | |||||
baseCommit.getName(), commit.getName(), imageExtensions); | baseCommit.getName(), commit.getName(), imageExtensions); | ||||
diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML, handler).content; | diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML, handler).content; | ||||
if (handler.getImgDiffCount() > 0) { | if (handler.getImgDiffCount() > 0) { |
add(new CommitHeaderPanel("commitHeader", repositoryName, commit)); | add(new CommitHeaderPanel("commitHeader", repositoryName, commit)); | ||||
final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions); | final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions); | ||||
final ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName, | |||||
final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName, | |||||
parents.isEmpty() ? null : parents.get(0), commit.getName(), imageExtensions); | parents.isEmpty() ? null : parents.get(0), commit.getName(), imageExtensions); | ||||
final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, DiffOutputType.HTML, handler); | final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, DiffOutputType.HTML, handler); | ||||
if (handler.getImgDiffCount() > 0) { | if (handler.getImgDiffCount() > 0) { |
toCommitId.setObject(endId); | toCommitId.setObject(endId); | ||||
final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions); | final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions); | ||||
final ImageDiffHandler handler = new ImageDiffHandler(getContextUrl(), repositoryName, | |||||
final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName, | |||||
fromCommit.getName(), toCommit.getName(), imageExtensions); | fromCommit.getName(), toCommit.getName(), imageExtensions); | ||||
final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, DiffOutputType.HTML, handler); | final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, DiffOutputType.HTML, handler); |
import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||
import java.util.List; | import java.util.List; | ||||
import org.apache.wicket.protocol.http.WebApplication; | |||||
import org.apache.wicket.protocol.http.WicketURLEncoder; | import org.apache.wicket.protocol.http.WicketURLEncoder; | ||||
import org.eclipse.jgit.diff.DiffEntry; | import org.eclipse.jgit.diff.DiffEntry; | ||||
import org.eclipse.jgit.diff.DiffEntry.Side; | import org.eclipse.jgit.diff.DiffEntry.Side; | ||||
private final String oldCommitId; | private final String oldCommitId; | ||||
private final String newCommitId; | private final String newCommitId; | ||||
private final String repositoryName; | private final String repositoryName; | ||||
private final String baseUrl; | |||||
private final BasePage page; | |||||
private final List<String> imageExtensions; | private final List<String> imageExtensions; | ||||
private int imgDiffCount = 0; | private int imgDiffCount = 0; | ||||
public ImageDiffHandler(final String baseUrl, final String repositoryName, final String oldCommitId, | |||||
final String newCommitId, final List<String> imageExtensions) { | |||||
this.baseUrl = baseUrl; | |||||
public ImageDiffHandler(final BasePage page, final String repositoryName, final String oldCommitId, final String newCommitId, | |||||
final List<String> imageExtensions) { | |||||
this.page = page; | |||||
this.repositoryName = repositoryName; | this.repositoryName = repositoryName; | ||||
this.oldCommitId = oldCommitId; | this.oldCommitId = oldCommitId; | ||||
this.newCommitId = newCommitId; | this.newCommitId = newCommitId; | ||||
old.appendElement("img").attr("class", "imgdiff-old").attr("id", id).attr("style", "max-width:640px;").attr("src", oldUrl); | old.appendElement("img").attr("class", "imgdiff-old").attr("id", id).attr("style", "max-width:640px;").attr("src", oldUrl); | ||||
container.appendElement("img").attr("class", "imgdiff").attr("style", "max-width:640px;").attr("src", newUrl); | container.appendElement("img").attr("class", "imgdiff").attr("style", "max-width:640px;").attr("src", newUrl); | ||||
wrapper.appendElement("br"); | wrapper.appendElement("br"); | ||||
wrapper.appendElement("div").attr("class", "imgdiff-opa-container").appendElement("div").attr("class", "imgdiff-opa-slider"); | |||||
Element controls = wrapper.appendElement("div"); | |||||
// Opacity slider | |||||
controls.appendElement("div").attr("class", "imgdiff-opa-container").appendElement("a").attr("class", "imgdiff-opa-slider") | |||||
.attr("href", "#").attr("title", page.getString("gb.opacityAdjust")); | |||||
// Blink comparator: find Pluto! | |||||
controls.appendElement("a").attr("class", "imgdiff-link imgdiff-blink").attr("href", "#") | |||||
.attr("title", page.getString("gb.blinkComparator")) | |||||
.appendElement("img").attr("src", getStaticResourceUrl("blink32.png")).attr("width", "20"); | |||||
// Pixel subtraction, initially not displayed, will be shown by imgdiff.js depending on feature test. | |||||
// (Uses CSS mix-blend-mode, which isn't supported on all browsers yet). | |||||
controls.appendElement("a").attr("class", "imgdiff-link imgdiff-subtract").attr("href", "#") | |||||
.attr("title", page.getString("gb.imgdiffSubtract")).attr("style", "display:none;") | |||||
.appendElement("img").attr("src", getStaticResourceUrl("sub32.png")).attr("width", "20"); | |||||
return builder.toString(); | return builder.toString(); | ||||
} | } | ||||
break; | break; | ||||
if (ext.equalsIgnoreCase(extension)) { | if (ext.equalsIgnoreCase(extension)) { | ||||
String commitId = Side.NEW.equals(side) ? newCommitId : oldCommitId; | String commitId = Side.NEW.equals(side) ? newCommitId : oldCommitId; | ||||
if (commitId != null) { | if (commitId != null) { | ||||
return RawServlet.asLink(baseUrl, urlencode(repositoryName), commitId, urlencode(path)); | |||||
return RawServlet.asLink(page.getContextUrl(), urlencode(repositoryName), commitId, urlencode(path)); | |||||
} else { | } else { | ||||
return null; | return null; | ||||
} | } | ||||
return null; | return null; | ||||
} | } | ||||
/** | |||||
* Returns a URL that will fetch the designated static resource from within GitBlit. | |||||
*/ | |||||
protected String getStaticResourceUrl(String contextRelativePath) { | |||||
return WebApplication.get().getRequestCycleProcessor().getRequestCodingStrategy().rewriteStaticRelativeUrl(contextRelativePath); | |||||
} | |||||
/** | /** | ||||
* Encode a URL component of a {@link RawServlet} URL in the special way that the servlet expects it. Note that | * Encode a URL component of a {@link RawServlet} URL in the special way that the servlet expects it. Note that | ||||
* the %-encoding used does not encode '&' or '<'. Slashes are not encoded in the result. | * the %-encoding used does not encode '&' or '<'. Slashes are not encoded in the result. |
* | * | ||||
* The styling of the slider is to be done in CSS. Currently recognized options: | * The styling of the slider is to be done in CSS. Currently recognized options: | ||||
* - initial: <float> clipped to [0..1], default 0 | * - initial: <float> clipped to [0..1], default 0 | ||||
* - handleClass: <string> to assign to the handle div element created. | |||||
* - handleClass: <string> to assign to the handle span element created. | |||||
* If no handleClass is specified, a very plain default style is assigned. | * If no handleClass is specified, a very plain default style is assigned. | ||||
*/ | */ | ||||
function rangeSlider(elem, options) { | function rangeSlider(elem, options) { | ||||
options.initial = Math.min(1.0, Math.max(0, options.initial)); | options.initial = Math.min(1.0, Math.max(0, options.initial)); | ||||
var $elem = $(elem); | var $elem = $(elem); | ||||
var $handle = $('<div></div>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' }); | |||||
var $handle = $('<span></span>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' }); | |||||
var $root = $(document.documentElement); | var $root = $(document.documentElement); | ||||
var $doc = $(document); | var $doc = $(document); | ||||
var lastRatio = options.initial; | var lastRatio = options.initial; | ||||
var opacityAccess = rangeSlider($opacitySlider, {handleClass: 'imgdiff-opa-handle'}); | var opacityAccess = rangeSlider($opacitySlider, {handleClass: 'imgdiff-opa-handle'}); | ||||
var $img = $('#' + this.id.substr(this.id.indexOf('-')+1)); // Here we change opacity | var $img = $('#' + this.id.substr(this.id.indexOf('-')+1)); // Here we change opacity | ||||
var $div = $img.parent(); // This controls visibility: here we change width. | var $div = $img.parent(); // This controls visibility: here we change width. | ||||
var blinking = false; | |||||
$overlaySlider.on('slider:pos', function(e, data) { | $overlaySlider.on('slider:pos', function(e, data) { | ||||
var pos = $(data.handle).offset().left; | var pos = $(data.handle).offset().left; | ||||
} | } | ||||
}); | }); | ||||
$opacitySlider.on('slider:pos', function(e, data) { | $opacitySlider.on('slider:pos', function(e, data) { | ||||
if ($div.width() <= 0) overlayAccess.moveAuto(1.0); // Make old image visible in a nice way | |||||
if ($div.width() <= 0 && !blinking) overlayAccess.moveAuto(1.0); // Make old image visible in a nice way | |||||
$img.css('opacity', 1.0 - data.ratio); | $img.css('opacity', 1.0 - data.ratio); | ||||
}); | }); | ||||
$opacitySlider.css('cursor', 'pointer'); | |||||
$opacitySlider.on('mousedown', function(e) { | |||||
$opacitySlider.on('click', function(e) { | |||||
var newRatio = (e.pageX - $opacitySlider.offset().left) / $opacitySlider.innerWidth(); | var newRatio = (e.pageX - $opacitySlider.offset().left) / $opacitySlider.innerWidth(); | ||||
var oldRatio = opacityAccess.getRatio(); | var oldRatio = opacityAccess.getRatio(); | ||||
if (newRatio !== oldRatio) { | if (newRatio !== oldRatio) { | ||||
e.preventDefault(); | e.preventDefault(); | ||||
}); | }); | ||||
// Blinking before and after images is a good way for the human eye to catch differences. | |||||
var $blinker = $this.find('.imgdiff-blink'); | |||||
var initialOpacity = null; | |||||
$blinker.on('click', function(e) { | |||||
if (blinking) { | |||||
window.clearTimeout(blinking); | |||||
$blinker.children('img').first().css('border', '1px solid transparent'); | |||||
opacityAccess.setRatio(initialOpacity); | |||||
blinking = null; | |||||
} else { | |||||
$blinker.children('img').first().css('border', '1px solid #AAA'); | |||||
initialOpacity = opacityAccess.getRatio(); | |||||
var currentOpacity = 1.0; | |||||
function blink() { | |||||
opacityAccess.setRatio(currentOpacity); | |||||
currentOpacity = 1.0 - currentOpacity; | |||||
// Keep frequeny below 2Hz (i.e., delay above 500ms) | |||||
blinking = window.setTimeout(blink, 600); | |||||
} | |||||
if ($div.width() <= 0) { | |||||
overlayAccess.moveRatio(1.0, 500, blink); | |||||
} else { | |||||
blink(); | |||||
} | |||||
} | |||||
e.preventDefault(); | |||||
}); | |||||
// Subtracting before and after images is another good way to detect differences. Result will be | |||||
// black where identical. | |||||
if (typeof $img[0].style.mixBlendMode != 'undefined') { | |||||
// Feature test: does the browser support the mix-blend-mode CSS property from the Compositing | |||||
// and Blending Level 1 spec (http://dev.w3.org/fxtf/compositing-1/#mix-blend-mode )? | |||||
// As of 2014-11, only Firefox >= 32 and Safari >= 7.1 support this. Other browsers will have to | |||||
// make do with the blink comparator only. | |||||
var $sub = $this.find('.imgdiff-subtract'); | |||||
$sub.css('display', 'inline-block'); | |||||
$sub.on('click', function (e) { | |||||
var curr = $img.css('mix-blend-mode'); | |||||
if (curr != 'difference') { | |||||
curr = 'difference'; | |||||
$sub.children('img').first().css('border', '1px solid #AAA'); | |||||
if ($div.width() <= 0) overlayAccess.moveRatio(1.0, 500); | |||||
opacityAccess.setRatio(0); | |||||
} else { | |||||
curr = 'normal'; | |||||
$sub.children('img').first().css('border', '1px solid transparent'); | |||||
} | |||||
$img.css('mix-blend-mode', curr); | |||||
e.preventDefault(); | |||||
}); | |||||
} | |||||
}); | }); | ||||
} | } | ||||
user-select: none; | user-select: none; | ||||
border: 1px solid #F00; | border: 1px solid #F00; | ||||
} | } | ||||
.imgdiff-opa-container { | .imgdiff-opa-container { | ||||
display: inline-block; | |||||
width: 200px; | width: 200px; | ||||
height: 4px; | height: 4px; | ||||
margin: 12px 35px; | |||||
margin: 12px 35px 6px 35px; | |||||
padding: 0; | padding: 0; | ||||
position: relative; | position: relative; | ||||
border: 1px solid #888; | border: 1px solid #888; | ||||
} | } | ||||
.imgdiff-opa-handle { | .imgdiff-opa-handle { | ||||
display: inline-block; | |||||
width: 10px; | width: 10px; | ||||
height: 10px; | height: 10px; | ||||
position: absolute; | position: absolute; | ||||
} | } | ||||
.imgdiff-ovr-handle { | .imgdiff-ovr-handle { | ||||
display: inline-block; | |||||
width : 1px; | width : 1px; | ||||
height: 100%; | height: 100%; | ||||
top: 0px; | top: 0px; | ||||
/* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */ | /* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */ | ||||
} | } | ||||
.imgdiff-link { | |||||
margin: 0px 4px; | |||||
text-decoration: none; | |||||
border: none; | |||||
} | |||||
.imgdiff-link > img { | |||||
border: 1px solid transparent; /* Avoid jumping when we change the border */ | |||||
width: 20px; | |||||
height: 20px; | |||||
margin-bottom: 10px; | |||||
} | |||||
/* End image diffs */ | /* End image diffs */ | ||||
td.changeType { | td.changeType { |