Initial version of hot-reload of config, menus, and prompts
* Themes use ES6 Map vs object{} * Re-write and re-enable config cache using sane * Events sent for config, prompt, or menu changes * Event sent for theme changes * Theme (or parent menu/prompt) changes cause re-merge and updates to connected clients
This commit is contained in:
parent
1870db7d38
commit
4aab8224ed
|
@ -190,10 +190,13 @@ function initialize(cb) {
|
||||||
function initStatLog(callback) {
|
function initStatLog(callback) {
|
||||||
return require('./stat_log.js').init(callback);
|
return require('./stat_log.js').init(callback);
|
||||||
},
|
},
|
||||||
|
function initConfigs(callback) {
|
||||||
|
return require('./config_util.js').init(callback);
|
||||||
|
},
|
||||||
function initThemes(callback) {
|
function initThemes(callback) {
|
||||||
// Have to pull in here so it's after Config init
|
// Have to pull in here so it's after Config init
|
||||||
require('./theme.js').initAvailableThemes(function onThemesInit(err, themeCount) {
|
require('./theme.js').initAvailableThemes( (err, themeCount) => {
|
||||||
logger.log.info({ themeCount : themeCount }, 'Themes initialized');
|
logger.log.info({ themeCount }, 'Themes initialized');
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,6 +38,7 @@ const User = require('./user.js');
|
||||||
const Config = require('./config.js').config;
|
const Config = require('./config.js').config;
|
||||||
const MenuStack = require('./menu_stack.js');
|
const MenuStack = require('./menu_stack.js');
|
||||||
const ACS = require('./acs.js');
|
const ACS = require('./acs.js');
|
||||||
|
const Events = require('./events.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
|
@ -110,6 +111,12 @@ function Client(/*input, output*/) {
|
||||||
this.input.on('data', this.dataHandler);
|
this.input.on('data', this.dataHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Events.on(Events.getSystemEvents().ThemeChanged, ( { themeId } ) => {
|
||||||
|
if(_.get(this.currentTheme, 'info.themeId') === themeId) {
|
||||||
|
this.currentTheme = require('./theme.js').getAvailableThemes().get(themeId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Peek at incoming |data| and emit events for any special
|
// Peek at incoming |data| and emit events for any special
|
||||||
|
|
|
@ -2,13 +2,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
|
const Errors = require('./enig_error.js').Errors;
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const fs = require('graceful-fs');
|
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const hjson = require('hjson');
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
exports.init = init;
|
exports.init = init;
|
||||||
|
@ -40,39 +39,13 @@ function hasMessageConferenceAndArea(config) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(configPath, options, cb) {
|
function mergeValidateAndFinalize(config, cb) {
|
||||||
if(!cb && _.isFunction(options)) {
|
|
||||||
cb = options;
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function loadUserConfig(callback) {
|
function mergeWithDefaultConfig(callback) {
|
||||||
if(!_.isString(configPath)) {
|
|
||||||
return callback(null, { } );
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.readFile(configPath, { encoding : 'utf8' }, (err, configData) => {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let configJson;
|
|
||||||
try {
|
|
||||||
configJson = hjson.parse(configData, options);
|
|
||||||
} catch(e) {
|
|
||||||
return callback(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, configJson);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function mergeWithDefaultConfig(configJson, callback) {
|
|
||||||
|
|
||||||
const mergedConfig = _.mergeWith(
|
const mergedConfig = _.mergeWith(
|
||||||
getDefaultConfig(),
|
getDefaultConfig(),
|
||||||
configJson, (conf1, conf2) => {
|
config, (conf1, conf2) => {
|
||||||
// Arrays should always concat
|
// Arrays should always concat
|
||||||
if(_.isArray(conf1)) {
|
if(_.isArray(conf1)) {
|
||||||
// :TODO: look for collisions & override dupes
|
// :TODO: look for collisions & override dupes
|
||||||
|
@ -89,26 +62,53 @@ function init(configPath, options, cb) {
|
||||||
//
|
//
|
||||||
// :TODO: Logic is broken here:
|
// :TODO: Logic is broken here:
|
||||||
if(hasMessageConferenceAndArea(mergedConfig)) {
|
if(hasMessageConferenceAndArea(mergedConfig)) {
|
||||||
var msgAreasErr = new Error('Please create at least one message conference and area!');
|
return callback(Errors.MissingConfig('Please create at least one message conference and area!'));
|
||||||
msgAreasErr.code = 'EBADCONFIG';
|
|
||||||
return callback(msgAreasErr);
|
|
||||||
} else {
|
|
||||||
return callback(null, mergedConfig);
|
|
||||||
}
|
}
|
||||||
|
return callback(null, mergedConfig);
|
||||||
|
},
|
||||||
|
function setIt(mergedConfig, callback) {
|
||||||
|
exports.config = mergedConfig;
|
||||||
|
|
||||||
|
exports.config.get = (path) => {
|
||||||
|
return _.get(exports.config, path);
|
||||||
|
};
|
||||||
|
|
||||||
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err, mergedConfig) {
|
err => {
|
||||||
exports.config = mergedConfig;
|
if(cb) {
|
||||||
|
return cb(err);
|
||||||
exports.config.get = function(path) {
|
}
|
||||||
return _.get(exports.config, path);
|
|
||||||
};
|
|
||||||
|
|
||||||
return cb(err);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function init(configPath, options, cb) {
|
||||||
|
if(!cb && _.isFunction(options)) {
|
||||||
|
cb = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const changed = ( { fileName, fileRoot } ) => {
|
||||||
|
const reCachedPath = paths.join(fileRoot, fileName);
|
||||||
|
ConfigCache.getConfig(reCachedPath, (err, config) => {
|
||||||
|
if(!err) {
|
||||||
|
mergeValidateAndFinalize(config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConfigCache = require('./config_cache.js');
|
||||||
|
ConfigCache.getConfigWithOptions( { filePath : configPath, callback : changed }, (err, config) => {
|
||||||
|
if(err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeValidateAndFinalize(config, cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getDefaultPath() {
|
function getDefaultPath() {
|
||||||
// e.g. /enigma-bbs-install-path/config/
|
// e.g. /enigma-bbs-install-path/config/
|
||||||
return './config/';
|
return './config/';
|
||||||
|
@ -804,7 +804,7 @@ function getDefaultConfig() {
|
||||||
},
|
},
|
||||||
|
|
||||||
misc : {
|
misc : {
|
||||||
preAuthIdleLogoutSeconds : 60 * 3, // 2m
|
preAuthIdleLogoutSeconds : 60 * 3, // 3m
|
||||||
idleLogoutSeconds : 60 * 6, // 6m
|
idleLogoutSeconds : 60 * 6, // 6m
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,85 +1,70 @@
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var Config = require('./config.js').config;
|
// deps
|
||||||
var Log = require('./logger.js').log;
|
const paths = require('path');
|
||||||
|
const fs = require('graceful-fs');
|
||||||
|
const hjson = require('hjson');
|
||||||
|
const sane = require('sane');
|
||||||
|
|
||||||
var paths = require('path');
|
module.exports = new class ConfigCache
|
||||||
var fs = require('graceful-fs');
|
{
|
||||||
var events = require('events');
|
constructor() {
|
||||||
var util = require('util');
|
this.cache = new Map(); // path->parsed config
|
||||||
var assert = require('assert');
|
}
|
||||||
var hjson = require('hjson');
|
|
||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
function ConfigCache() {
|
getConfigWithOptions(options, cb) {
|
||||||
events.EventEmitter.call(this);
|
const cached = this.cache.has(options.filePath);
|
||||||
|
|
||||||
var self = this;
|
if(options.forceReCache || !cached) {
|
||||||
this.cache = {}; // filePath -> HJSON
|
this.recacheConfigFromFile(options.filePath, (err, config) => {
|
||||||
//this.gaze = new Gaze();
|
if(!err && !cached) {
|
||||||
|
const watcher = sane(
|
||||||
|
paths.dirname(options.filePath),
|
||||||
|
{
|
||||||
|
glob : `**/${paths.basename(options.filePath)}`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.reCacheConfigFromFile = function(filePath, cb) {
|
watcher.on('change', (fileName, fileRoot) => {
|
||||||
fs.readFile(filePath, { encoding : 'utf-8' }, function fileRead(err, data) {
|
require('./logger.js').log.info( { fileName, fileRoot }, 'Configuration file changed; re-caching');
|
||||||
try {
|
|
||||||
self.cache[filePath] = hjson.parse(data);
|
|
||||||
cb(null, self.cache[filePath]);
|
|
||||||
} catch(e) {
|
|
||||||
Log.error( { filePath : filePath, error : e.toString() }, 'Failed recaching');
|
|
||||||
cb(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
this.recacheConfigFromFile(paths.join(fileRoot, fileName), err => {
|
||||||
this.gaze.on('error', function gazeErr(err) {
|
if(!err) {
|
||||||
|
if(options.callback) {
|
||||||
|
options.callback( { fileName, fileRoot } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cb(err, config, true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return cb(null, this.cache.get(options.filePath), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
getConfig(filePath, cb) {
|
||||||
|
return this.getConfigWithOptions( { filePath }, cb);
|
||||||
|
}
|
||||||
|
|
||||||
this.gaze.on('changed', function fileChanged(filePath) {
|
recacheConfigFromFile(path, cb) {
|
||||||
assert(filePath in self.cache);
|
fs.readFile(path, { encoding : 'utf-8' }, (err, data) => {
|
||||||
|
|
||||||
Log.info( { path : filePath }, 'Configuration file changed; re-caching');
|
|
||||||
|
|
||||||
self.reCacheConfigFromFile(filePath, function reCached(err) {
|
|
||||||
if(err) {
|
if(err) {
|
||||||
Log.error( { error : err.message, path : filePath } , 'Failed re-caching configuration');
|
return cb(err);
|
||||||
} else {
|
|
||||||
self.emit('recached', filePath);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
let parsed;
|
||||||
|
try {
|
||||||
util.inherits(ConfigCache, events.EventEmitter);
|
parsed = hjson.parse(data);
|
||||||
|
this.cache.set(path, parsed);
|
||||||
ConfigCache.prototype.getConfigWithOptions = function(options, cb) {
|
} catch(e) {
|
||||||
assert(_.isString(options.filePath));
|
require('./logger.js').log.error( { filePath : path, error : e.message }, 'Failed to re-cache' );
|
||||||
|
return cb(e);
|
||||||
// var self = this;
|
|
||||||
var isCached = (options.filePath in this.cache);
|
|
||||||
|
|
||||||
if(options.forceReCache || !isCached) {
|
|
||||||
this.reCacheConfigFromFile(options.filePath, function fileCached(err, config) {
|
|
||||||
if(!err && !isCached) {
|
|
||||||
//self.gaze.add(options.filePath);
|
|
||||||
}
|
}
|
||||||
cb(err, config, true);
|
|
||||||
|
return cb(null, parsed);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
cb(null, this.cache[options.filePath], false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
ConfigCache.prototype.getConfig = function(filePath, cb) {
|
|
||||||
this.getConfigWithOptions( { filePath : filePath }, cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigCache.prototype.getModConfig = function(fileName, cb) {
|
|
||||||
this.getConfig(paths.join(Config.paths.mods, fileName), cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = exports = new ConfigCache();
|
|
||||||
|
|
|
@ -1,18 +1,65 @@
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
const Config = require('./config.js').config;
|
|
||||||
const configCache = require('./config_cache.js');
|
|
||||||
const paths = require('path');
|
|
||||||
|
|
||||||
|
const Config = require('./config.js').config;
|
||||||
|
const ConfigCache = require('./config_cache.js');
|
||||||
|
const Events = require('./events.js');
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const paths = require('path');
|
||||||
|
const async = require('async');
|
||||||
|
|
||||||
|
exports.init = init;
|
||||||
exports.getFullConfig = getFullConfig;
|
exports.getFullConfig = getFullConfig;
|
||||||
|
|
||||||
function getFullConfig(filePath, cb) {
|
function getConfigPath(filePath) {
|
||||||
// |filePath| is assumed to be in the config path if it's only a file name
|
// |filePath| is assumed to be in the config path if it's only a file name
|
||||||
if('.' === paths.dirname(filePath)) {
|
if('.' === paths.dirname(filePath)) {
|
||||||
filePath = paths.join(Config.paths.config, filePath);
|
filePath = paths.join(Config.paths.config, filePath);
|
||||||
}
|
}
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
configCache.getConfig(filePath, function loaded(err, configJson) {
|
function init(cb) {
|
||||||
cb(err, configJson);
|
// pre-cache menu.hjson and prompt.hjson + establish events
|
||||||
|
const changed = ( { fileName, fileRoot } ) => {
|
||||||
|
const reCachedPath = paths.join(fileRoot, fileName);
|
||||||
|
if(reCachedPath === getConfigPath(Config.general.menuFile)) {
|
||||||
|
Events.emit(Events.getSystemEvents().MenusChanged);
|
||||||
|
} else if(reCachedPath === getConfigPath(Config.general.promptFile)) {
|
||||||
|
Events.emit(Events.getSystemEvents().PromptsChanged);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
function menu(callback) {
|
||||||
|
return ConfigCache.getConfigWithOptions(
|
||||||
|
{
|
||||||
|
filePath : getConfigPath(Config.general.menuFile),
|
||||||
|
callback : changed,
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function prompt(callback) {
|
||||||
|
return ConfigCache.getConfigWithOptions(
|
||||||
|
{
|
||||||
|
filePath : getConfigPath(Config.general.promptFile),
|
||||||
|
callback : changed,
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFullConfig(filePath, cb) {
|
||||||
|
ConfigCache.getConfig(getConfigPath(filePath), (err, config) => {
|
||||||
|
return cb(err, config);
|
||||||
});
|
});
|
||||||
}
|
}
|
13
core/fse.js
13
core/fse.js
|
@ -532,12 +532,13 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
art[n],
|
art[n],
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font },
|
{ font : self.menuConfig.font, acsCondMember : 'art' },
|
||||||
function displayed(err) {
|
function displayed(err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}, function complete(err) {
|
}, function complete(err) {
|
||||||
|
//self.body.height = self.client.term.termHeight - self.header.height - 1;
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -607,14 +608,11 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
self.beforeArt(callback);
|
self.beforeArt(callback);
|
||||||
},
|
},
|
||||||
function displayHeaderAndBodyArt(callback) {
|
function displayHeaderAndBodyArt(callback) {
|
||||||
assert(_.isString(art.header));
|
|
||||||
assert(_.isString(art.body));
|
|
||||||
|
|
||||||
async.eachSeries( [ 'header', 'body' ], function dispArt(n, next) {
|
async.eachSeries( [ 'header', 'body' ], function dispArt(n, next) {
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
art[n],
|
art[n],
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font },
|
{ font : self.menuConfig.font, acsCondMember : 'art' },
|
||||||
function displayed(err, artData) {
|
function displayed(err, artData) {
|
||||||
if(artData) {
|
if(artData) {
|
||||||
mciData[n] = artData;
|
mciData[n] = artData;
|
||||||
|
@ -879,13 +877,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function clearAndDisplayArt(callback) {
|
function clearAndDisplayArt(callback) {
|
||||||
|
|
||||||
// :TODO: use termHeight, not hard coded 24 here:
|
|
||||||
|
|
||||||
// :TODO: NetRunner does NOT support delete line, so this does not work:
|
// :TODO: NetRunner does NOT support delete line, so this does not work:
|
||||||
self.client.term.rawWrite(
|
self.client.term.rawWrite(
|
||||||
ansi.goto(self.header.height + 1, 1) +
|
ansi.goto(self.header.height + 1, 1) +
|
||||||
ansi.deleteLine(24 - self.header.height));
|
ansi.deleteLine((self.client.term.termHeight - self.header.height) - 1));
|
||||||
|
|
||||||
theme.displayThemeArt( { name : self.menuConfig.config.art.quote, client : self.client }, function displayed(err, artData) {
|
theme.displayThemeArt( { name : self.menuConfig.config.art.quote, client : self.client }, function displayed(err, artData) {
|
||||||
callback(err, artData);
|
callback(err, artData);
|
||||||
|
|
205
core/theme.js
205
core/theme.js
|
@ -5,12 +5,13 @@ const Config = require('./config.js').config;
|
||||||
const art = require('./art.js');
|
const art = require('./art.js');
|
||||||
const ansi = require('./ansi_term.js');
|
const ansi = require('./ansi_term.js');
|
||||||
const Log = require('./logger.js').log;
|
const Log = require('./logger.js').log;
|
||||||
const configCache = require('./config_cache.js');
|
const ConfigCache = require('./config_cache.js');
|
||||||
const getFullConfig = require('./config_util.js').getFullConfig;
|
const getFullConfig = require('./config_util.js').getFullConfig;
|
||||||
const asset = require('./asset.js');
|
const asset = require('./asset.js');
|
||||||
const ViewController = require('./view_controller.js').ViewController;
|
const ViewController = require('./view_controller.js').ViewController;
|
||||||
const Errors = require('./enig_error.js').Errors;
|
const Errors = require('./enig_error.js').Errors;
|
||||||
const ErrorReasons = require('./enig_error.js').ErrorReasons;
|
const ErrorReasons = require('./enig_error.js').ErrorReasons;
|
||||||
|
const Events = require('./events.js');
|
||||||
|
|
||||||
const fs = require('graceful-fs');
|
const fs = require('graceful-fs');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
|
@ -63,11 +64,23 @@ function refreshThemeHelpers(theme) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadTheme(themeID, cb) {
|
function loadTheme(themeId, cb) {
|
||||||
|
const path = paths.join(Config.paths.themes, themeId, 'theme.hjson');
|
||||||
|
|
||||||
const path = paths.join(Config.paths.themes, themeID, 'theme.hjson');
|
const changed = ( { fileName, fileRoot } ) => {
|
||||||
|
const reCachedPath = paths.join(fileRoot, fileName);
|
||||||
|
if(reCachedPath === path) {
|
||||||
|
reloadTheme(themeId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
configCache.getConfigWithOptions( { filePath : path, forceReCache : true }, (err, theme) => {
|
const getOpts = {
|
||||||
|
filePath : path,
|
||||||
|
forceReCache : true,
|
||||||
|
callback : changed,
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigCache.getConfigWithOptions(getOpts, (err, theme) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
@ -89,7 +102,7 @@ function loadTheme(themeID, cb) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableThemes = {};
|
const availableThemes = new Map();
|
||||||
|
|
||||||
const IMMUTABLE_MCI_PROPERTIES = [
|
const IMMUTABLE_MCI_PROPERTIES = [
|
||||||
'maxLength', 'argName', 'submit', 'validate'
|
'maxLength', 'argName', 'submit', 'validate'
|
||||||
|
@ -248,6 +261,56 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||||
return mergedTheme;
|
return mergedTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reloadTheme(themeId) {
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
function loadMenuConfig(callback) {
|
||||||
|
getFullConfig(Config.general.menuFile, (err, menuConfig) => {
|
||||||
|
return callback(err, menuConfig);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function loadPromptConfig(menuConfig, callback) {
|
||||||
|
getFullConfig(Config.general.promptFile, (err, promptConfig) => {
|
||||||
|
return callback(err, menuConfig, promptConfig);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function loadIt(menuConfig, promptConfig, callback) {
|
||||||
|
loadTheme(themeId, (err, theme) => {
|
||||||
|
if(err) {
|
||||||
|
if(ErrorReasons.NotEnabled !== err.reasonCode) {
|
||||||
|
Log.warn( { themeId : themeId, err : err.message }, 'Failed loading theme');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(theme.info, { themeId } );
|
||||||
|
availableThemes.set(themeId, getMergedTheme(menuConfig, promptConfig, theme));
|
||||||
|
|
||||||
|
Events.emit(
|
||||||
|
Events.getSystemEvents().ThemeChanged,
|
||||||
|
{ themeId }
|
||||||
|
);
|
||||||
|
|
||||||
|
return callback(null, theme);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
],
|
||||||
|
(err, theme) => {
|
||||||
|
if(err) {
|
||||||
|
Log.warn( { themeId, error : err.message }, 'Failed to reload theme');
|
||||||
|
} else {
|
||||||
|
Log.debug( { info : theme.info }, 'Theme recached' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadAllThemes()
|
||||||
|
{
|
||||||
|
async.each([ ...availableThemes.keys() ], themeId => reloadTheme(themeId));
|
||||||
|
}
|
||||||
|
|
||||||
function initAvailableThemes(cb) {
|
function initAvailableThemes(cb) {
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
|
@ -281,7 +344,7 @@ function initAvailableThemes(cb) {
|
||||||
},
|
},
|
||||||
function populateAvailable(menuConfig, promptConfig, themeDirectories, callback) {
|
function populateAvailable(menuConfig, promptConfig, themeDirectories, callback) {
|
||||||
async.each(themeDirectories, (themeId, nextThemeDir) => { // theme dir = theme ID
|
async.each(themeDirectories, (themeId, nextThemeDir) => { // theme dir = theme ID
|
||||||
loadTheme(themeId, (err, theme, themePath) => {
|
loadTheme(themeId, (err, theme) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
if(ErrorReasons.NotEnabled !== err.reasonCode) {
|
if(ErrorReasons.NotEnabled !== err.reasonCode) {
|
||||||
Log.warn( { themeId : themeId, err : err.message }, 'Failed loading theme');
|
Log.warn( { themeId : themeId, err : err.message }, 'Failed loading theme');
|
||||||
|
@ -290,31 +353,27 @@ function initAvailableThemes(cb) {
|
||||||
return nextThemeDir(null); // try next
|
return nextThemeDir(null); // try next
|
||||||
}
|
}
|
||||||
|
|
||||||
availableThemes[themeId] = getMergedTheme(menuConfig, promptConfig, theme);
|
Object.assign(theme.info, { themeId } );
|
||||||
|
availableThemes.set(themeId, getMergedTheme(menuConfig, promptConfig, theme));
|
||||||
configCache.on('recached', recachedPath => {
|
|
||||||
if(themePath === recachedPath) {
|
|
||||||
loadTheme(themeId, (err, reloadedTheme) => {
|
|
||||||
if(!err) {
|
|
||||||
// :TODO: This is still broken - Need to reapply *latest* menu config and prompt configs to theme at very least
|
|
||||||
Log.debug( { info : theme.info }, 'Theme recached' );
|
|
||||||
availableThemes[themeId] = getMergedTheme(menuConfig, promptConfig, reloadedTheme);
|
|
||||||
} else if(ErrorReasons.NotEnabled === err.reasonCode) {
|
|
||||||
// :TODO: we need to disable this theme -- users may be using it! We'll need to re-assign them if so
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return nextThemeDir(null);
|
return nextThemeDir(null);
|
||||||
});
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
function initEvents(callback) {
|
||||||
|
Events.on(Events.getSystemEvents().MenusChanged, () => {
|
||||||
|
return reloadAllThemes();
|
||||||
|
});
|
||||||
|
Events.on(Events.getSystemEvents().PromptsChanged, () => {
|
||||||
|
return reloadAllThemes();
|
||||||
|
});
|
||||||
|
|
||||||
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err, availableThemes ? availableThemes.length : 0);
|
return cb(err, availableThemes.size);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -324,31 +383,30 @@ function getAvailableThemes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomTheme() {
|
function getRandomTheme() {
|
||||||
if(Object.getOwnPropertyNames(availableThemes).length > 0) {
|
if(availableThemes.size > 0) {
|
||||||
var themeIds = Object.keys(availableThemes);
|
const themeIds = [ ...availableThemes.keys() ];
|
||||||
return themeIds[Math.floor(Math.random() * themeIds.length)];
|
return themeIds[Math.floor(Math.random() * themeIds.length)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setClientTheme(client, themeId) {
|
function setClientTheme(client, themeId) {
|
||||||
let logMsg;
|
|
||||||
|
|
||||||
const availThemes = getAvailableThemes();
|
const availThemes = getAvailableThemes();
|
||||||
|
|
||||||
client.currentTheme = availThemes[themeId];
|
let msg;
|
||||||
if(client.currentTheme) {
|
let setThemeId;
|
||||||
logMsg = 'Set client theme';
|
if(availThemes.has(themeId)) {
|
||||||
|
msg = 'Set client theme';
|
||||||
|
setThemeId = themeId;
|
||||||
|
} else if(availThemes.has(Config.defaults.theme)) {
|
||||||
|
msg = 'Failed setting theme by supplied ID; Using default';
|
||||||
|
setThemeId = Config.defaults.theme;
|
||||||
} else {
|
} else {
|
||||||
client.currentTheme = availThemes[Config.defaults.theme];
|
msg = 'Failed setting theme by system default ID; Using the first one we can find';
|
||||||
if(client.currentTheme) {
|
setThemeId = availThemes.keys().next().value;
|
||||||
logMsg = 'Failed setting theme by supplied ID; Using default';
|
|
||||||
} else {
|
|
||||||
client.currentTheme = availThemes[Object.keys(availThemes)[0]];
|
|
||||||
logMsg = 'Failed setting theme by system default ID; Using the first one we can find';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client.log.debug( { themeId : themeId, info : client.currentTheme.info }, logMsg);
|
client.currentTheme = availThemes.get(setThemeId);
|
||||||
|
client.log.debug( { setThemeId, requestedThemeId : themeId, info : client.currentTheme.info }, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThemeArt(options, cb) {
|
function getThemeArt(options, cb) {
|
||||||
|
@ -465,73 +523,6 @@ function displayThemeArt(options, cb) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
function displayThemedPrompt(name, client, options, cb) {
|
|
||||||
|
|
||||||
async.waterfall(
|
|
||||||
[
|
|
||||||
function loadConfig(callback) {
|
|
||||||
configCache.getModConfig('prompt.hjson', (err, promptJson) => {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_.has(promptJson, [ 'prompts', name ] )) {
|
|
||||||
return callback(Errors.DoesNotExist(`Prompt "${name}" does not exist`));
|
|
||||||
}
|
|
||||||
|
|
||||||
const promptConfig = promptJson.prompts[name];
|
|
||||||
if(!_.isObject(promptConfig)) {
|
|
||||||
return callback(Errors.Invalid(`Prompt "${name} is invalid`));
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, promptConfig);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function display(promptConfig, callback) {
|
|
||||||
if(options.clearScreen) {
|
|
||||||
client.term.rawWrite(ansi.clearScreen());
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// If we did not clear the screen, don't let the font change
|
|
||||||
//
|
|
||||||
const dispOptions = Object.assign( {}, promptConfig.options );
|
|
||||||
if(!options.clearScreen) {
|
|
||||||
dispOptions.font = 'not_really_a_font!';
|
|
||||||
}
|
|
||||||
|
|
||||||
displayThemedAsset(
|
|
||||||
promptConfig.art,
|
|
||||||
client,
|
|
||||||
dispOptions,
|
|
||||||
(err, artData) => {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, promptConfig, artData.mciMap);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function prepViews(promptConfig, mciMap, callback) {
|
|
||||||
vc = new ViewController( { client : client } );
|
|
||||||
|
|
||||||
const loadOpts = {
|
|
||||||
promptName : name,
|
|
||||||
mciMap : mciMap,
|
|
||||||
config : promptConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
vc.loadFromPromptConfig(loadOpts, err => {
|
|
||||||
callback(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function displayThemedPrompt(name, client, options, cb) {
|
function displayThemedPrompt(name, client, options, cb) {
|
||||||
|
|
||||||
const useTempViewController = _.isUndefined(options.viewController);
|
const useTempViewController = _.isUndefined(options.viewController);
|
||||||
|
@ -663,6 +654,10 @@ function displayThemedAsset(assetSpec, client, options, cb) {
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Array.isArray(assetSpec) && _.isString(options.acsCondMember)) {
|
||||||
|
assetSpec = client.acs.getConditionalValue(assetSpec, options.acsCondMember);
|
||||||
|
}
|
||||||
|
|
||||||
const artAsset = asset.getArtAsset(assetSpec);
|
const artAsset = asset.getArtAsset(assetSpec);
|
||||||
if(!artAsset) {
|
if(!artAsset) {
|
||||||
return cb(new Error('Asset not found: ' + assetSpec));
|
return cb(new Error('Asset not found: ' + assetSpec));
|
||||||
|
|
|
@ -164,13 +164,14 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
||||||
vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
||||||
},
|
},
|
||||||
function prepareAvailableThemes(callback) {
|
function prepareAvailableThemes(callback) {
|
||||||
self.availThemeInfo = _.sortBy(_.map(theme.getAvailableThemes(), function makeThemeInfo(t, themeId) {
|
self.availThemeInfo = _.sortBy([...theme.getAvailableThemes()].map(entry => {
|
||||||
|
const theme = entry[1];
|
||||||
return {
|
return {
|
||||||
themeId : themeId,
|
themeId : theme.info.themeId,
|
||||||
name : t.info.name,
|
name : theme.info.name,
|
||||||
author : t.info.author,
|
author : theme.info.author,
|
||||||
desc : _.isString(t.info.desc) ? t.info.desc : '',
|
desc : _.isString(theme.info.desc) ? theme.info.desc : '',
|
||||||
group : _.isString(t.info.group) ? t.info.group : '',
|
group : _.isString(theme.info.group) ? theme.info.group : '',
|
||||||
};
|
};
|
||||||
}), 'name');
|
}), 'name');
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue