in th emiddle of transitioning to box flex

This commit is contained in:
Peter Cottle 2012-09-16 12:16:27 -07:00
parent f0e45deb1e
commit fb2a1325c6
7 changed files with 108 additions and 555 deletions

View file

@ -1,255 +0,0 @@
var Command = Backbone.Model.extend({
defaults: {
status: 'inqueue',
result: '',
error: null,
generalArgs: [],
supportedMap: {},
options: null,
method: null,
createTime: null,
rawStr: null
},
validateAtInit: function() {
if (!this.get('rawStr')) {
throw new Error('Give me a string!');
}
if (!this.get('createTime')) {
this.set('createTime', new Date().toString());
}
this.on('change:error', this.errorChanged, this);
},
initialize: function() {
this.validateAtInit();
this.parseOrCatch();
},
parseOrCatch: function() {
try {
this.parse();
} catch (err) {
if (err instanceof CommandProcessError ||
err instanceof GitError) {
this.set('status', 'error');
this.set('error', err);
} else if (err instanceof CommandResult) {
this.set('status', 'finished');
this.set('error', err);
} else {
throw err;
}
}
},
errorChanged: function(model, err) {
this.set('err', err);
this.set('status', 'error');
this.formatError();
},
formatError: function() {
this.set('result', this.get('err').toResult());
},
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: ""});
}
// 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
// preserve options if so
_.each(this.getShortcutMap(), function(regex, method) {
var results = regex.exec(str);
if (results) {
str = method + ' ' + str.slice(results[0].length);
}
});
// 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
*/
function OptionParser(method, options) {
this.method = method;
this.rawOptions = options;
this.supportedMap = this.getMasterOptionMap()[method];
if (this.supportedMap === undefined) {
throw new Error('No option map for ' + method);
}
this.generalArgs = [];
this.explodeAndSet();
}
OptionParser.prototype.getMasterOptionMap = function() {
// here a value of false means that we support it, even if its just a
// pass-through option. If the value is not here (aka will be undefined
// when accessed), we do not support it.
return {
commit: {
'--amend': false,
'-a': false, // warning
'-am': false
},
add: {},
branch: {
'-d': false,
'-D': false
},
checkout: {
'-b': false
},
reset: {
'--hard': false,
'--soft': false, // this will raise an error but we catch it in gitEngine
},
merge: {},
rebase: {},
revert: {}
};
};
OptionParser.prototype.explodeAndSet = function() {
// split on spaces, except when inside quotes, and strip quotes after.
// for some reason the regex includes the quotes even if i move the parantheses
// inside
var exploded = this.rawOptions.match(/('.*?'|".*?"|\S+)/g) || [];
_.each(exploded, function(part, i) {
exploded[i] = part.replace(/['"]/g, '');
});
for (var i = 0; i < exploded.length; i++) {
var part = exploded[i];
if (part.slice(0,1) == '-') {
// it's an option, check supportedMap
if (this.supportedMap[part] === undefined) {
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
var optionArgs = [];
var next = i + 1;
while (next < exploded.length && exploded[next].slice(0,1) != '-') {
optionArgs.push(exploded[next]);
next += 1;
}
i = next - 1;
// **phew** we are done grabbing those. theseArgs is truthy even with an empty array
this.supportedMap[part] = optionArgs;
} else {
// must be a general arg
this.generalArgs.push(part);
}
}
// done!
};

View file

@ -1,4 +1,3 @@
// backbone or something uses _.uniqueId, so we make our own here // backbone or something uses _.uniqueId, so we make our own here
var uniqueId = (function() { var uniqueId = (function() {
var n = 0; var n = 0;
@ -799,7 +798,8 @@ var Branch = Ref.extend({
var Commit = Backbone.Model.extend({ var Commit = Backbone.Model.extend({
defaults: { defaults: {
type: 'commit', type: 'commit',
children: [] children: [],
parents: []
}, },
validateAtInit: function() { validateAtInit: function() {
@ -808,9 +808,7 @@ var Commit = Backbone.Model.extend({
} }
// root commits have no parents // root commits have no parents
if (this.get('rootCommit')) { if (!this.get('rootCommit')) {
this.set('parents', []);
} else {
if (!this.get('parents') || !this.get('parents').length) { if (!this.get('parents') || !this.get('parents').length) {
throw new Error('needs parents'); throw new Error('needs parents');
} }

View file

@ -10,23 +10,34 @@
<link rel="stylesheet" href="style/font-awesome.css" type="text/css" charset="utf-8"> <link rel="stylesheet" href="style/font-awesome.css" type="text/css" charset="utf-8">
</head> </head>
<body> <body>
<canvas id="viewport" width="800" height="600"></canvas>
<div id="interfaceWrapper" class="box horizontal flex1">
<div id="controls" class="box vertical flex1">
<div id="hintsEtc" class="box vertical flex3">
</div>
<div id="controls"> <div id="commandLineHistory" class="box vertical flex3">
<div id="commandLineHistory"> <div id="commandDisplay">
<div id="commandDisplay"> </div>
</div>
<div id="commandLineBar" class="box vertical flex0">
<textarea id="commandTextField"></textarea>
</div> </div>
</div> </div>
<div id="commandLineBar">
<textarea id="commandTextField"></textarea> <div id="canvasWrapper" class="box flex1">
<canvas id="treeCanvas" width="20" height="20"></canvas>
</div> </div>
</div> </div>
<!-- *******************************************
Scripts from here on out
****************************************** -->
<script src="../lib/jquery-1.8.0.min.js"></script> <script src="../lib/jquery-1.8.0.min.js"></script>
<script src="../lib/Tween.js"></script>
<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>
<script src="../lib/raphael-min.js"></script>
<!-- Templates --> <!-- Templates -->
<script type="text/html" id="command-template"> <script type="text/html" id="command-template">
@ -47,15 +58,24 @@
</script> </script>
<!-- My files! --> <!-- My files! -->
<script src="async.js"></script>
<script src="main.js"></script> <script src="main.js"></script>
<script src="async.js"></script>
<script src="constants.js"></script> <script src="constants.js"></script>
<script src="git.js"></script>
<script src="commandline.js"></script>
<script src="views.js"></script>
<script src="errors.js"></script> <script src="errors.js"></script>
<!-- the beefy git engine -->
<script src="git.js"></script>
<!-- command line -->
<script src="commandModel.js"></script>
<script src="commandViews.js"></script>
<!-- vis -->
<script src="collections.js"></script> <script src="collections.js"></script>
<script src="visuals.js"></script> <script src="visuals.js"></script>
<script src="tree.js"></script> <script src="tree.js"></script>
</body> </body>
</html> </html>

View file

@ -1,18 +1,62 @@
html, body { html {
overflow:hidden; height: 100%;
}
body {
margin: 0px;
border: 0px;
padding: 0px;
}
html, body {
/*
background: -moz-radial-gradient(center, ellipse cover, #0066cc 0%, #000000 90%); background: -moz-radial-gradient(center, ellipse cover, #0066cc 0%, #000000 90%);
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,#0066cc), color-stop(90%,#000000)); background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,#0066cc), color-stop(90%,#000000));
background: -webkit-radial-gradient(center, ellipse cover, #0066cc 0%,#000 90%); background: -webkit-radial-gradient(center, ellipse cover, #0066cc 0%,#000 90%);
background: -o-radial-gradient(center, ellipse cover, #0066cc 0%,#000000 90%); background: -o-radial-gradient(center, ellipse cover, #0066cc 0%,#000000 90%);
background: -ms-radial-gradient(center, ellipse cover, #0066cc 0%,#000000 90%); background: -ms-radial-gradient(center, ellipse cover, #0066cc 0%,#000000 90%);
background: radial-gradient(center, ellipse cover, #0066cc 0%,#000000 90%); background: radial-gradient(center, ellipse cover, #0066cc 0%,#000000 90%); */
-webkit-perspective:600px;
-webkit-perspective: 600px;
font-family: Monaco, Courier, font-monospace; font-family: Monaco, Courier, font-monospace;
color: #eee; color: #eee;
} }
/* Box Model */
html,
body,
div.box {
display: -webkit-box;
}
div.flex0 {
-webkit-box-flex: 0;
}
body,
div.flex1 {
-webkit-box-flex: 1;
}
div.flex2 {
-webkit-box-flex: 2;
}
div.flex3 {
-webkit-box-flex: 3;
}
html,
body,
div.vertical {
-webkit-box-orient: vertical;
}
div.horizontal {
-webkit-box-orient: horizontal;
}
/* Transition */ /* Transition */
.transitionBackground { .transitionBackground {
-webkit-transition: background 700ms cubic-bezier(0.260, 0.860, 0.440, 0.985); -webkit-transition: background 700ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
@ -30,17 +74,21 @@ html, body {
transition: opacity 1700ms cubic-bezier(0.260, 0.860, 0.440, 0.985); transition: opacity 1700ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
} }
#treeCanvas {
border: 3px solid black;
}
#interfaceWrapper {
min-width: 600px;
}
#controls { #controls {
position: absolute; }
top: 0px;
left: 0px; #canvasWrapper {
width: 50%;
height: 100%;
-webkit-box-orientation: vertical;
} }
#commandTextField { #commandTextField {
width: 90%;
} }
p.commandLine, p.commandLineResult { p.commandLine, p.commandLineResult {
@ -103,8 +151,6 @@ p.commandLine span.arrows {
} }
#commandLineHistory { #commandLineHistory {
width: 100%;
height: 200px;
overflow-y: scroll; overflow-y: scroll;
background: #000; background: #000;
opacity: 0.85; opacity: 0.85;

View file

@ -0,0 +1,5 @@
var VisNode = Backbone.Model.extend({
});
var VisEdge = Backbone.Model.extend({
});

View file

@ -1,187 +0,0 @@
var CommandPromptView = Backbone.View.extend({
initialize: function(options) {
this.collection = options.collection;
this.commands = [];
this.index = -1;
},
events: {
'keyup #commandTextField': 'keyUp'
},
keyUp: function(e) {
// we need to capture some of these events.
// WARNING: this key map is not internationalized :(
var keyMap = {
// enter
13: _.bind(function() {
this.submit();
}, this),
// up
38: _.bind(function() {
this.commandSelectChange(1);
}, this),
// down
40: _.bind(function() {
this.commandSelectChange(-1);
}, this)
};
if (keyMap[e.which] !== undefined) {
e.preventDefault();
keyMap[e.which]();
}
},
commandSelectChange: function(delta) {
this.index += delta;
// 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;
return;
}
// yay! we actually can display something
this.setTextField(this.commands[this.index]);
},
setTextField: function(value) {
this.$('#commandTextField').val(value);
},
clear: function() {
this.setTextField('');
},
submit: function() {
var value = this.$('#commandTextField').val().replace('\n', '');
this.clear();
// if we are entering a real command, add it to our history
if (value.length) {
this.commands.unshift(value);
}
this.index = -1;
// split commands on semicolon
_.each(value.split(';'), _.bind(function(command) {
command = command.replace(/^(\s+)/, '');
command = command.replace(/(\s+)$/, '');
if (command.length) {
this.addToCollection(command);
}
}, this));
},
addToCollection: function(value) {
var command = new Command({
rawStr: value
});
this.collection.add(command);
}
});
// 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': 'clicked'
},
clicked: function(e) {
console.log('was clicked');
},
initialize: function() {
this.model.bind('change', this.wasChanged, this);
this.model.bind('destroy', this.remove, this);
},
wasChanged: function(model, changeEvent) {
console.log('command changed', model, changeEvent);
// for changes that are just comestic, we actually only want to toggle classes
// with jquery rather than brutally delete a html of HTML
var changes = changeEvent.changes;
var changeKeys = _.keys(changes);
if (_.difference(changeKeys, ['status']) == 0) {
this.updateStatus();
} else if (_.difference(changeKeys, ['error']) == 0) {
// the above will
this.render();
} else {
this.render();
}
},
updateStatus: function() {
var statuses = ['inqueue', 'processing', 'finished'];
var toggleMap = {};
_.each(statuses, function(status) {
toggleMap[status] = false;
});
toggleMap[this.model.get('status')] = true;
var query = this.$('p.commandLine');
_.each(toggleMap, function(value, key) {
query.toggleClass(key, value);
});
},
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) {
this.collection = options.collection;
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.addAll, this);
this.collection.on('all', this.render, this);
this.collection.on('change', this.scrollDown, this);
},
scrollDown: function() {
var el = $('#commandLineHistory')[0];
el.scrollTop = el.scrollHeight;
},
addOne: function(command) {
var view = new CommandView({
model: command
});
this.$('#commandDisplay').append(view.render().el);
this.scrollDown();
},
addAll: function() {
this.collection.each(this.addOne);
}
});

View file

@ -3,13 +3,14 @@ function GitVisuals(options) {
this.nodeMap = {}; this.nodeMap = {};
this.collection.on('change', _.bind(this.collectionChanged, this)); this.collection.on('change', _.bind(this.collectionChanged, this));
events.on('drawGitVisuals', _.bind(this.drawVisuals, this));
events.on('fixNodePositions', _.bind(this.fixNodes, this));
} }
GitVisuals.prototype.addNode = function(id) { GitVisuals.prototype.addNode = function(id) {
var visNode = sys.addNode(id); var visNode = new VisNode({
id: id
});
this.nodeMap[id] = visNode; this.nodeMap[id] = visNode;
return visNode; return visNode;
}; };
@ -22,88 +23,13 @@ GitVisuals.prototype.addEdge = function(idTail, idHead) {
', ' + idHead + ') does not exist'); ', ' + idHead + ') does not exist');
} }
sys.addEdge(visNodeTail, visNodeHead); var edge = new VisEdge({
}; tail: visNodeTail,
head: visNodeHead
GitVisuals.prototype.drawVisuals = function(sys, ctx, canvas) { });
this.drawRefs(sys, ctx, canvas);
};
GitVisuals.prototype.fixNodes = function(sys) {
this.fixRootCommit(sys);
};
GitVisuals.prototype.drawRefs = function(sys, ctx, canvas) {
var sFill = graphics.refSelectedFontFill;
// 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('visNode');
var fillStyle = branch.selected ? sFill : undefined;
this.drawLabel(ctx, sys, node, branch.id, fillStyle);
}, this));
if (detachedHead) {
var node = HEAD.get('target').get('visNode');
this.drawLabel(ctx, sys, node, 'HEAD', sFill);
}
};
GitVisuals.prototype.drawLabel = function(ctx, sys, node, name, fillStyle) {
fillStyle = fillStyle || graphics.refFontFill;
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 = fillStyle;
ctx.fillText(name, screenPoint.x, screenPoint.y);
// also draw an arrow
var offset = Math.round(graphics.nodeRadius * 2.5);
this.drawArrow(ctx, screenPoint, nodePoint, graphics.arrowHeadWidth, offset);
};
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() { GitVisuals.prototype.collectionChanged = function() {
// redo the algorithms // redo stuff
}; };
GitVisuals.prototype.fixRootCommit = function(sys) {
// get the viewports bottom center
var bottomPosScreen = {
x: Math.round($('#viewport').width() * 0.5),
y: $('#viewport').height() - graphics.nodeRadius * 2.5
};
var bottomPos = sys.fromScreen(bottomPosScreen);
// fix the root commit to the bottom
gitEngine.rootCommit.get('visNode').p = bottomPos;
};