BIG move towards backbone models and collections for everything

This commit is contained in:
Peter Cottle 2012-09-15 12:04:08 -07:00
parent 95277013e5
commit 5ac6b035f5
9 changed files with 290 additions and 234 deletions

View file

@ -3,3 +3,75 @@ var CommitCollection = Backbone.Collection.extend({
}); });
var commitCollection = new CommitCollection(); 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();

View file

@ -1,16 +1,25 @@
/** var Command = Backbone.Model.extend({
* class Command defaults: {
* @desc A parser for commands given status: 'inqueue',
*/ generalArgs: [],
function Command(str) { supportedMap: {},
this.fullCommand = null; options: null,
this.options = null; method: null,
this.method = null; rawStr: null
},
this.parse(str); validateAtInit: function() {
} if (!this.get('rawStr')) {
throw new Error('Give me a string!');
}
},
Command.prototype.getShortcutMap = function() { initialize: function() {
this.validateAtInit();
this.parse();
},
getShortcutMap: function() {
return { return {
'git commit': /^gc($|\s)/, 'git commit': /^gc($|\s)/,
'git add': /^ga($|\s)/, 'git add': /^ga($|\s)/,
@ -18,9 +27,9 @@ Command.prototype.getShortcutMap = function() {
'git rebase': /^gr($|\s)/, 'git rebase': /^gr($|\s)/,
'git branch': /^gb($|\s)/ 'git branch': /^gb($|\s)/
}; };
}; },
Command.prototype.getRegexMap = function() { getRegexMap: function() {
return { return {
// ($|\s) means that we either have to end the string // ($|\s) means that we either have to end the string
// after the command or there needs to be a space for options // after the command or there needs to be a space for options
@ -33,9 +42,9 @@ Command.prototype.getRegexMap = function() {
revert: /^revert($|\s)/, revert: /^revert($|\s)/,
merge: /^merge($|\s)/ merge: /^merge($|\s)/
}; };
}; },
Command.prototype.getSandboxCommands = function() { getSandboxCommands: function() {
return [ return [
[/^ls/, function() { [/^ls/, function() {
throw new CommandResult({ throw new CommandResult({
@ -59,9 +68,10 @@ Command.prototype.getSandboxCommands = function() {
}); });
}] }]
]; ];
}; },
Command.prototype.parse = function(str) { parse: function() {
var str = this.get('rawStr');
// first if the string is empty, they just want a blank line // first if the string is empty, they just want a blank line
if (!str.length) { if (!str.length) {
throw new CommandResult({msg: ""}); throw new CommandResult({msg: ""});
@ -93,32 +103,38 @@ Command.prototype.parse = function(str) {
// ok, we have a (probably) valid command. actually parse it // ok, we have a (probably) valid command. actually parse it
this.gitParse(str); this.gitParse(str);
}; },
Command.prototype.gitParse = function(str) { gitParse: function(str) {
// now slice off command part // now slice off command part
this.fullCommand = str.slice('git '.length); var fullCommand = str.slice('git '.length);
// see if we support this particular command // see if we support this particular command
var matched = false;
_.each(this.getRegexMap(), function(regex, method) { _.each(this.getRegexMap(), function(regex, method) {
if (regex.exec(this.fullCommand)) { if (regex.exec(fullCommand)) {
this.options = this.fullCommand.slice(method.length + 1); this.set('options', fullCommand.slice(method.length + 1));
this.method = method; this.set('method', method);
// we should stop iterating, but the regex will only match // we should stop iterating, but the regex will only match
// one command in practice // one command in practice. we could stop iterating if we used
matched = true; // jqeurys for each but im using underscore (for no real reason other
// than style)
} }
}, this); }, this);
if (!matched) { if (!this.get('method')) {
throw new CommandProcessError({ throw new CommandProcessError({
msg: "Sorry, this demo does not support that git command: " + this.fullCommand msg: "Sorry, this demo does not support that git command: " + fullCommand
}); });
} }
this.optionParser = new OptionParser(this.method, this.options); // 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 * OptionParser
@ -203,4 +219,3 @@ OptionParser.prototype.explodeAndSet = function() {
// done! // done!
}; };

View file

@ -20,7 +20,7 @@ function GitEngine() {
this.commandOptions = {}; this.commandOptions = {};
this.generalArgs = []; this.generalArgs = [];
events.on('gitCommandReady', _.bind(this.dispatch, this)); events.on('processCommand', _.bind(this.dispatch, this));
this.init(); this.init();
} }
@ -675,11 +675,14 @@ GitEngine.prototype.deleteBranch = function(name) {
this.branches.splice(toDelete, 1); this.branches.splice(toDelete, 1);
}; };
GitEngine.prototype.dispatch = function(commandObj) { GitEngine.prototype.dispatch = function(command) {
this.commandOptions = commandObj.optionParser.supportedMap; // current command, options, and args are stored in the gitEngine
this.generalArgs = commandObj.optionParser.generalArgs; // 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() { GitEngine.prototype.addStarter = function() {

View file

@ -42,6 +42,18 @@
<script src="../lib/underscore-min.js"></script> <script src="../lib/underscore-min.js"></script>
<script src="../lib/backbone-min.js"></script> <script src="../lib/backbone-min.js"></script>
<!-- Templates -->
<script type="text/html" id="command-template">
<p class="commandLine <%= status %>">
<span class="arrows">&gt; &gt; &gt;</span>
<%= rawStr %>
</p>
<p class="commandLine <%= resultType %>">
<%= result %>
</p>
</script>
<!-- My files! --> <!-- My files! -->
<script src="async.js"></script> <script src="async.js"></script>
<script src="mine.js"></script> <script src="mine.js"></script>

View file

@ -11,7 +11,7 @@ $(document).ready(function(){
sys = arbor.ParticleSystem(4000, 200, 0.5, false, 55, 0.005, 'verlet'); sys = arbor.ParticleSystem(4000, 200, 0.5, false, 55, 0.005, 'verlet');
sys.renderer = Renderer('#viewport'); sys.renderer = Renderer('#viewport');
new CommandLineView({ new CommandPromptView({
el: $('#commandLineBar') el: $('#commandLineBar')
}); });
new CommandLineHistoryView({ new CommandLineHistoryView({
@ -20,14 +20,6 @@ $(document).ready(function(){
gitEngine = new GitEngine(); gitEngine = new GitEngine();
gitVisuals = new GitVisuals(); 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(); 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();
};

View file

@ -43,7 +43,7 @@ p.commandError, p.errorResult {
color: red; color: red;
} }
span.arrows { p.commandLine span.arrows {
color: greenyellow; color: greenyellow;
font-weight: bold; font-weight: bold;
} }

View file

@ -1,4 +0,0 @@
<p class="<%= class %>">
<span class="arrows"> &gt; &gt; &gt; </span>
<%= command %>
</p>

View file

@ -1,15 +1,19 @@
var CommandLineView = Backbone.View.extend({ var CommandPromptView = Backbone.View.extend({
initialize: function(options) { initialize: function(options) {
this.commands = []; this.commands = [];
this.index = -1; this.index = -1;
this.$('#commandTextField').keyup( events.on('commandSubmitted', _.bind(
$.proxy(this.keyUp, this)
);
events.on('commandReadyForProcess', _.bind(
this.parseOrCatch, this this.parseOrCatch, this
)); ));
events.on('processErrorGeneral', _.bind(
this.processError, this
));
},
events: {
'keyup #commandTextField': 'keyUp'
}, },
keyUp: function(e) { keyUp: function(e) {
@ -39,7 +43,8 @@ var CommandLineView = Backbone.View.extend({
commandSelectChange: function(delta) { commandSelectChange: function(delta) {
this.index += 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) { if (this.index >= this.commands.length || this.index < 0) {
this.clear(); this.clear();
this.index = -1; this.index = -1;
@ -51,6 +56,8 @@ var CommandLineView = Backbone.View.extend({
}, },
processError: function(err) { processError: function(err) {
// TODO move this somewhere else!!! it's awkward here
// in this demo, every command that's not a git command will // in this demo, every command that's not a git command will
// throw an exception. Some of these errors might be just to // throw an exception. Some of these errors might be just to
// short-circuit the normal programatic flow and print stuff, // short-circuit the normal programatic flow and print stuff,
@ -59,6 +66,8 @@ var CommandLineView = Backbone.View.extend({
events.trigger('commandProcessError', err); events.trigger('commandProcessError', err);
} else if (err instanceof CommandResult) { } else if (err instanceof CommandResult) {
events.trigger('commandResultPrint', err); events.trigger('commandResultPrint', err);
} else if (err instanceof GitError) {
events.trigger('commandGitError', err);
} else { } else {
throw err; throw err;
} }
@ -82,26 +91,70 @@ var CommandLineView = Backbone.View.extend({
} }
this.index = -1; this.index = -1;
// split commands on semicolon
_.each(value.split(';'), function(command) { _.each(value.split(';'), function(command) {
command = command.replace(/^(\s+)/, ''); command = command.replace(/^(\s+)/, '');
command = command.replace(/(\s+)$/, ''); command = command.replace(/(\s+)$/, '');
if (command.length) { if (command.length) {
events.trigger('commandSubmitted', command); events.trigger('commandSubmitted', command);
events.trigger('commandReadyForProcess', command);
} }
}); });
}, },
parseOrCatch: function(value) { parseOrCatch: function(value) {
// TODO: move this also
try { 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); events.trigger('gitCommandReady', command);
} catch (err) { } 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({ var CommandLineHistoryView = Backbone.View.extend({
initialize: function(options) { initialize: function(options) {
events.on('commandSubmitted', _.bind( events.on('commandSubmitted', _.bind(
@ -112,6 +165,11 @@ var CommandLineHistoryView = Backbone.View.extend({
this.commandError, this this.commandError, this
)); ));
// TODO special errors for git?
events.on('commandGitError', _.bind(
this.commandError, this
));
events.on('commandProcessWarn', _.bind( events.on('commandProcessWarn', _.bind(
this.commandWarn, this this.commandWarn, this
)); ));

View file

@ -1,7 +1,4 @@
function GitVisuals() { function GitVisuals() {
this.collection = commitCollection;
this.collection.on('change', _.bind(this.collectionChanged, this));
events.on('drawGitVisuals', _.bind(this.drawVisuals, this)); events.on('drawGitVisuals', _.bind(this.drawVisuals, this));
events.on('fixNodePositions', _.bind(this.fixNodes, this)); events.on('fixNodePositions', _.bind(this.fixNodes, this));
} }
@ -74,10 +71,6 @@ GitVisuals.prototype.drawArrow = function(ctx, start, end, headWidth, offset) {
ctx.stroke(); ctx.stroke();
}; };
GitVisuals.prototype.collectionChanged = function() {
// redo the algorithms
};
GitVisuals.prototype.fixRootCommit = function(sys) { GitVisuals.prototype.fixRootCommit = function(sys) {
// get the viewports bottom center // get the viewports bottom center
var bottomPosScreen = { var bottomPosScreen = {