|
|
var Q = require('q'); |
|
|
var Backbone = require('backbone'); |
|
|
|
|
|
var util = require('../util'); |
|
|
var intl = require('../intl'); |
|
|
var Main = require('../app'); |
|
|
var Errors = require('../util/errors'); |
|
|
|
|
|
var Visualization = require('../visuals/visualization').Visualization; |
|
|
var ParseWaterfall = require('../level/parseWaterfall').ParseWaterfall; |
|
|
var DisabledMap = require('../level/disabledMap').DisabledMap; |
|
|
var Command = require('../models/commandModel').Command; |
|
|
var GitShim = require('../git/gitShim').GitShim; |
|
|
var LevelActions = require('../actions/LevelActions'); |
|
|
var LevelStore = require('../stores/LevelStore'); |
|
|
|
|
|
var Views = require('../views'); |
|
|
var ModalTerminal = Views.ModalTerminal; |
|
|
var ModalAlert = Views.ModalAlert; |
|
|
var BuilderViews = require('../views/builderViews'); |
|
|
var MultiView = require('../views/multiView').MultiView; |
|
|
|
|
|
var Sandbox = Backbone.View.extend({ |
|
|
|
|
|
|
|
|
tagName: 'div', |
|
|
initialize: function(options) { |
|
|
options = options || {}; |
|
|
this.options = options; |
|
|
|
|
|
this.initVisualization(options); |
|
|
this.initCommandCollection(options); |
|
|
this.initParseWaterfall(options); |
|
|
this.initGitShim(options); |
|
|
this.initUndoStack(options); |
|
|
|
|
|
if (!options.wait) { |
|
|
this.takeControl(); |
|
|
} |
|
|
}, |
|
|
|
|
|
getDefaultVisEl: function() { |
|
|
return $('#mainVisSpace')[0]; |
|
|
}, |
|
|
|
|
|
getAnimationTime: function() { return 700 * 1.5; }, |
|
|
|
|
|
initVisualization: function(options) { |
|
|
this.mainVis = new Visualization({ |
|
|
el: options.el || this.getDefaultVisEl() |
|
|
}); |
|
|
}, |
|
|
|
|
|
initUndoStack: function(options) { |
|
|
this.undoStack = []; |
|
|
}, |
|
|
|
|
|
initCommandCollection: function(options) { |
|
|
|
|
|
|
|
|
this.commandCollection = Main.getCommandUI().commandCollection; |
|
|
}, |
|
|
|
|
|
initParseWaterfall: function(options) { |
|
|
this.parseWaterfall = new ParseWaterfall(); |
|
|
}, |
|
|
|
|
|
initGitShim: function(options) { |
|
|
this.gitShim = new GitShim({ |
|
|
beforeCB: this.beforeCommandCB.bind(this) |
|
|
}); |
|
|
}, |
|
|
|
|
|
takeControl: function() { |
|
|
|
|
|
|
|
|
Main.getEventBaton().stealBaton('commandSubmitted', this.commandSubmitted, this); |
|
|
|
|
|
Main.getEventBaton().stealBaton('processSandboxCommand', this.processSandboxCommand, this); |
|
|
|
|
|
|
|
|
Main.getEventBaton().stealBaton('levelExited', this.levelExited, this); |
|
|
|
|
|
this.insertGitShim(); |
|
|
}, |
|
|
|
|
|
releaseControl: function() { |
|
|
|
|
|
|
|
|
Main.getEventBaton().releaseBaton('commandSubmitted', this.commandSubmitted, this); |
|
|
|
|
|
Main.getEventBaton().releaseBaton('processSandboxCommand', this.processSandboxCommand, this); |
|
|
|
|
|
Main.getEventBaton().releaseBaton('levelExited', this.levelExited, this); |
|
|
|
|
|
this.releaseGitShim(); |
|
|
}, |
|
|
|
|
|
releaseGitShim: function() { |
|
|
if (this.gitShim) { |
|
|
this.gitShim.removeShim(); |
|
|
} |
|
|
}, |
|
|
|
|
|
insertGitShim: function() { |
|
|
|
|
|
|
|
|
if (this.gitShim) { |
|
|
this.mainVis.customEvents.on('gitEngineReady', function() { |
|
|
this.gitShim.insertShim(); |
|
|
},this); |
|
|
} |
|
|
}, |
|
|
|
|
|
beforeCommandCB: function(command) { |
|
|
this.pushUndo(); |
|
|
}, |
|
|
|
|
|
pushUndo: function() { |
|
|
|
|
|
this.undoStack.push(this.mainVis.gitEngine.printTree()); |
|
|
}, |
|
|
|
|
|
undo: function(command, deferred) { |
|
|
var toRestore = this.undoStack.pop(); |
|
|
if (!toRestore) { |
|
|
command.set('error', new Errors.GitError({ |
|
|
msg: intl.str('undo-stack-empty') |
|
|
})); |
|
|
deferred.resolve(); |
|
|
return; |
|
|
} |
|
|
|
|
|
this.mainVis.reset(toRestore); |
|
|
setTimeout(function() { |
|
|
command.finishWith(deferred); |
|
|
}, this.mainVis.getAnimationTime()); |
|
|
}, |
|
|
|
|
|
commandSubmitted: function(value) { |
|
|
|
|
|
Main.getEvents().trigger('commandSubmittedPassive', value); |
|
|
|
|
|
util.splitTextCommand(value, function(command) { |
|
|
this.commandCollection.add(new Command({ |
|
|
rawStr: command, |
|
|
parseWaterfall: this.parseWaterfall |
|
|
})); |
|
|
}, this); |
|
|
}, |
|
|
|
|
|
startLevel: function(command, deferred) { |
|
|
var regexResults = command.get('regexResults') || []; |
|
|
var desiredID = regexResults[1] || ''; |
|
|
var levelJSON = LevelStore.getLevel(desiredID); |
|
|
|
|
|
|
|
|
if (!levelJSON) { |
|
|
command.addWarning( |
|
|
intl.str( |
|
|
'level-no-id', |
|
|
{ id: desiredID } |
|
|
) |
|
|
); |
|
|
Main.getEventBaton().trigger('commandSubmitted', 'levels'); |
|
|
|
|
|
command.set('status', 'error'); |
|
|
deferred.resolve(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
this.hide(); |
|
|
this.clear(); |
|
|
|
|
|
|
|
|
|
|
|
var whenLevelOpen = Q.defer(); |
|
|
var Level = require('../level').Level; |
|
|
|
|
|
this.currentLevel = new Level({ |
|
|
level: levelJSON, |
|
|
deferred: whenLevelOpen, |
|
|
command: command |
|
|
}); |
|
|
|
|
|
whenLevelOpen.promise.then(function() { |
|
|
command.finishWith(deferred); |
|
|
}); |
|
|
}, |
|
|
|
|
|
buildLevel: function(command, deferred) { |
|
|
this.hide(); |
|
|
this.clear(); |
|
|
|
|
|
var whenBuilderOpen = Q.defer(); |
|
|
var LevelBuilder = require('../level/builder').LevelBuilder; |
|
|
|
|
|
var regexResults = command.get('regexResults') || []; |
|
|
var toEdit = regexResults[1] || false; |
|
|
this.levelBuilder = new LevelBuilder({ |
|
|
deferred: whenBuilderOpen, |
|
|
editLevel: toEdit |
|
|
}); |
|
|
whenBuilderOpen.promise.then(function() { |
|
|
command.finishWith(deferred); |
|
|
}); |
|
|
}, |
|
|
|
|
|
exitLevel: function(command, deferred) { |
|
|
command.addWarning( |
|
|
intl.str('level-cant-exit') |
|
|
); |
|
|
command.set('status', 'error'); |
|
|
deferred.resolve(); |
|
|
}, |
|
|
|
|
|
showLevels: function(command, deferred) { |
|
|
var whenClosed = Q.defer(); |
|
|
Main.getLevelDropdown().show(whenClosed, command); |
|
|
whenClosed.promise.done(function() { |
|
|
command.finishWith(deferred); |
|
|
}); |
|
|
}, |
|
|
|
|
|
sharePermalink: function(command, deferred) { |
|
|
var treeJSON = JSON.stringify(this.mainVis.gitEngine.exportTree()); |
|
|
var url = |
|
|
'https://learngitbranching.js.org/?NODEMO&command=importTreeNow%20' + escape(treeJSON); |
|
|
command.setResult( |
|
|
intl.todo('Here is a link to the current state of the tree: ') + '\n' + url |
|
|
); |
|
|
command.finishWith(deferred); |
|
|
}, |
|
|
|
|
|
resetSolved: function(command, deferred) { |
|
|
if (command.get('regexResults').input !== 'reset solved --confirm') { |
|
|
command.set('error', new Errors.GitError({ |
|
|
msg: 'Reset solved will mark each level as not yet solved; because ' + |
|
|
'this is a destructive command, please pass in --confirm to execute', |
|
|
})); |
|
|
command.finishWith(deferred); |
|
|
return; |
|
|
} |
|
|
|
|
|
LevelActions.resetLevelsSolved(); |
|
|
command.addWarning( |
|
|
intl.str('solved-map-reset') |
|
|
); |
|
|
command.finishWith(deferred); |
|
|
}, |
|
|
|
|
|
processSandboxCommand: function(command, deferred) { |
|
|
|
|
|
|
|
|
var commandMap = { |
|
|
'reset solved': this.resetSolved, |
|
|
'undo': this.undo, |
|
|
'help general': this.helpDialog, |
|
|
'help': this.helpDialog, |
|
|
'reset': this.reset, |
|
|
'delay': this.delay, |
|
|
'clear': this.clear, |
|
|
'exit level': this.exitLevel, |
|
|
'level': this.startLevel, |
|
|
'sandbox': this.exitLevel, |
|
|
'levels': this.showLevels, |
|
|
'mobileAlert': this.mobileAlert, |
|
|
'build level': this.buildLevel, |
|
|
'export tree': this.exportTree, |
|
|
'import tree': this.importTree, |
|
|
'importTreeNow': this.importTreeNow, |
|
|
'import level': this.importLevel, |
|
|
'importLevelNow': this.importLevelNow, |
|
|
'share permalink': this.sharePermalink, |
|
|
}; |
|
|
|
|
|
var method = commandMap[command.get('method')]; |
|
|
if (!method) { throw new Error('no method for that wut'); } |
|
|
|
|
|
method.apply(this, [command, deferred]); |
|
|
}, |
|
|
|
|
|
hide: function() { |
|
|
this.mainVis.hide(); |
|
|
}, |
|
|
|
|
|
levelExited: function() { |
|
|
this.show(); |
|
|
}, |
|
|
|
|
|
show: function() { |
|
|
this.mainVis.show(); |
|
|
}, |
|
|
|
|
|
importLevelNow: function(command, deferred) { |
|
|
var options = command.get('regexResults') || []; |
|
|
if (options.length < 2) { |
|
|
command.set('error', new Errors.GitError({ |
|
|
msg: intl.str('git-error-options') |
|
|
})); |
|
|
command.finishWith(deferred); |
|
|
return; |
|
|
} |
|
|
var string = options.input.replace(/importLevelNow\s+/g, ''); |
|
|
var Level = require('../level').Level; |
|
|
try { |
|
|
var levelJSON = JSON.parse(unescape(string)); |
|
|
var whenLevelOpen = Q.defer(); |
|
|
this.currentLevel = new Level({ |
|
|
level: levelJSON, |
|
|
deferred: whenLevelOpen, |
|
|
command: command |
|
|
}); |
|
|
this.hide(); |
|
|
|
|
|
whenLevelOpen.promise.then(function() { |
|
|
command.finishWith(deferred); |
|
|
}); |
|
|
} catch(e) { |
|
|
command.set('error', new Errors.GitError({ |
|
|
msg: 'Something went wrong ' + String(e) |
|
|
})); |
|
|
throw e; |
|
|
} |
|
|
command.finishWith(deferred); |
|
|
}, |
|
|
|
|
|
importTreeNow: function(command, deferred) { |
|
|
var options = command.get('regexResults') || []; |
|
|
if (options.length < 2) { |
|
|
command.set('error', new Errors.GitError({ |
|
|
msg: intl.str('git-error-options') |
|
|
})); |
|
|
command.finishWith(deferred); |
|
|
} |
|
|
var string = options.input.replace(/importTreeNow\s+/g, ''); |
|
|
try { |
|
|
this.mainVis.gitEngine.loadTreeFromString(string); |
|
|
} catch (e) { |
|
|
command.set('error', new Errors.GitError({ |
|
|
msg: String(e) |
|
|
})); |
|
|
} |
|
|
command.finishWith(deferred); |
|
|
}, |
|
|
|
|
|
importTree: function(command, deferred) { |
|
|
var jsonGrabber = new BuilderViews.MarkdownPresenter({ |
|
|
previewText: intl.str('paste-json'), |
|
|
fillerText: ' ' |
|
|
}); |
|
|
jsonGrabber.deferred.promise |
|
|
.then(function(treeJSON) { |
|
|
try { |
|
|
this.mainVis.gitEngine.loadTree(JSON.parse(treeJSON)); |
|
|
} catch(e) { |
|
|
this.mainVis.reset(); |
|
|
new MultiView({ |
|
|
childViews: [{ |
|
|
type: 'ModalAlert', |
|
|
options: { |
|
|
markdowns: [ |
|
|
'## Error!', |
|
|
'', |
|
|
'Something is wrong with that JSON! Here is the error:', |
|
|
'', |
|
|
String(e) |
|
|
] |
|
|
} |
|
|
}] |
|
|
}); |
|
|
} |
|
|
}.bind(this)) |
|
|
.fail(function() { }) |
|
|
.done(function() { |
|
|
command.finishWith(deferred); |
|
|
}); |
|
|
}, |
|
|
|
|
|
importLevel: function(command, deferred) { |
|
|
var jsonGrabber = new BuilderViews.MarkdownPresenter({ |
|
|
previewText: intl.str('paste-json'), |
|
|
fillerText: ' ' |
|
|
}); |
|
|
|
|
|
jsonGrabber.deferred.promise |
|
|
.then(function(inputText) { |
|
|
var Level = require('../level').Level; |
|
|
try { |
|
|
var levelJSON = JSON.parse(inputText); |
|
|
var whenLevelOpen = Q.defer(); |
|
|
this.currentLevel = new Level({ |
|
|
level: levelJSON, |
|
|
deferred: whenLevelOpen, |
|
|
command: command |
|
|
}); |
|
|
this.hide(); |
|
|
|
|
|
whenLevelOpen.promise.then(function() { |
|
|
command.finishWith(deferred); |
|
|
}); |
|
|
} catch(e) { |
|
|
new MultiView({ |
|
|
childViews: [{ |
|
|
type: 'ModalAlert', |
|
|
options: { |
|
|
markdowns: [ |
|
|
'## Error!', |
|
|
'', |
|
|
'Something is wrong with that level JSON, this happened:', |
|
|
'', |
|
|
String(e) |
|
|
] |
|
|
} |
|
|
}] |
|
|
}); |
|
|
command.finishWith(deferred); |
|
|
} |
|
|
}.bind(this)) |
|
|
.fail(function() { |
|
|
command.finishWith(deferred); |
|
|
}) |
|
|
.done(); |
|
|
}, |
|
|
|
|
|
exportTree: function(command, deferred) { |
|
|
var treeJSON = JSON.stringify(this.mainVis.gitEngine.exportTree(), null, 2); |
|
|
|
|
|
var showJSON = new MultiView({ |
|
|
childViews: [{ |
|
|
type: 'MarkdownPresenter', |
|
|
options: { |
|
|
previewText: intl.str('share-tree'), |
|
|
fillerText: treeJSON, |
|
|
noConfirmCancel: true |
|
|
} |
|
|
}] |
|
|
}); |
|
|
showJSON.getPromise() |
|
|
.then(function() { |
|
|
command.finishWith(deferred); |
|
|
}) |
|
|
.done(); |
|
|
}, |
|
|
|
|
|
clear: function(command, deferred) { |
|
|
Main.getEvents().trigger('clearOldCommands'); |
|
|
if (command && deferred) { |
|
|
command.finishWith(deferred); |
|
|
} |
|
|
}, |
|
|
|
|
|
mobileAlert: function(command, deferred) { |
|
|
alert(intl.str('mobile-alert')); |
|
|
command.finishWith(deferred); |
|
|
}, |
|
|
|
|
|
delay: function(command, deferred) { |
|
|
var amount = parseInt(command.get('regexResults')[1], 10); |
|
|
setTimeout(function() { |
|
|
command.finishWith(deferred); |
|
|
}, amount); |
|
|
}, |
|
|
|
|
|
reset: function(command, deferred) { |
|
|
this.mainVis.reset(); |
|
|
this.initUndoStack(); |
|
|
|
|
|
setTimeout(function() { |
|
|
command.finishWith(deferred); |
|
|
}, this.mainVis.getAnimationTime()); |
|
|
}, |
|
|
|
|
|
helpDialog: function(command, deferred) { |
|
|
var helpDialog = new MultiView({ |
|
|
childViews: intl.getDialog(require('../dialogs/sandbox')) |
|
|
}); |
|
|
helpDialog.getPromise().then(function() { |
|
|
|
|
|
command.finishWith(deferred); |
|
|
}.bind(this)) |
|
|
.done(); |
|
|
} |
|
|
}); |
|
|
|
|
|
exports.Sandbox = Sandbox; |
|
|
|