From c9674e68fbb38616f02b453ea267a28b31c641a2 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 16 Jun 2018 10:01:08 -0600 Subject: [PATCH] * Re-work menu stack goto() a bit - cleaner, support 'mergeFlags', and 'forwardArgs' menuFlags. * Add show_art.js module: Advanced ways to show art in menu stacks. For example, by extraArgs, fileBase area art, etc -- this will replace e.g. showing message conf art later as to be more generic --- core/file_base_area_select.js | 2 +- core/menu_stack.js | 27 +++++- core/show_art.js | 170 ++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 core/show_art.js diff --git a/core/file_base_area_select.js b/core/file_base_area_select.js index df859bea..6c45a5d1 100644 --- a/core/file_base_area_select.js +++ b/core/file_base_area_select.js @@ -33,7 +33,7 @@ exports.getModule = class FileAreaSelectModule extends MenuModule { extraArgs : { filterCriteria : filterCriteria, }, - menuFlags : [ 'popParent' ], + menuFlags : [ 'popParent', 'mergeFlags' ], }; return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb); diff --git a/core/menu_stack.js b/core/menu_stack.js index 360c2b38..3775c065 100644 --- a/core/menu_stack.js +++ b/core/menu_stack.js @@ -97,6 +97,7 @@ module.exports = class MenuStack { options = {}; } + options = options || {}; const self = this; if(currentModuleInfo && name === currentModuleInfo.name) { @@ -111,10 +112,12 @@ module.exports = class MenuStack { client : self.client, }; - if(_.isObject(options)) { - loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value'); - loadOpts.lastMenuResult = options.lastMenuResult; + if(currentModuleInfo && currentModuleInfo.menuFlags.includes('forwardArgs')) { + loadOpts.extraArgs = currentModuleInfo.extraArgs; + } else { + loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value'); } + loadOpts.lastMenuResult = options.lastMenuResult; loadMenu(loadOpts, (err, modInst) => { if(err) { @@ -124,7 +127,21 @@ module.exports = class MenuStack { } else { self.client.log.debug( { menuName : name }, 'Goto menu module'); - const menuFlags = (options && Array.isArray(options.menuFlags)) ? options.menuFlags : modInst.menuConfig.options.menuFlags; + // + // If menuFlags were supplied in menu.hjson, they should win over + // anything supplied in code. + // + let menuFlags; + if(0 === modInst.menuConfig.options.menuFlags.length) { + menuFlags = Array.isArray(options.menuFlags) ? options.menuFlags : []; + } else { + menuFlags = modInst.menuConfig.options.menuFlags; + + // in code we can ask to merge in + if(Array.isArray(options.menuFlags) && options.menuFlags.includes('mergeFlags')) { + menuFlags = _.uniq(menuFlags.concat(options.menuFlags)); + } + } if(currentModuleInfo) { // save stack state @@ -149,7 +166,7 @@ module.exports = class MenuStack { }); // restore previous state if requested - if(options && options.savedState) { + if(options.savedState) { modInst.restoreSavedState(options.savedState); } diff --git a/core/show_art.js b/core/show_art.js new file mode 100644 index 00000000..fbbb3141 --- /dev/null +++ b/core/show_art.js @@ -0,0 +1,170 @@ +/* jslint node: true */ +'use strict'; + +// ENiGMA½ +const MenuModule = require('./menu_module.js').MenuModule; +const Errors = require('../core/enig_error.js').Errors; +const ANSI = require('./ansi_term.js'); +const Config = require('./config.js').config; + +// deps +const async = require('async'); +const _ = require('lodash'); + +exports.moduleInfo = { + name : 'Show Art', + desc : 'Module for more advanced methods of displaying art', + author : 'NuSkooler', +}; + +exports.getModule = class ShowArtModule extends MenuModule { + constructor(options) { + super(options); + this.config = Object.assign({}, _.get(options, 'menuConfig.config'), { extraArgs : options.extraArgs }); + + this.config.method = this.config.method || 'random'; + this.config.optional = _.get(this.config, 'optional', true); + } + + initSequence() { + const self = this; + + async.series( + [ + function before(callback) { + return self.beforeArt(callback); + }, + function showArt(callback) { + // + // How we show art depends on our configuration + // + let handler = { + extraArgs : self.showByExtraArgs, + sequence : self.showBySequence, + random : self.showByRandom, + fileBaseArea : self.showByFileBaseArea, + }[self.config.method] || self.showRandomArt; + + handler = handler.bind(self); + + return handler(callback); + } + ], + err => { + if(err && !self.config.optional) { + self.client.log.warn('Error during init sequence', { error : err.message } ); + return self.prevMenu( () => { /* dummy */ } ); + } + + self.finishedLoading(); + return self.autoNextMenu( () => { /* dummy */ } ); + } + ); + } + + showByExtraArgs(cb) { + this.getArtKeyValue( (err, artSpec) => { + if(err) { + return cb(err); + } + const options = { + pause : this.shouldPause(), + desc : 'extraArgs', + }; + return this.displaySingleArtWithOptions(artSpec, options, cb); + }); + } + + showBySequence(cb) { + return cb(null); + } + + showByRandom(cb) { + return cb(null); + } + + showByFileBaseArea(cb) { + this.getArtKeyValue( (err, key) => { + if(err) { + return cb(err); + } + + // further resolve key -> file base area art + const artSpec = _.get(Config, [ 'fileBase', 'areas', key, 'art' ]); + if(!artSpec) { + return cb(Errors.MissingConfig(`No art defined for file base area "${key}"`)); + } + const options = { + pause : this.shouldPause(), + desc : 'fileBaseArea', + }; + return this.displaySingleArtWithOptions(artSpec, options, cb); + }); + } + + getArtKeyValue(cb) { + const key = this.config.key; + if(!_.isString(key)) { + return cb(Errors.MissingConfig('Config option "key" is required for method "extraArgs"')); + } + + const path = key.split('.'); + const artKey = _.get(this.config, [ 'extraArgs' ].concat(path) ); + if(!_.isString(artKey)) { + return cb(Errors.MissingParam(`Invalid or missing "extraArgs.${key}" value`)); + } + + return cb(null, artKey); + } + + displaySingleArtWithOptions(artSpec, options, cb) { + const self = this; + async.waterfall( + [ + function art(callback) { + // :TODO: we really need a way to supply an explicit path to look in, e.g. general/area_art/ + self.displayAsset( + artSpec, + self.menuConfig.options, + (err, artData) => { + if(err) { + return callback(err); + } + const mciData = { menu : artData.mciMap }; + return callback(null, mciData); + } + ); + }, + function recordCursorPosition(mciData, callback) { + if(!options.pause) { + return callback(null, mciData, null); // cursor position not needed + } + + self.client.once('cursor position report', pos => { + const pausePosition = { row : pos[0], col : 1 }; + return callback(null, mciData, pausePosition); + }); + + self.client.term.rawWrite(ANSI.queryPos()); + }, + function afterArtDisplayed(mciData, pausePosition, callback) { + self.mciReady(mciData, err => { + return callback(err, pausePosition); + }); + }, + function displayPauseIfRequested(pausePosition, callback) { + if(!options.pause) { + return callback(null); + } + return self.pausePrompt(pausePosition, callback); + }, + ], + err => { + if(err) { + self.client.log.warn( { artSpec, error : err.message }, `Failed to display "${options.desc}" art`); + } + return cb(err); + } + ); + } +};