diff --git a/core/bbs.js b/core/bbs.js index fbc1a013..4e5906a0 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -224,9 +224,9 @@ function startListening() { }); client.on('idle timeout', function idleTimeout() { - client.log.info('User idle timeout expired'); + client.log.info('User idle timeout expired'); - client.gotoMenuModule( { name : 'idleLogoff' }, function goMenuRes(err) { + client.menuStack.goto('idleLogoff', function goMenuRes(err) { if(err) { // likely just doesn't exist client.term.write('\nIdle timeout expired. Goodbye!\n'); diff --git a/core/client.js b/core/client.js index 8f53446b..4e7ed707 100644 --- a/core/client.js +++ b/core/client.js @@ -94,7 +94,7 @@ function Client(input, output) { this.user = new user.User(); this.currentTheme = { info : { name : 'N/A', description : 'None' } }; this.lastKeyPressMs = Date.now(); - this.menuStack = new MenuStack(); + this.menuStack = new MenuStack(this); Object.defineProperty(this, 'node', { get : function() { @@ -102,6 +102,12 @@ function Client(input, output) { } }); + Object.defineProperty(this, 'currentMenuModule', { + get : function() { + return self.menuStack.getCurrentModule(); + } + }); + // // Peek at incoming |data| and emit events for any special @@ -373,20 +379,6 @@ function Client(input, output) { } }); }); - - self.detachCurrentMenuModule = function() { - if(self.currentMenuModule) { - - var savedState = self.currentMenuModule.getSaveState(); - - self.currentMenuModule.leave(); - - self.lastMenuModuleInfo = self.currentMenuModuleInfo; - self.lastMenuModuleInfo.savedState = savedState; - - self.currentMenuModule = null; - } - }; } require('util').inherits(Client, stream); @@ -423,7 +415,7 @@ Client.prototype.startIdleMonitor = function() { }; Client.prototype.end = function () { - this.detachCurrentMenuModule(); + this.menuStack.getCurrentModule().leave(); clearInterval(this.idleCheck); @@ -456,94 +448,15 @@ Client.prototype.address = function() { return this.input.address(); }; +// :TODO: remove these deprecated wrappers: Client.prototype.gotoMenuModule = function(options, cb) { - var self = this; - - assert(_.isString(options.name), 'Missing options.name'); - - // Assign a default missing module handler callback if none was provided - var callbackOnErrorOnly = !_.isFunction(cb); - - cb = miscUtil.valueWithDefault(cb, self.defaultHandlerMissingMod()); - - self.detachCurrentMenuModule(); - - var loadOptions = { - name : options.name, - client : self, - extraArgs : options.extraArgs, - }; - - menuUtil.loadMenu(loadOptions, function menuModuleLoaded(err, modInst) { - if(err) { - cb(err); - } else { - self.log.debug( { menuName : options.name }, 'Goto menu module'); - - self.currentMenuModule = modInst; // :TODO: should probably be before enter() above - - self.currentMenuModuleInfo = { - // :TODO: This is quite the hack... doesn't seem right... - menuName : self.currentMenuModule.menuName, - extraArgs : options.extraArgs, - } - - if(options.savedState) { - modInst.restoreSavedState(options.savedState); - } - - modInst.enter(self); - - if(!callbackOnErrorOnly) { - cb(null); - } - } - }); + this.menuStack.goto(options.name, options, cb); }; Client.prototype.fallbackMenuModule = function(options, cb) { - var self = this; - - var modOpts; - - if(_.isString(self.currentMenuModule.menuConfig.fallback)) { - modOpts = { - name : self.currentMenuModule.menuConfig.fallback, - extraArgs : options.extraArgs, - }; - } else if(self.lastMenuModuleInfo) { - modOpts = { - name : self.lastMenuModuleInfo.menuName, - extraArgs : self.lastMenuModuleInfo.extraArgs, - savedState : self.lastMenuModuleInfo.savedState, - }; - } - - if(modOpts) { - self.gotoMenuModule(modOpts, cb); - } else { - cb(new Error('Nothing to fallback to!')); - } + this.menuStack.prev(cb); }; -/* -Client.prototype.fallbackMenuModule = function(cb) { - var self = this; - - if(self.lastMenuModuleInfo) { - var modOpts = { - name : self.lastMenuModuleInfo.menuName, - extraArgs : self.lastMenuModuleInfo.extraArgs, - savedState : self.lastMenuModuleInfo.savedState, - }; - - self.gotoMenuModule(modOpts, cb); - } else { - cb(new Error('Nothing to fallback to!')); - } -}; -*/ - /////////////////////////////////////////////////////////////////////////////// // Default error handlers /////////////////////////////////////////////////////////////////////////////// diff --git a/core/connect.js b/core/connect.js index 3cb566b5..cd20a619 100644 --- a/core/connect.js +++ b/core/connect.js @@ -123,7 +123,7 @@ function connectEntry(client, nextMenu) { displayBanner(term); setTimeout(function onTimeout() { - client.gotoMenuModule( { name : nextMenu } ); + client.menuStack.goto(nextMenu); }, 500); }); } diff --git a/core/menu_module.js b/core/menu_module.js index 3b2c0fbd..c4c9a076 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -131,7 +131,7 @@ function MenuModule(options) { } self.finishedLoading(); - self.nextMenu(); + self.autoNextMenu(); } ); }; @@ -144,14 +144,12 @@ function MenuModule(options) { return _.isNumber(self.menuConfig.options.nextTimeout); }; - this.nextMenu = function() { + this.autoNextMenu = function() { function goNext() { if(_.isString(self.menuConfig.next)) { menuUtil.handleNext(self.client, self.menuConfig.next); } else { - self.client.fallbackMenuModule( { }, function fallback(err) { - // :TODO: this seems sloppy... look into further - }); + self.prevMenu(); } } @@ -217,15 +215,15 @@ MenuModule.prototype.restoreSavedState = function(savedState) { }; MenuModule.prototype.nextMenu = function(cb) { - self.client.menuStack.next(cb); + this.client.menuStack.next(cb); }; MenuModule.prototype.prevMenu = function(cb) { - self.client.menuStack.prev(cb); + this.client.menuStack.prev(cb); }; MenuModule.prototype.gotoMenu = function(name, options, cb) { - self.client.menuStack.goto(name, options, cb); + this.client.menuStack.goto(name, options, cb); }; MenuModule.prototype.leave = function() { @@ -302,16 +300,4 @@ MenuModule.prototype.standardMCIReadyHandler = function(mciData, cb) { }; MenuModule.prototype.finishedLoading = function() { -/* - var self = this; - - if(_.isNumber(this.menuConfig.options.nextTimeout) && - _.isString(this.menuConfig.next)) - { - setTimeout(function nextTimeout() { - menuUtil.handleNext(self.client, self.menuConfig.next); - //self.client.gotoMenuModule( { name : self.menuConfig.next } ); - }, this.menuConfig.options.nextTimeout); - } - */ }; \ No newline at end of file diff --git a/core/menu_stack.js b/core/menu_stack.js index eca1e8ad..f60da56c 100644 --- a/core/menu_stack.js +++ b/core/menu_stack.js @@ -5,6 +5,7 @@ var loadMenu = require('./menu_util.js').loadMenu; var _ = require('lodash'); +var assert = require('assert'); /* MenuStack(client) @@ -21,7 +22,8 @@ MenuModule */ // :TODO: Clean up client attach/detach/etc. -// :TODO: Cleanup up client currentMenuModule related stuff (all over!). Make this a property that returns .menuStack.getCurrentModule() +// :TODO: gotoMenuModule() -> MenuModule.gotoMenu() +// :TODO: fallbackMenuModule() -> MenuModule.prevMenu() module.exports = MenuStack; @@ -39,6 +41,12 @@ function MenuStack(client) { return self.stack.pop(); }; + this.peekPrev = function() { + if(this.stackSize() > 1) { + return self.stack[self.stack.length - 2]; + } + }; + this.top = function() { if(self.stackSize() > 0) { return self.stack[self.stack.length - 1]; @@ -52,23 +60,27 @@ function MenuStack(client) { MenuStack.prototype.next = function(cb) { var currentModuleInfo = this.top(); + assert(currentModuleInfo, 'Empty menu stack!'); - if(!_.isString(currentModuleInfo.menuConfig.next)) { - this.log.error('No \'next\' member in menu config!'); + var menuConfig = currentModuleInfo.instance.menuConfig; + + if(!_.isString(menuConfig.next)) { + cb(new Error('No \'next\' member in menu config!')); return; } - if(current.menuConfig.next === currentModuleInfo.name) { - this.log.warn('Menu config \'next\' specifies current menu!'); + if(menuConfig.next === currentModuleInfo.name) { + cb(new Error('Menu config \'next\' specifies current menu!')); return; } - this.goto(current.menuConfig.next, { }, cb); + this.goto(menuConfig.next, { }, cb); }; MenuStack.prototype.prev = function(cb) { - var previousModuleInfo = this.pop(); - + this.pop().instance.leave(); // leave & remove current + var previousModuleInfo = this.pop(); // get previous + if(previousModuleInfo) { this.goto(previousModuleInfo.name, { extraArgs : previousModuleInfo.extraArgs, savedState : previousModuleInfo.savedState }, cb); } else { @@ -77,17 +89,17 @@ MenuStack.prototype.prev = function(cb) { }; MenuStack.prototype.goto = function(name, options, cb) { - var currentModuleInfo = this.menuStack.top(); + var currentModuleInfo = this.top(); + + if(!cb && _.isFunction(options)) { + cb = options; + } var self = this; if(currentModuleInfo && name === currentModuleInfo.name) { - var err = new Error('Already at supplied menu!'); - - self.client.log.warn( { menuName : name, error : err.toString() }, 'Cannot go to menu'); - if(cb) { - cb(err); // non-fatal + cb(new Error('Already at supplied menu!')); } return; } @@ -95,14 +107,18 @@ MenuStack.prototype.goto = function(name, options, cb) { var loadOpts = { name : name, client : self.client, - extraArgs : options.extraArgs, }; + if(options) { + loadOpts.extraArgs = options.extraArgs; + } + loadMenu(loadOpts, function menuLoaded(err, modInst) { if(err) { var errCb = cb || self.defaultHandlerMissingMod(); errCb(err); } else { + // :TODO: Move this log to caller self.client.log.debug( { menuName : name }, 'Goto menu module'); if(currentModuleInfo) { @@ -112,19 +128,23 @@ MenuStack.prototype.goto = function(name, options, cb) { currentModuleInfo.instance.leave(); } - self.push( { + self.push({ name : name, instance : modInst, - extraArgs : options.extraArgs, + extraArgs : loadOpts.extraArgs, }); // restore previous state if requested - if(options.savedState) { + if(options && options.savedState) { modInst.restoreSavedState(options.savedState); } modInst.enter(self.client); + self.client.log.trace( + { stack : _.map(self.stack, function(si) { return si.name; } ) }, + 'Updated menu stack'); + if(cb) { cb(null); } diff --git a/core/menu_util.js b/core/menu_util.js index d8643562..02a91841 100644 --- a/core/menu_util.js +++ b/core/menu_util.js @@ -214,7 +214,7 @@ function handleAction(client, formData, conf) { break; case 'menu' : - client.gotoMenuModule( { name : actionAsset.asset, formData : formData, extraArgs : conf.extraArgs } ); + client.currentMenuModule.gotoMenu(actionAsset.asset, { formData : formData, extraArgs : conf.extraArgs } ); break; } } @@ -231,7 +231,7 @@ function handleNext(client, nextSpec, conf) { case 'method' : case 'systemMethod' : if(_.isString(nextAsset.location)) { - callModuleMenuMethod(client, nextAsset, paths.join(Config.paths.mods, actionAsset.location), {}, extraArgs); + callModuleMenuMethod(client, nextAsset, paths.join(Config.paths.mods, nextAsset.location), {}, extraArgs); } else { if('systemMethod' === nextAsset.type) { // :TODO: see other notes about system_menu_method.js here @@ -239,15 +239,15 @@ function handleNext(client, nextSpec, conf) { } else { // local to current module var currentModule = client.currentMenuModule; - if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) { - currentModule.menuMethods[actionAsset.asset]( { }, extraArgs ); + if(_.isFunction(currentModule.menuMethods[nextAsset.asset])) { + currentModule.menuMethods[nextAsset.asset]( { }, extraArgs ); } } } break; case 'menu' : - client.gotoMenuModule( { name : nextAsset.asset, extraArgs : extraArgs } ); + client.currentMenuModule.gotoMenu(nextAsset.asset, { extraArgs : extraArgs } ); break; default : diff --git a/core/message_area.js b/core/message_area.js index 859f516b..7444ede0 100644 --- a/core/message_area.js +++ b/core/message_area.js @@ -14,7 +14,6 @@ exports.getDefaultMessageArea = getDefaultMessageArea; exports.getMessageAreaByName = getMessageAreaByName; exports.changeMessageArea = changeMessageArea; exports.getMessageListForArea = getMessageListForArea; -exports.gotoMsgAreaFSEModuleForMessage = gotoMsgAreaFSEModuleForMessage; exports.getMessageAreaLastReadId = getMessageAreaLastReadId; exports.updateMessageAreaLastReadId = updateMessageAreaLastReadId; @@ -158,34 +157,6 @@ function getMessageListForArea(options, areaName, cb) { ); } -function gotoMsgAreaFSEModuleForMessage(options, cb) { - // options.client - // options.msgAreaName - // options.messageUuid - // options.moduleName - // options.msgNumber - // options.msgTotal - - var msg = new Message(); - msg.load( { uuid : options.messageUuid, user : options.client.user }, function loaded(err) { - if(err) { - cb(err); - } else { - var modOpts = { - name : options.moduleName, - extraArgs : { - message : msg, - messageAreaName : options.msgAreaName, - messageNumber : options.msgNumber, - messageTotal : options.msgTotal, - }, - }; - - options.client.gotoMenuModule(modOpts, cb); - } - }); -} - function getMessageAreaLastReadId(userId, areaName, cb) { msgDb.get( 'SELECT message_id ' + diff --git a/core/predefined_mci.js b/core/predefined_mci.js index 3b067906..8ff760d8 100644 --- a/core/predefined_mci.js +++ b/core/predefined_mci.js @@ -96,9 +96,12 @@ function getPredefinedMCIValue(client, code) { // // Clean up CPU strings a bit for better display // - return os.cpus()[0].model.replace(/\s+(?= )|\(R\)|\(TM\)|processor|CPU/g, ''); + return os.cpus()[0].model.replace(/\(R\)|\(TM\)|processor|CPU/g, '') + .replace(/\s+(?= )/g, ''); }, + // :TODO: MCI for core count, e.g. os.cpus().length + // :TODO: cpu load average (over N seconds): http://stackoverflow.com/questions/9565912/convert-the-output-of-os-cpus-in-node-js-to-percentage // :TODO: Node version/info diff --git a/core/system_menu_method.js b/core/system_menu_method.js index 4730709b..8fa12505 100644 --- a/core/system_menu_method.js +++ b/core/system_menu_method.js @@ -14,7 +14,7 @@ var iconv = require('iconv-lite'); exports.login = login; exports.logoff = logoff; -exports.fallbackMenu = fallbackMenu; +exports.prevMenu = prevMenu; function login(callingMenu, formData, extraArgs) { var client = callingMenu.client; @@ -49,7 +49,7 @@ function login(callingMenu, formData, extraArgs) { } else { // success! - client.gotoMenuModule( { name : callingMenu.menuConfig.next } ); + callingMenu.nextMenu(); } }); } @@ -74,10 +74,10 @@ function logoff(callingMenu, formData, extraArgs) { }, 500); } -function fallbackMenu(callingMenu, formData, extraArgs) { - callingMenu.client.fallbackMenuModule( { extraArgs : extraArgs }, function result(err) { +function prevMenu(callingMenu, formData, extraArgs) { + callingMenu.prevMenu(function result(err) { if(err) { - callingMenu.client.log.error( { error : err }, 'Error attempting to fallback!'); + callingMenu.client.log.error( { error : err.toString() }, 'Error attempting to fallback!'); } }); } diff --git a/mods/menu.hjson b/mods/menu.hjson index 42267fd1..3b70f49a 100644 --- a/mods/menu.hjson +++ b/mods/menu.hjson @@ -107,7 +107,7 @@ actionKeys: [ { keys: [ "escape" ] - action: @menu:matrix + action: @systemMethod:prevMenu } ] } @@ -192,7 +192,7 @@ } { value: { "submission" : 1 } - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } ] } @@ -200,7 +200,7 @@ actionKeys: [ { keys: [ "escape" ] - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } ] } @@ -278,7 +278,7 @@ } { value: { "submission" : 1 } - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } ] } @@ -286,7 +286,7 @@ actionKeys: [ { keys: [ "escape" ] - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } ] } @@ -524,7 +524,7 @@ actionKeys: [ { keys: [ "escape", "q", "shift + q" ] - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } ] } @@ -580,7 +580,7 @@ } { value: { command: "Q" } - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } { value: { command: "1" } @@ -646,7 +646,7 @@ } { value: { command: "Q" } - action: @menu:mainMenu + action: @systemMethod:prevMenu } { value: { command: "G" } @@ -681,7 +681,7 @@ actionKeys: [ { keys: [ "escape", "q", "shift + q" ] - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } ] } @@ -714,7 +714,7 @@ actionKeys: [ { keys: [ "escape", "q", "shift + q" ] - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } ] } @@ -821,7 +821,7 @@ } { keys: [ "escape", "q", "shift + q" ] - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } { keys: [ "?" ] @@ -911,7 +911,7 @@ } { value: { 1: 1 } - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } { value: { 1: 2 }, @@ -935,7 +935,7 @@ } { keys: [ "d", "shift + d" ] - action: @systemMethod:fallbackMenu + action: @systemMethod:prevMenu } { keys: [ "q", "shift + q" ] diff --git a/mods/msg_list.js b/mods/msg_list.js index 3dd34158..8359bf72 100644 --- a/mods/msg_list.js +++ b/mods/msg_list.js @@ -68,7 +68,7 @@ function MessageListModule(options) { } }; - self.client.gotoMenuModule(modOpts); + self.gotoMenu(config.menuViewPost || 'messageAreaViewPost', modOpts); } } };