From 31559418ba664bf66587660f325597fa4bbd09f8 Mon Sep 17 00:00:00 2001 From: nazoking Date: Sat, 9 May 2015 01:50:51 +0900 Subject: [PATCH] add image diff --- .../scala/gitbucket/core/util/JGitUtil.scala | 18 +- .../gitbucket/core/helper/diff.scala.html | 23 +- .../webapp/assets/common/css/gitbucket.css | 143 ++++++++++++ .../webapp/assets/common/images/checker.png | Bin 0 -> 949 bytes src/main/webapp/assets/common/js/gitbucket.js | 208 ++++++++++++++++++ 5 files changed, 384 insertions(+), 8 deletions(-) create mode 100644 src/main/webapp/assets/common/images/checker.png diff --git a/src/main/scala/gitbucket/core/util/JGitUtil.scala b/src/main/scala/gitbucket/core/util/JGitUtil.scala index 69b8c6f30..096db1e06 100644 --- a/src/main/scala/gitbucket/core/util/JGitUtil.scala +++ b/src/main/scala/gitbucket/core/util/JGitUtil.scala @@ -100,7 +100,8 @@ object JGitUtil { def isDifferentFromAuthor: Boolean = authorName != committerName || authorEmailAddress != committerEmailAddress } - case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String]) + case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String], + oldIsImage: Boolean, newIsImage: Boolean, oldObjectId: Option[String], newObjectId: Option[String]) /** * The file content data for the file content view of the repository viewer. @@ -459,11 +460,13 @@ object JGitUtil { treeWalk.addTree(revCommit.getTree) val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]() while(treeWalk.next){ + val newIsImage = FileUtil.isImage(treeWalk.getPathString) buffer.append((if(!fetchContent){ - DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None, None) + DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None, None, false, newIsImage, None, Option(treeWalk.getObjectId(0)).map(_.name)) } else { DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None, - JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray)) + JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray), + false, newIsImage, None, Option(treeWalk.getObjectId(0)).map(_.name)) })) } (buffer.toList, None) @@ -483,12 +486,15 @@ object JGitUtil { import scala.collection.JavaConverters._ git.getRepository.getConfig.setString("diff", null, "renames", "copies") git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff => - if(!fetchContent || FileUtil.isImage(diff.getOldPath) || FileUtil.isImage(diff.getNewPath)){ - DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, None, None) + val oldIsImage = FileUtil.isImage(diff.getOldPath) + val newIsImage = FileUtil.isImage(diff.getNewPath) + if(!fetchContent || oldIsImage || newIsImage){ + DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, None, None, oldIsImage, newIsImage, Option(diff.getOldId).map(_.name), Option(diff.getNewId).map(_.name)) } else { DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, JGitUtil.getContentFromId(git, diff.getOldId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray), - JGitUtil.getContentFromId(git, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray)) + JGitUtil.getContentFromId(git, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray), + oldIsImage, newIsImage, Option(diff.getOldId).map(_.name), Option(diff.getNewId).map(_.name)) } }.toList } diff --git a/src/main/twirl/gitbucket/core/helper/diff.scala.html b/src/main/twirl/gitbucket/core/helper/diff.scala.html index 441884ef3..42b87c0e5 100644 --- a/src/main/twirl/gitbucket/core/helper/diff.scala.html +++ b/src/main/twirl/gitbucket/core/helper/diff.scala.html @@ -90,13 +90,32 @@ - @if(diff.newContent != None || diff.oldContent != None){ + @if(diff.oldObjectId == diff.newObjectId){ +
File renamed without changes
+ } else { @if(diff.newContent != None || diff.oldContent != None){
+ } else { @if(diff.newIsImage || diff.oldIsImage){ +
+ @if(oldCommitId.isDefined && diff.oldIsImage){ +
+ } else { + @if(diff.changeType != ChangeType.ADD){ + Not supported + } + } + @if(newCommitId.isDefined && diff.newIsImage){ +
+ } else { + @if(diff.changeType != ChangeType.DELETE){ + Not supported + } + } +
} else { Not supported - } + } } } diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css index 925897b7f..31ac5de64 100644 --- a/src/main/webapp/assets/common/css/gitbucket.css +++ b/src/main/webapp/assets/common/css/gitbucket.css @@ -1151,6 +1151,149 @@ table.diff tbody tr.not-diff:hover td{ white-space: nowrap; letter-spacing: 0px; } +.diff-same{ + background: #DDD; + color: #BBB; + font-size: 16px; + padding: 20px; + font-weight: bold; + text-align: center; +} +/* ------- for imageDiff */ +.diff-image-frame{ + display: table-cell; + *float: left; /* for ie7 */ + vertical-align: middle; + padding: 20px; + background-color: #eee; +} +.diff-image-frame.diff-old{ + padding-right: 2px; +} +.diff-image-frame.diff-new{ + padding-left: 2px; +} +.diff-image-frame .diff-meta{ + margin-top: 12px; + color: #999; + font-family: Helvetica,arial,freesans,clean,sans-serif; + font-size: 12px; +} +.diff-image-frame.diff-old .diff-meta .diff{ + color: #bd2c00; +} +.diff-image-frame.diff-new .diff-meta .diff{ + color: #55a532; +} +.diff-image-frame img{ + max-height: 410px; + max-width: 410px; + background: url(../images/checker.png); +} +.diff-image-render.diff2up{ + width:100%; + text-align: center; + display: table; +} +.diff-image-frame.diff-new img{ + border: 1px solid #55a532; +} +.diff-image-frame.diff-old img{ + border: 1px solid #bd2c00; +} +.diff-image-stack{ + position: relative; + background: #EEE; + padding-top: 20px; +} +.diff-image-stack .diff-old, +.diff-image-stack .diff-new{ + position:absolute; + overflow: hidden; + margin:0 20px; +} +.diff-image-stack img { + max-width: none; + background: url(../images/checker.png); +} +.diff-image-stack .diff-new{ + border: 1px solid #55a532; + background: #EEE; +} +.diff-image-stack .diff-old{ + border: 1px solid #bd2c00; +} +.diff-swipe-handle{ + position:absolute; + margin-left: 325px; + left: 100px; +} +.diff-silde-bar{ + width: 200px; + position: absolute; + left: 325px; + margin: 6px 0 0 7px; + box-sizing: border-box; + border: 1px solid gray; + height: 8px; +} +.image-diff-tools{ + text-align: center; + padding: 4px; + background: #f7f7f7; +} +.image-diff-tools{ + font-family: 'Helvetica Neue', Helvetica, arial, freesans, clean, sans-serif; + font-size: 12px; + background: #f7f7f7; + margin: 0; + text-align: center; +} +.image-diff-tools li{ + background: none; + display: inline; + cursor: pointer; + border-right: 1px solid #ccc; + padding: 0 5px; + position: relative; + color: #666; +} +.image-diff-tools li:last-child{ + border-right: none; +} +.image-diff-tools li.active { + font-weight: bold; +} +.no-canvas .need-canvas{ + display: none; +} +.diff-image-stack.swipe .diff-new{ + border-right: 1px solid #888; +} +.diff-image-stack.swipe .diff-swipe-handle{ + margin-left: 15px; + left: 410px; +} +.diff-image-stack.swipe .diff-silde-bar{ + display: none; +} + +.diff-image-stack.onion .diff-silde-bar{ + background: -ms-linear-gradient(left, #bd2c00 0%,#55a532 100%); /* IE10+ */ + background: linear-gradient(to right, #bd2c00 0%,#55a532 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#bd2c00', endColorstr='#55a532',GradientType=1 ); /* IE6-9 */ +} + +.diff-image-stack.blink .diff-silde-bar{ + border-style: dotted; + background-image: linear-gradient(to right, #bd2c00, #bd2c00 50%, #55a532 50%, #55a532 100%); + background-size: 2px 2px; +} + +.diff-image-stack.difference { + padding-bottom: 18px; + text-align: center; +} /****************************************************************************/ /* Repository Settings */ /****************************************************************************/ diff --git a/src/main/webapp/assets/common/images/checker.png b/src/main/webapp/assets/common/images/checker.png new file mode 100644 index 0000000000000000000000000000000000000000..07e211556d8ee963c7839d5f437508fcb988ab34 GIT binary patch literal 949 zcmaJ=&yUhT6s~TvF`I1UCVJ3GH!&XUZ%gTfx`DR1O}2ypi5$1I12k+qwH>5zTlM1A zKf{AZFUEK_;Vts;yIs27Cw8B7@sP~C6fyMx4-jx7Z-hF?3+AuBqZ-2;uwk!H`tCXOam0HChR>sf zvKgj!nkSw=K?DrYD2xsE_m^)h2m^zCsk)+@m}nfHEmG87w0izx;_Cr>`W)19NChH9 zJdj71aRzgPUF$-6F5ChO)(|o=*z2VFZX1|5ML^|6&KD~ps7Sn|JkvDoDUd};6hv8& zWKPncB12IGn~$Z@(qITXR&x`JS_V5NB!Pl3o6Y!{!sB!#NV={U8nVn$1eeWY;^kbN zoop2>l=*3xkPye9Q1k|PN(`1}dR;=4>}2C?b4~QZgxpI6i5H8Kwm{eYKQxMV=!|sG zO}_sW&U*6%2_2N-Y3kF)4NrCJ8v9qHw=Oz8#c?;b)YuI^ zis`hcbgVJ_@RL4?!}fX4UaeNk<+A$g<6G*v=UDY#{$p_Q?)G8146bf{e)yOvT`+cu Txq3vtmWzkv*e&Z* + */ +function onLoadedDiffImages(img){ + setNatural(img); + img = $(img); + img.show(); + var tb = img.parents(".diff-image-render"); + // Find images. If the image has not loaded yet, value is undefined. + var old = tb.find(".diff-old img.diff-image:visible")[0]; + var neo = tb.find(".diff-new img.diff-image:visible")[0]; + imageDiff.appendImageMeta(tb, old, neo); + if(old && neo){ + imageDiff.createToolSelector(old, neo).appendTo(tb.parent()); + } +} +var imageDiff ={ + /** append image meta div after image nodes. + * @param tb
+ * @param old ||undefined + * @param neo ||undefined + */ + appendImageMeta:function(tb, old, neo){ + old = old || {}; + neo = neo || {}; + tb.find(".diff-meta").remove(); + // before loaded, image is not visible. + tb.find("img.diff-image:visible").each(function(){ + var div = $('

W: | W:

'); + div.find('.w').text(this.naturalWidth+"px").toggleClass("diff", old.naturalWidth != neo.naturalWidth); + div.find('.h').text(this.naturalHeight+"px").toggleClass("diff", old.naturalHeight != neo.naturalHeight); + div.appendTo(this.parentNode); + }); + }, + /** check this browser can use canvas tag. + */ + hasCanvasSupport:function(){ + if(!this.hasCanvasSupport.hasOwnProperty('resultCache')){ + this.hasCanvasSupport.resultCache = (typeof $('')[0].getContext)=='function'; + } + return this.hasCanvasSupport.resultCache; + }, + /** create toolbar + * @param old + * @param neo + * @return jQuery(
    ) + */ + createToolSelector:function(old, neo){ + var self = this; + return $('
      '+ + '
    • 2-up
    • '+ + '
    • Swipe
    • '+ + '
    • Onion Skin
    • '+ + '
    • Difference
    • '+ + '
    • Blink
    • '+ + '
    ') + .toggleClass('no-canvas', !this.hasCanvasSupport()) + .on('click', 'li', function(e){ + var td = $(this).parents("td"); + $(e.delegateTarget).find('li').each(function(){ $(this).toggleClass('active',this == e.target); }); + var mode = $(e.target).data('mode'); + td.find(".diff-image-render").hide(); + // create div if not created yet + if(td.find(".diff-image-render."+mode).show().length===0){ + self[mode](old, neo).insertBefore(e.delegateTarget).addClass("diff-image-render"); + } + return false; + }); + }, + /** (private) calc size from images and css (const) + * @param old + * @param neo + */ + calcSizes:function(old, neo){ + var maxWidth = 869 - 20 - 20 - 4; // set by css + var h = Math.min(Math.max(old.naturalHeight, neo.naturalHeight),maxWidth); + var w = Math.min(Math.max(old.naturalWidth, neo.naturalWidth),maxWidth); + var oldRate = Math.min(h/old.naturalHeight, w/old.naturalWidth); + var neoRate = Math.min(h/neo.naturalHeight, w/neo.naturalWidth); + var neoW = neo.naturalWidth*neoRate; + var neoH = neo.naturalHeight*neoRate; + var oldW = old.naturalWidth*oldRate; + var oldH = old.naturalHeight*oldRate; + var paddingLeft = (maxWidth/2)-Math.max(neoW,oldW)/2; + return { + height:Math.max(oldH, neoH), + width:w, + padding:paddingLeft, + paddingTop:20, // set by css + barWidth:200, // set by css + old:{ rate:oldRate, width:oldW, height:oldH }, + neo:{ rate:neoRate, width:neoW, height:neoH } + }; + }, + /** (private) create div + * @param old + * @param neo + */ + stack:function(old, neo){ + var size = this.calcSizes(old, neo); + var diffNew = $('
    ') + .append($("").attr('src',neo.src).css({width:size.neo.width, height:size.neo.height})); + var diffOld = $('
    ') + .append($("").attr('src',old.src).css({width:size.old.width, height:size.old.height})); + var handle = $('') + .css({marginTop:size.height-5}); + var bar = $('
    ').css({top:size.height+size.paddingTop}); + var div = $('
    ') + .css({height:size.height+size.paddingTop, paddingLeft:size.padding}) + .append(diffOld, diffNew, bar, handle); + return { + neo:diffNew, + old:diffOld, + size:size, + handle:handle, + bar:bar, + div:div, + /* add event listener 'on mousemove' */ + onMoveHandleOnBar:function(callback){ + div.on('mousemove',function(e){ + var x = Math.max(Math.min((e.pageX - bar.offset().left), size.barWidth), 0); + handle.css({left:x}); + callback(x, e); + }); + } + }; + }, + /** create swipe box + * @param old + * @param neo + * @return jQuery(
    ) + */ + swipe:function(old, neo){ + var stack = this.stack(old, neo); + function setX(x){ + stack.neo.css({width:x}); + stack.handle.css({left:x+stack.size.padding}); + } + setX(stack.size.neo.width/2); + stack.div.on('mousemove',function(e){ + setX(Math.max(Math.min(e.pageX - stack.neo.offset().left, stack.size.neo.width),0)); + }); + return stack.div.addClass('swipe'); + }, + /** create blink box + * @param old + * @param neo + * @return jQuery(