2015-03-19 05:08:23 +00:00
|
|
|
/* jslint node: true */
|
|
|
|
'use strict';
|
|
|
|
|
2015-03-23 04:52:04 +00:00
|
|
|
var PluginModule = require('./plugin_module.js').PluginModule;
|
|
|
|
var theme = require('./theme.js');
|
2015-04-19 08:13:13 +00:00
|
|
|
var art = require('./art.js');
|
2015-04-04 20:41:04 +00:00
|
|
|
var Log = require('./logger.js').log;
|
2015-04-10 04:49:56 +00:00
|
|
|
var ansi = require('./ansi_term.js');
|
2015-04-19 08:13:13 +00:00
|
|
|
var asset = require('./asset.js');
|
|
|
|
var ViewController = require('./view_controller.js').ViewController;
|
2015-04-21 04:50:58 +00:00
|
|
|
var menuUtil = require('./menu_util.js');
|
2015-09-09 04:08:45 +00:00
|
|
|
var Config = require('./config.js').config;
|
2015-03-23 04:52:04 +00:00
|
|
|
|
|
|
|
var async = require('async');
|
|
|
|
var assert = require('assert');
|
2015-04-19 08:13:13 +00:00
|
|
|
var _ = require('lodash');
|
2015-03-19 05:08:23 +00:00
|
|
|
|
|
|
|
exports.MenuModule = MenuModule;
|
|
|
|
|
2015-09-08 03:18:09 +00:00
|
|
|
// :TODO: some of this is a bit off... should pause after finishedLoading()
|
|
|
|
|
2015-04-04 20:41:04 +00:00
|
|
|
function MenuModule(options) {
|
|
|
|
PluginModule.call(this, options);
|
2015-03-19 05:08:23 +00:00
|
|
|
|
2015-03-23 04:52:04 +00:00
|
|
|
var self = this;
|
2015-05-14 04:21:55 +00:00
|
|
|
this.menuName = options.menuName;
|
2015-04-04 20:41:04 +00:00
|
|
|
this.menuConfig = options.menuConfig;
|
2015-04-10 04:49:56 +00:00
|
|
|
this.menuConfig.options = options.menuConfig.options || {};
|
2015-04-19 08:13:13 +00:00
|
|
|
this.menuMethods = {}; // methods called from @method's
|
2015-09-07 06:12:01 +00:00
|
|
|
|
2015-09-09 04:08:45 +00:00
|
|
|
this.cls = _.isBoolean(this.menuConfig.options.cls) ?
|
|
|
|
this.menuConfig.options.cls :
|
|
|
|
Config.menus.cls;
|
|
|
|
|
2015-10-10 05:35:40 +00:00
|
|
|
this.menuConfig.config = this.menuConfig.config || {};
|
|
|
|
|
2015-08-12 23:26:05 +00:00
|
|
|
this.initViewControllers();
|
2015-04-19 08:13:13 +00:00
|
|
|
|
2015-03-24 05:12:19 +00:00
|
|
|
this.initSequence = function() {
|
2015-04-19 08:13:13 +00:00
|
|
|
var mciData = { };
|
|
|
|
|
2015-09-06 21:58:58 +00:00
|
|
|
async.series(
|
2015-04-19 08:13:13 +00:00
|
|
|
[
|
|
|
|
function beforeDisplayArt(callback) {
|
|
|
|
self.beforeArt();
|
|
|
|
callback(null);
|
|
|
|
},
|
|
|
|
function displayMenuArt(callback) {
|
|
|
|
if(_.isString(self.menuConfig.art)) {
|
2015-07-25 22:10:12 +00:00
|
|
|
theme.displayThemedAsset(
|
2015-07-25 00:33:59 +00:00
|
|
|
self.menuConfig.art,
|
|
|
|
self.client,
|
2015-09-27 21:35:24 +00:00
|
|
|
self.menuConfig.options, // can include .font, .trailingLF, etc.
|
2015-08-10 01:32:38 +00:00
|
|
|
function displayed(err, artData) {
|
|
|
|
if(err) {
|
2015-09-27 21:35:24 +00:00
|
|
|
self.client.log.debug( { art : self.menuConfig.art, err : err }, 'Could not display art');
|
2015-08-10 01:32:38 +00:00
|
|
|
} else {
|
2015-07-25 00:33:59 +00:00
|
|
|
mciData.menu = artData.mciMap;
|
|
|
|
}
|
2015-08-10 01:32:38 +00:00
|
|
|
callback(null); // non-fatal
|
|
|
|
}
|
|
|
|
);
|
2015-05-15 05:01:00 +00:00
|
|
|
} else {
|
2015-04-19 08:13:13 +00:00
|
|
|
callback(null);
|
|
|
|
}
|
|
|
|
},
|
2015-05-15 05:01:00 +00:00
|
|
|
function moveToPromptLocation(callback) {
|
2015-04-19 08:13:13 +00:00
|
|
|
if(self.menuConfig.prompt) {
|
|
|
|
// :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null);
|
|
|
|
},
|
|
|
|
function displayPromptArt(callback) {
|
|
|
|
if(_.isString(self.menuConfig.prompt)) {
|
|
|
|
// If a prompt is specified, we need the configuration
|
|
|
|
if(!_.isObject(self.menuConfig.promptConfig)) {
|
|
|
|
callback(new Error('Prompt specified but configuraiton not found!'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prompts *must* have art. If it's missing it's an error
|
2015-04-21 04:50:58 +00:00
|
|
|
// :TODO: allow inline prompts in the future, e.g. @inline:memberName -> { "memberName" : { "text" : "stuff", ... } }
|
2015-04-19 08:13:13 +00:00
|
|
|
var promptConfig = self.menuConfig.promptConfig;
|
2015-07-25 22:10:12 +00:00
|
|
|
theme.displayThemedAsset(
|
2015-07-25 00:33:59 +00:00
|
|
|
promptConfig.art,
|
|
|
|
self.client,
|
2015-09-27 21:35:24 +00:00
|
|
|
self.menuConfig.options, // can include .font, .trailingLF, etc.
|
2015-07-25 00:33:59 +00:00
|
|
|
function displayed(err, artData) {
|
|
|
|
if(!err) {
|
|
|
|
mciData.prompt = artData.mciMap;
|
|
|
|
}
|
|
|
|
callback(err);
|
|
|
|
});
|
2015-05-15 05:01:00 +00:00
|
|
|
} else {
|
2015-04-19 08:13:13 +00:00
|
|
|
callback(null);
|
|
|
|
}
|
|
|
|
},
|
2015-07-29 04:31:28 +00:00
|
|
|
function recordCursorPosition(callback) {
|
|
|
|
if(self.shouldPause()) {
|
|
|
|
self.client.once('cursor position report', function cpr(pos) {
|
|
|
|
self.afterArtPos = pos;
|
|
|
|
self.client.log.trace( { position : pos }, 'After art position recorded');
|
|
|
|
callback(null);
|
|
|
|
});
|
|
|
|
self.client.term.write(ansi.queryPos());
|
|
|
|
} else {
|
|
|
|
callback(null);
|
|
|
|
}
|
|
|
|
},
|
2015-04-19 08:13:13 +00:00
|
|
|
function afterArtDisplayed(callback) {
|
2015-07-30 04:09:51 +00:00
|
|
|
self.mciReady(mciData, callback);
|
|
|
|
},
|
|
|
|
function displayPauseIfRequested(callback) {
|
|
|
|
if(self.shouldPause()) {
|
|
|
|
self.client.term.write(ansi.goto(self.afterArtPos[0], 1));
|
|
|
|
|
|
|
|
// :TODO: really need a client.term.pause() that uses the correct art/etc.
|
|
|
|
theme.displayThemedPause( { client : self.client }, function keyPressed() {
|
|
|
|
callback(null);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
callback(null);
|
|
|
|
}
|
2015-04-19 08:13:13 +00:00
|
|
|
}
|
|
|
|
],
|
|
|
|
function complete(err) {
|
|
|
|
if(err) {
|
2015-07-06 01:05:55 +00:00
|
|
|
console.log(err)
|
2015-04-19 08:13:13 +00:00
|
|
|
// :TODO: what to do exactly?????
|
|
|
|
}
|
|
|
|
|
|
|
|
self.finishedLoading();
|
2015-11-04 06:15:49 +00:00
|
|
|
self.autoNextMenu();
|
2015-04-19 08:13:13 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
2015-07-29 04:31:28 +00:00
|
|
|
|
|
|
|
this.shouldPause = function() {
|
2015-07-30 04:09:51 +00:00
|
|
|
return 'end' === self.menuConfig.options.pause || true === self.menuConfig.options.pause;
|
2015-07-29 04:31:28 +00:00
|
|
|
};
|
|
|
|
|
2015-09-09 04:31:38 +00:00
|
|
|
this.hasNextTimeout = function() {
|
|
|
|
return _.isNumber(self.menuConfig.options.nextTimeout);
|
|
|
|
};
|
|
|
|
|
2015-11-04 06:15:49 +00:00
|
|
|
this.autoNextMenu = function() {
|
2015-09-27 21:35:24 +00:00
|
|
|
function goNext() {
|
2015-11-14 19:22:21 +00:00
|
|
|
if(_.isString(self.menuConfig.next) || _.isArray(self.menuConfig.next)) {
|
2015-09-27 21:35:24 +00:00
|
|
|
menuUtil.handleNext(self.client, self.menuConfig.next);
|
|
|
|
} else {
|
2015-11-04 06:15:49 +00:00
|
|
|
self.prevMenu();
|
2015-09-27 21:35:24 +00:00
|
|
|
}
|
2015-07-29 04:31:28 +00:00
|
|
|
}
|
2015-09-09 04:31:38 +00:00
|
|
|
|
2015-09-27 21:35:24 +00:00
|
|
|
if(!_.isObject(self.menuConfig.form) && !_.isString(self.menuConfig.prompt)) {
|
2015-09-10 03:31:04 +00:00
|
|
|
/*
|
2015-09-27 21:35:24 +00:00
|
|
|
If 'next' is supplied, we'll use it. Otherwise, utlize fallback which
|
|
|
|
may be explicit (supplied) or non-explicit (previous menu)
|
|
|
|
|
2015-09-12 23:17:00 +00:00
|
|
|
'next' may be a simple asset, or a object with next.asset and
|
|
|
|
extrArgs
|
|
|
|
|
|
|
|
next: assetSpec
|
|
|
|
|
|
|
|
-or-
|
|
|
|
|
2015-09-10 03:31:04 +00:00
|
|
|
next: {
|
2015-09-12 23:17:00 +00:00
|
|
|
asset: assetSpec
|
|
|
|
extraArgs: ...
|
2015-09-10 03:31:04 +00:00
|
|
|
}
|
2015-09-27 21:35:24 +00:00
|
|
|
*/
|
2015-09-09 04:31:38 +00:00
|
|
|
if(self.hasNextTimeout()) {
|
|
|
|
setTimeout(function nextTimeout() {
|
2015-09-27 21:35:24 +00:00
|
|
|
goNext();
|
2015-09-09 04:31:38 +00:00
|
|
|
}, this.menuConfig.options.nextTimeout);
|
|
|
|
} else {
|
2015-09-27 21:35:24 +00:00
|
|
|
goNext();
|
2015-09-09 04:31:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2015-11-14 19:22:21 +00:00
|
|
|
|
|
|
|
this.haveNext = function() {
|
|
|
|
return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next));
|
|
|
|
};
|
2015-03-19 05:08:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
require('util').inherits(MenuModule, PluginModule);
|
|
|
|
|
2015-08-12 23:26:05 +00:00
|
|
|
require('./mod_mixins.js').ViewControllerManagement.call(MenuModule.prototype);
|
|
|
|
|
2015-09-18 05:11:21 +00:00
|
|
|
|
2015-03-19 05:08:23 +00:00
|
|
|
MenuModule.prototype.enter = function(client) {
|
2015-03-23 04:52:04 +00:00
|
|
|
this.client = client;
|
2015-04-19 08:13:13 +00:00
|
|
|
assert(_.isObject(client));
|
2015-03-24 05:12:19 +00:00
|
|
|
|
2015-10-10 05:35:40 +00:00
|
|
|
menuUtil.applyGeneralThemeCustomization( {
|
|
|
|
name : this.menuName,
|
|
|
|
client : this.client,
|
|
|
|
type : 'menus',
|
|
|
|
config : this.menuConfig.config,
|
|
|
|
});
|
|
|
|
|
2015-09-07 06:12:01 +00:00
|
|
|
if(_.isString(this.menuConfig.status)) {
|
|
|
|
this.client.currentStatus = this.menuConfig.status;
|
|
|
|
} else {
|
|
|
|
this.client.currentStatus = 'Browsing menus';
|
|
|
|
}
|
|
|
|
|
2015-03-24 05:12:19 +00:00
|
|
|
this.initSequence();
|
2015-03-19 05:08:23 +00:00
|
|
|
};
|
|
|
|
|
2015-09-18 05:11:21 +00:00
|
|
|
MenuModule.prototype.getSaveState = function() {
|
|
|
|
// nothing in base
|
|
|
|
};
|
|
|
|
|
|
|
|
MenuModule.prototype.restoreSavedState = function(savedState) {
|
|
|
|
// nothing in base
|
|
|
|
};
|
|
|
|
|
2015-11-03 23:42:11 +00:00
|
|
|
MenuModule.prototype.nextMenu = function(cb) {
|
2015-11-14 19:22:21 +00:00
|
|
|
//
|
|
|
|
// If we don't actually have |next|, we'll go previous
|
|
|
|
//
|
|
|
|
if(!this.haveNext()) {
|
|
|
|
this.prevMenu(cb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-13 23:13:20 +00:00
|
|
|
// :TODO: this, prevMenu(), and gotoMenu() need a default |cb| handler if none is supplied.
|
|
|
|
// ...if the error is that we do not meet ACS requirements and did not get a match, then what?
|
|
|
|
if(!cb) {
|
|
|
|
cb = function(err) {
|
2015-12-14 00:32:05 +00:00
|
|
|
if(err) {
|
|
|
|
// :TODO: Don't console.log() here!
|
|
|
|
console.log(err)
|
|
|
|
}
|
2015-11-13 23:13:20 +00:00
|
|
|
}
|
|
|
|
}
|
2015-11-04 06:15:49 +00:00
|
|
|
this.client.menuStack.next(cb);
|
2015-11-03 23:42:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
MenuModule.prototype.prevMenu = function(cb) {
|
2015-11-04 06:15:49 +00:00
|
|
|
this.client.menuStack.prev(cb);
|
2015-11-03 23:42:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
MenuModule.prototype.gotoMenu = function(name, options, cb) {
|
2015-11-04 06:15:49 +00:00
|
|
|
this.client.menuStack.goto(name, options, cb);
|
2015-11-03 23:42:11 +00:00
|
|
|
};
|
|
|
|
|
2015-03-19 05:08:23 +00:00
|
|
|
MenuModule.prototype.leave = function() {
|
2015-08-12 23:26:05 +00:00
|
|
|
this.detachViewControllers();
|
2015-03-23 04:52:04 +00:00
|
|
|
};
|
|
|
|
|
2015-03-24 05:12:19 +00:00
|
|
|
MenuModule.prototype.beforeArt = function() {
|
2015-09-09 04:08:45 +00:00
|
|
|
if(this.cls) {
|
2015-04-10 04:49:56 +00:00
|
|
|
this.client.term.write(ansi.resetScreen());
|
|
|
|
}
|
2015-07-03 05:28:39 +00:00
|
|
|
|
|
|
|
if(_.isNumber(this.menuConfig.options.baudRate)) {
|
|
|
|
this.client.term.write(ansi.setEmulatedBaudRate(this.menuConfig.options.baudRate));
|
|
|
|
}
|
2015-03-24 05:12:19 +00:00
|
|
|
};
|
|
|
|
|
2015-07-30 04:09:51 +00:00
|
|
|
MenuModule.prototype.mciReady = function(mciData, cb) {
|
|
|
|
// Reserved for sub classes
|
|
|
|
cb(null);
|
2015-03-24 05:12:19 +00:00
|
|
|
};
|
|
|
|
|
2015-07-30 04:09:51 +00:00
|
|
|
MenuModule.prototype.standardMCIReadyHandler = function(mciData, cb) {
|
2015-04-21 04:50:58 +00:00
|
|
|
//
|
|
|
|
// A quick rundown:
|
|
|
|
// * We may have mciData.menu, mciData.prompt, or both.
|
|
|
|
// * Prompt form is favored over menu form if both are present.
|
|
|
|
// * Standard/prefdefined MCI entries must load both (e.g. %BN is expected to resolve)
|
|
|
|
//
|
2015-07-30 04:09:51 +00:00
|
|
|
var self = this;
|
2015-07-29 04:31:28 +00:00
|
|
|
|
2015-07-30 04:09:51 +00:00
|
|
|
async.series(
|
|
|
|
[
|
|
|
|
function addViewControllers(callback) {
|
|
|
|
_.forEach(mciData, function entry(mciMap, name) {
|
|
|
|
assert('menu' === name || 'prompt' === name);
|
|
|
|
self.addViewController(name, new ViewController( { client : self.client } ));
|
|
|
|
});
|
|
|
|
callback(null);
|
|
|
|
},
|
|
|
|
function createMenu(callback) {
|
|
|
|
if(self.viewControllers.menu) {
|
|
|
|
var menuLoadOpts = {
|
|
|
|
mciMap : mciData.menu,
|
|
|
|
callingMenu : self,
|
|
|
|
withoutForm : _.isObject(mciData.prompt),
|
|
|
|
};
|
|
|
|
|
|
|
|
self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, function menuLoaded(err) {
|
|
|
|
callback(err);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
callback(null);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function createPrompt(callback) {
|
|
|
|
if(self.viewControllers.prompt) {
|
|
|
|
var promptLoadOpts = {
|
|
|
|
callingMenu : self,
|
|
|
|
mciMap : mciData.prompt,
|
|
|
|
};
|
|
|
|
|
|
|
|
self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, function promptLoaded(err) {
|
|
|
|
callback(err);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
callback(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
function complete(err) {
|
|
|
|
cb(err);
|
2015-04-21 04:50:58 +00:00
|
|
|
}
|
2015-07-30 04:09:51 +00:00
|
|
|
);
|
2015-04-21 04:50:58 +00:00
|
|
|
};
|
|
|
|
|
2015-03-24 05:12:19 +00:00
|
|
|
MenuModule.prototype.finishedLoading = function() {
|
2015-03-19 05:08:23 +00:00
|
|
|
};
|