* 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
This commit is contained in:
parent
2b68201f7d
commit
30ba609fb4
|
@ -40,110 +40,6 @@ function MenuModule(options) {
|
||||||
|
|
||||||
this.initViewControllers();
|
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() {
|
this.shouldPause = function() {
|
||||||
return 'end' === self.menuConfig.options.pause || true === self.menuConfig.options.pause;
|
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() {
|
MenuModule.prototype.enter = function() {
|
||||||
if(_.isString(this.menuConfig.status)) {
|
if(_.isString(this.menuConfig.desc)) {
|
||||||
this.client.currentStatus = this.menuConfig.status;
|
this.client.currentStatus = this.menuConfig.desc;
|
||||||
} else {
|
} else {
|
||||||
this.client.currentStatus = 'Browsing menus';
|
this.client.currentStatus = 'Browsing menus';
|
||||||
}
|
}
|
||||||
|
@ -208,6 +104,111 @@ MenuModule.prototype.enter = function() {
|
||||||
this.initSequence();
|
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() {
|
MenuModule.prototype.getSaveState = function() {
|
||||||
// nothing in base
|
// nothing in base
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const View = require('./view.js').View;
|
const View = require('./view.js').View;
|
||||||
const miscUtil = require('./misc_util.js');
|
const miscUtil = require('./misc_util.js');
|
||||||
|
const pipeToAnsi = require('./color_codes.js').pipeToAnsi;
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
@ -15,9 +16,11 @@ exports.MenuView = MenuView;
|
||||||
function MenuView(options) {
|
function MenuView(options) {
|
||||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||||
|
|
||||||
View.call(this, options);
|
View.call(this, options);
|
||||||
|
|
||||||
|
this.disablePipe = options.disablePipe || false;
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if(options.items) {
|
if(options.items) {
|
||||||
|
@ -60,10 +63,16 @@ function MenuView(options) {
|
||||||
util.inherits(MenuView, View);
|
util.inherits(MenuView, View);
|
||||||
|
|
||||||
MenuView.prototype.setItems = function(items) {
|
MenuView.prototype.setItems = function(items) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
if(items) {
|
if(items) {
|
||||||
this.items = [];
|
this.items = [];
|
||||||
items.forEach( itemText => {
|
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) {
|
MenuView.prototype.setFocusItems = function(items) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
if(items) {
|
if(items) {
|
||||||
this.focusItems = [];
|
this.focusItems = [];
|
||||||
items.forEach( itemText => {
|
items.forEach( itemText => {
|
||||||
this.focusItems.push( { text : itemText } );
|
this.focusItems.push(
|
||||||
|
{
|
||||||
|
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,24 @@ const _ = require('lodash');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
exports.getPredefinedMCIValue = getPredefinedMCIValue;
|
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) {
|
function getPredefinedMCIValue(client, code) {
|
||||||
|
|
||||||
|
@ -138,6 +156,13 @@ function getPredefinedMCIValue(client, code) {
|
||||||
|
|
||||||
TC : function totalCalls() { return StatLog.getSystemStat('login_count').toString(); },
|
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
|
// Special handling for XY
|
||||||
//
|
//
|
||||||
|
|
|
@ -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) {
|
setNonPeristentSystemStat(statName, statValue) {
|
||||||
this.systemStats[statName] = statValue;
|
this.systemStats[statName] = statValue;
|
||||||
}
|
}
|
||||||
|
@ -123,6 +138,13 @@ class StatLog {
|
||||||
//
|
//
|
||||||
// Handle keepDays
|
// Handle keepDays
|
||||||
//
|
//
|
||||||
|
if(-1 === keepDays) {
|
||||||
|
if(cb) {
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sysDb.run(
|
sysDb.run(
|
||||||
`DELETE FROM system_event_log
|
`DELETE FROM system_event_log
|
||||||
WHERE log_name = ? AND timestamp <= DATETIME("now", "-${keepDays} day");`,
|
WHERE log_name = ? AND timestamp <= DATETIME("now", "-${keepDays} day");`,
|
||||||
|
@ -145,9 +167,17 @@ class StatLog {
|
||||||
WHERE log_name = ?`;
|
WHERE log_name = ?`;
|
||||||
|
|
||||||
switch(order) {
|
switch(order) {
|
||||||
case 'timestamp' :
|
case 'timestamp' :
|
||||||
case 'timestamp_desc' :
|
case 'timestamp_asc' :
|
||||||
sql += ' ORDER BY timestamp DESC';
|
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)) {
|
if(!cb && _.isFunction(limit)) {
|
||||||
|
@ -177,6 +207,13 @@ class StatLog {
|
||||||
//
|
//
|
||||||
// Handle keepDays
|
// Handle keepDays
|
||||||
//
|
//
|
||||||
|
if(-1 === keepDays) {
|
||||||
|
if(cb) {
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sysDb.run(
|
sysDb.run(
|
||||||
`DELETE FROM user_event_log
|
`DELETE FROM user_event_log
|
||||||
WHERE user_id = ? AND log_name = ? AND timestamp <= DATETIME("now", "-${keepDays} day");`,
|
WHERE user_id = ? AND log_name = ? AND timestamp <= DATETIME("now", "-${keepDays} day");`,
|
||||||
|
|
|
@ -236,7 +236,8 @@ exports.stringFormatExtensions = stringFormatExtensions;
|
||||||
|
|
||||||
// :TODO: Add other codes from ansi_escape_parser
|
// :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_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
|
// 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*
|
// Pipe and ANSI color codes. Note that currently ANSI *movement*
|
||||||
// codes are not considred!
|
// 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) {
|
function renderStringLength(str) {
|
||||||
return str.replace(PIPE_AND_ANSI_RE, '').length;
|
return str.replace(ANSI_OR_PIPE_REGEXP, '').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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];
|
|
||||||
}
|
|
|
@ -34,7 +34,6 @@ function TextView(options) {
|
||||||
this.justify = options.justify || 'right';
|
this.justify = options.justify || 'right';
|
||||||
this.resizable = miscUtil.valueWithDefault(options.resizable, true);
|
this.resizable = miscUtil.valueWithDefault(options.resizable, true);
|
||||||
this.horizScroll = miscUtil.valueWithDefault(options.horizScroll, true);
|
this.horizScroll = miscUtil.valueWithDefault(options.horizScroll, true);
|
||||||
this.text = options.text || '';
|
|
||||||
|
|
||||||
if(_.isString(options.textOverflow)) {
|
if(_.isString(options.textOverflow)) {
|
||||||
this.textOverflow = options.textOverflow;
|
this.textOverflow = options.textOverflow;
|
||||||
|
@ -136,8 +135,7 @@ function TextView(options) {
|
||||||
return this.position.col + offset;
|
return this.position.col + offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
// :TODO: Whatever needs init here should be done separately from setText() since it redraws/etc.
|
this.setText(options.text || '', false); // false=do not redraw now
|
||||||
// this.setText(options.text || '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(TextView, View);
|
util.inherits(TextView, View);
|
||||||
|
@ -175,7 +173,9 @@ TextView.prototype.getData = function() {
|
||||||
return this.text;
|
return this.text;
|
||||||
};
|
};
|
||||||
|
|
||||||
TextView.prototype.setText = function(text) {
|
TextView.prototype.setText = function(text, redraw) {
|
||||||
|
redraw = _.isBoolean(redraw) ? redraw : true;
|
||||||
|
|
||||||
if(!_.isString(text)) {
|
if(!_.isString(text)) {
|
||||||
text = text.toString();
|
text = text.toString();
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,9 @@ TextView.prototype.setText = function(text) {
|
||||||
this.dimens.width = this.text.length + widthDelta;
|
this.dimens.width = this.text.length + widthDelta;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redraw();
|
if(redraw) {
|
||||||
|
this.redraw();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -15,7 +15,7 @@ exports.VerticalMenuView = VerticalMenuView;
|
||||||
function VerticalMenuView(options) {
|
function VerticalMenuView(options) {
|
||||||
options.cursor = options.cursor || 'hide';
|
options.cursor = options.cursor || 'hide';
|
||||||
options.justify = options.justify || 'right'; // :TODO: default to center
|
options.justify = options.justify || 'right'; // :TODO: default to center
|
||||||
|
|
||||||
MenuView.call(this, options);
|
MenuView.call(this, options);
|
||||||
|
|
||||||
const self = this;
|
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) {
|
this.drawItem = function(index) {
|
||||||
const item = self.items[index];
|
const item = self.items[index];
|
||||||
|
|
||||||
if(!item) {
|
if(!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let focusItem;
|
|
||||||
let text;
|
let text;
|
||||||
|
let sgr;
|
||||||
if(self.hasFocusItems()) {
|
if(item.focused && self.hasFocusItems()) {
|
||||||
focusItem = self.focusItems[index];
|
const focusItem = self.focusItems[index];
|
||||||
}
|
text = strUtil.stylizeString(
|
||||||
|
focusItem ? focusItem.text : item.text,
|
||||||
if(focusItem) {
|
self.textStyle
|
||||||
if(item.focused) {
|
);
|
||||||
text = strUtil.stylizeString(focusItem.text, self.focusTextStyle);
|
sgr = '';
|
||||||
} 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)
|
|
||||||
);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle);
|
text = strUtil.stylizeString(item.text, self.textStyle);
|
||||||
|
sgr = (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
||||||
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 += 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.positionCacheExpired = false; // skip standard behavior
|
||||||
this.performAutoScale();
|
this.performAutoScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -254,7 +254,7 @@ View.prototype.setFocus = function(focused) {
|
||||||
|
|
||||||
View.prototype.onKeyPress = function(ch, key) {
|
View.prototype.onKeyPress = function(ch, key) {
|
||||||
if(false === this.hasFocus) {
|
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.hasFocus, 'View does not have focus');
|
||||||
assert(this.acceptsInput, 'View does not accept input');
|
assert(this.acceptsInput, 'View does not accept input');
|
||||||
|
@ -272,6 +272,8 @@ View.prototype.onKeyPress = function(ch, key) {
|
||||||
if(ch) {
|
if(ch) {
|
||||||
assert(1 === ch.length);
|
assert(1 === ch.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.emit('key press', ch, key);
|
||||||
};
|
};
|
||||||
|
|
||||||
View.prototype.getData = function() {
|
View.prototype.getData = function() {
|
||||||
|
|
|
@ -23,7 +23,6 @@ function ViewController(options) {
|
||||||
assert(_.isObject(options));
|
assert(_.isObject(options));
|
||||||
assert(_.isObject(options.client));
|
assert(_.isObject(options.client));
|
||||||
|
|
||||||
|
|
||||||
events.EventEmitter.call(this);
|
events.EventEmitter.call(this);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -83,28 +82,28 @@ function ViewController(options) {
|
||||||
|
|
||||||
this.viewActionListener = function(action, key) {
|
this.viewActionListener = function(action, key) {
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case 'next' :
|
case 'next' :
|
||||||
self.emit('action', { view : this, action : action, key : key });
|
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 {
|
|
||||||
self.nextFocus();
|
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -117,21 +117,11 @@ function BBSListModule(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setEntries = function(entriesView) {
|
this.setEntries = function(entriesView) {
|
||||||
/*
|
|
||||||
:TODO: This is currently disabled until VerticalMenuView 'justify' works properly with pipe code strings
|
|
||||||
|
|
||||||
const listFormat = config.listFormat || '{bbsName}';
|
const listFormat = config.listFormat || '{bbsName}';
|
||||||
const focusListFormat = config.focusListFormat || '{bbsName}';
|
const focusListFormat = config.focusListFormat || '{bbsName}';
|
||||||
|
|
||||||
entriesView.setItems(self.entries.map( e => {
|
entriesView.setItems(self.entries.map( e => listFormat.format(e) ) );
|
||||||
return listFormat.format(e);
|
entriesView.setFocusItems(self.entries.map( e => focusListFormat.format(e) ) );
|
||||||
}));
|
|
||||||
|
|
||||||
entriesView.setFocusItems(self.entries.map( e => {
|
|
||||||
return focusListFormat.format(e);
|
|
||||||
}));
|
|
||||||
*/
|
|
||||||
entriesView.setItems(self.entries.map(e => e.bbsName));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.displayBBSList = function(clearScreen, cb) {
|
this.displayBBSList = function(clearScreen, cb) {
|
||||||
|
|
|
@ -66,7 +66,7 @@ LastCallersModule.prototype.mciReady = function(mciData, cb) {
|
||||||
function fetchHistory(callback) {
|
function fetchHistory(callback) {
|
||||||
callersView = vc.getView(MciCodeIds.CallerList);
|
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;
|
loginHistory = lh;
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue