diff --git a/core/ansi_term.js b/core/ansi_term.js index d4b35d3d..4fafd46e 100644 --- a/core/ansi_term.js +++ b/core/ansi_term.js @@ -101,7 +101,7 @@ var SGRValues = { yellowBG : 43, blueBG : 44, - cyanBG : 47, + cyanBG : 46, whiteBG : 47, }; diff --git a/core/asset.js b/core/asset.js index 8015a351..59769eb7 100644 --- a/core/asset.js +++ b/core/asset.js @@ -1,6 +1,8 @@ /* jslint node: true */ 'use strict'; +var Config = require('./config.js').config; + var _ = require('lodash'); var assert = require('assert'); @@ -49,4 +51,4 @@ function getArtAsset(art, cb) { asset : art, }; } -} \ No newline at end of file +} diff --git a/core/bbs.js b/core/bbs.js index 226fff0c..28a4652d 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -3,7 +3,6 @@ // ENiGMA½ var conf = require('./config.js'); -var moduleUtil = require('./module_util.js'); var logger = require('./logger.js'); var miscUtil = require('./misc_util.js'); var database = require('./database.js'); @@ -75,55 +74,6 @@ function bbsMain() { ); } -/* -function bbsMain() { - var mainArgs = parseArgs(); - - var configPathSupplied = false; - var configPath = conf.defaultPath(); - - if(mainArgs.indexOf('--help') > 0) { - // :TODO: display help - } else { - var argCount = mainArgs.length; - for(var i = 0; i < argCount; ++i) { - var arg = mainArgs[i]; - if('--config' == arg) { - configPathSupplied = true; - configPath = mainArgs[i + 1]; - } - } - } - - try { - conf.initFromFile(configPath); - } catch(e) { - // - // If the user supplied a config and we can't read, parse, whatever - // then output a error and bail. - // - if(configPathSupplied) { - if(e.code === 'ENOENT') { - console.error('Configuration file does not exist: ' + configPath); - } - return; - } - - console.log('No configuration file found, creating defaults.'); - conf.createDefault(); - } - - initialize(function onInit(err) { - if(err) { - console.error('Error initializing: ' + util.inspect(err)); - return; - } - - startListening(); - }); -} -*/ - function parseArgs() { var args = []; process.argv.slice(2).forEach(function(val, index, array) { @@ -179,6 +129,8 @@ function startListening() { return []; } + var moduleUtil = require('./module_util.js'); // late load so we get Config + moduleUtil.loadModulesForCategory('servers', function onServerModule(err, module) { if(err) { logger.log.info(err); @@ -261,12 +213,12 @@ function prepareClient(client, cb) { [ function getRandTheme(callback) { theme.getRandomTheme(function randTheme(err, themeId) { - client.user.properties.art_theme_id = themeId || ''; + client.user.properties.theme_id = themeId || ''; callback(null); }); }, function setCurrentThemeInfo(callback) { - theme.getThemeInfo(client.user.properties.art_theme_id, function themeInfo(err, info) { + theme.getThemeInfo(client.user.properties.theme_id, function themeInfo(err, info) { client.currentThemeInfo = info; callback(null); }); @@ -277,7 +229,7 @@ function prepareClient(client, cb) { } ); } else { - client.user.properties.art_theme_id = conf.config.preLoginTheme; + client.user.properties.theme_id = conf.config.preLoginTheme; cb(null); } } \ No newline at end of file diff --git a/core/menu_module.js b/core/menu_module.js index 22462df4..000a0631 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -8,6 +8,7 @@ var Log = require('./logger.js').log; var ansi = require('./ansi_term.js'); var asset = require('./asset.js'); var ViewController = require('./view_controller.js').ViewController; +var menuUtil = require('./menu_util.js'); var async = require('async'); var assert = require('assert'); @@ -90,7 +91,7 @@ function MenuModule(options) { } // Prompts *must* have art. If it's missing it's an error - // :TODO: allow inline prompts in the future, e.g. @inline:memberName -> "memberName" : { ... } + // :TODO: allow inline prompts in the future, e.g. @inline:memberName -> { "memberName" : { "text" : "stuff", ... } } var promptConfig = self.menuConfig.promptConfig; self.displayArtAsset(promptConfig.art, function displayed(err, mciMap) { mciData.prompt = mciMap; @@ -114,92 +115,6 @@ function MenuModule(options) { } ); }; -/* - this.initSequence2 = function() { - async.waterfall( - [ - function beforeDisplayArt(callback) { - self.beforeArt(); - callback(null); - }, - function displayArtAsset(callback) { - var artAsset = asset.getArtAsset(self.menuConfig.art); - - if(!artAsset) { - // no art to display - callback(null, null); - return; - } - - var dispOptions = { - name : artAsset.asset, - client : self.client, - font : self.menuConfig.font, - }; - - if('art' === artAsset.type) { - theme.displayThemeArt(dispOptions, function displayedArt(err, mciMap) { - callback(null, mciMap); - }); - } else if('method' === artAsset.type) { - // :TODO: support fetching the asset (e.g. rendering into buffer) -> display it. - } - }, - function afterArtDisplayed(mciMap, callback) { - if(mciMap) { - self.mciReady(mciMap); - } - - callback(null); - }, - function loadPrompt(callback) { - if(!_.isString(self.menuConfig.prompt)) { - // no prompt supplied - callback(null, null); - return; - } - - var loadPromptOpts = { - name : self.menuConfig.prompt, - client : self.client, - }; - - promptUtil.loadPrompt(loadPromptOpts, function promptLoaded(err, prompt) { - callback(err, prompt); - }); - }, - function displayPrompt(prompt, callback) { - if(!prompt) { - callback(null); - return; - } - - // :TODO: go to proper prompt location before displaying - - var dispOptions = { - art : prompt.artInfo.data, - sauce : prompt.artInfo.sauce, - client : self.client, - font : prompt.config.font, - }; - - - art.display(dispOptions, function displayed(err, mciMap) { - - }); - } - ], - function complete(err) { - if(err) { - // :TODO: Log me!!! ... and what else? - console.log(err); - } - - self.finishedLoading(); - } - ); - }; - */ } require('util').inherits(MenuModule, PluginModule); @@ -225,7 +140,7 @@ MenuModule.prototype.addViewController = function(name, vc) { }; MenuModule.prototype.beforeArt = function() { - if(this.menuConfig.options.clearScreen) { + if(this.menuConfig.options.cls) { this.client.term.write(ansi.resetScreen()); } }; @@ -233,6 +148,50 @@ MenuModule.prototype.beforeArt = function() { MenuModule.prototype.mciReady = function(mciData) { }; +MenuModule.prototype.standardMCIReadyHandler = function(mciData) { + // + // 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) + // + var self = this; + self.viewControllers = {}; + + //var vcOpts = { client : self.client }; + + _.forEach(mciData, function entry(mciMap, name) { + assert('menu' === name || 'prompt' === name); + self.viewControllers[name] = new ViewController( { client : self.client } ); + }); + + var viewsReady = function(err) { + // :TODO: what should really happen here? + if(err) { + Log.warn(err); + } + }; + + if(self.viewControllers.menu) { + var menuLoadOpts = { + mciMap : mciData.menu, + callingMenu : self, + withoutForm : _.isObject(mciData.prompt), + }; + + self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, viewsReady); + } + + if(self.viewControllers.prompt) { + var promptLoadOpts = { + callingMenu : self, + mciMap : mciData.prompt, + }; + + self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, viewsReady); + } +}; + MenuModule.prototype.finishedLoading = function() { var self = this; @@ -243,5 +202,12 @@ MenuModule.prototype.finishedLoading = function() { setTimeout(function nextTimeout() { self.client.gotoMenuModule( { name : self.menuConfig.next } ); }, this.menuConfig.options.nextTimeout); + } else { + if(!_.isObject(self.menuConfig.form) && !_.isString(self.menuConfig.prompt) && + _.isString(self.menuConfig.action)) + { + menuUtil.handleAction(self.client, null, self.menuConfig); + } + } }; \ No newline at end of file diff --git a/core/menu_util.js b/core/menu_util.js index b705a157..b8b1ab3a 100644 --- a/core/menu_util.js +++ b/core/menu_util.js @@ -19,6 +19,7 @@ var stripJsonComments = require('strip-json-comments'); exports.loadMenu = loadMenu; exports.getFormConfigByIDAndMap = getFormConfigByIDAndMap; +exports.handleAction = handleAction; function loadModJSON(fileName, cb) { @@ -93,11 +94,18 @@ function loadMenu(options, cb) { }); }, function loadMenuModule(menuConfig, callback) { - var moduleName = menuConfig.module || 'standard_menu'; - moduleUtil.loadModule(moduleName, 'mods', function moduleLoaded(err, mod) { + var modSupplied = _.isString(menuConfig.module); + + var modLoadOpts = { + name : modSupplied ? menuConfig.module : 'standard_menu', + path : modSupplied ? Config.paths.mods : __dirname, + category : modSupplied ? 'mods' : null, + }; + + moduleUtil.loadModuleEx(modLoadOpts, function moduleLoaded(err, mod) { var modData = { - name : moduleName, + name : modLoadOpts.name, config : menuConfig, mod : mod, }; @@ -124,63 +132,6 @@ function loadMenu(options, cb) { ); } -function loadMenu2(options, cb) { - - assert(options); - assert(options.name); - assert(options.client); - - var name = options.name; - var client = options.client; - /* - TODO: - * check access / ACS - * - */ - - async.waterfall( - [ - // :TODO: Need a good way to cache this information & only (re)load if modified - function loadMenuConfig(callback) { - var configJsonPath = paths.join(conf.config.paths.mods, 'menu.json'); - - fs.readFile(configJsonPath, { encoding : 'utf8' }, function onMenuConfig(err, data) { - try { - var menuJson = JSON.parse(stripJsonComments(data)); - - if(!_.isObject(menuJson[name])) { - callback(new Error('No configuration entry for \'' + name + '\'')); - } else { - callback(err, menuJson[name]); - } - } catch(e) { - callback(e); - } - }); - }, - function menuConfigLoaded(menuConfig, callback) { - var moduleName = menuConfig.module || 'standard_menu'; - - moduleUtil.loadModule(moduleName, 'mods', function onModule(err, mod) { - callback(err, mod, menuConfig, moduleName); - }); - } - ], - function complete(err, mod, menuConfig, moduleName) { - if(err) { - cb(err); - } else { - Log.debug( - { moduleName : moduleName, args : options.args, config : menuConfig, info : mod.moduleInfo }, - 'Creating menu module instance'); - - // :TODO: throw from MenuModule() - catch here - cb(null, new mod.getModule({ menuConfig : menuConfig, args : options.args } )); - } - } - ); -} - function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) { assert(_.isObject(menuConfig)); @@ -209,3 +160,42 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) { cb(new Error('No matching form configuration found')); } + +function handleAction(client, formData, conf) { + assert(_.isObject(conf)); + assert(_.isString(conf.action)); + + var actionAsset = asset.parseAsset(conf.action); + assert(_.isObject(actionAsset)); + + switch(actionAsset.type) { + case 'method' : + if(_.isString(actionAsset.location)) { + try { + // allow ".js" omission + if(''=== paths.extname(actionAsset.location)) { + actionAsset.location += '.js'; + } + + var methodMod = require(paths.join(Config.paths.mods, actionAsset.location)); + + if(_.isFunction(methodMod[actionAsset.asset])) { + methodMod[actionAsset.asset](client.currentMenuModule, formData, conf.extraArgs); + } + } catch(e) { + Log.error( { error : e, methodName : actionAsset.asset }, 'Failed to execute asset method'); + } + } else { + // local to current module + var currentModule = client.currentMenuModule; + if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) { + currentModule.menuMethods[actionAsset.asset](formData, conf.extraArgs); + } + } + break; + + case 'menu' : + client.gotoMenuModule( { name : actionAsset.asset, formData : formData, extraArgs : conf.extraArgs } ); + break; + } +} diff --git a/core/module_util.js b/core/module_util.js index bc5f8b25..55dd61ed 100644 --- a/core/module_util.js +++ b/core/module_util.js @@ -1,51 +1,46 @@ /* jslint node: true */ 'use strict'; +var Config = require('./config.js').config; +var miscUtil = require('./misc_util.js'); + var fs = require('fs'); var paths = require('path'); - -var conf = require('./config.js'); -var miscUtil = require('./misc_util.js'); var _ = require('lodash'); +var assert = require('assert'); // exports +exports.loadModuleEx = loadModuleEx; exports.loadModule = loadModule; exports.loadModulesForCategory = loadModulesForCategory; -function loadModule(name, category, cb) { - var config = conf.config; - var path = config.paths[category]; +function loadModuleEx(options, cb) { + assert(_.isObject(options)); + assert(_.isString(options.name)); + assert(_.isString(options.path)); - if(!path) { - cb(new Error('not sure where to look for "' + name + '" of category "' + category + '"')); - return; - } + var modConfig = _.isObject(Config[options.category]) ? Config[options.category][options.name] : null; - // update conf to point at this module's section, if any - config = config[category] ? config[category][name] : null; - - if(config && false === config.enabled) { - cb(new Error('module "' + name + '" is disabled')); + if(_.isObject(modConfig) && false === modConfig.enabled) { + cb(new Error('Module "' + options.name + '" is disabled')); return; } try { - var mod = require(paths.join(path, name + '.js')); + var mod = require(paths.join(options.path, options.name + '.js')); - if(!mod.moduleInfo) { - cb(new Error('module is missing \'moduleInfo\' section')); + if(!_.isObject(mod.moduleInfo)) { + cb(new Error('Module is missing "moduleInfo" section')); return; } if(!_.isFunction(mod.getModule)) { - cb(new Error('Invalid or missing missing \'getModule\' method')); + cb(new Error('Invalid or missing "getModule" method for module!')); return; } - // :TODO: what was the point of this? Remove it - mod.runtime = { - config : config - }; + // Safe configuration, if any, for convience to the module + mod.runtime = { config : modConfig }; cb(null, mod); } catch(e) { @@ -53,8 +48,21 @@ function loadModule(name, category, cb) { } } +function loadModule(name, category, cb) { + var path = Config.paths[category]; + + if(!_.isString(path)) { + cb(new Error('Not sure where to look for "' + name + '" of category "' + category + '"')); + return; + } + + loadModuleEx( { name : name, path : path, category : category }, function loaded(err, mod) { + cb(err, mod); + }); +} + function loadModulesForCategory(category, cb) { - var path = conf.config.paths[category]; + var path = Config.paths[category]; fs.readdir(path, function onFiles(err, files) { if(err) { diff --git a/core/standard_menu.js b/core/standard_menu.js new file mode 100644 index 00000000..5c61368f --- /dev/null +++ b/core/standard_menu.js @@ -0,0 +1,34 @@ +/* jslint node: true */ +'use strict'; + +var MenuModule = require('./menu_module.js').MenuModule; + +exports.getModule = StandardMenuModule; + +exports.moduleInfo = { + name : 'Standard Menu Module', + desc : 'A Menu Module capable of handing standard configurations', + author : 'NuSkooler', +}; + +function StandardMenuModule(menuConfig) { + MenuModule.call(this, menuConfig); +} + +require('util').inherits(StandardMenuModule, MenuModule); + + +StandardMenuModule.prototype.enter = function(client) { + StandardMenuModule.super_.prototype.enter.call(this, client); +}; + +StandardMenuModule.prototype.beforeArt = function() { + StandardMenuModule.super_.prototype.beforeArt.call(this); +}; + +StandardMenuModule.prototype.mciReady = function(mciData) { + StandardMenuModule.super_.prototype.mciReady.call(this, mciData); + + // we do this so other modules can be both customized and still perform standard tasks + StandardMenuModule.super_.prototype.standardMCIReadyHandler.call(this, mciData); +}; diff --git a/core/theme.js b/core/theme.js index a51f049e..ab0afe08 100644 --- a/core/theme.js +++ b/core/theme.js @@ -138,7 +138,7 @@ function displayThemeArt(options, cb) { assert(_.isObject(options.client)); assert(_.isString(options.name)); - getThemeArt(options.name, options.client.user.properties.art_theme_id, function themeArt(err, artInfo) { + getThemeArt(options.name, options.client.user.properties.theme_id, function themeArt(err, artInfo) { if(err) { cb(err); } else { diff --git a/core/view.js b/core/view.js index a4997278..aad3e320 100644 --- a/core/view.js +++ b/core/view.js @@ -183,7 +183,7 @@ View.prototype.onSpecialKeyPress = function(keyName) { if(this.isSpecialKeyMapped('accept', keyName)) { this.emit('action', 'accept'); } else if(this.isSpecialKeyMapped('next', keyName)) { - console.log('next') + this.emit('action', 'next'); } }; diff --git a/core/view_backup.js b/core/view_backup.js deleted file mode 100644 index 00f6982b..00000000 --- a/core/view_backup.js +++ /dev/null @@ -1,432 +0,0 @@ -/* jslint node: true */ -'use strict'; - -var util = require('util'); -var ansi = require('./ansi_term.js'); -var miscUtil = require('./misc_util.js'); -var strUtil = require('./string_util.js'); -var assert = require('assert'); -var events = require('events'); -var logger = require('./logger.js'); - -exports.View = View; -exports.LabelView = LabelView; -exports.TextEditView = TextEditView; - -exports.ViewsController = ViewsController; - -function View(client, options) { - events.EventEmitter.call(this); - - var self = this; - - this.client = client; - this.options = options; - this.acceptsFocus = false; - this.acceptsKeys = false; -} - -util.inherits(View, events.EventEmitter); - -View.prototype.place = function(pos) { - // - // We allow [x, y], { x : x, y : y }, or (x, y) - // - if(util.isArray(pos)) { - this.x = pos[0]; - this.y = pos[1]; - } else if(pos.x && pos.y) { - this.x = pos.x; - this.y = pos.y; - } else if(2 === arguments.length) { - var x = parseInt(arguments[0], 10); - var y = parseInt(arguments[1], 10); - if(!isNaN(x) && !isNaN(y)) { - this.x = x; - this.y = y; - } - } - - assert(this.x > 0 && this.x < this.client.term.termHeight); - assert(this.y > 0 && this.y < this.client.term.termWidth); - - this.client.term.write(ansi.goto(this.x, this.y)); -}; - -View.prototype.setFocus = function(focused) { - assert(this.x); - assert(this.y); -}; - -View.prototype.getColor = function() { - return this.options.color; -}; - -View.prototype.getFocusColor = function() { - return this.options.focusColor || this.getColor(); -}; - -function LabelView(client, text, options) { - View.call(this, client); - - var self = this; - - if(options) { - if(options.maxWidth) { - text = text.substr(0, options.maxWidth); - } - - text = strUtil.stylizeString(text, options.style); - } - - this.value = text; - this.height = 1; - this.width = this.value.length; -} - -util.inherits(LabelView, View); - -LabelView.prototype.place = function(pos) { - LabelView.super_.prototype.place.apply(this, arguments); - - this.client.term.write(this.value); -}; - -/////////////////////////////////////////////////////////////////////////////// - -var INTERACTIVE_VIEW_DEFAULT_SPECIAL_KEYSET = { - enter : [ 'enter' ], - exit : [ 'esc' ], - backspace : [ 'backspace', 'del' ], - next : [ 'tab' ], -}; - -function InteractiveView(client, options) { - View.call(this, client); - - this.acceptsFocus = true; - this.acceptsKeys = true; - - if(options) { - this.options = options; - } else { - this.options = { - }; - } - - this.options.specialKeySet = miscUtil.valueWithDefault( - options.specialKeySet, INTERACTIVE_VIEW_DEFAULT_SPECIAL_KEYSET - ); - - this.isSpecialKeyFor = function(checkFor, specialKey) { - return this.options.specialKeySet[checkFor].indexOf(specialKey) > -1; - }; - - this.backspace = function() { - this.client.term.write('\b \b'); - }; -} - -util.inherits(InteractiveView, View); - -InteractiveView.prototype.setFocus = function(focused) { - InteractiveView.super_.prototype.setFocus.call(this, focused); - - this.hasFocus = focused; -}; - -InteractiveView.prototype.setNextView = function(id) { - this.nextId = id; -}; - - -var TEXT_EDIT_INPUT_TYPES = [ - 'normal', 'N', - 'password', 'P', - 'upper', 'U', - 'lower', 'l', -]; - - -function TextEditView(client, options) { - InteractiveView.call(this, client, options); - - if(!options) { - this.options.multiLine = false; - } - - this.options.inputType = miscUtil.valueWithDefault(this.options.inputType, 'normal'); - assert(TEXT_EDIT_INPUT_TYPES.indexOf(this.options.inputType) > -1); - - if('password' === this.options.inputType || 'P' === this.options.inputType) { - this.options.inputMaskChar = miscUtil.valueWithDefault(this.options.inputMaskChar, '*').substr(0,1); - } - - this.value = miscUtil.valueWithDefault(options.defaultValue, ''); - - - // :TODO: hilight, text, etc., should come from options or default for theme if not provided - - // focus=fg + bg - // standard=fg +bg - -} - -util.inherits(TextEditView, InteractiveView); - - -TextEditView.prototype.place = function(pos) { - TextEditView.super_.prototype.place.apply(this, arguments); - - if(!this.options.maxWidth) { - this.options.maxWidth = this.client.term.termWidth - this.x; - } - - this.width = this.options.maxWidth; -}; - -TextEditView.prototype.setFocus = function(focused) { - TextEditView.super_.prototype.setFocus.call(this, focused); - - this.client.term.write(ansi.goto(this.x, this.y)); - this.redraw(); - this.client.term.write(ansi.goto(this.x, this.y + this.value.length)); -}; - -TextEditView.prototype.redraw = function() { - var color = this.hasFocus ? this.getFocusColor() : this.getColor(); - - this.client.term.write(ansi.sgr(color.flags, color.fg, color.bg)); - this.client.term.write(strUtil.pad(this.value, this.width)); -}; - -TextEditView.prototype.onKeyPressed = function(k, isSpecial) { - assert(this.hasFocus); - - if(isSpecial) { - return; // handled via onSpecialKeyPressed() - } - - if(this.value.length < this.options.maxWidth) { - - k = strUtil.stylizeString(k, this.options.inputType); - - this.value += k; - - if('P' === this.options.inputType.charAt(0).toUpperCase()) { - this.client.term.write(this.options.inputMaskChar); - } else { - this.client.term.write(k); - } - } -}; - -TextEditView.prototype.onSpecialKeyPressed = function(keyName) { - assert(this.hasFocus); - - console.log(keyName); - - if(this.isSpecialKeyFor('backspace', keyName)) { - if(this.value.length > 0) { - this.value = this.value.substr(0, this.value.length - 1); - this.backspace(); - } - } else if(this.isSpecialKeyFor('enter', keyName)) { - if(this.options.multiLine) { - - } else { - this.emit('action', 'accepted'); - } - } else if(this.isSpecialKeyFor('next', keyName)) { - this.emit('action', 'next'); - } -}; - - -function MenuView(options) { - -} - -function VerticalMenuView(options) { - -} - -/////////////////////////////////////////////////////// -// :TODO: Move to view_controller.js -function ViewsController(client) { - events.EventEmitter.call(this); - - var self = this; - - this.views = {}; - this.client = client; - - client.on('key press', function onKeyPress(k, isSpecial) { - if(self.focusedView && self.focusedView.acceptsKeys) { - self.focusedView.onKeyPressed(k, isSpecial); - } - }); - - client.on('special key', function onSpecialKey(keyName) { - if(self.focusedView && self.focusedView.acceptsKeys) { - self.focusedView.onSpecialKeyPressed(keyName); - } - }); - - this.onViewAction = function(action) { - console.log(action + ' @ ' + this.id); - - if('next' === action) { - self.emit('action', { view : this, action : action }); - self.nextFocus(); - } else if('accepted' === action) { - if(self.submitViewId == this.id) { - self.emit('action', { view : this, action : 'submit' }); - } else { - self.nextFocus(); - } - } - /* - if(self.submitViewId == this.id) { - self.emit('action', { view : this, action : 'submit' }); - } else { - self.emit('action', { view : this, action : action }); - - if('accepted' === action || 'next' === action) { - self.nextFocus(); - } - }*/ - }; - -} - -util.inherits(ViewsController, events.EventEmitter); - -ViewsController.prototype.addView = function(viewInfo) { - viewInfo.view.id = viewInfo.id; - - this.views[viewInfo.id] = { - view : viewInfo.view, - pos : viewInfo.pos - }; - - viewInfo.view.place(viewInfo.pos); -}; - -ViewsController.prototype.viewExists = function(id) { - return id in this.views; -}; - -ViewsController.prototype.getView = function(id) { - return this.views[id].view; -}; - -ViewsController.prototype.switchFocus = function(id) { - var view = this.getView(id); - - if(!view) { - logger.log.warn('Invalid view', { id : id }); - return false; - } - - if(!view.acceptsFocus) { - logger.log.warn('View does not accept focus', { id : id }); - return false; - } - - this.focusedView = view; - view.setFocus(true); -}; - -ViewsController.prototype.nextFocus = function() { - var nextId = this.focusedView.nextId; - - this.focusedView.setFocus(false); - - if(nextId > 0) { - this.switchFocus(nextId); - } else { - this.switchFocus(this.firstId); - } -}; - -ViewsController.prototype.setSubmitView = function(id) { - this.submitViewId = id; -}; - -ViewsController.prototype.loadFromMCIMap = function(mciMap) { - var factory = new MCIViewFactory(this.client); - var view; - var mci; - - for(var entry in mciMap) { - mci = mciMap[entry]; - view = factory.createFromMCI(mci); - - if(view) { - this.addView({ - id : mci.id, - view : view, - pos : mci.position - }); - - view.on('action', this.onViewAction); - } - } -}; - -ViewsController.prototype.setViewOrder = function(order) { - var idOrder = []; - - if(order) { - // :TODO: - } else { - for(var id in this.views) { - idOrder.push(id); - } - // :TODO: simply sort - console.log(idOrder); - this.firstId = idOrder[0]; - } - - var view; - for(var i = 0; i < idOrder.length - 1; ++i) { - view = this.getView(idOrder[i]); - if(view) { - view.setNextView(idOrder[i + 1]); - } - } -}; - -/////////////////////////////////////////////////// - -function MCIViewFactory(client, mci) { - this.client = client; -} - -MCIViewFactory.prototype.createFromMCI = function(mci) { - assert(mci.code); - assert(mci.id > 0); - - var view; - var options = {}; - - switch(mci.code) { - case 'EV' : - if(mci.args.length > 0) { - options.maxWidth = mci.args[0]; - } - - if(mci.args.length > 1) { - options.inputType = mci.args[1]; - } - - options.color = mci.color; - options.focusColor = mci.focusColor; - - view = new TextEditView(this.client, options); - break; - } - - return view; -}; \ No newline at end of file diff --git a/core/view_controller.js b/core/view_controller.js index 6cd66dc1..9e0a97c5 100644 --- a/core/view_controller.js +++ b/core/view_controller.js @@ -22,6 +22,7 @@ var MCI_REGEXP = /([A-Z]{2})([0-9]{1,2})/; function ViewController(options) { assert(_.isObject(options)); assert(_.isObject(options.client)); + events.EventEmitter.call(this); @@ -32,7 +33,7 @@ function ViewController(options) { this.formId = options.formId || 0; this.mciViewFactory = new MCIViewFactory(this.client); - this.onClientKeyPress = function(key, isSpecial) { + this.clientKeyPressHandler = function(key, isSpecial) { if(isSpecial) { return; } @@ -43,7 +44,10 @@ function ViewController(options) { } }; - this.onClientSpecialKeyPress = function(keyName) { + this.clientSpecialKeyHandler = function(keyName) { + + // :TODO: Handle special key mappings from config, e.g. 'esc' + if(self.focusedView && self.focusedView.acceptsInput) { self.focusedView.onSpecialKeyPress(keyName); } @@ -127,42 +131,6 @@ function ViewController(options) { self.emitSwitchFocus = false; }; - this.handleSubmitAction = function(callingMenu, formData, conf) { - assert(_.isObject(conf)); - assert(_.isString(conf.action)); - - var actionAsset = asset.parseAsset(conf.action); - assert(_.isObject(actionAsset)); - - var extraArgs; - if(conf.extraArgs) { - extraArgs = self.formatMenuArgs(conf.extraArgs); - } - - switch(actionAsset.type) { - case 'method' : - if(_.isString(actionAsset.location)) { - // :TODO: allow omition of '.js' - var methodMod = require(paths.join(Config.paths.mods, actionAsset.location)); - if(_.isFunction(methodMod[actionAsset.asset])) { - methodMod[actionAsset.asset](callingMenu, formData, extraArgs); - } - } else { - // local to current module - var currentModule = self.client.currentMenuModule; - if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) { - currentModule.menuMethods[actionAsset.asset](formData, extraArgs); - } - } - break; - - case 'menu' : - // :TODO: update everythign to handle this format - self.client.gotoMenuModule( { name : actionAsset.asset, submitData : formData, extraArgs : extraArgs } ); - break; - } - }; - this.createViewsFromMCI = function(mciMap, cb) { async.each(Object.keys(mciMap), function entry(name, nextItem) { var mci = mciMap[name]; @@ -247,8 +215,8 @@ ViewController.prototype.attachClientEvents = function() { return; } - this.client.on('key press', this.onClientKeyPress); - this.client.on('special key', this.onClientSpecialKeyPress); + this.client.on('key press', this.clientKeyPressHandler); + this.client.on('special key', this.clientSpecialKeyHandler); this.attached = true; }; @@ -258,8 +226,8 @@ ViewController.prototype.detachClientEvents = function() { return; } - this.client.removeListener('key press', this.onClientKeyPress); - this.client.removeListener('special key', this.onClientSpecialKeyPress); + this.client.removeListener('key press', this.clientKeyPressHandler); + this.client.removeListener('special key', this.clientSpecialKeyHandler); for(var id in this.views) { this.views[id].removeAllListeners(); @@ -356,13 +324,10 @@ ViewController.prototype.loadFromMCIMap = function(mciMap) { ViewController.prototype.loadFromPromptConfig = function(options, cb) { assert(_.isObject(options)); - assert(_.isObject(options.callingMenu)); - assert(_.isObject(options.callingMenu.menuConfig)); - assert(_.isObject(options.callingMenu.menuConfig.promptConfig)); assert(_.isObject(options.mciMap)); - - var promptConfig = options.callingMenu.menuConfig.promptConfig; + var self = this; + var promptConfig = self.client.currentMenuModule.menuConfig.promptConfig; var initialFocusId = 1; // default to first async.waterfall( @@ -383,7 +348,7 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) { self.on('submit', function promptSubmit(formData) { Log.trace( { formData : self.getLogFriendlyFormData(formData) }, 'Prompt submit'); - self.handleSubmitAction(options.callingMenu, formData, options.callingMenu.menuConfig); + menuUtil.handleAction(self.client, formData, self.client.currentMenuModule.menuConfig); }); callback(null); @@ -403,8 +368,6 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) { ViewController.prototype.loadFromMenuConfig = function(options, cb) { assert(_.isObject(options)); - assert(_.isObject(options.callingMenu)); - assert(_.isObject(options.callingMenu.menuConfig)); assert(_.isObject(options.mciMap)); var self = this; @@ -417,33 +380,40 @@ ViewController.prototype.loadFromMenuConfig = function(options, cb) { // method for comparing submitted form data to configuration entries var actionBlockValueComparator = function(formValue, actionValue) { // - // Any key(s) in actionValue must: - // 1) Be present in formValue - // 2) Either: - // a) Be set to null (wildcard/any) - // b) Have matching value(s) + // For a match to occur, one of the following must be true: // - var keys = Object.keys(actionValue); - for(var i = 0; i < keys.length; ++i) { - var name = keys[i]; - - // submit data contains config key? - if(!_.has(formValue, name)) { - return false; // not present in what was submitted - } - - if(null !== actionValue[name] && actionValue[name] !== formValue[name]) { + // * actionValue is a Object: + // a) All key/values must exactly match + // b) value is null; The key (view ID) must be present + // in formValue. This is a wildcard/any match. + // * actionValue is a Number: This represents a view ID that + // must be present in formValue. + // + if(_.isNumber(actionValue)) { + if(_.isUndefined(formValue[actionValue])) { return false; } + } else { + var actionValueKeys = Object.keys(actionValue); + for(var i = 0; i < actionValueKeys.length; ++i) { + var viewId = actionValueKeys[i]; + if(!_.has(formValue, viewId)) { + return false; + } + + if(null !== actionValue[viewId] && actionValue[viewId] !== formValue[viewId]) { + return false; + } + } } - + return true; }; async.waterfall( [ function findMatchingFormConfig(callback) { - menuUtil.getFormConfigByIDAndMap(options.callingMenu.menuConfig, formIdKey, options.mciMap, function matchingConfig(err, fc) { + menuUtil.getFormConfigByIDAndMap(self.client.currentMenuModule.menuConfig, formIdKey, options.mciMap, function matchingConfig(err, fc) { formConfig = fc; if(err) { @@ -501,7 +471,7 @@ ViewController.prototype.loadFromMenuConfig = function(options, cb) { var actionBlock = confForFormId[c]; if(_.isEqual(formData.value, actionBlock.value, actionBlockValueComparator)) { - self.handleSubmitAction(options.callingMenu, formData, actionBlock); + menuUtil.handleAction(self.client, formData, actionBlock); break; // there an only be one... } } @@ -524,176 +494,6 @@ ViewController.prototype.loadFromMenuConfig = function(options, cb) { ); }; -ViewController.prototype.loadFromMCIMapAndConfig = function(options, cb) { - assert(options.mciMap); - - var factory = new MCIViewFactory(this.client); - var self = this; - var formIdKey = options.formId ? options.formId.toString() : '0'; - var initialFocusId; - var formConfig; - - var mciRegEx = /([A-Z]{2})([0-9]{1,2})/; - - // :TODO: remove all the passing of fromConfig - use local - // :TODO: break all of this up ... a lot - - async.waterfall( - [ - function getFormConfig(callback) { - menuUtil.getFormConfigByIDAndMap(options.menuConfig, formIdKey, options.mciMap, function onFormConfig(err, fc) { - formConfig = fc; - - if(err) { - // :TODO: fix logging of err here: - Log.trace( - { err : err.toString(), mci : Object.keys(options.mciMap), formIdKey : formIdKey } , - 'Unable to load menu configuration'); - } - - callback(null); - }); - }, - function createViews(callback) { - self.createViewsFromMCI(options.mciMap, function viewsCreated(err) { - callback(err); - }); - }, - function applyFormConfig(callback) { - if(formConfig) { - async.each(Object.keys(formConfig.mci), function onMciConf(mci, eachCb) { - var mciMatch = mci.match(mciRegEx); // :TODO: what about auto-generated IDs? Do they simply not apply to menu configs? - var viewId = parseInt(mciMatch[2]); - var view = self.getView(viewId); - var mciConf = formConfig.mci[mci]; - - // :TODO: Break all of this up ... and/or better way of doing it - if(mciConf.items) { - view.setItems(mciConf.items); - } - - if(mciConf.submit) { - view.submit = true; // :TODO: should really be actual value - } - - if(mciConf.text) { - view.setText(mciConf.text); - } - - if(mciConf.focus) { - initialFocusId = viewId; - } - - eachCb(null); - }, - function eachMciConfComplete(err) { - callback(err); - }); - } else { - callback(null); - } - }, - function mapMenuSubmit(callback) { - if(formConfig) { - // - // If we have a 'submit' section, create a submit handler - // and map the various entries to menus/etc. - // - if(_.isObject(formConfig.submit)) { - // :TODO: If this model is kept, formData does not need to include actual data, just form ID & submitID - // we can get the rest here via each view in form -> getData() - self.on('submit', function onSubmit(formData) { - Log.debug( { formData : formData }, 'Submit form'); - - var confForFormId; - if(_.isObject(formConfig.submit[formData.submitId])) { - confForFormId = formConfig.submit[formData.submitId]; - } else if(_.isObject(formConfig.submit['*'])) { - confForFormId = formConfig.submit['*']; - } else { - // no configuration for this submitId - return; - } - - var formValueCompare = function(formDataValue, formConfigValue) { - // - // Any key(s) in formConfigValue must: - // 1) be present in formDataValue - // 2) must either: - // a) be set to null (wildcard/any) - // b) have matching values - // - var formConfigValueKeys = Object.keys(formConfigValue); - for(var k = 0; k < formConfigValueKeys.length; ++k) { - var memberKey = formConfigValueKeys[k]; - - // submit data contains config key? - if(!_.has(formDataValue, memberKey)) { - return false; // not present in what was submitted - } - - if(null !== formConfigValue[memberKey] && formConfigValue[memberKey] !== formDataValue[memberKey]) { - return false; - } - } - - return true; - }; - - var conf; - for(var c = 0; c < confForFormId.length; ++c) { - conf = confForFormId[c]; - if(_.isEqual(formData.value, conf.value, formValueCompare)) { - - if(!conf.action) { - continue; - } - - var formattedArgs; - if(conf.args) { - formattedArgs = self.formatMenuArgs(conf.args); - } - - var actionAsset = asset.parseAsset(conf.action); - assert(_.isObject(actionAsset)); - - if('method' === actionAsset.type) { - if(actionAsset.location) { - // :TODO: call with (client, args, ...) at least. - } else { - // local to current module - var currentMod = self.client.currentMenuModule; - if(currentMod.menuMethods[actionAsset.asset]) { - currentMod.menuMethods[actionAsset.asset](formattedArgs); - } - } - } else if('menu' === actionAsset.type) { - self.client.gotoMenuModule( { name : actionAsset.asset, args : formattedArgs } ); - } - } - } - }); - } - } - - callback(null); - }, - function setInitialFocus(callback) { - if(initialFocusId) { - self.switchFocus(initialFocusId); - } - - callback(null); - } - ], - function complete(err) { - if(cb) { - cb(err); - } - } - ); -}; - ViewController.prototype.formatMCIString = function(format) { var self = this; var view; @@ -709,6 +509,7 @@ ViewController.prototype.formatMCIString = function(format) { }); }; +/* ViewController.prototype.formatMenuArgs = function(args) { var self = this; @@ -718,4 +519,5 @@ ViewController.prototype.formatMenuArgs = function(args) { } return value; }); -}; \ No newline at end of file +}; +*/ \ No newline at end of file diff --git a/mods/apply.js b/mods/apply.js index fa702a4e..5967a632 100644 --- a/mods/apply.js +++ b/mods/apply.js @@ -12,132 +12,101 @@ var Config = require('../core/config.js').config; var util = require('util'); -//var async = require('async'); +exports.submitApplication = submitApplication; -// :TODO: clean up requires +function validateApplicationData(formData, cb) { + if(formData.value.username.length < Config.users.usernameMin) { + cb('Handle too short!', [ 1 ]); + return; + } -exports.moduleInfo = { - name : 'Apply', - desc : 'Application Module', - author : 'NuSkooler', -}; + if(formData.value.username.length > Config.users.usernameMax) { + cb('Handle too long!', [ 1 ]); + return; + } -exports.getModule = ApplyModule; + var re = new RegExp(Config.users.usernamePattern); + if(!re.test(formData.value.username)) { + cb('Handle contains invalid characters!', [ 1 ] ); + return; + } + if(formData.value.password.length < Config.users.passwordMin) { + cb('Password too short!', [ 9, 10 ]); + return; + } -function ApplyModule(menuConfig) { - MenuModule.call(this, menuConfig); + if(formData.value.password !== formData.value.pwConfirm) { + cb('Passwords do not match!', [ 9, 10 ]); + return; + } - var self = this; - - this.menuMethods.submitApplication = function(formData, extraArgs) { - var usernameView = self.viewController.getView(1); - var passwordView = self.viewController.getView(9); - var pwConfirmView = self.viewController.getView(10); - var statusView = self.viewController.getView(11); - - self.validateApplication(formData, function validated(errString, clearFields) { - if(errString) { - statusView.setText(errString); - - clearFields.forEach(function formId(id) { - self.viewController.getView(id).setText(''); - }); - - self.viewController.switchFocus(clearFields[0]); - } else { - var newUser = new user.User(); - newUser.username = formData.value.username; - - newUser.properties = { - real_name : formData.value.realName, - age : formData.value.age, - sex : formData.value.sex, - location : formData.value.location, - affiliation : formData.value.affils, - email_address : formData.value.email, - web_address : formData.value.web, - - art_theme_id : Config.defaults.theme, // :TODO: allow '*' = random - account_status : user.User.AccountStatus.inactive, - - // :TODO: Other defaults - // :TODO: should probably have a place to create defaults/etc. - // :TODO: set account_status to default based on Config.user... - }; - - newUser.create({ password : formData.value.pw }, function created(err) { - if(err) { - self.client.gotoMenuModule( { name : extraArgs.error } ); - } else { - Log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created'); - - if(user.User.AccountStatus.inactive === self.client.user.properties.account_status) { - self.client.gotoMenuModule( { name : extraArgs.inactive } ); - } else { - self.client.gotoMenuModule( { name : this.menuConfig.next } ); - } - } - }); - } - }); - }; - - this.validateApplication = function(formData, cb) { - if(formData.value.username.length < Config.users.usernameMin) { - cb('Handle too short!', [ 1 ]); - return; + user.getUserIdAndName(formData.value.username, function userIdAndName(err) { + var alreadyExists = !err; + if(alreadyExists) { + cb('Username unavailable!', [ 1 ] ); + } else { + cb(null); } - - if(formData.value.username.length > Config.users.usernameMax) { - cb('Handle too long!', [ 1 ]); - return; - } - - var re = new RegExp(Config.users.usernamePattern); - if(!re.test(formData.value.username)) { - cb('Handle contains invalid characters!', [ 1 ] ); - return; - } - - if(formData.value.pw.length < Config.users.passwordMin) { - cb('Password too short!', [ 9, 10 ]); - return; - } - - if(formData.value.pw !== formData.value.pwConfirm) { - cb('Passwords do not match!', [ 9, 10 ]); - return; - } - - user.getUserIdAndName(formData.value.username, function userIdAndName(err) { - var alreadyExists = !err; - if(alreadyExists) { - cb('Username unavailable!', [ 1 ] ); - } else { - cb(null); - } - }); - }; + }); } -util.inherits(ApplyModule, MenuModule); +function submitApplication(callingMenu, formData, extraArgs) { + var client = callingMenu.client; + var menuConfig = callingMenu.menuConfig; + var menuViewController = callingMenu.viewControllers.menu; -ApplyModule.prototype.enter = function(client) { - ApplyModule.super_.prototype.enter.call(this, client); -}; + var views = { + username : menuViewController.getView(1), + password : menuViewController.getView(9), + confirm : menuViewController.getView(10), + errorMsg : menuViewController.getView(11) + }; -ApplyModule.prototype.beforeArt = function() { - ApplyModule.super_.prototype.beforeArt.call(this); -}; + validateApplicationData(formData, function validationResult(errorMsg, viewIds) { + if(errorMsg) { + views.errorMsg.setText(errorMsg); -ApplyModule.prototype.mciReady = function(mciData) { - ApplyModule.super_.prototype.mciReady.call(this, mciData); + viewIds.forEach(function formId(id) { + menuViewController.getView(id).clearText(''); + }); - var self = this; + menuViewController.switchFocus(viewIds[0]); + } else { + // Seems legit! + var newUser = new user.User(); - self.viewController = self.addViewController(new ViewController({ client : self.client } )); - self.viewController.loadFromMCIMapAndConfig( { mciMap : mciData.menu, menuConfig : self.menuConfig }, function onViewReady(err) { - + newUser.username = formData.value.username; + + newUser.properties = { + real_name : formData.value.realName, + age : formData.value.age, + sex : formData.value.sex, + location : formData.value.location, + affiliation : formData.value.affils, + email_address : formData.value.email, + web_address : formData.value.web, + + theme_id : Config.defaults.theme, // :TODO: allow '*' = random + account_status : Config.users.requireActivation ? user.User.AccountStatus.inactive : user.User.AccountStatus.active, + + // :TODO: Other defaults + // :TODO: should probably have a place to create defaults/etc. + }; + + newUser.create( { password : formData.value.password }, function created(err) { + if(err) { + client.gotoMenuModule( { name : extraArgs.error } ); + } else { + Log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created'); + + if(user.User.AccountStatus.inactive === client.user.properties.account_status) { + client.gotoMenuModule( { name : extraArgs.inactive } ); + } else { + client.gotoMenuModule( { name : menuConfig.next } ); + } + } + }); + } }); -}; \ No newline at end of file +} diff --git a/mods/login.js b/mods/login.js index b2f2daba..90cb25bc 100644 --- a/mods/login.js +++ b/mods/login.js @@ -1,29 +1,14 @@ /* jslint node: true */ 'use strict'; -var ansi = require('../core/ansi_term.js'); -var art = require('../core/art.js'); -var user = require('../core/user.js'); var theme = require('../core/theme.js'); var Log = require('../core/logger.js').log; -var MenuModule = require('../core/menu_module.js').MenuModule; -var ViewController = require('../core/view_controller.js').ViewController; var async = require('async'); -// :TODO: clean up requires +exports.login = login; -/*exports.moduleInfo = { - name : 'Login', - desc : 'Login Module', - author : 'NuSkooler', -};*/ - -//exports.getModule = LoginModule; - -exports.attemptLogin = attemptLogin; - -function attemptLogin(callingMenu, formData, extraArgs) { +function login(callingMenu, formData, extraArgs) { var client = callingMenu.client; client.user.authenticate(formData.value.username, formData.value.password, function authenticated(err) { @@ -36,99 +21,18 @@ function attemptLogin(callingMenu, formData, extraArgs) { Log.info( { username : callingMenu.client.user.username }, 'Successful login'); async.parallel( - [ - function loadThemeConfig(callback) { - theme.getThemeInfo(client.user.properties.art_theme_id, function themeInfo(err, info) { - client.currentThemeInfo = info; - callback(null); - }); - } - ], - function complete(err, results) { - client.gotoMenuModule( { name : callingMenu.menuConfig.next } ); + [ + function loadThemeConfig(callback) { + theme.getThemeInfo(client.user.properties.theme_id, function themeInfo(err, info) { + client.currentThemeInfo = info; + callback(null); + }); } - ); + ], + function complete(err, results) { + client.gotoMenuModule( { name : callingMenu.menuConfig.next } ); + } + ); } }); } - -/* -function LoginModule(menuConfig) { - MenuModule.call(this, menuConfig); - - var self = this; - - // :TODO: Handle max login attempts before hangup - // :TODO: probably should persist failed login attempts to user DB - - this.menuMethods.attemptLogin = function(args) { - self.client.user.authenticate(args.username, args.password, function onAuth(err) { - if(err) { - // :TODO: change to simple login/username prompts - no buttons. - - Log.info( { username : args.username }, 'Failed login attempt %s', err); - - // :TODO: localize: - // :TODO: create a blink label of sorts - simulate blink with ICE - self.viewController.getView(5).setText('Invalid username or password!'); - self.clearForm(); - self.viewController.switchFocus(1); - - setTimeout(function onTimeout() { - // :TODO: should there be a block input type of pattern here? self.client.ignoreInput() ... self.client.acceptInput() - - self.viewController.getView(5).clearText(); // :TODO: for some reason this doesn't clear the last character - self.viewController.switchFocus(1); - }, 2000); - - } else { - Log.info( { username : self.client.user.username }, 'Successful login'); - - // :TODO: persist information about login to user - - async.parallel( - [ - function loadThemeConfig(callback) { - theme.getThemeInfo(self.client.user.properties.art_theme_id, function themeInfo(err, info) { - self.client.currentThemeInfo = info; - callback(null); - }); - } - ], - function complete(err, results) { - self.client.gotoMenuModule( { name : args.next.success } ); - } - ); - } - }); - }; - - this.clearForm = function() { - [ 1, 2, ].forEach(function onId(id) { - self.viewController.getView(id).clearText(); - }); - }; -} - -require('util').inherits(LoginModule, MenuModule); - -LoginModule.prototype.enter = function(client) { - LoginModule.super_.prototype.enter.call(this, client); -}; - -LoginModule.prototype.beforeArt = function() { - LoginModule.super_.prototype.beforeArt.call(this); - - //this.client.term.write(ansi.resetScreen()); -}; - -LoginModule.prototype.mciReady = function(mciData) { - LoginModule.super_.prototype.mciReady.call(this, mciData); - - var self = this; - - self.viewController = self.addViewController(new ViewController( { client : self.client } )); - self.viewController.loadFromMCIMapAndConfig( { mciMap : mciData.menu, menuConfig : self.menuConfig }, function onViewReady(err) { - }); -}; -*/ \ No newline at end of file diff --git a/mods/menu.json b/mods/menu.json index bdb4d10d..0e735c85 100644 --- a/mods/menu.json +++ b/mods/menu.json @@ -16,16 +16,17 @@ "focus" : ... } - ..note that script/methods should be part of a *theme* - or at least checked first with fallback - .....why? + NOte that @draw & @art should check theme first. + @draw:myMethod -> theme/draw.js::myMethod(opts) + } } */ "connected" : { - "art" : "connect", + "art" : "CONNECT", "next" : "matrix", "options" : { - "clearScreen" : true, + "cls" : true, "nextTimeout" : 1500 } }, @@ -62,7 +63,7 @@ } }, "options" : { - "clearScreen" : true + "cls" : true } }, "login" : { @@ -70,22 +71,35 @@ "prompt" : "userCredentials", "fallback" : "matrix", "next" : "newUserActive", - "action" : "@method:login.js/attemptLogin", + "action" : "@method:general_menu_methods/login", + + // :TODO: support alt submit method for prompts + // if present, standard filters apply. No need for multiple submit ID's + // since a prompt can only utilize one: + "submit" : [ + { + "value" : { "1" : "thing" }, + "action" : "@method:doThings" + } + ], + "options" : { - "clearScreen" : true + "cls" : true } }, "logoff" : { - "art" : "logoff", - "module" : "logoff" + "art" : "LOGOFF", + //"module" : "logoff", + "action" : "@method:general_menu_methods/logoff", + "options" : { "cls" : true } }, "apply" : { - "art" : "apply", - "module" : "apply", + "art" : "APPLY", "next" : "newUserActive", "form" : { "0" : { "BT12BT13ET1ET10ET2ET3ET4ET5ET6ET7ET8ET9TL11" : { + "cancelKeys" : [ "esc" ], "mci" : { "ET1" : { "focus" : true, @@ -98,7 +112,7 @@ "ET6" : { "argName" : "affils" }, "ET7" : { "argName" : "email" }, "ET8" : { "argName" : "web" }, - "ET9" : { "argName" : "pw" }, + "ET9" : { "argName" : "password" }, "ET10" : { "argName" : "pwConfirm" }, "BT12" : { "submit" : true, @@ -112,8 +126,8 @@ "submit" : { "12" : [ // Apply { - "value" : { "12" : null }, - "action" : "@method:submitApplication", + "value" : 12, + "action" : "@method:apply/submitApplication", "extraArgs" : { "inactive" : "userNeedsActivated", "error" : "newUserCreateError" @@ -122,7 +136,7 @@ ], "13" : [ // Cancel { - "value" : { "13" : null }, // :TODO: allow just "13" (number) + "value" : 13, "action" : "@menu:matrix" } ] @@ -131,14 +145,14 @@ } }, "options" : { - "clearScreen" : true + "cls" : true } }, "newUserActive" : { "art" : "STATS", "options" : { // :TODO: implement MCI codes for this - "clearScreen" : true + "cls" : true } } } \ No newline at end of file diff --git a/mods/standard_menu.js b/mods/standard_menu.js deleted file mode 100644 index bd186e33..00000000 --- a/mods/standard_menu.js +++ /dev/null @@ -1,92 +0,0 @@ -/* jslint node: true */ -'use strict'; - -var ansi = require('../core/ansi_term.js'); -var MenuModule = require('../core/menu_module.js').MenuModule; -var ViewController = require('../core/view_controller.js').ViewController; -var menuUtil = require('../core/menu_util.js'); - -var _ = require('lodash'); - -exports.getModule = StandardMenuModule; - -exports.moduleInfo = { - name : 'Standard Menu Module', - desc : 'Menu module handling most standard stuff', - author : 'NuSkooler', -}; - -function StandardMenuModule(menuConfig) { - MenuModule.call(this, menuConfig); -} - -require('util').inherits(StandardMenuModule, MenuModule); - - -StandardMenuModule.prototype.enter = function(client) { - StandardMenuModule.super_.prototype.enter.call(this, client); -}; - -StandardMenuModule.prototype.beforeArt = function() { - StandardMenuModule.super_.prototype.beforeArt.call(this); -}; - -StandardMenuModule.prototype.mciReady = function(mciData) { - StandardMenuModule.super_.prototype.mciReady.call(this, mciData); - - var self = this; - - // - // 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) - // - // :TODO: Create MenuModule.standardMciReady() method that others can call that does this -- even custom modules will generally want most of this - self.viewControllers = {}; - - var vcOpts = { client : self.client }; - - if(mciData.menu) { - self.viewControllers.menu = new ViewController(vcOpts); - } - - if(mciData.prompt) { - self.viewControllers.prompt = new ViewController(vcOpts); - } - - var viewsReady = function(err) { - // :TODO: Hrm..... - }; - - - if(self.viewControllers.menu) { - var menuLoadOpts = { - mciMap : mciData.menu, - callingMenu : self, - //menuConfig : self.menuConfig, - withoutForm : _.isObject(mciData.prompt), - }; - - self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, viewsReady); - } - - if(self.viewControllers.prompt) { - var promptLoadOpts = { - callingMenu : self, - mciMap : mciData.prompt, - }; - - self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, viewsReady); - } - - /* - var vc = self.addViewController(new ViewController({ client : self.client } )); - vc.loadFromMCIMapAndConfig( { mciMap : mciData.menu, menuConfig : self.menuConfig }, function onViewReady(err) { - if(err) { - console.log(err); - } else { - } - }); -*/ -}; diff --git a/mods/test_module1.js b/mods/test_module1.js index 20d2e55a..0402fb74 100644 --- a/mods/test_module1.js +++ b/mods/test_module1.js @@ -23,7 +23,7 @@ function entryPoint(client) { async.waterfall( [ function getArt(callback) { - theme.getThemeArt('MCI_VM1.ANS', client.user.properties.art_theme_id, function onArt(err, theArt) { + theme.getThemeArt('MCI_VM1.ANS', client.user.properties.theme_id, function onArt(err, theArt) { callback(err, theArt); }); },