* JSONCache -> ConfigCache
* ConfigCache used for theme.hjson * Reformatted theme.hjson JSON to HJSON * Specific form ID used when applying themes if available, else generic match used * Pass extraArgs when processing 'next'
This commit is contained in:
parent
cc6d214882
commit
481c3171f4
|
@ -7,16 +7,16 @@ var Log = require('./logger.js').log;
|
|||
var paths = require('path');
|
||||
var fs = require('fs');
|
||||
var Gaze = require('gaze').Gaze;
|
||||
var stripJsonComments = require('strip-json-comments');
|
||||
var events = require('events');
|
||||
var util = require('util');
|
||||
var assert = require('assert');
|
||||
var hjson = require('hjson');
|
||||
|
||||
module.exports = exports = new JSONCache();
|
||||
|
||||
function JSONCache() {
|
||||
function ConfigCache() {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
this.cache = {}; // filePath -> JSON
|
||||
this.cache = {}; // filePath -> HJSON
|
||||
this.gaze = new Gaze();
|
||||
|
||||
this.reCacheJSONFromFile = function(filePath, cb) {
|
||||
|
@ -45,14 +45,18 @@ function JSONCache() {
|
|||
self.reCacheJSONFromFile(filePath, function reCached(err) {
|
||||
if(err) {
|
||||
Log.error( { error : err, filePath : filePath } , 'Error recaching JSON');
|
||||
} else {
|
||||
self.emit('recached', filePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
JSONCache.prototype.getJSON = function(fileName, cb) {
|
||||
util.inherits(ConfigCache, events.EventEmitter);
|
||||
|
||||
ConfigCache.prototype.getConfig = function(filePath, cb) {
|
||||
var self = this;
|
||||
var filePath = paths.join(Config.paths.mods, fileName);
|
||||
|
||||
if(filePath in this.cache) {
|
||||
cb(null, this.cache[filePath], false);
|
||||
|
@ -65,3 +69,9 @@ JSONCache.prototype.getJSON = function(fileName, cb) {
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
ConfigCache.prototype.getModConfig = function(fileName, cb) {
|
||||
this.getConfig(paths.join(Config.paths.mods, fileName), cb);
|
||||
};
|
||||
|
||||
module.exports = exports = new ConfigCache();
|
|
@ -154,8 +154,15 @@ function MenuModule(options) {
|
|||
|
||||
this.nextMenu = function() {
|
||||
if(!_.isObject(self.menuConfig.form) && !_.isString(self.menuConfig.prompt) &&
|
||||
_.isString(self.menuConfig.next))
|
||||
(_.isString(self.menuConfig.next) || _.isObject(self.menuConfig.next)))
|
||||
{
|
||||
/*
|
||||
next : "spec"
|
||||
next: {
|
||||
"asset" : "spec",
|
||||
"extraArgs" : ...
|
||||
}
|
||||
*/
|
||||
if(self.hasNextTimeout()) {
|
||||
setTimeout(function nextTimeout() {
|
||||
menuUtil.handleNext(self.client, self.menuConfig.next);
|
||||
|
|
|
@ -8,7 +8,7 @@ var conf = require('./config.js'); // :TODO: remove me!
|
|||
var Config = require('./config.js').config;
|
||||
var asset = require('./asset.js');
|
||||
var theme = require('./theme.js');
|
||||
var jsonCache = require('./json_cache.js');
|
||||
var configCache = require('./config_cache.js');
|
||||
var MCIViewFactory = require('./mci_view_factory.js').MCIViewFactory;
|
||||
|
||||
var fs = require('fs');
|
||||
|
@ -29,7 +29,7 @@ function getMenuConfig(name, cb) {
|
|||
async.waterfall(
|
||||
[
|
||||
function loadMenuJSON(callback) {
|
||||
jsonCache.getJSON('menu.hjson', function loaded(err, menuJson) {
|
||||
configCache.getModConfig('menu.hjson', function loaded(err, menuJson) {
|
||||
callback(err, menuJson);
|
||||
});
|
||||
},
|
||||
|
@ -43,7 +43,7 @@ function getMenuConfig(name, cb) {
|
|||
},
|
||||
function loadPromptJSON(callback) {
|
||||
if(_.isString(menuConfig.prompt)) {
|
||||
jsonCache.getJSON('prompt.hjson', function loaded(err, promptJson, reCached) {
|
||||
configCache.getModConfig('prompt.hjson', function loaded(err, promptJson, reCached) {
|
||||
callback(err, promptJson);
|
||||
});
|
||||
} else {
|
||||
|
@ -166,14 +166,14 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
|
|||
}
|
||||
|
||||
// :TODO: Most of this should be moved elsewhere .... DRY...
|
||||
function callModuleMenuMethod(client, asset, path, formData) {
|
||||
function callModuleMenuMethod(client, asset, path, formData, extraArgs) {
|
||||
if('' === paths.extname(path)) {
|
||||
path += '.js';
|
||||
}
|
||||
|
||||
try {
|
||||
var methodMod = require(path);
|
||||
methodMod[asset.asset](client.currentMenuModule, formData || { }, conf.extraArgs);
|
||||
methodMod[asset.asset](client.currentMenuModule, formData || { }, extraArgs);
|
||||
} catch(e) {
|
||||
client.log.error( { error : e.toString(), methodName : asset.asset }, 'Failed to execute asset method');
|
||||
}
|
||||
|
@ -190,12 +190,12 @@ function handleAction(client, formData, conf) {
|
|||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
if(_.isString(actionAsset.location)) {
|
||||
callModuleMenuMethod(client, actionAsset, paths.join(Config.paths.mods, actionAsset.location, formData));
|
||||
callModuleMenuMethod(client, actionAsset, paths.join(Config.paths.mods, actionAsset.location, formData, conf.extraArgs));
|
||||
} else {
|
||||
if('systemMethod' === actionAsset.type) {
|
||||
// :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. ()
|
||||
// :TODO: Probably better as system_method.js
|
||||
callModuleMenuMethod(client, actionAsset, paths.join(__dirname, 'system_menu_method.js'), formData);
|
||||
callModuleMenuMethod(client, actionAsset, paths.join(__dirname, 'system_menu_method.js'), formData, conf.extraArgs);
|
||||
} else {
|
||||
// local to current module
|
||||
var currentModule = client.currentMenuModule;
|
||||
|
@ -212,32 +212,35 @@ function handleAction(client, formData, conf) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleNext(client, nextSpec) {
|
||||
function handleNext(client, nextSpec, conf) {
|
||||
assert(_.isString(nextSpec));
|
||||
|
||||
var nextAsset = asset.getAssetWithShorthand(nextSpec, 'menu');
|
||||
|
||||
conf = conf || {};
|
||||
var extraArgs = conf.extraArgs || {};
|
||||
|
||||
switch(nextAsset.type) {
|
||||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
if(_.isString(nextAsset.location)) {
|
||||
callModuleMenuMethod(client, nextAsset, paths.join(Config.paths.mods, actionAsset.location));
|
||||
callModuleMenuMethod(client, nextAsset, paths.join(Config.paths.mods, actionAsset.location), {}, extraArgs);
|
||||
} else {
|
||||
if('systemMethod' === nextAsset.type) {
|
||||
// :TODO: see other notes about system_menu_method.js here
|
||||
callModuleMenuMethod(client, nextAsset, paths.join(__dirname, 'system_menu_method.js'));
|
||||
callModuleMenuMethod(client, nextAsset, paths.join(__dirname, 'system_menu_method.js'), {}, extraArgs);
|
||||
} else {
|
||||
// local to current module
|
||||
var currentModule = client.currentMenuModule;
|
||||
if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) {
|
||||
currentModule.menuMethods[actionAsset.asset]( { }, { } );
|
||||
currentModule.menuMethods[actionAsset.asset]( { }, extraArgs );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'menu' :
|
||||
client.gotoMenuModule( { name : nextAsset.asset } );
|
||||
client.gotoMenuModule( { name : nextAsset.asset, extraArgs : extraArgs } );
|
||||
break;
|
||||
|
||||
default :
|
||||
|
@ -246,17 +249,24 @@ function handleNext(client, nextSpec) {
|
|||
}
|
||||
}
|
||||
|
||||
// :TODO: This should be in theme.js
|
||||
|
||||
// :TODO: Need to take (optional) form ID to search for (e.g. for multi-form menus)
|
||||
// :TODO: custom art needs a way to be themed -- e.g. config.art.someArtThing
|
||||
// :TODO: custom art needs a way to be themed -- e.g. config.art.someArtThing -- what does this mean exactly?
|
||||
|
||||
// :TODO: Seems better in theme.js, but that includes ViewController...which would then include theme.js
|
||||
function applyThemeCustomization(options) {
|
||||
//
|
||||
// options.name : menu/prompt name
|
||||
// options.configMci : menu or prompt config (menu.json / prompt.json) specific mci section
|
||||
// options.client : client
|
||||
// options.type : menu|prompt
|
||||
// options.formId : (optional) form ID in cases where multiple forms may exist wanting their own customization
|
||||
//
|
||||
// In the case of formId, the theme must include the ID as well, e.g.:
|
||||
// {
|
||||
// ...
|
||||
// "2" : {
|
||||
// "TL1" : { ... }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
assert(_.isString(options.name));
|
||||
assert("menus" === options.type || "prompts" === options.type);
|
||||
|
@ -268,10 +278,16 @@ function applyThemeCustomization(options) {
|
|||
|
||||
if(_.has(options.client.currentTheme, [ 'customization', options.type, options.name ])) {
|
||||
var themeConfig = options.client.currentTheme.customization[options.type][options.name];
|
||||
|
||||
if(options.formId && _.has(themeConfig, options.formId.toString())) {
|
||||
// form ID found - use exact match
|
||||
themeConfig = themeConfig[options.formId];
|
||||
}
|
||||
|
||||
Object.keys(themeConfig).forEach(function mciEntry(mci) {
|
||||
_.defaults(options.configMci[mci], themeConfig[mci]);
|
||||
});
|
||||
}
|
||||
|
||||
// :TODO: apply generic stuff, e.g. "VM" (vs "VM1")
|
||||
}
|
||||
}
|
||||
|
|
145
core/theme.js
145
core/theme.js
|
@ -6,7 +6,7 @@ var art = require('./art.js');
|
|||
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 configCache = require('./config_cache.js');
|
||||
var asset = require('./asset.js');
|
||||
var ViewController = require('./view_controller.js').ViewController;
|
||||
|
||||
|
@ -25,77 +25,73 @@ exports.displayThemeArt = displayThemeArt;
|
|||
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) {
|
||||
var path = paths.join(Config.paths.themes, themeID, 'theme.json');
|
||||
function refreshThemeHelpers(theme) {
|
||||
//
|
||||
// Create some handy helpers
|
||||
//
|
||||
theme.helpers = {
|
||||
getPasswordChar : function() {
|
||||
var pwChar = Config.defaults.passwordChar;
|
||||
if(_.has(theme, 'customization.defaults.general')) {
|
||||
var themePasswordChar = theme.customization.defaults.general.passwordChar;
|
||||
if(_.isString(themePasswordChar)) {
|
||||
pwChar = themePasswordChar.substr(0, 1);
|
||||
} else if(_.isNumber(themePasswordChar)) {
|
||||
pwChar = String.fromCharCode(themePasswordChar);
|
||||
}
|
||||
}
|
||||
return pwChar;
|
||||
},
|
||||
getDateFormat : function(style) {
|
||||
style = style || 'short';
|
||||
|
||||
fs.readFile(path, { encoding : 'utf8' }, function onData(err, data) {
|
||||
var format = Config.defaults.dateFormat[style] || 'MM/DD/YYYY';
|
||||
|
||||
if(_.has(theme, 'customization.defaults.dateFormat')) {
|
||||
return theme.customization.defaults.dateFormat[style] || format;
|
||||
}
|
||||
return format;
|
||||
},
|
||||
getTimeFormat : function(style) {
|
||||
style = style || 'short';
|
||||
|
||||
var format = Config.defaults.timeFormat[style] || 'h:mm a';
|
||||
|
||||
if(_.has(theme, 'customization.defaults.timeFormat')) {
|
||||
return theme.customization.defaults.timeFormat[style] || format;
|
||||
}
|
||||
return format;
|
||||
},
|
||||
getDateTimeFormat : function(style) {
|
||||
style = style || 'short';
|
||||
|
||||
var format = Config.defaults.dateTimeFormat[style] || 'MM/DD/YYYY h:mm a';
|
||||
|
||||
if(_.has(theme, 'customization.defaults.dateTimeFormat')) {
|
||||
return theme.customization.defaults.dateTimeFormat[style] || format;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadTheme(themeID, cb) {
|
||||
|
||||
var path = paths.join(Config.paths.themes, themeID, 'theme.hjson');
|
||||
|
||||
configCache.getConfig(path, function loaded(err, theme) {
|
||||
if(err) {
|
||||
cb(err);
|
||||
} else {
|
||||
try {
|
||||
var theme = JSON.parse(stripJsonComments(data));
|
||||
|
||||
if(!_.isObject(theme.info)) {
|
||||
cb(new Error('Invalid theme JSON'));
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!_.isObject(theme.helpers)); // we create this on the fly!
|
||||
|
||||
//
|
||||
// Create some handy helpers
|
||||
//
|
||||
theme.helpers = {
|
||||
getPasswordChar : function() {
|
||||
var pwChar = Config.defaults.passwordChar;
|
||||
if(_.has(theme, 'customization.defaults.general')) {
|
||||
var themePasswordChar = theme.customization.defaults.general.passwordChar;
|
||||
if(_.isString(themePasswordChar)) {
|
||||
pwChar = themePasswordChar.substr(0, 1);
|
||||
} else if(_.isNumber(themePasswordChar)) {
|
||||
pwChar = String.fromCharCode(themePasswordChar);
|
||||
}
|
||||
}
|
||||
return pwChar;
|
||||
},
|
||||
getDateFormat : function(style) {
|
||||
style = style || 'short';
|
||||
|
||||
var format = Config.defaults.dateFormat[style] || 'MM/DD/YYYY';
|
||||
|
||||
if(_.has(theme, 'customization.defaults.dateFormat')) {
|
||||
return theme.customization.defaults.dateFormat[style] || format;
|
||||
}
|
||||
return format;
|
||||
},
|
||||
getTimeFormat : function(style) {
|
||||
style = style || 'short';
|
||||
|
||||
var format = Config.defaults.timeFormat[style] || 'h:mm a';
|
||||
|
||||
if(_.has(theme, 'customization.defaults.timeFormat')) {
|
||||
return theme.customization.defaults.timeFormat[style] || format;
|
||||
}
|
||||
return format;
|
||||
},
|
||||
getDateTimeFormat : function(style) {
|
||||
style = style || 'short';
|
||||
|
||||
var format = Config.defaults.dateTimeFormat[style] || 'MM/DD/YYYY h:mm a';
|
||||
|
||||
if(_.has(theme, 'customization.defaults.dateTimeFormat')) {
|
||||
return theme.customization.defaults.dateTimeFormat[style] || format;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
};
|
||||
|
||||
cb(null, theme);
|
||||
} catch(e) {
|
||||
cb(err);
|
||||
if(!_.isObject(theme.info)) {
|
||||
cb(new Error('Invalid theme or missing \'info\' section'));
|
||||
return;
|
||||
}
|
||||
|
||||
refreshThemeHelpers(theme);
|
||||
|
||||
cb(null, theme, path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -118,9 +114,20 @@ function initAvailableThemes(cb) {
|
|||
},
|
||||
function populateAvailable(filtered, callback) {
|
||||
filtered.forEach(function onTheme(themeId) {
|
||||
loadTheme(themeId, function themeLoaded(err, theme) {
|
||||
loadTheme(themeId, function themeLoaded(err, theme, themePath) {
|
||||
if(!err) {
|
||||
availableThemes[themeId] = theme;
|
||||
|
||||
configCache.on('recached', function recached(path) {
|
||||
if(themePath === path) {
|
||||
loadTheme(themeId, function reloaded(err, reloadedTheme) {
|
||||
Log.debug( { info : theme.info }, 'Theme recached' );
|
||||
|
||||
availableThemes[themeId] = reloadedTheme;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Log.debug( { info : theme.info }, 'Theme loaded');
|
||||
}
|
||||
});
|
||||
|
@ -227,7 +234,7 @@ function displayThemedPause(options, cb) {
|
|||
async.series(
|
||||
[
|
||||
function loadPromptJSON(callback) {
|
||||
jsonCache.getJSON('prompt.hjson', function loaded(err, promptJson) {
|
||||
configCache.getModConfig('prompt.hjson', function loaded(err, promptJson) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
} else {
|
||||
|
|
|
@ -562,6 +562,7 @@ ViewController.prototype.loadFromMenuConfig = function(options, cb) {
|
|||
type : 'menus',
|
||||
client : self.client,
|
||||
configMci : formConfig.mci,
|
||||
formId : formIdKey,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -440,8 +440,8 @@
|
|||
},
|
||||
"TL3" : {
|
||||
//"width" : 39,
|
||||
"width" : 27,
|
||||
"textOverflow" : "..."
|
||||
//"width" : 27,
|
||||
//"textOverflow" : "..."
|
||||
},
|
||||
"TL5" : {
|
||||
"width" : 27
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
info: {
|
||||
name: Nu Mayan
|
||||
author: NuSkooler
|
||||
}
|
||||
|
||||
customization: {
|
||||
|
||||
defaults: {
|
||||
general: {
|
||||
passwordChar: φ
|
||||
}
|
||||
|
||||
dateFormat: {
|
||||
short: YYYY-MMM-DD
|
||||
}
|
||||
|
||||
timeFormat: {
|
||||
short: h:mm:ss a
|
||||
}
|
||||
|
||||
mci: {
|
||||
TM: {
|
||||
styleSGR1: |00|30|01
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menus: {
|
||||
matrix: {
|
||||
VM1: {
|
||||
itemSpacing: 1
|
||||
justify: center
|
||||
width: 12
|
||||
focusTextStyle: l33t
|
||||
}
|
||||
}
|
||||
|
||||
apply: {
|
||||
ET1: { width: 21 }
|
||||
|
||||
ET2: { width: 21 }
|
||||
|
||||
ME3: {
|
||||
styleSGR1: |00|30|01
|
||||
styleSGR2: |00|37
|
||||
fillChar: "#"
|
||||
}
|
||||
|
||||
ET4: { width: 1 }
|
||||
ET5: { width: 21 }
|
||||
ET6: { width: 21 }
|
||||
ET7: { width: 21 }
|
||||
ET8: { width: 21 }
|
||||
ET9: { width: 21 }
|
||||
ET10: { width: 21 }
|
||||
}
|
||||
|
||||
lastCallers: {
|
||||
TL1: {
|
||||
resizable: false
|
||||
width: 16
|
||||
textOverflow: ...
|
||||
}
|
||||
|
||||
TL2: {
|
||||
resizable: false
|
||||
width: 19
|
||||
textOverflow: ...
|
||||
}
|
||||
|
||||
TL3: {
|
||||
resizable: false
|
||||
width: 17
|
||||
textOverflow: ...
|
||||
}
|
||||
}
|
||||
|
||||
messageAreaViewPost: {
|
||||
0: {
|
||||
TL3: {
|
||||
width: 27
|
||||
textOverflow: ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prompts: {
|
||||
userCredentials: {
|
||||
ET1: { width: 21 }
|
||||
ET2: { width: 21 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"name" : "Nu Mayan",
|
||||
"author" : "NuSkooler"
|
||||
},
|
||||
"customization" : {
|
||||
"defaults" : {
|
||||
"general" : {
|
||||
"passwordChar" : "φ"
|
||||
},
|
||||
"dateFormat" : {
|
||||
"short" : "YYYY-MMM-DD"
|
||||
},
|
||||
"timeFormat" : {
|
||||
"short" : "h:mm:ss a"
|
||||
},
|
||||
"mci" : {
|
||||
"TM" : {
|
||||
"styleSGR1" : "|00|30|01"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menus" : {
|
||||
"matrix" : {
|
||||
"VM1" : {
|
||||
"itemSpacing" : 1,
|
||||
"justify" : "center",
|
||||
"width" : 12,
|
||||
"focusTextStyle" : "l33t"
|
||||
}
|
||||
},
|
||||
"apply" : {
|
||||
"ET1" : { "width" : 21 },
|
||||
"ET2" : { "width" : 21 },
|
||||
//"ET3" : { "width" : 21 },
|
||||
"ME3" : {
|
||||
"styleSGR1" : "|00|30|01",
|
||||
"styleSGR2" : "|00|37",
|
||||
"fillChar" : "#"
|
||||
},
|
||||
"ET4" : { "width" : 1 },
|
||||
"ET5" : { "width" : 21 },
|
||||
"ET6" : { "width" : 21 },
|
||||
"ET7" : { "width" : 21 },
|
||||
"ET8" : { "width" : 21 },
|
||||
"ET9" : { "width" : 21 },
|
||||
"ET10" : { "width" : 21 }
|
||||
},
|
||||
"lastCallers" : {
|
||||
"TL1" : {
|
||||
"resizable" : false,
|
||||
"width" : 16,
|
||||
"textOverflow" : "..."
|
||||
},
|
||||
"TL2" : {
|
||||
"resizable" : false,
|
||||
"width" : 19,
|
||||
"textOverflow" : "..."
|
||||
},
|
||||
"TL3" : {
|
||||
"resizable" : false,
|
||||
"width" : 17,
|
||||
"textOverflow" : "..."
|
||||
}
|
||||
},
|
||||
"messageAreaViewPost" : {
|
||||
"TL3" : {
|
||||
"width" : 25
|
||||
}
|
||||
}
|
||||
},
|
||||
"prompts" : {
|
||||
"userCredentials" : {
|
||||
"ET1" : { "width" : 21 },
|
||||
"ET2" : { "width" : 21 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue