From 30ba609fb4d22ee763b8b3e39eae12fe92e5887a Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Wed, 10 Aug 2016 22:48:13 -0600 Subject: [PATCH] * Add rumorz mod * ANSI/pipe working properly in VerticalMenuView * Fix bug in renderStringLength() * Make initSequence() part of prototype chain for inheritance * Use proper 'desc' field vs 'status' for menus when setting client/user status * Move pipeToAnsi() to setItems/setFocusItems vs every render * Add %RR random rumor MCI * Predefined MCI's can be init @ startup - RR uses random as a test bed * Add some StatLog functionality for ordering, keep forever, etc. * Fix TextView redraw issue * Better VerticalMenuView drawItem() logic * Add 'key press' emit for View * Enable formats for BBS list - works with MCI * Remove old system_property.js --- core/menu_module.js | 213 ++++++++++++++++---------------- core/menu_view.js | 21 +++- core/predefined_mci.js | 25 ++++ core/stat_log.js | 43 ++++++- core/string_util.js | 7 +- core/system_property.js | 38 ------ core/text_view.js | 12 +- core/vertical_menu_view.js | 65 +++------- core/view.js | 4 +- core/view_controller.js | 43 ++++--- mods/bbs_list.js | 14 +-- mods/last_callers.js | 2 +- mods/rumorz.js | 246 +++++++++++++++++++++++++++++++++++++ 13 files changed, 492 insertions(+), 241 deletions(-) delete mode 100644 core/system_property.js create mode 100644 mods/rumorz.js diff --git a/core/menu_module.js b/core/menu_module.js index 5eb7c913..f8e23113 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -40,110 +40,6 @@ function MenuModule(options) { this.initViewControllers(); - this.initSequence = function() { - var mciData = { }; - - async.series( - [ - function beforeDisplayArt(callback) { - self.beforeArt(callback); - }, - function displayMenuArt(callback) { - if(_.isString(self.menuConfig.art)) { - theme.displayThemedAsset( - self.menuConfig.art, - self.client, - self.menuConfig.options, // can include .font, .trailingLF, etc. - function displayed(err, artData) { - if(err) { - self.client.log.trace( { art : self.menuConfig.art, error : err.message }, 'Could not display art'); - } else { - mciData.menu = artData.mciMap; - } - callback(null); // non-fatal - } - ); - } else { - callback(null); - } - }, - function moveToPromptLocation(callback) { - if(self.menuConfig.prompt) { - // :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements - } - - callback(null); - }, - function displayPromptArt(callback) { - if(_.isString(self.menuConfig.prompt)) { - // If a prompt is specified, we need the configuration - if(!_.isObject(self.menuConfig.promptConfig)) { - callback(new Error('Prompt specified but configuraiton not found!')); - return; - } - - // Prompts *must* have art. If it's missing it's an error - // :TODO: allow inline prompts in the future, e.g. @inline:memberName -> { "memberName" : { "text" : "stuff", ... } } - var promptConfig = self.menuConfig.promptConfig; - theme.displayThemedAsset( - promptConfig.art, - self.client, - self.menuConfig.options, // can include .font, .trailingLF, etc. - function displayed(err, artData) { - if(!err) { - mciData.prompt = artData.mciMap; - } - callback(err); - }); - } else { - callback(null); - } - }, - function recordCursorPosition(callback) { - if(self.shouldPause()) { - self.client.once('cursor position report', function cpr(pos) { - self.afterArtPos = pos; - self.client.log.trace( { position : pos }, 'After art position recorded'); - callback(null); - }); - self.client.term.write(ansi.queryPos()); - } else { - callback(null); - } - }, - function afterArtDisplayed(callback) { - self.mciReady(mciData, callback); - }, - function displayPauseIfRequested(callback) { - if(self.shouldPause()) { - self.client.term.write(ansi.goto(self.afterArtPos[0], 1)); - - // :TODO: really need a client.term.pause() that uses the correct art/etc. - theme.displayThemedPause( { client : self.client }, function keyPressed() { - callback(null); - }); - } else { - callback(null); - } - }, - function finishAndNext(callback) { - self.finishedLoading(); - - self.autoNextMenu(callback); - } - ], - function complete(err) { - if(err) { - console.log(err) - // :TODO: what to do exactly????? - return self.prevMenu( () => { - // dummy - }); - } - } - ); - }; - this.shouldPause = function() { return 'end' === self.menuConfig.options.pause || true === self.menuConfig.options.pause; }; @@ -199,8 +95,8 @@ require('./mod_mixins.js').ViewControllerManagement.call(MenuModule.prototype); MenuModule.prototype.enter = function() { - if(_.isString(this.menuConfig.status)) { - this.client.currentStatus = this.menuConfig.status; + if(_.isString(this.menuConfig.desc)) { + this.client.currentStatus = this.menuConfig.desc; } else { this.client.currentStatus = 'Browsing menus'; } @@ -208,6 +104,111 @@ MenuModule.prototype.enter = function() { this.initSequence(); }; +MenuModule.prototype.initSequence = function() { + var mciData = { }; + const self = this; + + async.series( + [ + function beforeDisplayArt(callback) { + self.beforeArt(callback); + }, + function displayMenuArt(callback) { + if(_.isString(self.menuConfig.art)) { + theme.displayThemedAsset( + self.menuConfig.art, + self.client, + self.menuConfig.options, // can include .font, .trailingLF, etc. + function displayed(err, artData) { + if(err) { + self.client.log.trace( { art : self.menuConfig.art, error : err.message }, 'Could not display art'); + } else { + mciData.menu = artData.mciMap; + } + callback(null); // non-fatal + } + ); + } else { + callback(null); + } + }, + function moveToPromptLocation(callback) { + if(self.menuConfig.prompt) { + // :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements + } + + callback(null); + }, + function displayPromptArt(callback) { + if(_.isString(self.menuConfig.prompt)) { + // If a prompt is specified, we need the configuration + if(!_.isObject(self.menuConfig.promptConfig)) { + callback(new Error('Prompt specified but configuraiton not found!')); + return; + } + + // Prompts *must* have art. If it's missing it's an error + // :TODO: allow inline prompts in the future, e.g. @inline:memberName -> { "memberName" : { "text" : "stuff", ... } } + var promptConfig = self.menuConfig.promptConfig; + theme.displayThemedAsset( + promptConfig.art, + self.client, + self.menuConfig.options, // can include .font, .trailingLF, etc. + function displayed(err, artData) { + if(!err) { + mciData.prompt = artData.mciMap; + } + callback(err); + }); + } else { + callback(null); + } + }, + function recordCursorPosition(callback) { + if(self.shouldPause()) { + self.client.once('cursor position report', function cpr(pos) { + self.afterArtPos = pos; + self.client.log.trace( { position : pos }, 'After art position recorded'); + callback(null); + }); + self.client.term.write(ansi.queryPos()); + } else { + callback(null); + } + }, + function afterArtDisplayed(callback) { + self.mciReady(mciData, callback); + }, + function displayPauseIfRequested(callback) { + if(self.shouldPause()) { + self.client.term.write(ansi.goto(self.afterArtPos[0], 1)); + + // :TODO: really need a client.term.pause() that uses the correct art/etc. + theme.displayThemedPause( { client : self.client }, function keyPressed() { + callback(null); + }); + } else { + callback(null); + } + }, + function finishAndNext(callback) { + self.finishedLoading(); + + self.autoNextMenu(callback); + } + ], + function complete(err) { + if(err) { + console.log(err) + // :TODO: what to do exactly????? + return self.prevMenu( () => { + // dummy + }); + } + } + ); +}; + MenuModule.prototype.getSaveState = function() { // nothing in base }; diff --git a/core/menu_view.js b/core/menu_view.js index dc33aeaf..0c11c51c 100644 --- a/core/menu_view.js +++ b/core/menu_view.js @@ -4,6 +4,7 @@ // ENiGMA½ const View = require('./view.js').View; const miscUtil = require('./misc_util.js'); +const pipeToAnsi = require('./color_codes.js').pipeToAnsi; // deps const util = require('util'); @@ -15,9 +16,11 @@ exports.MenuView = MenuView; function MenuView(options) { options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true); options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true); - + View.call(this, options); + this.disablePipe = options.disablePipe || false; + const self = this; if(options.items) { @@ -60,10 +63,16 @@ function MenuView(options) { util.inherits(MenuView, View); MenuView.prototype.setItems = function(items) { + const self = this; + if(items) { this.items = []; items.forEach( itemText => { - this.items.push( { text : itemText } ); + this.items.push( + { + text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client) + } + ); }); } }; @@ -110,10 +119,16 @@ MenuView.prototype.onKeyPress = function(ch, key) { }; MenuView.prototype.setFocusItems = function(items) { + const self = this; + if(items) { this.focusItems = []; items.forEach( itemText => { - this.focusItems.push( { text : itemText } ); + this.focusItems.push( + { + text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client) + } + ); }); } }; diff --git a/core/predefined_mci.js b/core/predefined_mci.js index 96cb7d98..985225bb 100644 --- a/core/predefined_mci.js +++ b/core/predefined_mci.js @@ -16,6 +16,24 @@ const _ = require('lodash'); const moment = require('moment'); exports.getPredefinedMCIValue = getPredefinedMCIValue; +exports.init = init; + +function init(cb) { + setNextRandomRumor(cb); +} + +function setNextRandomRumor(cb) { + StatLog.getSystemLogEntries('system_rumorz', StatLog.Order.Random, 1, (err, entry) => { + if(entry) { + entry = entry[0]; + } + const randRumor = entry && entry.log_value ? entry.log_value : ''; + StatLog.setNonPeristentSystemStat('random_rumor', randRumor); + if(cb) { + return cb(null); + } + }); +} function getPredefinedMCIValue(client, code) { @@ -138,6 +156,13 @@ function getPredefinedMCIValue(client, code) { TC : function totalCalls() { return StatLog.getSystemStat('login_count').toString(); }, + RR : function randomRumor() { + // start the process of picking another random one + setNextRandomRumor(); + + return StatLog.getSystemStat('random_rumor'); + }, + // // Special handling for XY // diff --git a/core/stat_log.js b/core/stat_log.js index 14b391d6..9bf46a66 100644 --- a/core/stat_log.js +++ b/core/stat_log.js @@ -44,6 +44,21 @@ class StatLog { ); } + get KeepDays() { + return { + Forever : -1, + }; + } + + get Order() { + return { + Timestamp : 'timestamp_asc', + TimestampAsc : 'timestamp_asc', + TimestampDesc : 'timestamp_desc', + Random : 'random', + }; + } + setNonPeristentSystemStat(statName, statValue) { this.systemStats[statName] = statValue; } @@ -123,6 +138,13 @@ class StatLog { // // Handle keepDays // + if(-1 === keepDays) { + if(cb) { + return cb(null); + } + return; + } + sysDb.run( `DELETE FROM system_event_log WHERE log_name = ? AND timestamp <= DATETIME("now", "-${keepDays} day");`, @@ -145,9 +167,17 @@ class StatLog { WHERE log_name = ?`; switch(order) { - case 'timestamp' : - case 'timestamp_desc' : - sql += ' ORDER BY timestamp DESC'; + case 'timestamp' : + case 'timestamp_asc' : + sql += ' ORDER BY timestamp ASC'; + break; + + case 'timestamp_desc' : + sql += ' ORDER BY timestamp DESC'; + break; + + case 'random' : + sql += ' ORDER BY RANDOM()'; } if(!cb && _.isFunction(limit)) { @@ -177,6 +207,13 @@ class StatLog { // // Handle keepDays // + if(-1 === keepDays) { + if(cb) { + return cb(null); + } + return; + } + sysDb.run( `DELETE FROM user_event_log WHERE user_id = ? AND log_name = ? AND timestamp <= DATETIME("now", "-${keepDays} day");`, diff --git a/core/string_util.js b/core/string_util.js index 9050d525..bf8fdf9e 100644 --- a/core/string_util.js +++ b/core/string_util.js @@ -236,7 +236,8 @@ exports.stringFormatExtensions = stringFormatExtensions; // :TODO: Add other codes from ansi_escape_parser const ANSI_REGEXP = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; -const ANSI_OR_PIPE_REGEXP = new RegExp(ANSI_REGEXP.source + '\\|[A-Z\d]{2}', 'g'); +const PIPE_REGEXP = /\|[A-Z\d]{2}/g; +const ANSI_OR_PIPE_REGEXP = new RegExp(ANSI_REGEXP.source + '|' + PIPE_REGEXP.source, 'g'); // // Similar to substr() but works with ANSI/Pipe code strings @@ -279,10 +280,8 @@ function renderSubstr(str, start, length) { // Pipe and ANSI color codes. Note that currently ANSI *movement* // codes are not considred! // -// :TODO: consolidate ANSI code RegExp's and such -const PIPE_AND_ANSI_RE = ANSI_OR_PIPE_REGEXP;// /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\|[A-Z\d]{2}/g function renderStringLength(str) { - return str.replace(PIPE_AND_ANSI_RE, '').length; + return str.replace(ANSI_OR_PIPE_REGEXP, '').length; } diff --git a/core/system_property.js b/core/system_property.js deleted file mode 100644 index 87f4f4fa..00000000 --- a/core/system_property.js +++ /dev/null @@ -1,38 +0,0 @@ -/* jslint node: true */ -'use strict'; - -var sysDb = require('./database.js').dbs.system; - -exports.loadSystemProperties = loadSystemProperties; -exports.persistSystemProperty = persistSystemProperty; -exports.getSystemProperty = getSystemProperty; - -var systemProperties = {}; -exports.systemProperties = systemProperties; - -function loadSystemProperties(cb) { - sysDb.each( - 'SELECT prop_name, prop_value ' + - 'FROM system_property;', - function rowResult(err, row) { - systemProperties[row.prop_name] = row.prop_value; - }, - cb - ); -} - -function persistSystemProperty(propName, propValue, cb) { - // update live - systemProperties[propName] = propValue; - - sysDb.run( - 'REPLACE INTO system_property ' + - 'VALUES (?, ?);', - [ propName, propValue ], - cb - ); -} - -function getSystemProperty(propName) { - return systemProperties[propName]; -} diff --git a/core/text_view.js b/core/text_view.js index 9ed9ad09..ee00af9a 100644 --- a/core/text_view.js +++ b/core/text_view.js @@ -34,7 +34,6 @@ function TextView(options) { this.justify = options.justify || 'right'; this.resizable = miscUtil.valueWithDefault(options.resizable, true); this.horizScroll = miscUtil.valueWithDefault(options.horizScroll, true); - this.text = options.text || ''; if(_.isString(options.textOverflow)) { this.textOverflow = options.textOverflow; @@ -136,8 +135,7 @@ function TextView(options) { return this.position.col + offset; }; - // :TODO: Whatever needs init here should be done separately from setText() since it redraws/etc. -// this.setText(options.text || ''); + this.setText(options.text || '', false); // false=do not redraw now } util.inherits(TextView, View); @@ -175,7 +173,9 @@ TextView.prototype.getData = function() { return this.text; }; -TextView.prototype.setText = function(text) { +TextView.prototype.setText = function(text, redraw) { + redraw = _.isBoolean(redraw) ? redraw : true; + if(!_.isString(text)) { text = text.toString(); } @@ -201,7 +201,9 @@ TextView.prototype.setText = function(text) { this.dimens.width = this.text.length + widthDelta; } - this.redraw(); + if(redraw) { + this.redraw(); + } }; /* diff --git a/core/vertical_menu_view.js b/core/vertical_menu_view.js index 1772e387..f72095e1 100644 --- a/core/vertical_menu_view.js +++ b/core/vertical_menu_view.js @@ -15,7 +15,7 @@ exports.VerticalMenuView = VerticalMenuView; function VerticalMenuView(options) { options.cursor = options.cursor || 'hide'; options.justify = options.justify || 'right'; // :TODO: default to center - + MenuView.call(this, options); const self = this; @@ -48,60 +48,33 @@ function VerticalMenuView(options) { }; }; - /* - this.drawItem = function(index) { - var item = self.items[index]; - if(!item) { - return; - } - - var text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle); - self.client.term.write( - ansi.goto(item.row, self.position.col) + - (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR()) + - strUtil.pad(text, this.dimens.width, this.fillChar, this.justify) - ); - }; - */ - this.drawItem = function(index) { const item = self.items[index]; - if(!item) { return; } - let focusItem; let text; - - if(self.hasFocusItems()) { - focusItem = self.focusItems[index]; - } - - if(focusItem) { - if(item.focused) { - text = strUtil.stylizeString(focusItem.text, self.focusTextStyle); - } else { - text = strUtil.stylizeString(item.text, self.textStyle); - } - - // :TODO: Need to support pad() - // :TODO: shoudl we detect if pipe codes are used? - self.client.term.write( - ansi.goto(item.row, self.position.col) + - colorCodes.pipeToAnsi(text, self.client) - ); - + let sgr; + if(item.focused && self.hasFocusItems()) { + const focusItem = self.focusItems[index]; + text = strUtil.stylizeString( + focusItem ? focusItem.text : item.text, + self.textStyle + ); + sgr = ''; } else { - text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle); - - self.client.term.write( - ansi.goto(item.row, self.position.col) + - (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR()) + - strUtil.pad(text, this.dimens.width, this.fillChar, this.justify) - ); + text = strUtil.stylizeString(item.text, self.textStyle); + sgr = (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR()); } + text += self.getSGR(); + + self.client.term.write( + ansi.goto(item.row, self.position.col) + + sgr + + strUtil.pad(text, this.dimens.width, this.fillChar, this.justify) + ); }; } @@ -174,7 +147,7 @@ VerticalMenuView.prototype.setFocusItemIndex = function(index) { this.positionCacheExpired = false; // skip standard behavior this.performAutoScale(); } - + this.redraw(); }; diff --git a/core/view.js b/core/view.js index 1889c98e..5bf0605a 100644 --- a/core/view.js +++ b/core/view.js @@ -254,7 +254,7 @@ View.prototype.setFocus = function(focused) { View.prototype.onKeyPress = function(ch, key) { if(false === this.hasFocus) { - console.log('doh!'); + console.log('doh!'); // :TODO: fix me -- assert here? } assert(this.hasFocus, 'View does not have focus'); assert(this.acceptsInput, 'View does not accept input'); @@ -272,6 +272,8 @@ View.prototype.onKeyPress = function(ch, key) { if(ch) { assert(1 === ch.length); } + + this.emit('key press', ch, key); }; View.prototype.getData = function() { diff --git a/core/view_controller.js b/core/view_controller.js index fa85a1b8..2cbf40b1 100644 --- a/core/view_controller.js +++ b/core/view_controller.js @@ -23,7 +23,6 @@ function ViewController(options) { assert(_.isObject(options)); assert(_.isObject(options.client)); - events.EventEmitter.call(this); var self = this; @@ -83,28 +82,28 @@ function ViewController(options) { this.viewActionListener = function(action, key) { switch(action) { - case 'next' : - self.emit('action', { view : this, action : action, key : key }); - self.nextFocus(); - break; - - case 'accept' : - if(self.focusedView && self.focusedView.submit) { - // :TODO: need to do validation here!!! - var focusedView = self.focusedView; - self.validateView(focusedView, function validated(err, newFocusedViewId) { - if(err) { - var newFocusedView = self.getView(newFocusedViewId) || focusedView; - self.setViewFocusWithEvents(newFocusedView, true); - } else { - self.submitForm(key); - } - }); - //self.submitForm(key); - } else { + case 'next' : + self.emit('action', { view : this, action : action, key : key }); self.nextFocus(); - } - break; + break; + + case 'accept' : + if(self.focusedView && self.focusedView.submit) { + // :TODO: need to do validation here!!! + var focusedView = self.focusedView; + self.validateView(focusedView, function validated(err, newFocusedViewId) { + if(err) { + var newFocusedView = self.getView(newFocusedViewId) || focusedView; + self.setViewFocusWithEvents(newFocusedView, true); + } else { + self.submitForm(key); + } + }); + //self.submitForm(key); + } else { + self.nextFocus(); + } + break; } }; diff --git a/mods/bbs_list.js b/mods/bbs_list.js index f941dd8f..13e56675 100644 --- a/mods/bbs_list.js +++ b/mods/bbs_list.js @@ -117,21 +117,11 @@ function BBSListModule(options) { }; this.setEntries = function(entriesView) { - /* - :TODO: This is currently disabled until VerticalMenuView 'justify' works properly with pipe code strings - const listFormat = config.listFormat || '{bbsName}'; const focusListFormat = config.focusListFormat || '{bbsName}'; - entriesView.setItems(self.entries.map( e => { - return listFormat.format(e); - })); - - entriesView.setFocusItems(self.entries.map( e => { - return focusListFormat.format(e); - })); - */ - entriesView.setItems(self.entries.map(e => e.bbsName)); + entriesView.setItems(self.entries.map( e => listFormat.format(e) ) ); + entriesView.setFocusItems(self.entries.map( e => focusListFormat.format(e) ) ); }; this.displayBBSList = function(clearScreen, cb) { diff --git a/mods/last_callers.js b/mods/last_callers.js index 4eb10077..bce1cebd 100644 --- a/mods/last_callers.js +++ b/mods/last_callers.js @@ -66,7 +66,7 @@ LastCallersModule.prototype.mciReady = function(mciData, cb) { function fetchHistory(callback) { callersView = vc.getView(MciCodeIds.CallerList); - StatLog.getSystemLogEntries('user_login_history', 'timestamp_desc', callersView.dimens.height, (err, lh) => { + StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, callersView.dimens.height, (err, lh) => { loginHistory = lh; return callback(err); }); diff --git a/mods/rumorz.js b/mods/rumorz.js new file mode 100644 index 00000000..abc88886 --- /dev/null +++ b/mods/rumorz.js @@ -0,0 +1,246 @@ +/* jslint node: true */ +'use strict'; + +// ENiGMA½ +const MenuModule = require('../core/menu_module.js').MenuModule; +const ViewController = require('../core/view_controller.js').ViewController; +const theme = require('../core/theme.js'); +const resetScreen = require('../core/ansi_term.js').resetScreen; +const StatLog = require('../core/stat_log.js'); +const renderStringLength = require('../core/string_util.js').renderStringLength; + +// deps +const async = require('async'); +const _ = require('lodash'); + +exports.moduleInfo = { + name : 'Rumorz', + desc : 'Standard local rumorz', + author : 'NuSkooler', + packageName : 'codes.l33t.enigma.rumorz', +}; + +const STATLOG_KEY_RUMORZ = 'system_rumorz'; + +const FormIds = { + View : 0, + Add : 1, +}; + +const MciCodeIds = { + ViewForm : { + Entries : 1, + AddPrompt : 2, + }, + AddForm : { + NewEntry : 1, + EntryPreview : 2, + AddPrompt : 3, + } +}; + +exports.getModule = class RumorzModule extends MenuModule { + constructor(options) { + super(options); + + this.menuMethods = { + viewAddScreen : (formData, extraArgs, cb) => { + return this.displayAddScreen(cb); + }, + + addEntry : (formData, extraArgs, cb) => { + if(_.isString(formData.value.rumor) && renderStringLength(formData.value.rumor) > 0) { + const rumor = formData.value.rumor.trim(); // remove any trailing ws + + StatLog.appendSystemLogEntry(STATLOG_KEY_RUMORZ, rumor, StatLog.KeepDays.Forever, () => { + this.clearAddForm(); + return this.displayViewScreen(true, cb); // true=cls + }); + } else { + // empty message - treat as if cancel was hit + return this.displayViewScreen(true, cb); // true=cls + } + }, + + cancelAdd : (formData, extraArgs, cb) => { + this.clearAddForm(); + return this.displayViewScreen(true, cb); // true=cls + } + }; + } + + get config() { return this.menuConfig.config; } + + clearAddForm() { + const newEntryView = this.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry); + const previewView = this.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview); + + newEntryView.setText(''); + + // preview is optional + if(previewView) { + previewView.setText(''); + } + } + + initSequence() { + const self = this; + + async.series( + [ + function beforeDisplayArt(callback) { + self.beforeArt(callback); + }, + function display(callback) { + self.displayViewScreen(false, callback); + } + ], + err => { + if(err) { + // :TODO: Handle me -- initSequence() should really take a completion callback + } + self.finishedLoading(); + } + ); + } + + displayViewScreen(clearScreen, cb) { + const self = this; + async.waterfall( + [ + function clearAndDisplayArt(callback) { + if(self.viewControllers.add) { + self.viewControllers.add.setFocus(false); + } + + if(clearScreen) { + self.client.term.rawWrite(resetScreen()); + } + + theme.displayThemedAsset( + self.config.art.entries, + self.client, + { font : self.menuConfig.font, trailingLF : false }, + (err, artData) => { + return callback(err, artData); + } + ); + }, + function initOrRedrawViewController(artData, callback) { + if(_.isUndefined(self.viewControllers.add)) { + const vc = self.addViewController( + 'view', + new ViewController( { client : self.client, formId : FormIds.View } ) + ); + + const loadOpts = { + callingMenu : self, + mciMap : artData.mciMap, + formId : FormIds.View, + }; + + return vc.loadFromMenuConfig(loadOpts, callback); + } else { + self.viewControllers.view.setFocus(true); + self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw(); + return callback(null); + } + }, + function fetchEntries(callback) { + const entriesView = self.viewControllers.view.getView(MciCodeIds.ViewForm.Entries); + + StatLog.getSystemLogEntries(STATLOG_KEY_RUMORZ, StatLog.Order.Timestamp, (err, entries) => { + return callback(err, entriesView, entries); + }); + }, + function populateEntries(entriesView, entries, callback) { + const config = self.config; + const listFormat = config.listFormat || '{rumor}'; + const focusListFormat = config.focusListFormat || listFormat; + + entriesView.setItems(entries.map( e => listFormat.format( { rumor : e.log_value } ) ) ); + entriesView.setFocusItems(entries.map(e => focusListFormat.format( { rumor : e.log_value } ) ) ); + entriesView.redraw(); + + return callback(null); + }, + function finalPrep(callback) { + const promptView = self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt); + promptView.setFocusItemIndex(1); // default to NO + return callback(null); + } + ], + err => { + if(cb) { + return cb(err); + } + } + ); + } + + displayAddScreen(cb) { + const self = this; + + async.waterfall( + [ + function clearAndDisplayArt(callback) { + self.viewControllers.view.setFocus(false); + self.client.term.rawWrite(resetScreen()); + + theme.displayThemedAsset( + self.config.art.add, + self.client, + { font : self.menuConfig.font }, + (err, artData) => { + return callback(err, artData); + } + ); + }, + function initOrRedrawViewController(artData, callback) { + if(_.isUndefined(self.viewControllers.add)) { + const vc = self.addViewController( + 'add', + new ViewController( { client : self.client, formId : FormIds.Add } ) + ); + + const loadOpts = { + callingMenu : self, + mciMap : artData.mciMap, + formId : FormIds.Add, + }; + + return vc.loadFromMenuConfig(loadOpts, callback); + } else { + self.viewControllers.add.setFocus(true); + self.viewControllers.add.redrawAll(); + self.viewControllers.add.switchFocus(MciCodeIds.AddForm.NewEntry); + return callback(null); + } + }, + function initPreviewUpdates(callback) { + const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview); + const entryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry); + if(previewView) { + let timerId; + entryView.on('key press', () => { + clearTimeout(timerId); + timerId = setTimeout( () => { + const focused = self.viewControllers.add.getFocusedView(); + if(focused === entryView) { + previewView.setText(entryView.getData()); + focused.setFocus(true); + } + }, 500); + }); + } + return callback(null); + } + ], + err => { + if(cb) { + return cb(err); + } + } + ); + } +};