diff --git a/src/collections.js b/src/collections.js index 0392b82e..1c9c20d0 100644 --- a/src/collections.js +++ b/src/collections.js @@ -3,3 +3,75 @@ var CommitCollection = Backbone.Collection.extend({ }); var commitCollection = new CommitCollection(); + +var CommandCollection = Backbone.Collection.extend({ + model: Command +}); + +var CommandBuffer = Backbone.Model.extend({ + initialize: function() { + events.on('gitCommandReady', _.bind( + this.addCommand, this + )); + + this.collection = new CommandCollection(); + this.buffer = []; + this.timeout = null; + this.delay = 300; + }, + + addCommand: function(command) { + this.collection.add(command); + this.buffer.push(command); + this.touchBuffer(); + }, + + touchBuffer: function() { + // touch buffer just essentially means we just check if our buffer is being + // processed. if it's not, we immediately process the first item + // and then set the timeout. + if (this.timeout) { + // timeout existence implies its being processed + return; + } + + // process first element now + this.popAndProcess(); + // always set the timeout, regardless of buffer size + this.setTimeout(); + }, + + + setTimeout: function() { + this.timeout = setTimeout(_.bind(function() { + this.sipFromBuffer(); + }, this), 300); + }, + + popAndProcess: function() { + var popped = this.buffer.pop(); + events.trigger('processCommand', popped); + }, + + clear: function() { + clearTimeout(this.timeout); + this.timeout = null; + }, + + sipFromBuffer: function() { + if (!this.buffer.length) { + this.clear(); + return; + } + + this.popAndProcess(); + if (this.buffer.length) { + this.setTimeout(); + } else { + this.clear(); + } + }, + +}); + +var commandBuffer = new CommandBuffer(); diff --git a/src/commandline.js b/src/commandline.js index 8023ec07..70517048 100644 --- a/src/commandline.js +++ b/src/commandline.js @@ -1,124 +1,140 @@ -/** - * class Command - * @desc A parser for commands given - */ -function Command(str) { - this.fullCommand = null; - this.options = null; - this.method = null; +var Command = Backbone.Model.extend({ + defaults: { + status: 'inqueue', + generalArgs: [], + supportedMap: {}, + options: null, + method: null, + rawStr: null + }, - this.parse(str); -} - -Command.prototype.getShortcutMap = function() { - return { - 'git commit': /^gc($|\s)/, - 'git add': /^ga($|\s)/, - 'git checkout': /^gchk($|\s)/, - 'git rebase': /^gr($|\s)/, - 'git branch': /^gb($|\s)/ - }; -}; - -Command.prototype.getRegexMap = function() { - return { - // ($|\s) means that we either have to end the string - // after the command or there needs to be a space for options - commit: /^commit($|\s)/, - add: /^add($|\s)/, - checkout: /^checkout($|\s)/, - rebase: /^rebase($|\s)/, - reset: /^reset($|\s)/, - branch: /^branch($|\s)/, - revert: /^revert($|\s)/, - merge: /^merge($|\s)/ - }; -}; - -Command.prototype.getSandboxCommands = function() { - return [ - [/^ls/, function() { - throw new CommandResult({ - msg: "DontWorryAboutFilesInThisDemo.txt" - }); - }], - [/^cd/, function() { - throw new CommandResult({ - msg: "Directory Changed to '/directories/dont/matter/in/this/demo'" - }); - }], - [/^git$/, function() { - // TODO better git description. also help, hint, etc - throw new CommandResult({ - msg: _.escape("\ - Git Version \n \ - PCOTTLE.1.0 \ - Usage: \n \ - git [] \ - ") - }); - }] - ]; -}; - -Command.prototype.parse = function(str) { - // first if the string is empty, they just want a blank line - if (!str.length) { - throw new CommandResult({msg: ""}); - } - - // then check if it's one of our sandbox commands - _.each(this.getSandboxCommands(), function(tuple) { - var regex = tuple[0]; - if (regex.exec(str)) { - tuple[1](); + validateAtInit: function() { + if (!this.get('rawStr')) { + throw new Error('Give me a string!'); } - }); + }, - // then check if shortcut exists, and replace, but - // preserve options if so - _.each(this.getShortcutMap(), function(regex, method) { - var results = regex.exec(str); - if (results) { - str = method + ' ' + str.slice(results[0].length); + initialize: function() { + this.validateAtInit(); + this.parse(); + }, + + getShortcutMap: function() { + return { + 'git commit': /^gc($|\s)/, + 'git add': /^ga($|\s)/, + 'git checkout': /^gchk($|\s)/, + 'git rebase': /^gr($|\s)/, + 'git branch': /^gb($|\s)/ + }; + }, + + getRegexMap: function() { + return { + // ($|\s) means that we either have to end the string + // after the command or there needs to be a space for options + commit: /^commit($|\s)/, + add: /^add($|\s)/, + checkout: /^checkout($|\s)/, + rebase: /^rebase($|\s)/, + reset: /^reset($|\s)/, + branch: /^branch($|\s)/, + revert: /^revert($|\s)/, + merge: /^merge($|\s)/ + }; + }, + + getSandboxCommands: function() { + return [ + [/^ls/, function() { + throw new CommandResult({ + msg: "DontWorryAboutFilesInThisDemo.txt" + }); + }], + [/^cd/, function() { + throw new CommandResult({ + msg: "Directory Changed to '/directories/dont/matter/in/this/demo'" + }); + }], + [/^git$/, function() { + // TODO better git description. also help, hint, etc + throw new CommandResult({ + msg: _.escape("\ + Git Version \n \ + PCOTTLE.1.0 \ + Usage: \n \ + git [] \ + ") + }); + }] + ]; + }, + + parse: function() { + var str = this.get('rawStr'); + // first if the string is empty, they just want a blank line + if (!str.length) { + throw new CommandResult({msg: ""}); } - }); - // see if begins with git - if (str.slice(0,3) !== 'git') { - throw new CommandProcessError({ - msg: 'Git commands only, sorry!' + // then check if it's one of our sandbox commands + _.each(this.getSandboxCommands(), function(tuple) { + var regex = tuple[0]; + if (regex.exec(str)) { + tuple[1](); + } }); - } - // ok, we have a (probably) valid command. actually parse it - this.gitParse(str); -}; - -Command.prototype.gitParse = function(str) { - // now slice off command part - this.fullCommand = str.slice('git '.length); - - // see if we support this particular command - var matched = false; - _.each(this.getRegexMap(), function(regex, method) { - if (regex.exec(this.fullCommand)) { - this.options = this.fullCommand.slice(method.length + 1); - this.method = method; - // we should stop iterating, but the regex will only match - // one command in practice - matched = true; - } - }, this); - - if (!matched) { - throw new CommandProcessError({ - msg: "Sorry, this demo does not support that git command: " + this.fullCommand + // then check if shortcut exists, and replace, but + // preserve options if so + _.each(this.getShortcutMap(), function(regex, method) { + var results = regex.exec(str); + if (results) { + str = method + ' ' + str.slice(results[0].length); + } }); - } - this.optionParser = new OptionParser(this.method, this.options); -}; + // see if begins with git + if (str.slice(0,3) !== 'git') { + throw new CommandProcessError({ + msg: 'Git commands only, sorry!' + }); + } + + // ok, we have a (probably) valid command. actually parse it + this.gitParse(str); + }, + + gitParse: function(str) { + // now slice off command part + var fullCommand = str.slice('git '.length); + + // see if we support this particular command + _.each(this.getRegexMap(), function(regex, method) { + if (regex.exec(fullCommand)) { + this.set('options', fullCommand.slice(method.length + 1)); + this.set('method', method); + // we should stop iterating, but the regex will only match + // one command in practice. we could stop iterating if we used + // jqeurys for each but im using underscore (for no real reason other + // than style) + } + }, this); + + if (!this.get('method')) { + throw new CommandProcessError({ + msg: "Sorry, this demo does not support that git command: " + fullCommand + }); + } + + // parse off the options and assemble the map / general args + var optionParser = new OptionParser(this.get('method'), this.get('options')); + + // steal these away so we can be completely JSON + this.set('generalArgs', optionParser.generalArgs); + this.set('supportedMap', optionParser.supportedMap); + }, +}); /** * OptionParser @@ -203,4 +219,3 @@ OptionParser.prototype.explodeAndSet = function() { // done! }; - diff --git a/src/git.js b/src/git.js index 8c1e53ac..072565e2 100644 --- a/src/git.js +++ b/src/git.js @@ -20,7 +20,7 @@ function GitEngine() { this.commandOptions = {}; this.generalArgs = []; - events.on('gitCommandReady', _.bind(this.dispatch, this)); + events.on('processCommand', _.bind(this.dispatch, this)); this.init(); } @@ -675,11 +675,14 @@ GitEngine.prototype.deleteBranch = function(name) { this.branches.splice(toDelete, 1); }; -GitEngine.prototype.dispatch = function(commandObj) { - this.commandOptions = commandObj.optionParser.supportedMap; - this.generalArgs = commandObj.optionParser.generalArgs; +GitEngine.prototype.dispatch = function(command) { + // current command, options, and args are stored in the gitEngine + // for easy reference during processing. + this.command = command; + this.commandOptions = command.get('supportedMap'); + this.generalArgs = command.get('generalArgs'); - this[commandObj.method + 'Starter'](); + this[command.get('method') + 'Starter'](); }; GitEngine.prototype.addStarter = function() { diff --git a/src/index.html b/src/index.html index 1757ce07..c3ac804b 100644 --- a/src/index.html +++ b/src/index.html @@ -42,6 +42,18 @@ + + + diff --git a/src/mine.js b/src/mine.js index 268ce6b5..4d5a054b 100644 --- a/src/mine.js +++ b/src/mine.js @@ -11,7 +11,7 @@ $(document).ready(function(){ sys = arbor.ParticleSystem(4000, 200, 0.5, false, 55, 0.005, 'verlet'); sys.renderer = Renderer('#viewport'); - new CommandLineView({ + new CommandPromptView({ el: $('#commandLineBar') }); new CommandLineHistoryView({ @@ -20,14 +20,6 @@ $(document).ready(function(){ gitEngine = new GitEngine(); gitVisuals = new GitVisuals(); - - var repulsionBreathe = function(r) { - sys.parameters({repulsion: r}); - }; - // TODO: decide on breather - // var b = new Breather(repulsionBreathe, 6050, 4000); - - graphicsEffects.edgeStrokeEffect = new GraphicsEffect('edgeStroke', {wait: 1000}); }); @@ -73,88 +65,3 @@ Edge.prototype.drawLine = function(ctx, pt1, pt2, opacityPercent) { ctx.stroke(); }; - -/** - * class GraphicsEffect - */ -function GraphicsEffect(gKey, options) { - this.baseColor = graphics[gKey]; - - this.closure = (function(base_color) { - var oSetter = function(o) { - var color = new Color(base_color); - color.a *= o; - graphics[gKey] = color.toRGBA(); - }; - return oSetter; - })(this.baseColor); - - this.breather = new Breather( - this.closure, - options.midpoint || 0.9, - options.amp || 0.85, - options.period || 0.1, - options.wait || 0 - ); -} - -GraphicsEffect.prototype.pause = function() { - this.breather.stop(); -}; - -GraphicsEffect.prototype.resume = function() { - this.breather.next(); -}; - -/** - * class Breather - */ -function Breather(closure, baseline, delta, period, wait) { - this.delta = delta; - this.baseline = baseline; - this.closure = closure; - - this.t = 0; - this.interval = 1/40 * 1000; // 40fps - - var period_in_seconds = period || time.breathePeriod; - this.period = 2 * Math.PI * 1000 * period_in_seconds; - - this.interpolationFunction = TWEEN.Easing.Cubic.EaseInOut; - - if (wait) { - var _this = this; - setTimeout(function() { - _this.start(); - }, wait); - } else { - this.start(); - } -} - -Breather.prototype.start = function() { - this.t = 0; - this.next(); -}; - -Breather.prototype.next = function() { - this.timeout = setTimeout( - $.proxy(function() { - this.breathe(); - }, this), - this.interval); -}; - -Breather.prototype.stop = function() { - clearTimeout(this.timeout); -}; - -Breather.prototype.breathe = function() { - this.t += this.interval; - - var value = Math.sin(this.t / this.period) * this.delta + this.baseline; - this.closure(value); - - this.next(); -}; - diff --git a/src/style/main.css b/src/style/main.css index 0941877d..7db744d4 100644 --- a/src/style/main.css +++ b/src/style/main.css @@ -43,7 +43,7 @@ p.commandError, p.errorResult { color: red; } -span.arrows { +p.commandLine span.arrows { color: greenyellow; font-weight: bold; } diff --git a/src/templates/command.html b/src/templates/command.html deleted file mode 100644 index d47f63b9..00000000 --- a/src/templates/command.html +++ /dev/null @@ -1,4 +0,0 @@ -

- > > > - <%= command %> -

diff --git a/src/views.js b/src/views.js index 4293f2a0..21cd057c 100644 --- a/src/views.js +++ b/src/views.js @@ -1,15 +1,19 @@ -var CommandLineView = Backbone.View.extend({ +var CommandPromptView = Backbone.View.extend({ initialize: function(options) { this.commands = []; this.index = -1; - this.$('#commandTextField').keyup( - $.proxy(this.keyUp, this) - ); - - events.on('commandReadyForProcess', _.bind( + events.on('commandSubmitted', _.bind( this.parseOrCatch, this )); + + events.on('processErrorGeneral', _.bind( + this.processError, this + )); + }, + + events: { + 'keyup #commandTextField': 'keyUp' }, keyUp: function(e) { @@ -39,7 +43,8 @@ var CommandLineView = Backbone.View.extend({ commandSelectChange: function(delta) { this.index += delta; - // if we are over / under, display blank line + // if we are over / under, display blank line. yes this eliminates your + // partially written command, but i doubt that is much in this demo if (this.index >= this.commands.length || this.index < 0) { this.clear(); this.index = -1; @@ -51,6 +56,8 @@ var CommandLineView = Backbone.View.extend({ }, processError: function(err) { + // TODO move this somewhere else!!! it's awkward here + // 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 and print stuff, @@ -59,6 +66,8 @@ var CommandLineView = Backbone.View.extend({ events.trigger('commandProcessError', err); } else if (err instanceof CommandResult) { events.trigger('commandResultPrint', err); + } else if (err instanceof GitError) { + events.trigger('commandGitError', err); } else { throw err; } @@ -82,26 +91,70 @@ var CommandLineView = Backbone.View.extend({ } this.index = -1; + // split commands on semicolon _.each(value.split(';'), function(command) { command = command.replace(/^(\s+)/, ''); command = command.replace(/(\s+)$/, ''); if (command.length) { events.trigger('commandSubmitted', command); - events.trigger('commandReadyForProcess', command); } }); }, parseOrCatch: function(value) { + // TODO: move this also try { - var command = new Command(value); + // parse validation + var command = new Command({ + rawStr: value + }); + // gitCommandReady actually gives it to the gitEngine for dispatch events.trigger('gitCommandReady', command); } catch (err) { - this.processError(err); + events.trigger('processErrorGeneral', err); } } }); + +// This is the view for all commands -- it will represent +// their status (inqueue, processing, finished, error), +// their value ("git commit --amend"), +// and the result (either errors or warnings or whatever) +var CommandView = Backbone.View.extend({ + tagName: 'div', + model: Command, + template: _.template($('#command-template').html()), + + events: { + 'click': 'alert' + }, + + alert: function() { alert('clicked!' + this.get('status')); }, + + initialize: function() { + this.model.bind('change', this.render, this); + this.model.bind('destroy', this.remove, this); + }, + + render: function() { + var json = _.extend( + { + resultType: '', + result: '' + }, + this.model.toJSON() + ); + this.$el.html(this.template(json)); + return this; + }, + + remove: function() { + $(this.el).hide(); + } +}); + + var CommandLineHistoryView = Backbone.View.extend({ initialize: function(options) { events.on('commandSubmitted', _.bind( @@ -112,6 +165,11 @@ var CommandLineHistoryView = Backbone.View.extend({ this.commandError, this )); + // TODO special errors for git? + events.on('commandGitError', _.bind( + this.commandError, this + )); + events.on('commandProcessWarn', _.bind( this.commandWarn, this )); diff --git a/src/visuals.js b/src/visuals.js index 7fb946d7..1c16ca0f 100644 --- a/src/visuals.js +++ b/src/visuals.js @@ -1,7 +1,4 @@ function GitVisuals() { - this.collection = commitCollection; - - this.collection.on('change', _.bind(this.collectionChanged, this)); events.on('drawGitVisuals', _.bind(this.drawVisuals, this)); events.on('fixNodePositions', _.bind(this.fixNodes, this)); } @@ -74,10 +71,6 @@ GitVisuals.prototype.drawArrow = function(ctx, start, end, headWidth, offset) { ctx.stroke(); }; -GitVisuals.prototype.collectionChanged = function() { - // redo the algorithms -}; - GitVisuals.prototype.fixRootCommit = function(sys) { // get the viewports bottom center var bottomPosScreen = {