diff --git a/src/collections.js b/src/collections.js new file mode 100644 index 00000000..0392b82e --- /dev/null +++ b/src/collections.js @@ -0,0 +1,5 @@ +var CommitCollection = Backbone.Collection.extend({ + model: Commit +}); + +var commitCollection = new CommitCollection(); diff --git a/src/commandline.js b/src/commandline.js index c8713f9f..fde0325a 100644 --- a/src/commandline.js +++ b/src/commandline.js @@ -37,22 +37,24 @@ Command.prototype.getRegexMap = function() { Command.prototype.getSandboxCommands = function() { return [ [/^ls/, function() { - throw new CommandResult("\ - DontWorryAboutFilesInThisDemo.txt\ - "); + throw new CommandResult({ + msg: "DontWorryAboutFilesInThisDemo.txt" + }); }], [/^cd/, function() { - throw new CommandResult("\ - Directory Changed to '/directories/dont/matter/in/this/demo' \ - "); + throw new CommandResult({ + msg: "Directory Changed to '/directories/dont/matter/in/this/demo'" + }); }], [/^git$/, function() { - throw new CommandResult(_.escape("\ - Git Version \n \ - PCOTTLE.1.0 \ - Usage: \n \ - git [] \ - ")); + throw new CommandResult({ + msg: _.escape("\ + Git Version \n \ + PCOTTLE.1.0 \ + Usage: \n \ + git [] \ + ") + }); }] ]; }; @@ -60,7 +62,7 @@ Command.prototype.getSandboxCommands = function() { Command.prototype.parse = function(str) { // first if the string is empty, they just want a blank line if (!str.length) { - throw new CommandResult(""); + throw new CommandResult({msg: ""}); } // then check if it's one of our sandbox commands @@ -82,7 +84,9 @@ Command.prototype.parse = function(str) { // see if begins with git if (str.slice(0,3) !== 'git') { - throw new CommandProcessError('Git commands only, sorry!'); + throw new CommandProcessError({ + msg: 'Git commands only, sorry!' + }); } // ok, we have a (probably) valid command. actually parse it @@ -106,9 +110,9 @@ Command.prototype.gitParse = function(str) { }, this); if (!matched) { - throw new CommandProcessError( - "Sorry, this demo does not support that git command: " + this.fullCommand - ); + throw new CommandProcessError({ + msg: "Sorry, this demo does not support that git command: " + this.fullCommand + }); } this.optionParser = new OptionParser(this.method, this.options); @@ -170,7 +174,9 @@ OptionParser.prototype.explodeAndSet = function() { if (part.slice(0,1) == '-') { // it's an option, check supportedMap if (this.supportedMap[part] === undefined) { - throw new CommandProcessError('The option "' + part + '" is not supported'); + throw new CommandProcessError({ + msg: 'The option "' + part + '" is not supported' + }); } // go through and include all the next args until we hit another option or the end diff --git a/src/constants.js b/src/constants.js index fe99076c..ce56f409 100644 --- a/src/constants.js +++ b/src/constants.js @@ -20,10 +20,21 @@ var graphics = { edgeStroke: 'rgba(94%, 96%, 98%, 0.5)', // '#EFF5FB', nodeEdge: 'rgba(94%, 96%, 98%, 0.9)', // '#EFF5FB', nodeFill: '#0066cc', + nodeRadius: 10, // widths nodeStrokeWidth: 15, edgeWidth: 2, + + // ref names + refFont: '14pt Courier New', + refFontFill: '#FFF', + + // ref arrows + arrowFill: '#FFF', + arrowStroke: '#000', + arrowWidth: 4, + arrowHeadWidth: 5 }; function randomString(string_length) { diff --git a/src/errors.js b/src/errors.js index d82863ca..0e3b3be6 100644 --- a/src/errors.js +++ b/src/errors.js @@ -1,31 +1,35 @@ -function CommandProcessError(msg) { - this.msg = msg; -} +var MyError = Backbone.Model.extend({ + defaults: { + type: 'MyError', + msg: 'Unknown Error' + }, + toString: function() { + return this.get('type') + ': ' + this.get('msg'); + }, -CommandProcessError.prototype.toString = function() { - return 'Command Process Error: ' + this.msg; -}; + getMsg: function() { + return this.get('msg') || 'Unknown Error'; + }, -CommandProcessError.prototype.toResult = function() { - return this.msg.replace('\n', '
'); -}; + toResult: function() { + return this.get('msg').replace('\n', '
'); + } +}); -function CommandResult(msg) { - this.msg = msg; -} +var CommandProcessError = MyError.extend({ + defaults: { + type: 'Command Process Error' + } +}); -CommandResult.prototype.toString = function() { - return 'Command Result: ' + this.msg; -}; +var CommandResult = MyError.extend({ + defaults: { + type: 'Command Result' + } +}); -CommandResult.prototype.toResult = function() { - return this.msg.replace('\n', '
'); -}; - -function GitError(msg) { - this.msg = msg; -} - -GitError.prototype.toString = function() { - return 'Git Error: ' + this.msg; -}; +var GitError = MyError.extend({ + defaults: { + type: 'Git Error' + } +}); diff --git a/src/git.js b/src/git.js index 3496c0f4..ab802dbd 100644 --- a/src/git.js +++ b/src/git.js @@ -13,6 +13,7 @@ function GitEngine() { this.HEAD = null; this.id_gen = 0; this.branches = []; + this.collection = commitCollection; // global variable to keep track of the options given // along with the command call. @@ -27,6 +28,8 @@ function GitEngine() { GitEngine.prototype.init = function() { // make an initial commit and a master branch this.rootCommit = new Commit({rootCommit: true}); + commitCollection.add(this.rootCommit); + this.refs[this.rootCommit.get('id')] = this.rootCommit; var master = this.makeBranch('master', this.rootCommit); @@ -50,10 +53,14 @@ GitEngine.prototype.getDetachedHead = function() { GitEngine.prototype.validateBranchName = function(name) { name = name.replace(/\s/g, ''); if (!/^[a-zA-Z0-9]+$/.test(name)) { - throw new Error('woah bad branch name!! This is not ok: ' + name); + throw new GitError({ + msg: 'woah bad branch name!! This is not ok: ' + name + }); } if (/[hH][eE][aA][dD]/.test(name)) { - throw new Error('branch name of "head" is ambiguous, dont name it that'); + throw new GitError({ + msg: 'branch name of "head" is ambiguous, dont name it that' + }); } return name; }; @@ -61,7 +68,9 @@ GitEngine.prototype.validateBranchName = function(name) { GitEngine.prototype.makeBranch = function(id, target) { id = this.validateBranchName(id); if (this.refs[id]) { - throw new Error('that branch id already exists!'); + throw new GitError({ + msg: 'that branch id already exists!' + }); } var branch = new Branch({ @@ -73,18 +82,34 @@ GitEngine.prototype.makeBranch = function(id, target) { return branch; }; +GitEngine.prototype.getHead = function() { + return _.clone(this.HEAD); +}; + GitEngine.prototype.getBranches = function() { var toReturn = []; _.each(this.branches, function(branch) { toReturn.push({ id: branch.get('id'), - selected: this.HEAD.get('target') === branch + selected: this.HEAD.get('target') === branch, + target: branch.get('target') }); }, this); return toReturn; }; GitEngine.prototype.printBranches = function() { + var branches = this.getBranches(); + var result = ''; + _.each(branches, function(branch) { + result += (branch.selected ? '* ' : '') + branch.id + '\n'; + }); + throw new CommandResult({ + msg: result + }); +}; + +GitEngine.prototype.logBranches = function() { var branches = this.getBranches(); _.each(branches, function(branch) { console.log((branch.selected ? '* ' : '') + branch.id); @@ -96,12 +121,15 @@ GitEngine.prototype.makeCommit = function(parent) { parents: [parent] }); this.refs[commit.get('id')] = commit; + this.collection.add(commit); return commit; }; GitEngine.prototype.acceptNoGeneralArgs = function() { if (this.generalArgs.length) { - throw new GitError("That command accepts no general arguments"); + throw new GitError({ + msg: "That command accepts no general arguments" + }); } }; @@ -167,10 +195,14 @@ GitEngine.prototype.resolveStringRef = function(ref) { }, this); if (!startRef) { - throw new Error('unknown ref ' + ref); + throw new GitError({ + msg: 'unknown ref ' + ref + }); } if (!this.refs[startRef]) { - throw new Error('the ref ' + startRef +' does not exist.'); + throw new GitError({ + msg: 'the ref ' + startRef +' does not exist.' + }); } var commit = this.getCommitFromRef(startRef); @@ -224,14 +256,18 @@ GitEngine.prototype.numBackFrom = function(commit, numBack) { } if (numBack !== 0 || pQueue.length == 0) { - throw new Error('exhausted search, sorry'); + throw new GitError({ + msg: "Sorry, I can't go that many commits back" + }); } return pQueue.shift(0); }; GitEngine.prototype.checkoutStarter = function() { if (this.generalArgs.length != 1) { - throw new GitError('I expect one argument along with git checkout (dont reference files)'); + throw new GitError({ + msg: 'I expect one argument along with git checkout (dont reference files)' + }); } this.checkout(this.generalArgs[0]); @@ -249,7 +285,9 @@ GitEngine.prototype.checkout = function(idOrTarget) { var type = target.get('type'); if (type !== 'branch' && type !== 'commit') { - throw new Error('can only checkout branches and commits!'); + throw new GitError({ + msg: 'can only checkout branches and commits!' + }); } this.HEAD.set('target', target); @@ -259,7 +297,9 @@ GitEngine.prototype.branchStarter = function() { // handle deletion first if (this.commandOptions['-d'] || this.commandOptions['-D']) { if (!this.generalArgs.length) { - throw new GitError('I expect branch names when deleting'); + throw new GitError({ + msg: 'I expect branch names when deleting' + }); } _.each(this.generalArgs, function(name) { this.deleteBranch(name); @@ -269,11 +309,14 @@ GitEngine.prototype.branchStarter = function() { var len = this.generalArgs.length; if (len > 2) { - throw new GitError('git branch with more than two general args does not make sense!'); + throw new GitError({ + msg: 'git branch with more than two general args does not make sense!' + }); } + + if (len == 0) { - throw new Error('going to implement eventually'); - //TODO: print branches, etc + this.printBranches(); return; } @@ -293,13 +336,19 @@ GitEngine.prototype.deleteBranch = function(name) { // trying to delete, lets check our refs var target = this.resolveId(name); if (target.get('type') !== 'branch') { - throw new GitError("You can't delete things that arent branches with branch command"); + throw new GitError({ + msg: "You can't delete things that arent branches with branch command" + }); } if (target.get('id') == 'master') { - throw new GitError("You can't delete the master branch!"); + throw new GitError({ + msg: "You can't delete the master branch!" + }); } if (this.HEAD.get('target') === target) { - throw new GitError("Cannot delete the branch you are currently on"); + throw new GitError({ + msg: "Cannot delete the branch you are currently on" + }); } var id = target.get('id'); @@ -315,10 +364,10 @@ GitEngine.prototype.dispatch = function(commandObj) { }; GitEngine.prototype.add = function() { - throw new Error( - "This demo is meant to demonstrate git branching, so don't worry about " + - "adding / staging files. Just go ahead and commit away!" - ); + throw new CommandResult({ + msg: "This demo is meant to demonstrate git branching, so don't worry about " + + "adding / staging files. Just go ahead and commit away!" + }); }; GitEngine.prototype.execute = function(command, callback) { @@ -401,7 +450,6 @@ var Commit = Backbone.Model.extend({ initialize: function() { this.validateAtInit(); this.addNodeToVisuals(); - console.log('MAKING NEW COMMIT', this.get('id')); _.each(this.get('parents'), function(parent) { parent.get('children').push(this); diff --git a/src/index.html b/src/index.html index 91fcb43b..1757ce07 100644 --- a/src/index.html +++ b/src/index.html @@ -51,5 +51,7 @@ + + diff --git a/src/legacy.js b/src/legacy.js index 77df5ec5..ee4fdcf9 100644 --- a/src/legacy.js +++ b/src/legacy.js @@ -39,6 +39,7 @@ Renderer = function(canvas) { ctx.clearRect(0,0, canvas.width, canvas.height); particleSystem.eachEdge(this.drawEdge); particleSystem.eachNode(this.drawNode); + events.trigger('drawGitVisuals', particleSystem, ctx, canvas); }, resize: function(){ diff --git a/src/mine.js b/src/mine.js index f1b7fb96..43f2e9c8 100644 --- a/src/mine.js +++ b/src/mine.js @@ -5,6 +5,7 @@ var events = _.clone(Backbone.Events); var sys = null; var graphicsEffects = {}; var gitEngine = null; +var gitVisuals = null; $(document).ready(function(){ sys = arbor.ParticleSystem(4000, 500, 0.5, false, 55, 0.005, 'verlet'); @@ -18,6 +19,7 @@ $(document).ready(function(){ }); gitEngine = new GitEngine(); + gitVisuals = new GitVisuals(); var repulsionBreathe = function(r) { sys.parameters({repulsion: r}); @@ -40,9 +42,10 @@ Node.prototype.drawCircleNode = function(ctx, pt) { ctx.strokeStyle = graphics.nodeEdge; ctx.lineWidth = graphics.nodeStrokeWidth; ctx.fillStyle = graphics.nodeFill; + var radius = graphics.nodeRadius; ctx.beginPath(); - ctx.arc(pt.x, pt.y, 10, 0, Math.PI*2, true); + ctx.arc(pt.x, pt.y, radius, 0, Math.PI*2, true); ctx.closePath(); ctx.stroke(); ctx.fill(); diff --git a/src/views.js b/src/views.js index 6739dfb4..bd00e81d 100644 --- a/src/views.js +++ b/src/views.js @@ -7,7 +7,7 @@ var CommandLineView = Backbone.View.extend({ $.proxy(this.keyUp, this) ); - events.on('commandConsumed', _.bind( + events.on('commandReadyForProcess', _.bind( this.parseOrCatch, this )); }, @@ -53,8 +53,8 @@ var CommandLineView = Backbone.View.extend({ processError: function(err) { // in this demo, every command that's not a git command will // throw an exception. Some of these errors might be just to - // short-circuit the normal programatic flow, so we handle them - // here + // short-circuit the normal programatic flow and print stuff, + // so we handle them here if (err instanceof CommandProcessError) { events.trigger('commandProcessError', err); } else if (err instanceof CommandResult) { @@ -81,13 +81,13 @@ var CommandLineView = Backbone.View.extend({ } this.index = -1; - events.trigger('commandConsumed', value); + events.trigger('commandSubmitted', value); + events.trigger('commandReadyForProcess', value); }, parseOrCatch: function(value) { try { var command = new Command(value); - console.log(command); events.trigger('gitCommandReady', command); } catch (err) { this.processError(err); @@ -97,7 +97,7 @@ var CommandLineView = Backbone.View.extend({ var CommandLineHistoryView = Backbone.View.extend({ initialize: function(options) { - events.on('commandConsumed', _.bind( + events.on('commandSubmitted', _.bind( this.addCommand, this )); @@ -165,7 +165,8 @@ var CommandLineHistoryView = Backbone.View.extend({ }, commandResultPrint: function(err) { - if (!err.msg.length) { + if (!err.get('msg') || !err.get('msg').length) { + console.log(err); // blank lines return; } diff --git a/src/visuals.js b/src/visuals.js new file mode 100644 index 00000000..efa7740d --- /dev/null +++ b/src/visuals.js @@ -0,0 +1,57 @@ +function GitVisuals() { + this.collection = commitCollection; + + this.collection.on('change', _.bind(this.collectionChanged, this)); + events.on('drawGitVisuals', _.bind(this.drawVisuals, this)); +} + +GitVisuals.prototype.drawVisuals = function(sys, ctx, canvas) { + // we need to draw refs here + var branches = gitEngine.getBranches(); + var detachedHead = gitEngine.getDetachedHead(); + var HEAD = gitEngine.getHead(); + + _.forEach(branches, _.bind(function(branch) { + // get the location of the arbor node and then somehow draw the ref to the side? + var node = branch.target.get('arborNode'); + var nodePoint = sys.toScreen(node._p); + + // text position + // TODO: better positioning of text here + var screenPoint = _.clone(nodePoint); + screenPoint.x += 100; + + ctx.font = graphics.refFont; + ctx.fillStyle = graphics.refFontFill; + ctx.fillText(branch.id, screenPoint.x, screenPoint.y); + + // also draw an arrow + var offset = Math.round(graphics.nodeRadius * 2.5); + this.drawArrow(ctx, screenPoint, nodePoint, graphics.arrowHeadWidth, offset); + }, this)); +}; + +GitVisuals.prototype.drawArrow = function(ctx, start, end, headWidth, offset) { + // TODO only horizontal arrows for now, fix this later + var end = _.clone(end); + end.x += offset; + + ctx.lineWidth = graphics.arrowWidth; + ctx.fillStyle = graphics.arrowFill; + ctx.strokeStyle = graphics.arrowStroke; + + ctx.beginPath(); + ctx.moveTo(start.x, start.y); + ctx.lineTo(end.x, end.y); + + // now do the little arrow head + ctx.lineTo(end.x + headWidth, end.y + headWidth); + ctx.lineTo(end.x + headWidth, end.y - headWidth); + ctx.lineTo(end.x, end.y); + + ctx.stroke(); +}; + +GitVisuals.prototype.collectionChanged = function() { + // redo the algorithms +};