diff --git a/src/js/git/commands.js b/src/js/git/commands.js index 6406f747..31dda0b5 100644 --- a/src/js/git/commands.js +++ b/src/js/git/commands.js @@ -731,6 +731,24 @@ var commandConfig = { source: source }); } + }, + + tag: { + regex: /^git +tag($|\s)/, + execute: function(engine, command) { + var generalArgs = command.getGeneralArgs(); + + + if (generalArgs.length === 0) { + var tags = engine.getTags(); + engine.printTags(tags); + return; + } + + command.twoArgsImpliedHead(generalArgs); + engine.tag(generalArgs[0], generalArgs[1]); + + } } }; diff --git a/src/js/git/index.js b/src/js/git/index.js index 97d12edf..fa4ad2e8 100644 --- a/src/js/git/index.js +++ b/src/js/git/index.js @@ -41,6 +41,7 @@ function GitEngine(options) { this.localRepo = null; this.branchCollection = options.branches; + this.tagCollection = options.tags; this.commitCollection = options.collection; this.gitVisuals = options.gitVisuals; @@ -214,6 +215,7 @@ GitEngine.prototype.exportTree = function() { _.each(this.branchCollection.toJSON(), function(branch) { branch.target = branch.target.get('id'); branch.visBranch = undefined; + branch.visTag = undefined; totalExport.branches[branch.id] = branch; }); @@ -235,8 +237,7 @@ GitEngine.prototype.exportTree = function() { }, this); var HEAD = this.HEAD.toJSON(); - HEAD.visBranch = undefined; - HEAD.lastTarget = HEAD.lastLastTarget = HEAD.visBranch = undefined; + HEAD.lastTarget = HEAD.lastLastTarget = HEAD.visBranch = HEAD.visTag =undefined; HEAD.target = HEAD.target.get('id'); totalExport.HEAD = HEAD; @@ -298,6 +299,12 @@ GitEngine.prototype.instantiateFromTree = function(tree) { this.branchCollection.add(branch, {silent: true}); }, this); + _.each(tree.tags, function(tagJSON) { + var tag = this.getOrMakeRecursive(tree, createdSoFar, tagJSON.id); + + this.tagCollection.add(tag, {silent: true}); + }, this); + var HEAD = this.getOrMakeRecursive(tree, createdSoFar, tree.HEAD.id); this.HEAD = HEAD; @@ -309,8 +316,11 @@ GitEngine.prototype.instantiateFromTree = function(tree) { this.gitVisuals.gitReady = false; this.branchCollection.each(function(branch) { - this.gitVisuals.addBranch(branch); - }, this); + this.gitVisuals.addBranch(branch); + }, this); + this.tagCollection.each(function(tag) { + this.gitVisuals.addTag(tag); + }, this); if (tree.originTree) { var treeString = JSON.stringify(tree.originTree); @@ -512,6 +522,19 @@ GitEngine.prototype.getOrMakeRecursive = function(tree, createdSoFar, objID) { return branch; } + if (type == 'tag') { + var tagJSON = tree.tags[objID]; + + var tag = new Tag(_.extend( + tree.tags[objID], + { + target: this.getOrMakeRecursive(tree, createdSoFar, tagJSON.target) + } + )); + createdSoFar[objID] = tag; + return tag; + } + if (type == 'commit') { // for commits, we need to grab all the parents var commitJSON = tree.commits[objID]; @@ -559,6 +582,7 @@ GitEngine.prototype.reloadGraphics = function() { GitEngine.prototype.removeAll = function() { this.branchCollection.reset(); + this.tagCollection.reset(); this.commitCollection.reset(); this.refs = {}; this.HEAD = null; @@ -625,6 +649,20 @@ GitEngine.prototype.validateAndMakeBranch = function(id, target) { return this.makeBranch(id, target); }; +GitEngine.prototype.validateAndMakeTag = function(id, target) { + id = this.validateBranchName(id); + if (this.refs[id]) { + throw new GitError({ + msg: intl.str( + 'bad-tag-name', + { tag: name } + ) + }); + } + + this.makeTag(id, target); +}; + GitEngine.prototype.makeBranch = function(id, target) { if (this.refs[id]) { throw new Error('woah already have that'); @@ -639,10 +677,37 @@ GitEngine.prototype.makeBranch = function(id, target) { return branch; }; +GitEngine.prototype.makeTag = function(id, target) { + if (this.refs[id]) { + throw new Error('woah already have that'); + } + + var tag = new Tag({ + target: target, + id: id + }); + this.tagCollection.add(tag); + this.refs[tag.get('id')] = tag; + return tag; +}; + GitEngine.prototype.getHead = function() { return _.clone(this.HEAD); }; +GitEngine.prototype.getTags = function() { + var toReturn = []; + this.tagCollection.each(function(tag) { + toReturn.push({ + id: tag.get('id'), + target: tag.get('target'), + remote: tag.getIsRemote(), + obj: tag + }); + }, this); + return toReturn; +}; + GitEngine.prototype.getBranches = function() { var toReturn = []; this.branchCollection.each(function(branch) { @@ -693,6 +758,17 @@ GitEngine.prototype.printBranches = function(branches) { }); }; +GitEngine.prototype.printTags = function(tags) { + var result = ''; + _.each(tags, function(tag) { + console.log(tag); + result += tag.id + '\n'; + }); + throw new CommandResult({ + msg: result + }); +}; + GitEngine.prototype.printRemotes = function(options) { var result = ''; if (options.verbose) { @@ -2261,12 +2337,15 @@ GitEngine.prototype.checkout = function(idOrTarget) { target = this.getCommitFromRef(target.get('id')); } - if (type !== 'branch' && type !== 'commit') { + if (type !== 'branch' && type !== 'tag' && type !== 'commit') { throw new GitError({ msg: intl.str('git-error-options') }); } - + if (type === 'tag') { + target = target.get('target'); + } + this.HEAD.set('target', target); }; @@ -2313,6 +2392,11 @@ GitEngine.prototype.isRemoteBranchRef = function(ref) { return resolved.getIsRemote(); }; +GitEngine.prototype.tag = function(name, ref) { + var target = this.getCommitFromRef(ref); + this.validateAndMakeTag(name, target); +}; + GitEngine.prototype.validateAndDeleteBranch = function(name) { // trying to delete, lets check our refs var target = this.resolveID(name); @@ -2810,8 +2894,20 @@ var Commit = Backbone.Model.extend({ } }); +var Tag = Ref.extend({ + defaults: { + visTag: null + }, + + initialize: function() { + Ref.prototype.initialize.call(this); + this.set('type', 'tag'); + } +}); + exports.GitEngine = GitEngine; exports.Commit = Commit; exports.Branch = Branch; +exports.Tag = Tag; exports.Ref = Ref; diff --git a/src/js/intl/strings.js b/src/js/intl/strings.js index 3d20eb9d..b8f2a9ab 100644 --- a/src/js/intl/strings.js +++ b/src/js/intl/strings.js @@ -288,6 +288,11 @@ exports.strings = { 'fr_FR': 'Ce nom de branche "{branch}" n\'est pas autorisé' }, /////////////////////////////////////////////////////////////////////////// + 'bad-tag-name': { + '__desc__': 'When the user enters a tag name thats not ok', + 'en_US': 'That tag name "{tag}" is not allowed!' + }, + /////////////////////////////////////////////////////////////////////////// 'option-not-supported': { '__desc__': 'When the user specifies an option that is not supported by our demo', 'en_US': 'The option "{option}" is not supported!', diff --git a/src/js/models/collections.js b/src/js/models/collections.js index 2247e83e..6007db5e 100644 --- a/src/js/models/collections.js +++ b/src/js/models/collections.js @@ -5,6 +5,7 @@ var Backbone = (!require('../util').isBrowser()) ? Backbone = require('backbone' var Commit = require('../git').Commit; var Branch = require('../git').Branch; +var Tag = require('../git').Tag; var Command = require('../models/commandModel').Command; var CommandEntry = require('../models/commandModel').CommandEntry; @@ -22,6 +23,10 @@ var BranchCollection = Backbone.Collection.extend({ model: Branch }); +var TagCollection = Backbone.Collection.extend({ + model: Tag +}); + var CommandEntryCollection = Backbone.Collection.extend({ model: CommandEntry, localStorage: (Backbone.LocalStorage) ? new Backbone.LocalStorage('CommandEntries') : null @@ -125,6 +130,7 @@ var CommandBuffer = Backbone.Model.extend({ exports.CommitCollection = CommitCollection; exports.CommandCollection = CommandCollection; exports.BranchCollection = BranchCollection; +exports.TagCollection = TagCollection; exports.CommandEntryCollection = CommandEntryCollection; exports.CommandBuffer = CommandBuffer; diff --git a/src/js/util/constants.js b/src/js/util/constants.js index c9ae1b76..c2a43261 100644 --- a/src/js/util/constants.js +++ b/src/js/util/constants.js @@ -33,6 +33,7 @@ var GRAPHICS = { originDash: '- ', multiBranchY: 20, + multiTagY: 15, upstreamHeadOpacity: 0.5, upstreamNoneOpacity: 0.2, edgeUpstreamHeadOpacity: 0.4, @@ -45,6 +46,10 @@ var GRAPHICS = { defaultNodeStrokeWidth: 2, defaultNodeStroke: '#FFF', + tagFill: 'hsb(0,0,0.9)', + tagStroke: '#FFF', + tagStrokeWidth: '2', + orphanNodeFill: 'hsb(0.5,0.8,0.7)' }; diff --git a/src/js/visuals/index.js b/src/js/visuals/index.js index 013e8b6f..5ddb6cec 100644 --- a/src/js/visuals/index.js +++ b/src/js/visuals/index.js @@ -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(); }; diff --git a/src/js/visuals/visEdge.js b/src/js/visuals/visEdge.js index 3e98df26..99ea9cec 100644 --- a/src/js/visuals/visEdge.js +++ b/src/js/visuals/visEdge.js @@ -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 }; diff --git a/src/js/visuals/visNode.js b/src/js/visuals/visNode.js index a8628562..22ee35da 100644 --- a/src/js/visuals/visNode.js +++ b/src/js/visuals/visNode.js @@ -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 }; diff --git a/src/js/visuals/visTag.js b/src/js/visuals/visTag.js new file mode 100644 index 00000000..1bb086f4 --- /dev/null +++ b/src/js/visuals/visTag.js @@ -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; + diff --git a/src/js/visuals/visualization.js b/src/js/visuals/visualization.js index 1a508991..bb0c4ff5 100644 --- a/src/js/visuals/visualization.js +++ b/src/js/visuals/visualization.js @@ -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 });