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,124 +1,140 @@
/** 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() {
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> [<args>] \
")
});
}]
];
};
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]();
} }
}); },
// then check if shortcut exists, and replace, but initialize: function() {
// preserve options if so this.validateAtInit();
_.each(this.getShortcutMap(), function(regex, method) { this.parse();
var results = regex.exec(str); },
if (results) {
str = method + ' ' + str.slice(results[0].length); 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 <command> [<args>] \
")
});
}]
];
},
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 // then check if it's one of our sandbox commands
if (str.slice(0,3) !== 'git') { _.each(this.getSandboxCommands(), function(tuple) {
throw new CommandProcessError({ var regex = tuple[0];
msg: 'Git commands only, sorry!' if (regex.exec(str)) {
tuple[1]();
}
}); });
}
// ok, we have a (probably) valid command. actually parse it // then check if shortcut exists, and replace, but
this.gitParse(str); // preserve options if so
}; _.each(this.getShortcutMap(), function(regex, method) {
var results = regex.exec(str);
Command.prototype.gitParse = function(str) { if (results) {
// now slice off command part str = method + ' ' + str.slice(results[0].length);
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
}); });
}
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 * 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 = {