diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 918c3125..85684a35 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -36,11 +36,13 @@ function ANSIEscapeParser(options) { mciReplaceChar : '', termHeight : 25, termWidth : 80, + omitTrailingLF : false, }); this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, ''); this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25); this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80); + this.omitTrailingLF = miscUtil.valueWithDefault(options.omitTrailingLF, false); function getArgArray(array) { @@ -243,7 +245,11 @@ function ANSIEscapeParser(options) { } while(0 !== re.lastIndex); if(pos < buffer.length) { - parseMCI(buffer.slice(pos)); + var lastBit = buffer.slice(pos); + if(self.omitTrailingLF && '\r\n' === lastBit.slice(-2).toString()) { + lastBit = lastBit.slice(pos, -2); // trim off last CRLF + } + parseMCI(lastBit) } self.emit('complete'); diff --git a/core/art.js b/core/art.js index 7c735f28..36479177 100644 --- a/core/art.js +++ b/core/art.js @@ -417,6 +417,7 @@ function display(options, cb) { mciReplaceChar : mciReplaceChar, termHeight : options.client.term.termHeight, termWidth : options.client.term.termWidth, + omitTrailingLF : options.omitTrailingLF, }); var mciMap = {}; diff --git a/core/asset.js b/core/asset.js index 4a9af043..8f428cc9 100644 --- a/core/asset.js +++ b/core/asset.js @@ -11,7 +11,6 @@ exports.parseAsset = parseAsset; exports.getArtAsset = getArtAsset; exports.resolveConfigAsset = resolveConfigAsset; exports.getViewPropertyAsset = getViewPropertyAsset; -exports.displayArtAsset = displayArtAsset; var ALL_ASSETS = [ 'art', @@ -85,47 +84,3 @@ function getViewPropertyAsset(src) { return parseAsset(src); }; - -function displayArtAsset(assetSpec, client, options, cb) { - assert(_.isObject(client)); - - // options are... optional - if(3 === arguments.length) { - cb = options; - options = {}; - } - - var artAsset = getArtAsset(assetSpec); - if(!artAsset) { - cb(new Error('Asset not found: ' + assetSpec)); - return; - } - - var dispOpts = { - name : artAsset.asset, - client : client, - font : options.font, - }; - - switch(artAsset.type) { - case 'art' : - theme.displayThemeArt(dispOpts, function displayed(err, artData) { - cb(err, err ? null : { mciMap : artData.mciMap, height : artData.extraInfo.height } ); - }); - break; - - case 'method' : - // :TODO: fetch & render via method - break; - - case 'inline ' : - // :TODO: think about this more in relation to themes, etc. How can this come - // from a theme (with override from menu.json) ??? - // look @ client.currentTheme.inlineArt[name] -> menu/prompt[name] - break; - - default : - cb(new Error('Unsupported art asset type: ' + artAsset.type)); - break; - } -} \ No newline at end of file diff --git a/core/config.js b/core/config.js index 1e69f803..3859ce1b 100644 --- a/core/config.js +++ b/core/config.js @@ -95,7 +95,7 @@ function getDefaultConfig() { short : 'MM/DD/YYYY', }, timeFormat : { - short : 'h:mm tt', + short : 'h:mm a', } }, diff --git a/core/menu_module.js b/core/menu_module.js index 320ab311..997f9b53 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -26,48 +26,6 @@ function MenuModule(options) { this.menuMethods = {}; // methods called from @method's this.viewControllers = {}; // name->vc - // :TODO: Move this elsewhere - /* - this.displayArtAsset = function(assetSpec, cb) { - var artAsset = asset.getArtAsset(assetSpec); - - if(!artAsset) { - cb(new Error('Asset not found: ' + assetSpec)); - return; - } - - var dispOptions = { - name : artAsset.asset, - client : self.client, - font : self.menuConfig.font, - }; - - switch(artAsset.type) { - case 'art' : - theme.displayThemeArt(dispOptions, function displayed(err, themeArtData) { - cb(err, err ? null : { mciMap : themeArtData.mciMap, height : themeArtData.extraInfo.height } ); - }); - break; - - case 'method' : - // :TODO: fetch and render via method/generator - break; - - case 'inline' : - if(_.isString(assetSpec.asset)) { - // :TODO: think about this more in relation to themes, etc. How can this come - // from a theme (with override from menu.json) ??? - // look @ client.currentTheme.inlineArt[name] -> menu/prompt[name] - } - break; - - default : - cb(new Error('Unsupported art asset type')); - break; - } - }; - */ - this.initSequence = function() { var mciData = { }; @@ -79,7 +37,7 @@ function MenuModule(options) { }, function displayMenuArt(callback) { if(_.isString(self.menuConfig.art)) { - asset.displayArtAsset( + theme.displayThemedAsset( self.menuConfig.art, self.client, { font : self.menuConfig.font }, @@ -90,12 +48,6 @@ function MenuModule(options) { } callback(err); }); - /*self.displayArtAsset(self.menuConfig.art, function displayed(err, artData) { - if(!err) { - mciData.menu = artData.mciMap; - } - callback(err); - });*/ } else { callback(null); } @@ -118,7 +70,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" : { "text" : "stuff", ... } } var promptConfig = self.menuConfig.promptConfig; - asset.displayArtAsset( + theme.displayThemedAsset( promptConfig.art, self.client, { font : self.menuConfig.font }, @@ -246,7 +198,7 @@ MenuModule.prototype.finishedLoading = function() { if('end' === self.menuConfig.pause || true === self.menuConfig.pause) { // :TODO: really need a client.term.pause() that uses the correct art/etc. - theme.displayThemePause( { client : self.client }, function keyPressed() { + theme.displayThemedPause( { client : self.client }, function keyPressed() { nextAction(); }); } else { diff --git a/core/theme.js b/core/theme.js index d1afae52..b6086aae 100644 --- a/core/theme.js +++ b/core/theme.js @@ -7,6 +7,8 @@ var ansi = require('./ansi_term.js'); var miscUtil = require('./misc_util.js'); var Log = require('./logger.js').log; var jsonCache = require('./json_cache.js'); +var asset = require('./asset.js'); +var ViewController = require('./view_controller.js').ViewController; var fs = require('fs'); var paths = require('path'); @@ -20,7 +22,8 @@ exports.getThemeArt = getThemeArt; exports.getRandomTheme = getRandomTheme; exports.initAvailableThemes = initAvailableThemes; exports.displayThemeArt = displayThemeArt; -exports.displayThemePause = displayThemePause; +exports.displayThemedPause = displayThemedPause; +exports.displayThemedAsset = displayThemedAsset; // :TODO: use JSONCache here... may need to fancy it up a bit in order to have events for after re-cache, e.g. to update helpers below: function loadTheme(themeID, cb) { @@ -69,7 +72,7 @@ function loadTheme(themeID, cb) { getTimeFormat : function(style) { style = style || 'short'; - var format = Config.defaults.timeFormat[style] || 'h:mm tt'; + var format = Config.defaults.timeFormat[style] || 'h:mm a'; if(_.has(theme, 'customization.defaults.timeFormat')) { return theme.customization.defaults.timeFormat[style] || format; @@ -176,10 +179,11 @@ function displayThemeArt(options, cb) { cb(err); } else { var dispOptions = { - art : artInfo.data, - sauce : artInfo.sauce, - client : options.client, - font : options.font, + art : artInfo.data, + sauce : artInfo.sauce, + client : options.client, + font : options.font, + omitTrailingLF : options.omitTrailingLF, }; art.display(dispOptions, function displayed(err, mciMap, extraInfo) { @@ -192,7 +196,7 @@ function displayThemeArt(options, cb) { // // Pause prompts are a special prompt by the name 'pause'. // -function displayThemePause(options, cb) { +function displayThemedPause(options, cb) { // // options.client // options clearPrompt @@ -208,8 +212,10 @@ function displayThemePause(options, cb) { // :TODO: Prompt should support MCI codes in general var artInfo; + var vc; + var promptConfig; - async.waterfall( + async.series( [ function loadPromptJSON(callback) { jsonCache.getJSON('prompt.json', function loaded(err, promptJson) { @@ -217,28 +223,38 @@ function displayThemePause(options, cb) { callback(err); } else { if(_.has(promptJson, [ 'prompts', 'pause' ] )) { - callback(null, promptJson.prompts.pause); + promptConfig = promptJson.prompts.pause; + callback(_.isObject(promptConfig) ? null : new Error('Invalid prompt config block!')); } else { callback(new Error('Missing standard \'pause\' prompt')) } } }); }, - function displayPausePrompt(pausePrompt, callback) { - // :TODO: use displayArtAsset() - displayThemeArt( { client : options.client, name : pausePrompt.art }, function pauseDisplayed(err, artData) { - artInfo = artData; - callback(err); - }); + function displayPausePrompt(callback) { + displayThemedAsset( + promptConfig.art, + options.client, + { font : promptConfig.font, omitTrailingLF : true }, + function displayed(err, artData) { + artInfo = artData; + callback(err); + } + ); }, function discoverCursorPosition(callback) { options.client.once('cursor position report', function cpr(pos) { - artInfo.startRow = pos[0] - artInfo.extraInfo.height; + artInfo.startRow = pos[0] - artInfo.height; callback(null); }); options.client.term.rawWrite(ansi.queryPos()); }, - // :TODO: use view Controller loadFromPromptConfig() with 'noInput' option or such + function createMCIViews(callback) { + vc = new ViewController( { client : options.client, noInput : true } ); + vc.loadFromPromptConfig( { promptName : 'pause', mciMap : artInfo.mciMap, config : promptConfig }, function loaded(err) { + callback(null); + }); + }, function pauseForUserInput(callback) { options.client.waitForKeyPress(function keyPressed() { callback(null); @@ -248,7 +264,7 @@ function displayThemePause(options, cb) { if(options.clearPrompt) { if(artInfo.startRow) { options.client.term.rawWrite(ansi.goto(artInfo.startRow, 1)); - options.client.term.rawWrite(ansi.deleteLine(artInfo.extraInfo.height)); + options.client.term.rawWrite(ansi.deleteLine(artInfo.height)); } else { options.client.term.rawWrite(ansi.up(1) + ansi.deleteLine()); } @@ -261,13 +277,63 @@ function displayThemePause(options, cb) { callback(null); }, 4000); } -*/ + */ ], function complete(err) { if(err) { Log.error(err); } + + if(vc) { + vc.detachClientEvents(); + } + cb(); } ); } + +function displayThemedAsset(assetSpec, client, options, cb) { + assert(_.isObject(client)); + + // options are... optional + if(3 === arguments.length) { + cb = options; + options = {}; + } + + var artAsset = asset.getArtAsset(assetSpec); + if(!artAsset) { + cb(new Error('Asset not found: ' + assetSpec)); + return; + } + + var dispOpts = { + name : artAsset.asset, + client : client, + font : options.font, + omitTrailingLF : options.omitTrailingLF, + }; + + switch(artAsset.type) { + case 'art' : + displayThemeArt(dispOpts, function displayed(err, artData) { + cb(err, err ? null : { mciMap : artData.mciMap, height : artData.extraInfo.height } ); + }); + break; + + case 'method' : + // :TODO: fetch & render via method + break; + + case 'inline ' : + // :TODO: think about this more in relation to themes, etc. How can this come + // from a theme (with override from menu.json) ??? + // look @ client.currentTheme.inlineArt[name] -> menu/prompt[name] + break; + + default : + cb(new Error('Unsupported art asset type: ' + artAsset.type)); + break; + } +} \ No newline at end of file diff --git a/core/view_controller.js b/core/view_controller.js index f1d9b4b7..2c42c140 100644 --- a/core/view_controller.js +++ b/core/view_controller.js @@ -33,6 +33,7 @@ function ViewController(options) { this.views = {}; // map of ID -> view this.formId = options.formId || 0; this.mciViewFactory = new MCIViewFactory(this.client); // :TODO: can this not be a singleton? + this.noInput = _.isBoolean(options.noInput) ? options.noInput : false; this.actionKeyMap = {}; @@ -114,7 +115,7 @@ function ViewController(options) { var mci = mciMap[name]; var view = self.mciViewFactory.createFromMCI(mci); - if(view) { + if(view && false === self.noInput) { view.on('action', self.viewActionListener); self.addView(view); @@ -335,7 +336,8 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) { assert(_.isObject(options.mciMap)); var self = this; - var promptConfig = self.client.currentMenuModule.menuConfig.promptConfig; + var promptName = _.isString(options.promptName) ? options.promptName : self.client.currentMenuModule.menuConfig.prompt; + var promptConfig = _.isObject(options.config) ? options.config : self.client.currentMenuModule.menuConfig.promptConfig; var initialFocusId = 1; // default to first async.waterfall( @@ -348,7 +350,7 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) { function applyThemeCustomization(callback) { if(_.isObject(promptConfig)) { menuUtil.applyThemeCustomization({ - name : self.client.currentMenuModule.menuConfig.prompt, + name : promptName,//self.client.currentMenuModule.menuConfig.prompt, type : "prompts", client : self.client, configMci : promptConfig.mci, @@ -367,12 +369,13 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) { } }, function prepareFormSubmission(callback) { + if(false === self.noInput) { + self.on('submit', function promptSubmit(formData) { + self.client.log.trace( { formData : self.getLogFriendlyFormData(formData) }, 'Prompt submit'); - self.on('submit', function promptSubmit(formData) { - self.client.log.trace( { formData : self.getLogFriendlyFormData(formData) }, 'Prompt submit'); - - menuUtil.handleAction(self.client, formData, self.client.currentMenuModule.menuConfig); - }); + menuUtil.handleAction(self.client, formData, self.client.currentMenuModule.menuConfig); + }); + } callback(null); }, diff --git a/mods/art/pause.ans b/mods/art/pause.ans new file mode 100644 index 00000000..e0bc33cf Binary files /dev/null and b/mods/art/pause.ans differ diff --git a/mods/fse.js b/mods/fse.js index d32c7d24..3d86f60b 100644 --- a/mods/fse.js +++ b/mods/fse.js @@ -7,7 +7,6 @@ var ansi = require('../core/ansi_term.js'); var theme = require('../core/theme.js'); var MultiLineEditTextView = require('../core/multi_line_edit_text_view.js').MultiLineEditTextView; var Message = require('../core/message.js'); -var asset = require('../core/asset.js'); var async = require('async'); var assert = require('assert'); @@ -103,19 +102,14 @@ function FullScreenEditorModule(options) { function displayFooterArt(callback) { var footerArt = self.menuConfig.config.art[options.footerName]; - asset.displayArtAsset( + theme.displayThemedAsset( footerArt, self.client, - function displayed(err, artData) - { + { font : self.menuConfig.font }, + function displayed(err, artData) { callback(err, artData); - }); - - /* - self.displayArtAsset(footerArt, function artDisplayed(err, artData) { - callback(err, artData); - }); -*/ + } + ); } ], function complete(err, artData) { @@ -134,18 +128,14 @@ function FullScreenEditorModule(options) { [ function displayHeaderAndBody(callback) { async.eachSeries( comps, function dispArt(n, next) { - asset.displayArtAsset( + theme.displayThemedAsset( art[n], self.client, + { font : self.menuConfig.font }, function displayed(err, artData) { next(err); - }); - - /* - self.displayArtAsset(art[n], function artDisplayed(err, artData) { - next(err); - }); - */ + } + ); }, function complete(err) { callback(err); }); @@ -218,20 +208,15 @@ function FullScreenEditorModule(options) { assert(_.isString(art.body)); async.eachSeries( [ 'header', 'body' ], function dispArt(n, next) { - asset.displayArtAsset( + theme.displayThemedAsset1( art[n], self.client, + { font : self.menuConfig.font }, function displayed(err, artData) { mciData[n] = artData; next(err); - }); - - /* - self.displayArtAsset(art[n], function artDisplayed(err, artData) { - mciData[n] = artData; - next(err); - }); - */ + } + ); }, function complete(err) { callback(err); }); diff --git a/mods/themes/NU-MAYA/theme.json b/mods/themes/NU-MAYA/theme.json index 3271d80c..a62a3a24 100644 --- a/mods/themes/NU-MAYA/theme.json +++ b/mods/themes/NU-MAYA/theme.json @@ -11,6 +11,9 @@ "dateFormat" : { "short" : "YYYY-MMM-DD" }, + "timeFormat" : { + "short" : "h:mm:ss a" + }, "mci" : { "TM" : { "styleSGR1" : "|00|30|01" @@ -26,7 +29,7 @@ "focusTextStyle" : "l33t" } }, - "apply" : { + "apply" : { "ET1" : { "width" : 21 }, "ET2" : { "width" : 21 }, //"ET3" : { "width" : 21 },