Initial sync up with master after Prettier
This commit is contained in:
394
core/theme.js
394
core/theme.js
@@ -1,36 +1,36 @@
|
||||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
const Config = require('./config.js').get;
|
||||
const art = require('./art.js');
|
||||
const ansi = require('./ansi_term.js');
|
||||
const Log = require('./logger.js').log;
|
||||
const asset = require('./asset.js');
|
||||
const ViewController = require('./view_controller.js').ViewController;
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
const Events = require('./events.js');
|
||||
const AnsiPrep = require('./ansi_prep.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
const Config = require('./config.js').get;
|
||||
const art = require('./art.js');
|
||||
const ansi = require('./ansi_term.js');
|
||||
const Log = require('./logger.js').log;
|
||||
const asset = require('./asset.js');
|
||||
const ViewController = require('./view_controller.js').ViewController;
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
const Events = require('./events.js');
|
||||
const AnsiPrep = require('./ansi_prep.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
const ConfigLoader = require('./config_loader');
|
||||
const ConfigLoader = require('./config_loader');
|
||||
const { getConfigPath } = require('./config_util');
|
||||
|
||||
// deps
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
|
||||
exports.getThemeArt = getThemeArt;
|
||||
exports.getAvailableThemes = getAvailableThemes;
|
||||
exports.getRandomTheme = getRandomTheme;
|
||||
exports.setClientTheme = setClientTheme;
|
||||
exports.displayPreparedArt = displayPreparedArt;
|
||||
exports.displayThemeArt = displayThemeArt;
|
||||
exports.displayThemedPause = displayThemedPause;
|
||||
exports.displayThemedPrompt = displayThemedPrompt;
|
||||
exports.displayThemedAsset = displayThemedAsset;
|
||||
exports.getThemeArt = getThemeArt;
|
||||
exports.getAvailableThemes = getAvailableThemes;
|
||||
exports.getRandomTheme = getRandomTheme;
|
||||
exports.setClientTheme = setClientTheme;
|
||||
exports.displayPreparedArt = displayPreparedArt;
|
||||
exports.displayThemeArt = displayThemeArt;
|
||||
exports.displayThemedPause = displayThemedPause;
|
||||
exports.displayThemedPrompt = displayThemedPrompt;
|
||||
exports.displayThemedAsset = displayThemedAsset;
|
||||
|
||||
// global instance of ThemeManager; see ThemeManager.create()
|
||||
let themeManagerInstance;
|
||||
@@ -44,13 +44,15 @@ exports.ThemeManager = class ThemeManager {
|
||||
themeManagerInstance = new ThemeManager();
|
||||
themeManagerInstance.init(err => {
|
||||
if (!err) {
|
||||
themeManagerInstance.getAvailableThemes().forEach( (themeConfig, themeId) => {
|
||||
const { name, author, group } = themeConfig.get().info;
|
||||
Log.info(
|
||||
{ themeId, themeName : name, author, group },
|
||||
'Theme loaded'
|
||||
);
|
||||
});
|
||||
themeManagerInstance
|
||||
.getAvailableThemes()
|
||||
.forEach((themeConfig, themeId) => {
|
||||
const { name, author, group } = themeConfig.get().info;
|
||||
Log.info(
|
||||
{ themeId, themeName: name, author, group },
|
||||
'Theme loaded'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
@@ -63,7 +65,7 @@ exports.ThemeManager = class ThemeManager {
|
||||
|
||||
init(cb) {
|
||||
this.menuConfig = new ConfigLoader({
|
||||
onReload : err => {
|
||||
onReload: err => {
|
||||
if (!err) {
|
||||
// menu.hjson/includes have changed; this could affect
|
||||
// all themes, so they must be reloaded
|
||||
@@ -91,46 +93,51 @@ exports.ThemeManager = class ThemeManager {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
async.filter(files, (filename, nextFilename) => {
|
||||
const fullPath = paths.join(themeDir, filename);
|
||||
fs.stat(fullPath, (err, stats) => {
|
||||
async.filter(
|
||||
files,
|
||||
(filename, nextFilename) => {
|
||||
const fullPath = paths.join(themeDir, filename);
|
||||
fs.stat(fullPath, (err, stats) => {
|
||||
if (err) {
|
||||
return nextFilename(err);
|
||||
}
|
||||
|
||||
return nextFilename(null, stats.isDirectory());
|
||||
});
|
||||
},
|
||||
(err, themeIds) => {
|
||||
if (err) {
|
||||
return nextFilename(err);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
return nextFilename(null, stats.isDirectory());
|
||||
});
|
||||
},
|
||||
(err, themeIds) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
async.each(
|
||||
themeIds,
|
||||
(themeId, nextThemeId) => {
|
||||
return this._loadTheme(themeId, nextThemeId);
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async.each(themeIds, (themeId, nextThemeId) => {
|
||||
return this._loadTheme(themeId, nextThemeId);
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_loadTheme(themeId, cb) {
|
||||
const themeConfig = new ConfigLoader({
|
||||
onReload : err => {
|
||||
onReload: err => {
|
||||
if (!err) {
|
||||
// this particular theme has changed
|
||||
this._themeLoaded(themeId, themeConfig, err => {
|
||||
if (!err) {
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ThemeChanged,
|
||||
{ themeId }
|
||||
);
|
||||
Events.emit(Events.getSystemEvents().ThemeChanged, {
|
||||
themeId,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const themeConfigPath = paths.join(Config().paths.themes, themeId, 'theme.hjson');
|
||||
@@ -150,14 +157,18 @@ exports.ThemeManager = class ThemeManager {
|
||||
|
||||
// do some basic validation
|
||||
// :TODO: schema validation here
|
||||
if(!_.isObject(theme.info) ||
|
||||
if (
|
||||
!_.isObject(theme.info) ||
|
||||
!_.isString(theme.info.name) ||
|
||||
!_.isString(theme.info.author))
|
||||
{
|
||||
return Log.warn({ themeId }, 'Theme contains invalid or missing "info" section');
|
||||
!_.isString(theme.info.author)
|
||||
) {
|
||||
return Log.warn(
|
||||
{ themeId },
|
||||
'Theme contains invalid or missing "info" section'
|
||||
);
|
||||
}
|
||||
|
||||
if(false === _.get(theme, 'info.enabled')) {
|
||||
if (false === _.get(theme, 'info.enabled')) {
|
||||
Log.info({ themeId }, 'Theme is disabled');
|
||||
return this.availableThemes.delete(themeId);
|
||||
}
|
||||
@@ -168,10 +179,7 @@ exports.ThemeManager = class ThemeManager {
|
||||
// Theme is valid and enabled; update it in available themes
|
||||
this.availableThemes.set(themeId, themeConfig);
|
||||
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ThemeChanged,
|
||||
{ themeId }
|
||||
);
|
||||
Events.emit(Events.getSystemEvents().ThemeChanged, { themeId });
|
||||
}
|
||||
|
||||
_finalizeTheme(themeId, themeConfig) {
|
||||
@@ -185,22 +193,20 @@ exports.ThemeManager = class ThemeManager {
|
||||
const theme = themeConfig.get();
|
||||
|
||||
// some data brought directly over
|
||||
mergedTheme.info = Object.assign({}, theme.info, { themeId });
|
||||
mergedTheme.achievements = _.get(theme, 'customization.achievements');
|
||||
mergedTheme.info = Object.assign({}, theme.info, { themeId });
|
||||
mergedTheme.achievements = _.get(theme, 'customization.achievements');
|
||||
|
||||
// Create some helpers for this theme
|
||||
this._setThemeHelpers(mergedTheme);
|
||||
|
||||
// merge customizer to disallow immutable MCI properties
|
||||
const ImmutableMCIProperties = [
|
||||
'maxLength', 'argName', 'submit', 'validate'
|
||||
];
|
||||
const ImmutableMCIProperties = ['maxLength', 'argName', 'submit', 'validate'];
|
||||
|
||||
const mciCustomizer = (objVal, srcVal, key) => {
|
||||
return ImmutableMCIProperties.indexOf(key) > -1 ? objVal : srcVal;
|
||||
};
|
||||
|
||||
const getFormKeys = (obj) => {
|
||||
const getFormKeys = obj => {
|
||||
// remove all non-numbers
|
||||
return _.remove(Object.keys(obj), k => !isNaN(k));
|
||||
};
|
||||
@@ -217,9 +223,9 @@ exports.ThemeManager = class ThemeManager {
|
||||
};
|
||||
|
||||
const applyThemeMciBlock = (dst, src, formKey) => {
|
||||
if(_.isObject(src.mci)) {
|
||||
if (_.isObject(src.mci)) {
|
||||
mergeMciProperties(dst, src.mci);
|
||||
} else if (_.has(src, [ formKey, 'mci' ])) {
|
||||
} else if (_.has(src, [formKey, 'mci'])) {
|
||||
mergeMciProperties(dst, src[formKey].mci);
|
||||
}
|
||||
};
|
||||
@@ -252,49 +258,59 @@ exports.ThemeManager = class ThemeManager {
|
||||
applyThemeMciBlock(form.mci, menuTheme, formKey);
|
||||
} else {
|
||||
// remove anything not uppercase
|
||||
const menuMciCodeKeys = _.remove(_.keys(form), k => k === k.toUpperCase());
|
||||
const menuMciCodeKeys = _.remove(
|
||||
_.keys(form),
|
||||
k => k === k.toUpperCase()
|
||||
);
|
||||
|
||||
menuMciCodeKeys.forEach(mciKey => {
|
||||
const src = _.has(menuTheme, [ mciKey, 'mci' ]) ?
|
||||
menuTheme[mciKey] :
|
||||
menuTheme;
|
||||
const src = _.has(menuTheme, [mciKey, 'mci'])
|
||||
? menuTheme[mciKey]
|
||||
: menuTheme;
|
||||
|
||||
applyThemeMciBlock(form[mciKey].mci, src, formKey);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
[ 'menus', 'prompts'].forEach(sectionName => {
|
||||
['menus', 'prompts'].forEach(sectionName => {
|
||||
if (!_.isObject(mergedTheme[sectionName])) {
|
||||
return Log.error({sectionName}, 'Merged theme is missing section');
|
||||
return Log.error({ sectionName }, 'Merged theme is missing section');
|
||||
}
|
||||
|
||||
Object.keys(mergedTheme[sectionName]).forEach(entryName => {
|
||||
let createdFormSection = false;
|
||||
const mergedThemeMenu = mergedTheme[sectionName][entryName];
|
||||
|
||||
const menuTheme = _.get(theme, [ 'customization', sectionName, entryName ]);
|
||||
const menuTheme = _.get(theme, ['customization', sectionName, entryName]);
|
||||
if (menuTheme) {
|
||||
if (menuTheme.config) {
|
||||
// :TODO: should this be _.merge() ?
|
||||
mergedThemeMenu.config = _.assign(mergedThemeMenu.config || {}, menuTheme.config);
|
||||
mergedThemeMenu.config = _.assign(
|
||||
mergedThemeMenu.config || {},
|
||||
menuTheme.config
|
||||
);
|
||||
}
|
||||
|
||||
if('menus' === sectionName) {
|
||||
if(_.isObject(mergedThemeMenu.form)) {
|
||||
if ('menus' === sectionName) {
|
||||
if (_.isObject(mergedThemeMenu.form)) {
|
||||
getFormKeys(mergedThemeMenu.form).forEach(formKey => {
|
||||
applyToForm(mergedThemeMenu.form[formKey], menuTheme, formKey);
|
||||
applyToForm(
|
||||
mergedThemeMenu.form[formKey],
|
||||
menuTheme,
|
||||
formKey
|
||||
);
|
||||
});
|
||||
} else if(_.isObject(menuTheme.mci)) {
|
||||
} else if (_.isObject(menuTheme.mci)) {
|
||||
//
|
||||
// Not specified at menu level means we apply anything from the
|
||||
// theme to form.0.mci{}
|
||||
//
|
||||
mergedThemeMenu.form = { 0 : { mci : { } } };
|
||||
mergedThemeMenu.form = { 0: { mci: {} } };
|
||||
mergeMciProperties(mergedThemeMenu.form[0], menuTheme);
|
||||
createdFormSection = true;
|
||||
}
|
||||
} else if('prompts' === sectionName) {
|
||||
} else if ('prompts' === sectionName) {
|
||||
// no 'form' or form keys for prompts -- direct to mci
|
||||
applyToForm(mergedThemeMenu, menuTheme);
|
||||
}
|
||||
@@ -308,11 +324,14 @@ exports.ThemeManager = class ThemeManager {
|
||||
// * There is/was no explicit 'form' section
|
||||
// * There is no 'prompt' specified
|
||||
//
|
||||
if('menus' === sectionName &&
|
||||
if (
|
||||
'menus' === sectionName &&
|
||||
!_.isString(mergedThemeMenu.prompt) &&
|
||||
(createdFormSection || !_.isObject(mergedThemeMenu.form)))
|
||||
{
|
||||
mergedThemeMenu.runtime = _.merge(mergedThemeMenu.runtime || {}, { autoNext : true } );
|
||||
(createdFormSection || !_.isObject(mergedThemeMenu.form))
|
||||
) {
|
||||
mergedThemeMenu.runtime = _.merge(mergedThemeMenu.runtime || {}, {
|
||||
autoNext: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -322,29 +341,30 @@ exports.ThemeManager = class ThemeManager {
|
||||
|
||||
_setThemeHelpers(theme) {
|
||||
theme.helpers = {
|
||||
getPasswordChar : function() {
|
||||
getPasswordChar: function () {
|
||||
let pwChar = _.get(
|
||||
theme,
|
||||
'customization.defaults.passwordChar',
|
||||
Config().theme.passwordChar
|
||||
);
|
||||
|
||||
if(_.isString(pwChar)) {
|
||||
if (_.isString(pwChar)) {
|
||||
pwChar = pwChar.substr(0, 1);
|
||||
} else if(_.isNumber(pwChar)) {
|
||||
} else if (_.isNumber(pwChar)) {
|
||||
pwChar = String.fromCharCode(pwChar);
|
||||
}
|
||||
|
||||
return pwChar;
|
||||
},
|
||||
getDateFormat : function(style = 'short') {
|
||||
getDateFormat: function (style = 'short') {
|
||||
const format = Config().theme.dateFormat[style] || 'MM/DD/YYYY';
|
||||
return _.get(theme, `customization.defaults.dateFormat.${style}`, format);
|
||||
},
|
||||
getTimeFormat : function(style = 'short') {
|
||||
getTimeFormat: function (style = 'short') {
|
||||
const format = Config().theme.timeFormat[style] || 'h:mm a';
|
||||
return _.get(theme, `customization.defaults.timeFormat.${style}`, format);
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
getDateTimeFormat : function(style = 'short') {
|
||||
const format = Config().theme.dateTimeFormat[style] || 'MM/DD/YYYY h:mm a';
|
||||
return _.get(theme, `customization.defaults.dateTimeFormat.${style}`, format);
|
||||
@@ -356,17 +376,27 @@ exports.ThemeManager = class ThemeManager {
|
||||
getStatusVisibleIndicators : function() {
|
||||
const format = Config().theme.statusVisibleIndicators || [ 'Y', 'N' ];
|
||||
return _.get(theme, 'customization.defaults.statusVisibleIndicators', format);
|
||||
=======
|
||||
getDateTimeFormat: function (style = 'short') {
|
||||
const format =
|
||||
Config().theme.dateTimeFormat[style] || 'MM/DD/YYYY h:mm a';
|
||||
return _.get(
|
||||
theme,
|
||||
`customization.defaults.dateTimeFormat.${style}`,
|
||||
format
|
||||
);
|
||||
>>>>>>> 7c01946d6e8ac0023e9692744803466aa96cbadf
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_reloadAllThemes() {
|
||||
async.each([ ...this.availableThemes.keys() ], (themeId, nextThemeId) => {
|
||||
async.each([...this.availableThemes.keys()], (themeId, nextThemeId) => {
|
||||
this._loadTheme(themeId, err => {
|
||||
if (!err) {
|
||||
Log.info({ themeId }, `Theme "${themeId}" reloaded`);
|
||||
}
|
||||
return nextThemeId(null); // always proceed
|
||||
return nextThemeId(null); // always proceed
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -378,8 +408,8 @@ function getAvailableThemes() {
|
||||
|
||||
function getRandomTheme() {
|
||||
const avail = getAvailableThemes();
|
||||
if(avail.size > 0) {
|
||||
const themeIds = [ ...avail.keys() ];
|
||||
if (avail.size > 0) {
|
||||
const themeIds = [...avail.keys()];
|
||||
return themeIds[Math.floor(Math.random() * themeIds.length)];
|
||||
}
|
||||
}
|
||||
@@ -390,19 +420,23 @@ function setClientTheme(client, themeId) {
|
||||
let msg;
|
||||
let setThemeId;
|
||||
const config = Config();
|
||||
if(availThemes.has(themeId)) {
|
||||
if (availThemes.has(themeId)) {
|
||||
msg = 'Set client theme';
|
||||
setThemeId = themeId;
|
||||
} else if(availThemes.has(config.theme.default)) {
|
||||
} else if (availThemes.has(config.theme.default)) {
|
||||
msg = 'Failed setting theme by supplied ID; Using default';
|
||||
setThemeId = config.theme.default;
|
||||
} else {
|
||||
msg = 'Failed setting theme by system default ID; Using the first one we can find';
|
||||
msg =
|
||||
'Failed setting theme by system default ID; Using the first one we can find';
|
||||
setThemeId = availThemes.keys().next().value;
|
||||
}
|
||||
|
||||
client.currentTheme = availThemes.get(setThemeId);
|
||||
client.log.debug( { setThemeId, requestedThemeId : themeId, info : client.currentTheme.info }, msg);
|
||||
client.log.debug(
|
||||
{ setThemeId, requestedThemeId: themeId, info: client.currentTheme.info },
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
function getThemeArt(options, cb) {
|
||||
@@ -418,7 +452,10 @@ function getThemeArt(options, cb) {
|
||||
// random
|
||||
//
|
||||
const config = Config();
|
||||
if(!options.themeId && _.has(options, [ 'client', 'user', 'properties', UserProps.ThemeId ])) {
|
||||
if (
|
||||
!options.themeId &&
|
||||
_.has(options, ['client', 'user', 'properties', UserProps.ThemeId])
|
||||
) {
|
||||
options.themeId = options.client.user.properties[UserProps.ThemeId];
|
||||
}
|
||||
|
||||
@@ -426,9 +463,9 @@ function getThemeArt(options, cb) {
|
||||
|
||||
// :TODO: replace asAnsi stuff with something like retrieveAs = 'ansi' | 'pipe' | ...
|
||||
// :TODO: Some of these options should only be set if not provided!
|
||||
options.asAnsi = true; // always convert to ANSI
|
||||
options.readSauce = _.get(options, 'readSauce', true); // read SAUCE, if avail
|
||||
options.random = _.get(options, 'random', true); // FILENAME<n>.EXT support
|
||||
options.asAnsi = true; // always convert to ANSI
|
||||
options.readSauce = _.get(options, 'readSauce', true); // read SAUCE, if avail
|
||||
options.random = _.get(options, 'random', true); // FILENAME<n>.EXT support
|
||||
|
||||
//
|
||||
// We look for themed art in the following order:
|
||||
@@ -443,12 +480,16 @@ function getThemeArt(options, cb) {
|
||||
//
|
||||
// We allow relative (to enigma-bbs) or full paths
|
||||
//
|
||||
if('/' === options.name.charAt(0)) {
|
||||
if ('/' === options.name.charAt(0)) {
|
||||
// just take the path as-is
|
||||
options.basePath = paths.dirname(options.name);
|
||||
} else if(options.name.indexOf('/') > -1) {
|
||||
} else if (options.name.indexOf('/') > -1) {
|
||||
// make relative to base BBS dir
|
||||
options.basePath = paths.join(__dirname, '../', paths.dirname(options.name));
|
||||
options.basePath = paths.join(
|
||||
__dirname,
|
||||
'../',
|
||||
paths.dirname(options.name)
|
||||
);
|
||||
} else {
|
||||
return callback(null, null);
|
||||
}
|
||||
@@ -458,7 +499,7 @@ function getThemeArt(options, cb) {
|
||||
});
|
||||
},
|
||||
function fromSuppliedTheme(artInfo, callback) {
|
||||
if(artInfo) {
|
||||
if (artInfo) {
|
||||
return callback(null, artInfo);
|
||||
}
|
||||
|
||||
@@ -468,7 +509,7 @@ function getThemeArt(options, cb) {
|
||||
});
|
||||
},
|
||||
function fromDefaultTheme(artInfo, callback) {
|
||||
if(artInfo || config.theme.default === options.themeId) {
|
||||
if (artInfo || config.theme.default === options.themeId) {
|
||||
return callback(null, artInfo);
|
||||
}
|
||||
|
||||
@@ -478,7 +519,7 @@ function getThemeArt(options, cb) {
|
||||
});
|
||||
},
|
||||
function fromGeneralArtDir(artInfo, callback) {
|
||||
if(artInfo) {
|
||||
if (artInfo) {
|
||||
return callback(null, artInfo);
|
||||
}
|
||||
|
||||
@@ -486,12 +527,12 @@ function getThemeArt(options, cb) {
|
||||
art.getArt(options.name, options, (err, artInfo) => {
|
||||
return callback(err, artInfo);
|
||||
});
|
||||
}
|
||||
},
|
||||
],
|
||||
function complete(err, artInfo) {
|
||||
if(err) {
|
||||
if (err) {
|
||||
const logger = _.get(options, 'client.log') || Log;
|
||||
logger.debug( { reason : err.message }, 'Cannot find theme art');
|
||||
logger.debug({ reason: err.message }, 'Cannot find theme art');
|
||||
}
|
||||
return cb(err, artInfo);
|
||||
}
|
||||
@@ -500,13 +541,13 @@ function getThemeArt(options, cb) {
|
||||
|
||||
function displayPreparedArt(options, artInfo, cb) {
|
||||
const displayOpts = {
|
||||
sauce : artInfo.sauce,
|
||||
font : options.font,
|
||||
trailingLF : options.trailingLF,
|
||||
startRow : options.startRow,
|
||||
sauce: artInfo.sauce,
|
||||
font: options.font,
|
||||
trailingLF: options.trailingLF,
|
||||
startRow: options.startRow,
|
||||
};
|
||||
art.display(options.client, artInfo.data, displayOpts, (err, mciMap, extraInfo) => {
|
||||
return cb(err, { mciMap : mciMap, artInfo : artInfo, extraInfo : extraInfo } );
|
||||
return cb(err, { mciMap: mciMap, artInfo: artInfo, extraInfo: extraInfo });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -521,24 +562,20 @@ function displayThemeArt(options, cb) {
|
||||
return getThemeArt(options, callback);
|
||||
},
|
||||
function prepWork(artInfo, callback) {
|
||||
if(_.isObject(options.ansiPrepOptions)) {
|
||||
AnsiPrep(
|
||||
artInfo.data,
|
||||
options.ansiPrepOptions,
|
||||
(err, prepped) => {
|
||||
if(!err && prepped) {
|
||||
artInfo.data = prepped;
|
||||
return callback(null, artInfo);
|
||||
}
|
||||
if (_.isObject(options.ansiPrepOptions)) {
|
||||
AnsiPrep(artInfo.data, options.ansiPrepOptions, (err, prepped) => {
|
||||
if (!err && prepped) {
|
||||
artInfo.data = prepped;
|
||||
return callback(null, artInfo);
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return callback(null, artInfo);
|
||||
}
|
||||
},
|
||||
function disp(artInfo, callback) {
|
||||
return displayPreparedArt(options, artInfo, callback);
|
||||
}
|
||||
},
|
||||
],
|
||||
(err, artData) => {
|
||||
return cb(err, artData);
|
||||
@@ -547,20 +584,21 @@ function displayThemeArt(options, cb) {
|
||||
}
|
||||
|
||||
function displayThemedPrompt(name, client, options, cb) {
|
||||
|
||||
const usingTempViewController = _.isUndefined(options.viewController);
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function display(callback) {
|
||||
const promptConfig = client.currentTheme.prompts[name];
|
||||
if(!promptConfig) {
|
||||
return callback(Errors.DoesNotExist(`Missing "${name}" prompt configuration!`));
|
||||
if (!promptConfig) {
|
||||
return callback(
|
||||
Errors.DoesNotExist(`Missing "${name}" prompt configuration!`)
|
||||
);
|
||||
}
|
||||
|
||||
if(options.clearScreen) {
|
||||
if (options.clearScreen) {
|
||||
client.term.rawWrite(ansi.resetScreen());
|
||||
options.position = {row: 1, column: 1};
|
||||
options.position = { row: 1, column: 1 };
|
||||
}
|
||||
|
||||
//
|
||||
@@ -568,7 +606,7 @@ function displayThemedPrompt(name, client, options, cb) {
|
||||
// doing so messes things up -- most terminals that support font
|
||||
// changing can only display a single font at at time.
|
||||
//
|
||||
const dispOptions = Object.assign( {}, options, promptConfig.config );
|
||||
const dispOptions = Object.assign({}, options, promptConfig.config);
|
||||
// :TODO: We can use term detection to do nifty things like avoid this kind of kludge:
|
||||
// if(!options.clearScreen) {
|
||||
// dispOptions.font = 'not_really_a_font!'; // kludge :)
|
||||
@@ -579,7 +617,7 @@ function displayThemedPrompt(name, client, options, cb) {
|
||||
client,
|
||||
dispOptions,
|
||||
(err, artInfo) => {
|
||||
if(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
@@ -588,14 +626,17 @@ function displayThemedPrompt(name, client, options, cb) {
|
||||
);
|
||||
},
|
||||
function discoverCursorPosition(promptConfig, artInfo, callback) {
|
||||
if(!options.clearPrompt) {
|
||||
if (!options.clearPrompt) {
|
||||
// no need to query cursor - we're not gonna use it
|
||||
return callback(null, promptConfig, artInfo);
|
||||
}
|
||||
|
||||
if(_.isNumber(options?.position?.row)) {
|
||||
if (_.isNumber(options?.position?.row)) {
|
||||
artInfo.startRow = options.position.row;
|
||||
if(client.term.termHeight > 0 && artInfo.startRow + artInfo.height > client.term.termHeight) {
|
||||
if (
|
||||
client.term.termHeight > 0 &&
|
||||
artInfo.startRow + artInfo.height > client.term.termHeight
|
||||
) {
|
||||
// in this case, we will have scrolled
|
||||
artInfo.startRow = client.term.termHeight - artInfo.height;
|
||||
}
|
||||
@@ -604,13 +645,15 @@ function displayThemedPrompt(name, client, options, cb) {
|
||||
return callback(null, promptConfig, artInfo);
|
||||
},
|
||||
function createMCIViews(promptConfig, artInfo, callback) {
|
||||
const assocViewController = usingTempViewController ? new ViewController( { client : client } ) : options.viewController;
|
||||
const assocViewController = usingTempViewController
|
||||
? new ViewController({ client: client })
|
||||
: options.viewController;
|
||||
|
||||
const loadOpts = {
|
||||
promptName : name,
|
||||
mciMap : artInfo.mciMap,
|
||||
config : promptConfig,
|
||||
submitNotify : options.submitNotify,
|
||||
promptName: name,
|
||||
mciMap: artInfo.mciMap,
|
||||
config: promptConfig,
|
||||
submitNotify: options.submitNotify,
|
||||
};
|
||||
|
||||
assocViewController.loadFromPromptConfig(loadOpts, () => {
|
||||
@@ -618,19 +661,19 @@ function displayThemedPrompt(name, client, options, cb) {
|
||||
});
|
||||
},
|
||||
function pauseForUserInput(artInfo, assocViewController, callback) {
|
||||
if(!options.pause) {
|
||||
if (!options.pause) {
|
||||
return callback(null, artInfo, assocViewController);
|
||||
}
|
||||
|
||||
client.waitForKeyPress( () => {
|
||||
client.waitForKeyPress(() => {
|
||||
return callback(null, artInfo, assocViewController);
|
||||
});
|
||||
},
|
||||
function clearPauseArt(artInfo, assocViewController, callback) {
|
||||
// Only clear with height if clearPrompt is true and if we were able
|
||||
// to determine the row
|
||||
if(options.clearPrompt && artInfo.startRow) {
|
||||
if(artInfo.startRow && artInfo.height) {
|
||||
if (options.clearPrompt && artInfo.startRow) {
|
||||
if (artInfo.startRow && artInfo.height) {
|
||||
client.term.rawWrite(ansi.goto(artInfo.startRow, 1));
|
||||
|
||||
// Note: Does not work properly in NetRunner < 2.0b17:
|
||||
@@ -641,14 +684,17 @@ function displayThemedPrompt(name, client, options, cb) {
|
||||
}
|
||||
|
||||
return callback(null, assocViewController, artInfo);
|
||||
}
|
||||
},
|
||||
],
|
||||
(err, assocViewController, artInfo) => {
|
||||
if(err) {
|
||||
client.log.warn( { error : err.message }, `Failed displaying "${name}" prompt` );
|
||||
if (err) {
|
||||
client.log.warn(
|
||||
{ error: err.message },
|
||||
`Failed displaying "${name}" prompt`
|
||||
);
|
||||
}
|
||||
|
||||
if(assocViewController && usingTempViewController) {
|
||||
if (assocViewController && usingTempViewController) {
|
||||
assocViewController.detachClientEvents();
|
||||
}
|
||||
|
||||
@@ -661,17 +707,16 @@ function displayThemedPrompt(name, client, options, cb) {
|
||||
// Pause prompts are a special prompt by the name 'pause'.
|
||||
//
|
||||
function displayThemedPause(client, options, cb) {
|
||||
|
||||
if(!cb && _.isFunction(options)) {
|
||||
if (!cb && _.isFunction(options)) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if(!_.isBoolean(options.clearPrompt)) {
|
||||
if (!_.isBoolean(options.clearPrompt)) {
|
||||
options.clearPrompt = true;
|
||||
}
|
||||
|
||||
const promptOptions = Object.assign( {}, options, { pause : true } );
|
||||
const promptOptions = Object.assign({}, options, { pause: true });
|
||||
return displayThemedPrompt('pause', client, promptOptions, cb);
|
||||
}
|
||||
|
||||
@@ -679,40 +724,45 @@ function displayThemedAsset(assetSpec, client, options, cb) {
|
||||
assert(_.isObject(client));
|
||||
|
||||
// options are... optional
|
||||
if(3 === arguments.length) {
|
||||
if (3 === arguments.length) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if(Array.isArray(assetSpec)) {
|
||||
if (Array.isArray(assetSpec)) {
|
||||
const acsCondMember = options.acsCondMember || 'art';
|
||||
assetSpec = client.acs.getConditionalValue(assetSpec, acsCondMember);
|
||||
}
|
||||
|
||||
const artAsset = asset.getArtAsset(assetSpec);
|
||||
if(!artAsset) {
|
||||
if (!artAsset) {
|
||||
return cb(new Error('Asset not found: ' + assetSpec));
|
||||
}
|
||||
|
||||
const dispOpts = Object.assign( {}, options, { client, name : artAsset.asset } );
|
||||
switch(artAsset.type) {
|
||||
case 'art' :
|
||||
const dispOpts = Object.assign({}, options, { client, name: artAsset.asset });
|
||||
switch (artAsset.type) {
|
||||
case 'art':
|
||||
displayThemeArt(dispOpts, function displayed(err, artData) {
|
||||
return cb(err, err ? null : { mciMap : artData.mciMap, height : artData.extraInfo.height } );
|
||||
return cb(
|
||||
err,
|
||||
err
|
||||
? null
|
||||
: { mciMap: artData.mciMap, height: artData.extraInfo.height }
|
||||
);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'method' :
|
||||
case 'method':
|
||||
// :TODO: fetch & render via method
|
||||
break;
|
||||
|
||||
case 'inline ' :
|
||||
case 'inline ':
|
||||
// :TODO: think about this more in relation to themes, etc. How can this come
|
||||
// from a theme (with override from menu.json) ???
|
||||
// look @ client.currentTheme.inlineArt[name] -> menu/prompt[name]
|
||||
break;
|
||||
|
||||
default :
|
||||
default:
|
||||
return cb(new Error('Unsupported art asset type: ' + artAsset.type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user