Merge branch 'tagSupport' Pull Request #137 Issue #134

Conflicts:
	src/js/git/index.js
This commit is contained in:
Peter Cottle 2013-10-27 12:37:48 -07:00
commit ebfd5e84d2
10 changed files with 647 additions and 6 deletions

View file

@ -8,10 +8,13 @@ var GLOBAL = require('../util/constants').GLOBAL;
var Collections = require('../models/collections');
var CommitCollection = Collections.CommitCollection;
var BranchCollection = Collections.BranchCollection;
var TagCollection = Collections.TagCollection;
var VisNode = require('../visuals/visNode').VisNode;
var VisBranch = require('../visuals/visBranch').VisBranch;
var VisBranchCollection = require('../visuals/visBranch').VisBranchCollection;
var VisTag = require('../visuals/visTag').VisTag;
var VisTagCollection = require('../visuals/visTag').VisTagCollection;
var VisEdge = require('../visuals/visEdge').VisEdge;
var VisEdgeCollection = require('../visuals/visEdge').VisEdgeCollection;
@ -21,14 +24,17 @@ function GitVisuals(options) {
this.visualization = options.visualization;
this.commitCollection = options.commitCollection;
this.branchCollection = options.branchCollection;
this.tagCollection = options.tagCollection;
this.visNodeMap = {};
this.visEdgeCollection = new VisEdgeCollection();
this.visBranchCollection = new VisBranchCollection();
this.visTagCollection = new VisTagCollection();
this.commitMap = {};
this.rootCommit = null;
this.branchStackMap = null;
this.tagStackMap = null;
this.upstreamBranchSet = null;
this.upstreamHeadSet = null;
@ -37,6 +43,10 @@ function GitVisuals(options) {
this.branchCollection.on('add', this.addBranchFromEvent, this);
this.branchCollection.on('remove', this.removeBranch, this);
this.tagCollection.on('add', this.addTagFromEvent, this);
this.tagCollection.on('remove', this.removeTag, this);
this.deferred = [];
this.flipFraction = 0.65;
@ -69,12 +79,18 @@ GitVisuals.prototype.resetAll = function() {
visBranch.remove();
}, this);
var tags = this.visTagCollection.toArray();
_.each(tags, function(visTag) {
visTag.remove();
}, this);
_.each(this.visNodeMap, function(visNode) {
visNode.remove();
}, this);
this.visEdgeCollection.reset();
this.visBranchCollection.reset();
this.visTagCollection.reset();
this.visNodeMap = {};
this.rootCommit = null;
@ -178,6 +194,7 @@ GitVisuals.prototype.animateAllAttrKeys = function(keys, attr, speed, easing) {
this.visBranchCollection.each(animate);
this.visEdgeCollection.each(animate);
this.visTagCollection.each(animate);
_.each(this.visNodeMap, animate);
var time = (speed !== undefined) ? speed : GRAPHICS.defaultAnimationTime;
@ -334,6 +351,7 @@ GitVisuals.prototype.animateAllFromAttrToAttr = function(fromSnapshot, toSnapsho
this.visBranchCollection.each(animate);
this.visEdgeCollection.each(animate);
this.visTagCollection.each(animate);
_.each(this.visNodeMap, animate);
};
@ -370,6 +388,10 @@ GitVisuals.prototype.genSnapshot = function() {
snapshot[visEdge.getID()] = visEdge.getAttributes();
}, this);
this.visTagCollection.each(function(visTag) {
snapshot[visTag.getID()] = visTag.getAttributes();
}, this);
return snapshot;
};
@ -411,6 +433,7 @@ GitVisuals.prototype.calcTreeCoords = function() {
this.calcUpstreamSets();
this.calcBranchStacks();
this.calcTagStacks();
this.calcDepth();
this.calcWidth();
@ -420,6 +443,9 @@ GitVisuals.prototype.calcGraphicsCoords = function() {
this.visBranchCollection.each(function(visBranch) {
visBranch.updateName();
});
this.visTagCollection.each(function(visTag) {
visTag.updateName();
});
};
GitVisuals.prototype.calcUpstreamSets = function() {
@ -496,6 +522,23 @@ GitVisuals.prototype.calcBranchStacks = function() {
this.branchStackMap = map;
};
GitVisuals.prototype.calcTagStacks = function() {
var tags = this.gitEngine.getTags();
var map = {};
_.each(tags, function(tag) {
var thisId = tag.target.get('id');
map[thisId] = map[thisId] || [];
map[thisId].push(tag);
map[thisId].sort(function(a, b) {
var aId = a.obj.get('id');
var bId = b.obj.get('id');
return aId.localeCompare(bId);
});
});
this.tagStackMap = map;
};
GitVisuals.prototype.calcWidth = function() {
this.maxWidthRecursive(this.rootCommit);
@ -626,10 +669,44 @@ GitVisuals.prototype.addBranch = function(branch) {
}
};
GitVisuals.prototype.addTagFromEvent = function(tag, collection, index) {
var action = _.bind(function() {
this.addTag(tag);
}, this);
if (!this.gitEngine || !this.gitReady) {
this.defer(action);
} else {
action();
}
};
GitVisuals.prototype.addTag = function(tag) {
var visTag = new VisTag({
tag: tag,
gitVisuals: this,
gitEngine: this.gitEngine
});
this.visTagCollection.add(visTag);
if (this.gitReady) {
visTag.genGraphics(this.paper);
} else {
this.defer(_.bind(function() {
visTag.genGraphics(this.paper);
}, this));
}
};
GitVisuals.prototype.removeVisBranch = function(visBranch) {
this.visBranchCollection.remove(visBranch);
};
GitVisuals.prototype.removeVisTag = function(visTag) {
this.visTagCollection.remove(visTag);
};
GitVisuals.prototype.removeVisNode = function(visNode) {
delete this.visNodeMap[visNode.getID()];
};
@ -642,6 +719,9 @@ GitVisuals.prototype.animateRefs = function(speed) {
this.visBranchCollection.each(function(visBranch) {
visBranch.animateUpdatedPos(speed);
}, this);
this.visTagCollection.each(function(visTag) {
visTag.animateUpdatedPos(speed);
}, this);
};
GitVisuals.prototype.animateEdges = function(speed) {
@ -756,6 +836,7 @@ GitVisuals.prototype.addEdge = function(idTail, idHead) {
GitVisuals.prototype.zIndexReflow = function() {
this.visNodesFront();
this.visBranchesFront();
this.visTagsFront();
};
GitVisuals.prototype.visNodesFront = function() {
@ -775,6 +856,17 @@ GitVisuals.prototype.visBranchesFront = function() {
});
};
GitVisuals.prototype.visTagsFront = function() {
this.visTagCollection.each(function(vTag) {
vTag.nonTextToFront();
vTag.textToFront();
});
this.visTagCollection.each(function(vTag) {
vTag.textToFrontIfInStack();
});
};
GitVisuals.prototype.drawTreeFromReload = function() {
this.gitReady = true;
// gen all the graphics we need
@ -799,6 +891,10 @@ GitVisuals.prototype.drawTreeFirstTime = function() {
visBranch.genGraphics(this.paper);
}, this);
this.visTagCollection.each(function(visTag) {
visTag.genGraphics(this.paper);
}, this);
this.zIndexReflow();
};

View file

@ -130,6 +130,7 @@ var VisEdge = VisBase.extend({
var stat = this.gitVisuals.getCommitUpstreamStatus(this.get('tail'));
var map = {
'branch': 1,
'tag': 1,
'head': GRAPHICS.edgeUpstreamHeadOpacity,
'none': GRAPHICS.edgeUpstreamNoneOpacity
};

View file

@ -74,6 +74,7 @@ var VisNode = VisBase.extend({
var stat = this.gitVisuals.getCommitUpstreamStatus(this.get('commit'));
var map = {
branch: 1,
tag: 1,
head: 0.3,
none: 0.1
};
@ -89,6 +90,7 @@ var VisNode = VisBase.extend({
getOpacity: function() {
var map = {
'branch': 1,
'tag' : 1,
'head': GRAPHICS.upstreamHeadOpacity,
'none': GRAPHICS.upstreamNoneOpacity
};

408
src/js/visuals/visTag.js Normal file
View file

@ -0,0 +1,408 @@
var _ = require('underscore');
var Backbone = require('backbone');
var GRAPHICS = require('../util/constants').GRAPHICS;
var VisBase = require('../visuals/visBase').VisBase;
var TreeCompare = require('../git/treeCompare').TreeCompare;
var randomHueString = function() {
var hue = Math.random();
var str = 'hsb(' + String(hue) + ',0.7,1)';
return str;
};
var VisTag = VisBase.extend({
defaults: {
pos: null,
text: null,
rect: null,
isHead: false,
fill: GRAPHICS.tagFill,
stroke: GRAPHICS.tagStroke,
'stroke-width': GRAPHICS.tagStrokeWidth,
offsetX: GRAPHICS.nodeRadius,
offsetY: GRAPHICS.nodeRadius,
vPad: 2,
hPad: 2,
animationSpeed: GRAPHICS.defaultAnimationTime,
animationEasing: GRAPHICS.defaultEasing
},
validateAtInit: function() {
if (!this.get('tag')) {
throw new Error('need a Tag!');
}
},
getID: function() {
return this.get('tag').get('id');
},
initialize: function() {
this.validateAtInit();
// shorthand notation for the main objects
this.gitVisuals = this.get('gitVisuals');
this.gitEngine = this.get('gitEngine');
if (!this.gitEngine) {
throw new Error('asd wtf');
}
this.get('tag').set('visTag', this);
},
getCommitPosition: function() {
var commit = this.gitEngine.getCommitFromRef(this.get('tag'));
var visNode = commit.get('visNode');
return visNode.getScreenCoords();
},
getDashArray: function() {
if (!this.get('gitVisuals').getIsGoalVis()) {
return '';
}
return (this.getIsLevelTagCompared()) ? '' : '--';
},
getIsGoalAndNotCompared: function() {
if (!this.get('gitVisuals').getIsGoalVis()) {
return false;
}
return !this.getIsLevelTagCompared();
},
/**
* returns true if we are a Tag that is not being
* compared in the goal (used in a goal visualization context
*/
getIsLevelTagCompared: function() {
// we are not master, so return true if its not just master being compared
var levelBlob = this.get('gitVisuals').getLevelBlob();
return !TreeCompare.onlyMasterCompared(levelBlob);
},
getTagStackIndex: function() {
if (this.get('isHead')) {
// head is never stacked with other Tages
return 0;
}
var myArray = this.getTagStackArray();
var index = -1;
_.each(myArray, function(Tag, i) {
if (Tag.obj == this.get('tag')) {
index = i;
}
}, this);
return index;
},
getTagStackLength: function() {
if (this.get('isHead')) {
// head is always by itself
return 1;
}
return this.getTagStackArray().length;
},
isTagStackEmpty: function() {
// useful function for head when computing flip logic
var arr = this.gitVisuals.tagStackMap[this.getCommitID()];
return (arr) ?
arr.length === 0 :
true;
},
getCommitID: function() {
var target = this.get('tag').get('target');
return target.get('id');
},
getTagStackArray: function() {
var arr = this.gitVisuals.tagStackMap[this.getCommitID()];
if (arr === undefined) {
// this only occurs when we are generating graphics inside of
// a new Tag instantiation, so we need to force the update
this.gitVisuals.calcTagStacks();
return this.getTagStackArray();
}
return arr;
},
getTextPosition: function() {
var pos = this.getCommitPosition();
// then order yourself accordingly. we use alphabetical sorting
// so everything is independent
var myPos = this.getTagStackIndex();
return {
x: pos.x + this.get('offsetX'),
y: pos.y + myPos * GRAPHICS.multiTagY + this.get('offsetY')
};
},
getRectPosition: function() {
var pos = this.getTextPosition();
// first get text width and height
var textSize = this.getTextSize();
return {
x: pos.x - this.get('hPad'),
y: pos.y - 0.5 * textSize.h - this.get('vPad')
};
},
getTextSize: function() {
var getTextWidth = function(visTag) {
var textNode = (visTag.get('text')) ? visTag.get('text').node : null;
return (textNode === null) ? 0 : textNode.clientWidth;
};
var firefoxFix = function(obj) {
if (!obj.w) { obj.w = 75; }
if (!obj.h) { obj.h = 20; }
return obj;
};
var textNode = this.get('text').node;
var maxWidth = 0;
_.each(this.getTagStackArray(), function(Tag) {
maxWidth = Math.max(maxWidth, getTextWidth(
Tag.obj.get('visTag')
));
});
return firefoxFix({
w: maxWidth,
h: textNode.clientHeight
});
},
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();
// enforce padding
var vPad = this.get('vPad');
var hPad = this.get('hPad');
// number of other Tag names we are housing
var totalNum = this.getTagStackLength();
return {
w: textSize.w + vPad * 2,
h: textSize.h * totalNum + hPad * 2
};
},
getIsRemote: function() {
return this.get('tag').getIsRemote();
},
getName: function() {
var name = this.get('tag').getName();
var isRemote = this.getIsRemote();
var isHg = this.gitEngine.getIsHg();
return name;
},
nonTextToFront: function() {
this.get('rect').toFront();
},
textToFront: function() {
this.get('text').toFront();
},
textToFrontIfInStack: function() {
if (this.getTagStackIndex() !== 0) {
this.get('text').toFront();
}
},
remove: function() {
this.removeKeys(['text', 'rect']);
// also need to remove from this.gitVisuals
this.gitVisuals.removeVisTag(this);
},
handleModeChange: function() {
},
genGraphics: function(paper) {
var textPos = this.getTextPosition();
var name = this.getName();
// when from a reload, we dont need to generate the text
var text = paper.text(textPos.x, textPos.y, String(name));
text.attr({
'font-size': 14,
'font-family': 'Monaco, Courier, font-monospace',
opacity: this.getTextOpacity(),
'text-anchor': 'start'
});
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);
// set CSS
var keys = ['text', 'rect'];
_.each(keys, 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')
];
_.each(objs, function(rObj) {
rObj.click(_.bind(this.onClick ,this));
}, this);
},
shouldDisableClick: function() {
return this.get('isHead') && !this.gitEngine.getDetachedHead();
},
onClick: function() {
if (this.shouldDisableClick()) {
return;
}
var commandStr = 'git checkout ' + this.get('tag').get('id');
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.getTagStackIndex() !== 0) {
return 0.0;
}
return 1;
},
getTextOpacity: function() {
if (this.get('isHead')) {
return this.gitEngine.getDetachedHead() ? 1 : 0;
}
if (this.getIsGoalAndNotCompared()) {
return (this.getTagStackIndex() === 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 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.get('fill'),
stroke: this.get('stroke'),
'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) {
// an animation of 0 is essentially setting the attribute directly
this.animateToAttr(fromAttr, 0);
this.animateToAttr(toAttr, speed, easing);
},
setAttr: function(attr, instant, speed, easing) {
var keys = ['text', 'rect'];
this.setAttrBase(keys, attr, instant, speed, easing);
}
});
var VisTagCollection = Backbone.Collection.extend({
model: VisTag
});
exports.VisTagCollection = VisTagCollection;
exports.VisTag = VisTag;
exports.randomHueString = randomHueString;

View file

@ -5,6 +5,7 @@ var Backbone = (!require('../util').isBrowser()) ? Backbone = require('backbone'
var Collections = require('../models/collections');
var CommitCollection = Collections.CommitCollection;
var BranchCollection = Collections.BranchCollection;
var TagCollection = Collections.TagCollection;
var EventBaton = require('../util/eventBaton').EventBaton;
var GitVisuals = require('../visuals').GitVisuals;
@ -43,10 +44,12 @@ var Visualization = Backbone.View.extend({
this.commitCollection = new CommitCollection();
this.branchCollection = new BranchCollection();
this.tagCollection = new TagCollection();
this.gitVisuals = new GitVisuals({
commitCollection: this.commitCollection,
branchCollection: this.branchCollection,
tagCollection: this.tagCollection,
paper: this.paper,
noClick: this.options.noClick,
isGoalVis: this.options.isGoalVis,
@ -58,6 +61,7 @@ var Visualization = Backbone.View.extend({
this.gitEngine = new GitEngine({
collection: this.commitCollection,
branches: this.branchCollection,
tags: this.tagCollection,
gitVisuals: this.gitVisuals,
eventBaton: this.eventBaton
});