|
|
var Backbone = require('backbone'); |
|
|
var GRAPHICS = require('../util/constants').GRAPHICS; |
|
|
|
|
|
var VisBase = require('../visuals/visBase').VisBase; |
|
|
var TreeCompare = require('../graph/treeCompare'); |
|
|
|
|
|
var randomHueString = function() { |
|
|
var hue = Math.random(); |
|
|
var str = 'hsb(' + String(hue) + ',0.6,1)'; |
|
|
return str; |
|
|
}; |
|
|
|
|
|
var VisBranch = VisBase.extend({ |
|
|
defaults: { |
|
|
pos: null, |
|
|
text: null, |
|
|
rect: null, |
|
|
arrow: null, |
|
|
isHead: false, |
|
|
flip: 1, |
|
|
|
|
|
fill: GRAPHICS.rectFill, |
|
|
stroke: GRAPHICS.rectStroke, |
|
|
'stroke-width': GRAPHICS.rectStrokeWidth, |
|
|
|
|
|
offsetX: GRAPHICS.nodeRadius * 4.75, |
|
|
offsetY: 0, |
|
|
arrowHeight: 14, |
|
|
arrowInnerSkew: 0, |
|
|
arrowEdgeHeight: 6, |
|
|
arrowLength: 14, |
|
|
arrowOffsetFromCircleX: 10, |
|
|
|
|
|
vPad: 5, |
|
|
hPad: 5, |
|
|
|
|
|
animationSpeed: GRAPHICS.defaultAnimationTime, |
|
|
animationEasing: GRAPHICS.defaultEasing |
|
|
}, |
|
|
|
|
|
validateAtInit: function() { |
|
|
if (!this.get('branch')) { |
|
|
throw new Error('need a branch!'); |
|
|
} |
|
|
}, |
|
|
|
|
|
getID: function() { |
|
|
return this.get('branch').get('id'); |
|
|
}, |
|
|
|
|
|
initialize: function() { |
|
|
this.validateAtInit(); |
|
|
|
|
|
|
|
|
this.gitVisuals = this.get('gitVisuals'); |
|
|
this.gitEngine = this.get('gitEngine'); |
|
|
if (!this.gitEngine) { |
|
|
throw new Error('asd wtf'); |
|
|
} |
|
|
|
|
|
this.get('branch').set('visBranch', this); |
|
|
var id = this.get('branch').get('id'); |
|
|
|
|
|
if (id == 'HEAD') { |
|
|
|
|
|
this.set('isHead', true); |
|
|
this.set('flip', -1); |
|
|
this.refreshOffset(); |
|
|
|
|
|
this.set('fill', GRAPHICS.headRectFill); |
|
|
} else if (id !== 'main') { |
|
|
|
|
|
this.set('fill', randomHueString()); |
|
|
} |
|
|
}, |
|
|
|
|
|
getCommitPosition: function() { |
|
|
var commit = this.gitEngine.getCommitFromRef(this.get('branch')); |
|
|
var visNode = commit.get('visNode'); |
|
|
|
|
|
this.set('flip', this.getFlipValue(commit, visNode)); |
|
|
this.refreshOffset(); |
|
|
return visNode.getScreenCoords(); |
|
|
}, |
|
|
|
|
|
getDashArray: function() { |
|
|
if (!this.get('gitVisuals').getIsGoalVis()) { |
|
|
return ''; |
|
|
} |
|
|
return (this.getIsLevelBranchCompared()) ? '' : '--'; |
|
|
}, |
|
|
|
|
|
getIsGoalAndNotCompared: function() { |
|
|
if (!this.get('gitVisuals').getIsGoalVis()) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
return !this.getIsLevelBranchCompared(); |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getIsLevelBranchCompared: function() { |
|
|
if (this.getIsMain()) { |
|
|
return true; |
|
|
} |
|
|
|
|
|
var levelBlob = this.get('gitVisuals').getLevelBlob(); |
|
|
return !TreeCompare.onlyMainCompared(levelBlob); |
|
|
}, |
|
|
|
|
|
getIsMain: function() { |
|
|
return this.get('branch').get('id') == 'main'; |
|
|
}, |
|
|
|
|
|
getFlipValue: function(commit, visNode) { |
|
|
var threshold = this.get('gitVisuals').getFlipPos(); |
|
|
var overThreshold = (visNode.get('pos').x > threshold); |
|
|
|
|
|
|
|
|
if (commit.get('id') === 'C0') { |
|
|
return -1; |
|
|
} |
|
|
if (!this.get('isHead')) { |
|
|
return (overThreshold) ? -1 : 1; |
|
|
} |
|
|
|
|
|
|
|
|
if (overThreshold) { |
|
|
|
|
|
|
|
|
return (this.isBranchStackEmpty()) ? -1 : 1; |
|
|
} else { |
|
|
return (this.isBranchStackEmpty()) ? 1 : -1; |
|
|
} |
|
|
}, |
|
|
|
|
|
refreshOffset: function() { |
|
|
var baseOffsetX = GRAPHICS.nodeRadius * 4.75; |
|
|
var offsetY = 33; |
|
|
var deltaX = 10; |
|
|
if (this.get('flip') === 1) { |
|
|
this.set('offsetY', -offsetY); |
|
|
this.set('offsetX', baseOffsetX - deltaX); |
|
|
} else { |
|
|
this.set('offsetY', offsetY); |
|
|
this.set('offsetX', baseOffsetX - deltaX); |
|
|
} |
|
|
}, |
|
|
|
|
|
getArrowTransform: function() { |
|
|
if (this.get('flip') === 1) { |
|
|
return 't-2,-20R-35'; |
|
|
} else { |
|
|
return 't2,20R-35'; |
|
|
} |
|
|
}, |
|
|
|
|
|
getBranchStackIndex: function() { |
|
|
if (this.get('isHead')) { |
|
|
|
|
|
return 0; |
|
|
} |
|
|
|
|
|
var myArray = this.getBranchStackArray(); |
|
|
var index = -1; |
|
|
myArray.forEach(function(branch, i) { |
|
|
if (branch.obj == this.get('branch')) { |
|
|
index = i; |
|
|
} |
|
|
}, this); |
|
|
return index; |
|
|
}, |
|
|
|
|
|
getBranchStackLength: function() { |
|
|
if (this.get('isHead')) { |
|
|
|
|
|
return 1; |
|
|
} |
|
|
|
|
|
return this.getBranchStackArray().length; |
|
|
}, |
|
|
|
|
|
isBranchStackEmpty: function() { |
|
|
|
|
|
var arr = this.gitVisuals.branchStackMap[this.getCommitID()]; |
|
|
return (arr) ? |
|
|
arr.length === 0 : |
|
|
true; |
|
|
}, |
|
|
|
|
|
getCommitID: function() { |
|
|
var target = this.get('branch').get('target'); |
|
|
if (target.get('type') === 'branch') { |
|
|
|
|
|
target = target.get('target'); |
|
|
} |
|
|
return target.get('id'); |
|
|
}, |
|
|
|
|
|
getBranchStackArray: function() { |
|
|
var arr = this.gitVisuals.branchStackMap[this.getCommitID()]; |
|
|
if (arr === undefined) { |
|
|
|
|
|
|
|
|
this.gitVisuals.calcBranchStacks(); |
|
|
return this.getBranchStackArray(); |
|
|
} |
|
|
return arr; |
|
|
}, |
|
|
|
|
|
getTextPosition: function() { |
|
|
var pos = this.getCommitPosition(); |
|
|
|
|
|
|
|
|
|
|
|
var myPos = this.getBranchStackIndex(); |
|
|
return { |
|
|
x: pos.x + this.get('flip') * this.get('offsetX'), |
|
|
y: pos.y + myPos * GRAPHICS.multiBranchY + this.get('offsetY') |
|
|
}; |
|
|
}, |
|
|
|
|
|
getRectPosition: function() { |
|
|
var pos = this.getTextPosition(); |
|
|
var f = this.get('flip'); |
|
|
|
|
|
|
|
|
var textSize = this.getTextSize(); |
|
|
return { |
|
|
x: pos.x - 0.5 * textSize.w - this.get('hPad'), |
|
|
y: pos.y - 0.5 * textSize.h - this.get('vPad') |
|
|
}; |
|
|
}, |
|
|
|
|
|
getArrowPath: function() { |
|
|
|
|
|
var offset2d = function(pos, x, y) { |
|
|
return { |
|
|
x: pos.x + x, |
|
|
y: pos.y + y |
|
|
}; |
|
|
}; |
|
|
var toStringCoords = function(pos) { |
|
|
return String(Math.round(pos.x)) + ',' + String(Math.round(pos.y)); |
|
|
}; |
|
|
var f = this.get('flip'); |
|
|
|
|
|
var arrowTip = offset2d(this.getCommitPosition(), |
|
|
f * this.get('arrowOffsetFromCircleX'), |
|
|
0 |
|
|
); |
|
|
var arrowEdgeUp = offset2d(arrowTip, f * this.get('arrowLength'), -this.get('arrowHeight')); |
|
|
var arrowEdgeLow = offset2d(arrowTip, f * this.get('arrowLength'), this.get('arrowHeight')); |
|
|
|
|
|
var arrowInnerUp = offset2d(arrowEdgeUp, |
|
|
f * this.get('arrowInnerSkew'), |
|
|
this.get('arrowEdgeHeight') |
|
|
); |
|
|
var arrowInnerLow = offset2d(arrowEdgeLow, |
|
|
f * this.get('arrowInnerSkew'), |
|
|
-this.get('arrowEdgeHeight') |
|
|
); |
|
|
|
|
|
var tailLength = 49; |
|
|
var arrowStartUp = offset2d(arrowInnerUp, f * tailLength, 0); |
|
|
var arrowStartLow = offset2d(arrowInnerLow, f * tailLength, 0); |
|
|
|
|
|
var pathStr = ''; |
|
|
pathStr += 'M' + toStringCoords(arrowStartUp) + ' '; |
|
|
var coords = [ |
|
|
arrowInnerUp, |
|
|
arrowEdgeUp, |
|
|
arrowTip, |
|
|
arrowEdgeLow, |
|
|
arrowInnerLow, |
|
|
arrowStartLow |
|
|
]; |
|
|
coords.forEach(function(pos) { |
|
|
pathStr += 'L' + toStringCoords(pos) + ' '; |
|
|
}, this); |
|
|
pathStr += 'z'; |
|
|
return pathStr; |
|
|
}, |
|
|
|
|
|
getTextSize: function() { |
|
|
var getTextWidth = function(visBranch) { |
|
|
var textNode = (visBranch.get('text')) ? visBranch.get('text').node : null; |
|
|
return (textNode === null) ? 0 : textNode.getBoundingClientRect().width; |
|
|
}; |
|
|
|
|
|
var firefoxFix = function(obj) { |
|
|
if (!obj.w) { obj.w = 75; } |
|
|
if (!obj.h) { obj.h = 20; } |
|
|
return obj; |
|
|
}; |
|
|
|
|
|
var textNode = this.get('text').node; |
|
|
if (this.get('isHead')) { |
|
|
|
|
|
var size = textNode.getBoundingClientRect(); |
|
|
return firefoxFix({ |
|
|
w: size.width, |
|
|
h: size.height |
|
|
}); |
|
|
} |
|
|
|
|
|
var maxWidth = 0; |
|
|
this.getBranchStackArray().forEach(function(branch) { |
|
|
maxWidth = Math.max(maxWidth, getTextWidth( |
|
|
branch.obj.get('visBranch') |
|
|
)); |
|
|
}); |
|
|
|
|
|
return firefoxFix({ |
|
|
w: maxWidth, |
|
|
h: textNode.getBoundingClientRect().height, |
|
|
}); |
|
|
}, |
|
|
|
|
|
getSingleRectSize: function() { |
|
|
var textSize = this.getTextSize(); |
|
|
var vPad = this.get('vPad'); |
|
|
var hPad = this.get('hPad'); |
|
|
return { |
|
|
w: textSize.w + vPad * 2, |
|
|
h: textSize.h + hPad * 2 |
|
|
}; |
|
|
}, |
|
|
|
|
|
getRectSize: function() { |
|
|
var textSize = this.getTextSize(); |
|
|
|
|
|
var vPad = this.get('vPad'); |
|
|
var hPad = this.get('hPad'); |
|
|
|
|
|
|
|
|
var totalNum = this.getBranchStackLength(); |
|
|
return { |
|
|
w: textSize.w + vPad * 2, |
|
|
h: textSize.h * totalNum * 1.1 + hPad * 2 |
|
|
}; |
|
|
}, |
|
|
|
|
|
getIsRemote: function() { |
|
|
return this.get('branch').getIsRemote(); |
|
|
}, |
|
|
|
|
|
getName: function() { |
|
|
var name = this.get('branch').getName(); |
|
|
var selected = this.get('branch') === this.gitEngine.HEAD.get('target'); |
|
|
var isRemote = this.getIsRemote(); |
|
|
var isHg = this.gitEngine.getIsHg(); |
|
|
|
|
|
if (name === 'HEAD' && isHg) { |
|
|
name = '.'; |
|
|
} |
|
|
|
|
|
var after = (selected && !this.getIsInOrigin() && !isRemote) ? '*' : ''; |
|
|
return name + after; |
|
|
}, |
|
|
|
|
|
nonTextToFront: function() { |
|
|
this.get('arrow').toFront(); |
|
|
this.get('rect').toFront(); |
|
|
}, |
|
|
|
|
|
textToFront: function() { |
|
|
this.get('text').toFront(); |
|
|
}, |
|
|
|
|
|
textToFrontIfInStack: function() { |
|
|
if (this.getBranchStackIndex() !== 0) { |
|
|
this.get('text').toFront(); |
|
|
} |
|
|
}, |
|
|
|
|
|
getFill: function() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.get('isHead') || |
|
|
this.getBranchStackLength() == 1 || |
|
|
this.getBranchStackIndex() !== 0) { |
|
|
return this.get('fill'); |
|
|
} |
|
|
|
|
|
|
|
|
return this.gitVisuals.blendHuesFromBranchStack(this.getBranchStackArray()); |
|
|
}, |
|
|
|
|
|
remove: function() { |
|
|
this.removeKeys(['text', 'arrow', 'rect']); |
|
|
|
|
|
this.gitVisuals.removeVisBranch(this); |
|
|
}, |
|
|
|
|
|
handleModeChange: function() { |
|
|
|
|
|
}, |
|
|
|
|
|
genGraphics: function(paper) { |
|
|
var textPos = this.getTextPosition(); |
|
|
var name = this.getName(); |
|
|
|
|
|
|
|
|
var text = paper.text(textPos.x, textPos.y, String(name)); |
|
|
text.attr({ |
|
|
'font-size': 14, |
|
|
'font-family': 'Menlo, Monaco, Consolas, \'Droid Sans Mono\', monospace', |
|
|
opacity: this.getTextOpacity() |
|
|
}); |
|
|
this.set('text', text); |
|
|
var attr = this.getAttributes(); |
|
|
|
|
|
var rectPos = this.getRectPosition(); |
|
|
var sizeOfRect = this.getRectSize(); |
|
|
var rect = paper |
|
|
.rect(rectPos.x, rectPos.y, sizeOfRect.w, sizeOfRect.h, 8) |
|
|
.attr(attr.rect); |
|
|
this.set('rect', rect); |
|
|
|
|
|
var arrowPath = this.getArrowPath(); |
|
|
var arrow = paper |
|
|
.path(arrowPath) |
|
|
.attr(attr.arrow); |
|
|
this.set('arrow', arrow); |
|
|
|
|
|
|
|
|
var keys = ['text', 'rect', 'arrow']; |
|
|
keys.forEach(function(key) { |
|
|
$(this.get(key).node).css(attr.css); |
|
|
}, this); |
|
|
|
|
|
this.attachClickHandlers(); |
|
|
rect.toFront(); |
|
|
text.toFront(); |
|
|
}, |
|
|
|
|
|
attachClickHandlers: function() { |
|
|
if (this.get('gitVisuals').options.noClick) { |
|
|
return; |
|
|
} |
|
|
var objs = [ |
|
|
this.get('rect'), |
|
|
this.get('text'), |
|
|
this.get('arrow') |
|
|
]; |
|
|
|
|
|
objs.forEach(function(rObj) { |
|
|
rObj.click(this.onClick.bind(this)); |
|
|
}, this); |
|
|
}, |
|
|
|
|
|
shouldDisableClick: function() { |
|
|
return this.get('isHead') && !this.gitEngine.getDetachedHead(); |
|
|
}, |
|
|
|
|
|
onClick: function() { |
|
|
if (this.shouldDisableClick()) { |
|
|
return; |
|
|
} |
|
|
|
|
|
var commandStr = 'git checkout ' + this.gitEngine.resolveNameNoPrefix(this.get('branch')) |
|
|
var Main = require('../app'); |
|
|
Main.getEventBaton().trigger('commandSubmitted', commandStr); |
|
|
}, |
|
|
|
|
|
updateName: function() { |
|
|
this.get('text').attr({ |
|
|
text: this.getName() |
|
|
}); |
|
|
}, |
|
|
|
|
|
getNonTextOpacity: function() { |
|
|
if (this.get('isHead')) { |
|
|
return this.gitEngine.getDetachedHead() ? 1 : 0; |
|
|
} |
|
|
if (this.getBranchStackIndex() !== 0) { |
|
|
return 0.0; |
|
|
} |
|
|
|
|
|
return 1; |
|
|
}, |
|
|
|
|
|
getTextOpacity: function() { |
|
|
if (this.get('isHead')) { |
|
|
return this.gitEngine.getDetachedHead() ? 1 : 0; |
|
|
} |
|
|
|
|
|
if (this.getIsGoalAndNotCompared()) { |
|
|
return (this.getBranchStackIndex() === 0) ? 0.7 : 0.3; |
|
|
} |
|
|
|
|
|
return 1; |
|
|
}, |
|
|
|
|
|
getStrokeWidth: function() { |
|
|
if (this.getIsGoalAndNotCompared()) { |
|
|
return this.get('stroke-width') / 5.0; |
|
|
} |
|
|
|
|
|
return this.get('stroke-width'); |
|
|
}, |
|
|
|
|
|
getAttributes: function() { |
|
|
var textOpacity = this.getTextOpacity(); |
|
|
this.updateName(); |
|
|
|
|
|
var textPos = this.getTextPosition(); |
|
|
var rectPos = this.getRectPosition(); |
|
|
var rectSize = this.getRectSize(); |
|
|
|
|
|
var arrowPath = this.getArrowPath(); |
|
|
var dashArray = this.getDashArray(); |
|
|
var cursorStyle = (this.shouldDisableClick()) ? |
|
|
'auto' : |
|
|
'pointer'; |
|
|
|
|
|
return { |
|
|
css: { |
|
|
cursor: cursorStyle |
|
|
}, |
|
|
text: { |
|
|
x: textPos.x, |
|
|
y: textPos.y, |
|
|
opacity: textOpacity |
|
|
}, |
|
|
rect: { |
|
|
x: rectPos.x, |
|
|
y: rectPos.y, |
|
|
width: rectSize.w, |
|
|
height: rectSize.h, |
|
|
opacity: this.getNonTextOpacity(), |
|
|
fill: this.getFill(), |
|
|
stroke: this.get('stroke'), |
|
|
'stroke-dasharray': dashArray, |
|
|
'stroke-width': this.getStrokeWidth() |
|
|
}, |
|
|
arrow: { |
|
|
path: arrowPath, |
|
|
opacity: this.getNonTextOpacity(), |
|
|
fill: this.getFill(), |
|
|
stroke: this.get('stroke'), |
|
|
transform: this.getArrowTransform(), |
|
|
'stroke-dasharray': dashArray, |
|
|
'stroke-width': this.getStrokeWidth() |
|
|
} |
|
|
}; |
|
|
}, |
|
|
|
|
|
animateUpdatedPos: function(speed, easing) { |
|
|
var attr = this.getAttributes(); |
|
|
this.animateToAttr(attr, speed, easing); |
|
|
}, |
|
|
|
|
|
animateFromAttrToAttr: function(fromAttr, toAttr, speed, easing) { |
|
|
|
|
|
this.animateToAttr(fromAttr, 0); |
|
|
this.animateToAttr(toAttr, speed, easing); |
|
|
}, |
|
|
|
|
|
setAttr: function(attr, instant, speed, easing) { |
|
|
var keys = ['text', 'rect', 'arrow']; |
|
|
this.setAttrBase(keys, attr, instant, speed, easing); |
|
|
} |
|
|
}); |
|
|
|
|
|
var VisBranchCollection = Backbone.Collection.extend({ |
|
|
model: VisBranch |
|
|
}); |
|
|
|
|
|
exports.VisBranchCollection = VisBranchCollection; |
|
|
exports.VisBranch = VisBranch; |
|
|
exports.randomHueString = randomHueString; |
|
|
|