mirror of
https://github.com/pcottle/learnGitBranching.git
synced 2025-06-27 16:38:50 +02:00
431 lines
11 KiB
JavaScript
431 lines
11 KiB
JavaScript
var VisBranch = Backbone.Model.extend({
|
|
defaults: {
|
|
pos: null,
|
|
text: null,
|
|
rect: null,
|
|
arrow: null,
|
|
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!');
|
|
}
|
|
},
|
|
|
|
initialize: function() {
|
|
this.validateAtInit();
|
|
},
|
|
|
|
getCommitPosition: function() {
|
|
var commit = this.get('branch').get('target');
|
|
var visNode = commit.get('visNode');
|
|
return visNode.getScreenCoords();
|
|
},
|
|
|
|
getBranchStackIndex: function() {
|
|
var myArray = this.getBranchStackArray();
|
|
var index = -1;
|
|
_.each(myArray, function(branch, i) {
|
|
if (branch.obj == this.get('branch')) {
|
|
index = i;
|
|
}
|
|
}, this);
|
|
return index;
|
|
},
|
|
|
|
getBranchStackLength: function() {
|
|
return this.getBranchStackArray().length;
|
|
},
|
|
|
|
getBranchStackArray: function() {
|
|
return gitVisuals.branchStackMap[this.get('branch').get('target').get('id')];
|
|
},
|
|
|
|
getTextPosition: function() {
|
|
var pos = this.getCommitPosition();
|
|
|
|
// then order yourself accordingly. we use alphabetical sorting
|
|
// so everything is independent
|
|
var myPos = this.getBranchStackIndex();
|
|
return {
|
|
x: pos.x + this.get('offsetX'),
|
|
y: pos.y + myPos * GRAPHICS.multiBranchY + this.get('offsetY')
|
|
};
|
|
},
|
|
|
|
getRectPosition: function() {
|
|
var pos = this.getTextPosition();
|
|
// first get text width and height
|
|
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() {
|
|
// should make these util functions...
|
|
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 arrowTip = offset2d(this.getCommitPosition(),
|
|
this.get('arrowOffsetFromCircleX'),
|
|
0
|
|
);
|
|
var arrowEdgeUp = offset2d(arrowTip, this.get('arrowLength'), -this.get('arrowHeight'));
|
|
var arrowEdgeLow = offset2d(arrowTip, this.get('arrowLength'), this.get('arrowHeight'));
|
|
|
|
var arrowInnerUp = offset2d(arrowEdgeUp,
|
|
this.get('arrowInnerSkew'),
|
|
this.get('arrowEdgeHeight')
|
|
);
|
|
var arrowInnerLow = offset2d(arrowEdgeLow,
|
|
this.get('arrowInnerSkew'),
|
|
-this.get('arrowEdgeHeight')
|
|
);
|
|
|
|
var tailLength = 30;
|
|
var arrowStartUp = offset2d(arrowInnerUp, tailLength, 0);
|
|
var arrowStartLow = offset2d(arrowInnerLow, tailLength, 0);
|
|
|
|
var pathStr = '';
|
|
pathStr += 'M' + toStringCoords(arrowStartUp) + ' ';
|
|
var coords = [
|
|
arrowInnerUp,
|
|
arrowEdgeUp,
|
|
arrowTip,
|
|
arrowEdgeLow,
|
|
arrowInnerLow,
|
|
arrowStartLow
|
|
];
|
|
_.each(coords, function(pos) {
|
|
pathStr += 'L' + toStringCoords(pos) + ' ';
|
|
}, this);
|
|
pathStr += 'z';
|
|
return pathStr;
|
|
},
|
|
|
|
getTextSize: function() {
|
|
var getTextWidth = function(visBranch) {
|
|
var textNode = visBranch.get('text').node;
|
|
return textNode.clientWidth;
|
|
};
|
|
|
|
var textNode = this.get('text').node;
|
|
|
|
var maxWidth = 0;
|
|
_.each(this.getBranchStackArray(), function(branch) {
|
|
maxWidth = Math.max(maxWidth, getTextWidth(
|
|
branch.obj.get('visBranch')
|
|
));
|
|
});
|
|
|
|
return {
|
|
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 branch names we are housing
|
|
var totalNum = this.getBranchStackLength();
|
|
return {
|
|
w: textSize.w + vPad * 2,
|
|
h: textSize.h * totalNum + hPad * 2
|
|
};
|
|
},
|
|
|
|
getName: function() {
|
|
var name = this.get('branch').get('id');
|
|
var selected = gitEngine.HEAD.get('target').get('id');
|
|
|
|
var add = (selected == name) ? '*' : '';
|
|
return name + add;
|
|
},
|
|
|
|
nonTextToFront: function() {
|
|
this.get('arrow').toFront();
|
|
this.get('rect').toFront();
|
|
},
|
|
|
|
textToFront: function() {
|
|
this.get('text').toFront();
|
|
},
|
|
|
|
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': 'Monaco, Courier, font-monospace'
|
|
});
|
|
this.set('text', text);
|
|
|
|
var rectPos = this.getRectPosition();
|
|
var sizeOfRect = this.getRectSize();
|
|
var rect = paper.rect(rectPos.x, rectPos.y, sizeOfRect.w, sizeOfRect.h, 8);
|
|
rect.attr({
|
|
fill: GRAPHICS.rectFill,
|
|
stroke: GRAPHICS.rectStroke,
|
|
'stroke-width': GRAPHICS.rectStrokeWidth
|
|
});
|
|
this.set('rect', rect);
|
|
|
|
var arrowPath = this.getArrowPath();
|
|
var arrow = paper.path(arrowPath);
|
|
arrow.attr({
|
|
fill: GRAPHICS.rectFill,
|
|
stroke: GRAPHICS.rectStroke,
|
|
'stroke-width': GRAPHICS.rectStrokeWidth
|
|
});
|
|
this.set('arrow', arrow);
|
|
|
|
rect.toFront();
|
|
text.toFront();
|
|
},
|
|
|
|
updateName: function() {
|
|
this.get('text').attr({
|
|
text: this.getName()
|
|
});
|
|
},
|
|
|
|
animateUpdatedPos: function(speed, easing) {
|
|
var s = speed !== undefined ? speed : this.get('animationSpeed');
|
|
var e = easing || this.get('animationEasing');
|
|
var masterOpacity = this.getBranchStackIndex() == 0 ? 1 : 0.0;
|
|
|
|
this.updateName();
|
|
var textPos = this.getTextPosition();
|
|
this.get('text').stop().animate({
|
|
x: textPos.x,
|
|
y: textPos.y
|
|
}, s, e);
|
|
|
|
var rectPos = this.getRectPosition();
|
|
var rectSize = this.getRectSize();
|
|
this.get('rect').stop().animate({
|
|
x: rectPos.x,
|
|
y: rectPos.y,
|
|
width: rectSize.w,
|
|
height: rectSize.h,
|
|
opacity: masterOpacity,
|
|
}, s, e);
|
|
|
|
var arrowPath = this.getArrowPath();
|
|
this.get('arrow').stop().animate({
|
|
path: arrowPath,
|
|
opacity: masterOpacity
|
|
}, s, e);
|
|
}
|
|
});
|
|
|
|
|
|
var VisNode = Backbone.Model.extend({
|
|
defaults: {
|
|
depth: undefined,
|
|
id: null,
|
|
pos: null,
|
|
radius: null,
|
|
commit: null,
|
|
animationSpeed: GRAPHICS.defaultAnimationTime,
|
|
animationEasing: GRAPHICS.defaultEasing
|
|
},
|
|
|
|
validateAtInit: function() {
|
|
if (!this.get('id')) {
|
|
throw new Error('need id for mapping');
|
|
}
|
|
if (!this.get('commit')) {
|
|
throw new Error('need commit for linking');
|
|
}
|
|
|
|
if (!this.get('pos')) {
|
|
this.set('pos', {
|
|
x: Math.random(),
|
|
y: Math.random()
|
|
});
|
|
}
|
|
},
|
|
|
|
initialize: function() {
|
|
this.validateAtInit();
|
|
},
|
|
|
|
setDepthBasedOn: function(depthIncrement) {
|
|
if (this.get('depth') === undefined) {
|
|
throw new Error('no depth yet!');
|
|
}
|
|
var pos = this.get('pos');
|
|
pos.y = this.get('depth') * depthIncrement;
|
|
},
|
|
|
|
toFront: function() {
|
|
this.get('circle').toFront();
|
|
},
|
|
|
|
animateUpdatedPosition: function(speed, easing) {
|
|
var pos = this.getScreenCoords();
|
|
this.get('circle').stop().animate({
|
|
cx: pos.x,
|
|
cy: pos.y
|
|
},
|
|
speed !== undefined ? speed : this.get('animationSpeed'),
|
|
easing || this.get('animationEasing')
|
|
);
|
|
},
|
|
|
|
getScreenCoords: function() {
|
|
var pos = this.get('pos');
|
|
return gitVisuals.toScreenCoords(pos);
|
|
},
|
|
|
|
getRadius: function() {
|
|
return this.get('radius') || GRAPHICS.nodeRadius;
|
|
},
|
|
|
|
genGraphics: function(paper) {
|
|
var pos = this.getScreenCoords();
|
|
var circle = cuteSmallCircle(paper, pos.x, pos.y, {
|
|
radius: this.getRadius()
|
|
});
|
|
this.set('circle', circle);
|
|
}
|
|
});
|
|
|
|
var VisEdge = Backbone.Model.extend({
|
|
defaults: {
|
|
tail: null,
|
|
head: null,
|
|
animationSpeed: GRAPHICS.defaultAnimationTime,
|
|
animationEasing: GRAPHICS.defaultEasing
|
|
},
|
|
|
|
validateAtInit: function() {
|
|
required = ['tail', 'head'];
|
|
_.each(required, function(key) {
|
|
if (!this.get(key)) {
|
|
throw new Error(key + ' is required!');
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
initialize: function() {
|
|
this.validateAtInit();
|
|
},
|
|
|
|
genSmoothBezierPathString: function(tail, head) {
|
|
var tailPos = tail.getScreenCoords();
|
|
var headPos = head.getScreenCoords();
|
|
// we need to generate the path and control points for the bezier. format
|
|
// is M(move abs) C (curve to) (control point 1) (control point 2) (final point)
|
|
// the control points have to be __below__ to get the curve starting off straight.
|
|
|
|
var coords = function(pos) {
|
|
return String(Math.round(pos.x)) + ',' + String(Math.round(pos.y));
|
|
};
|
|
var offset = function(pos, dir, delta) {
|
|
delta = delta || GRAPHICS.curveControlPointOffset;
|
|
return {
|
|
x: pos.x,
|
|
y: pos.y + delta * dir
|
|
};
|
|
};
|
|
var offset2d = function(pos, x, y) {
|
|
return {
|
|
x: pos.x + x,
|
|
y: pos.y + y
|
|
};
|
|
};
|
|
|
|
// first offset tail and head by radii
|
|
tailPos = offset(tailPos, -1, tail.getRadius());
|
|
headPos = offset(headPos, 1, head.getRadius());
|
|
|
|
var str = '';
|
|
// first move to bottom of tail
|
|
str += 'M' + coords(tailPos) + ' ';
|
|
// start bezier
|
|
str += 'C';
|
|
// then control points above tail and below head
|
|
str += coords(offset(tailPos, -1)) + ' ';
|
|
str += coords(offset(headPos, 1)) + ' ';
|
|
// now finish
|
|
str += coords(headPos);
|
|
|
|
// arrow head
|
|
// TODO default sizing
|
|
var delta = GRAPHICS.arrowHeadSize || 10;
|
|
str += ' L' + coords(offset2d(headPos, -delta, delta));
|
|
str += ' L' + coords(offset2d(headPos, delta, delta));
|
|
str += ' L' + coords(headPos);
|
|
|
|
return str;
|
|
},
|
|
|
|
getBezierCurve: function() {
|
|
return this.genSmoothBezierPathString(this.get('tail'), this.get('head'));
|
|
},
|
|
|
|
genGraphics: function(paper) {
|
|
var pathString = this.getBezierCurve();
|
|
var path = cutePath(paper, pathString);
|
|
path.toBack();
|
|
this.set('path', path);
|
|
},
|
|
|
|
animateUpdatedPath: function(speed, easing) {
|
|
var newPath = this.getBezierCurve();
|
|
this.get('path').toBack();
|
|
this.get('path').stop().animate({
|
|
path: newPath
|
|
},
|
|
speed !== undefined ? speed : this.get('animationSpeed'),
|
|
easing || this.get('animationEasing')
|
|
);
|
|
},
|
|
|
|
});
|
|
|
|
var VisEdgeCollection = Backbone.Collection.extend({
|
|
model: VisEdge
|
|
});
|
|
|
|
var VisBranchCollection = Backbone.Collection.extend({
|
|
model: VisBranch
|
|
});
|