* file.db: file_user_rating: Table for tracking average user rating of a file
* Default filter order to descending * File rating support including in search/filter * Default to passing submitted form data (if any) @ prevMenu() * Fix issues with byte/size formatting for 0 * Allow action keys for prompts * use MenuModule.pausePrompt() in various places * Add quick search to file area * Display dupes, if any @ upload
This commit is contained in:
@@ -214,6 +214,7 @@ const DB_INIT_TABLE = {
|
||||
);`
|
||||
);
|
||||
|
||||
|
||||
// :TODO: need SQL to ensure cleaned up if delete from message?
|
||||
/*
|
||||
dbs.message.run(
|
||||
@@ -335,6 +336,16 @@ const DB_INIT_TABLE = {
|
||||
);`
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_user_rating (
|
||||
file_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
rating INTEGER NOT NULL,
|
||||
|
||||
UNIQUE(file_id, user_id)
|
||||
);`
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_web_serve (
|
||||
hash_id VARCHAR NOT NULL PRIMARY KEY,
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = class FileBaseFilters {
|
||||
}
|
||||
|
||||
static get OrderByValues() {
|
||||
return [ 'ascending', 'descending' ];
|
||||
return [ 'descending', 'ascending' ];
|
||||
}
|
||||
|
||||
static get SortByValues() {
|
||||
@@ -116,7 +116,7 @@ module.exports = class FileBaseFilters {
|
||||
areaTag : '', // all
|
||||
terms : '', // *
|
||||
tags : '', // *
|
||||
order : 'ascending',
|
||||
order : 'descending',
|
||||
sort : 'upload_timestamp',
|
||||
uuid : uuid,
|
||||
};
|
||||
|
||||
@@ -26,7 +26,6 @@ const FILE_WELL_KNOWN_META = {
|
||||
est_release_year : (y) => parseInt(y) || new Date().getFullYear(),
|
||||
dl_count : (d) => parseInt(d) || 0,
|
||||
byte_size : (b) => parseInt(b) || 0,
|
||||
user_rating : (r) => Math.min(parseInt(r) || 0, 5),
|
||||
archive_type : null,
|
||||
};
|
||||
|
||||
@@ -38,50 +37,61 @@ module.exports = class FileEntry {
|
||||
this.areaTag = options.areaTag || '';
|
||||
this.meta = options.meta || {
|
||||
// values we always want
|
||||
user_rating : 0,
|
||||
dl_count : 0,
|
||||
};
|
||||
|
||||
|
||||
this.hashTags = options.hashTags || new Set();
|
||||
this.fileName = options.fileName;
|
||||
this.storageTag = options.storageTag;
|
||||
}
|
||||
|
||||
static loadBasicEntry(fileId, dest, cb) {
|
||||
if(!cb && _.isFunction(dest)) {
|
||||
cb = dest;
|
||||
dest = this;
|
||||
}
|
||||
|
||||
fileDb.get(
|
||||
`SELECT ${FILE_TABLE_MEMBERS.join(', ')}
|
||||
FROM file
|
||||
WHERE file_id=?
|
||||
LIMIT 1;`,
|
||||
[ fileId ],
|
||||
(err, file) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if(!file) {
|
||||
return cb(Errors.DoesNotExist('No file is available by that ID'));
|
||||
}
|
||||
|
||||
// assign props from |file|
|
||||
FILE_TABLE_MEMBERS.forEach(prop => {
|
||||
dest[_.camelCase(prop)] = file[prop];
|
||||
});
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
load(fileId, cb) {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadBasicEntry(callback) {
|
||||
fileDb.get(
|
||||
`SELECT ${FILE_TABLE_MEMBERS.join(', ')}
|
||||
FROM file
|
||||
WHERE file_id=?
|
||||
LIMIT 1;`,
|
||||
[ fileId ],
|
||||
(err, file) => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(!file) {
|
||||
return callback(Errors.DoesNotExist('No file is available by that ID'));
|
||||
}
|
||||
|
||||
// assign props from |file|
|
||||
FILE_TABLE_MEMBERS.forEach(prop => {
|
||||
self[_.camelCase(prop)] = file[prop];
|
||||
});
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
);
|
||||
FileEntry.loadBasicEntry(fileId, self, callback);
|
||||
},
|
||||
function loadMeta(callback) {
|
||||
return self.loadMeta(callback);
|
||||
},
|
||||
function loadHashTags(callback) {
|
||||
return self.loadHashTags(callback);
|
||||
},
|
||||
function loadUserRating(callback) {
|
||||
return self.loadRating(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
@@ -156,10 +166,19 @@ module.exports = class FileEntry {
|
||||
return paths.join(storageDir, this.fileName);
|
||||
}
|
||||
|
||||
static persistUserRating(fileId, userId, rating, cb) {
|
||||
return fileDb.run(
|
||||
`REPLACE INTO file_user_rating (file_id, user_id, rating)
|
||||
VALUES (?, ?, ?);`,
|
||||
[ fileId, userId, rating ],
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
static persistMetaValue(fileId, name, value, cb) {
|
||||
fileDb.run(
|
||||
return fileDb.run(
|
||||
`REPLACE INTO file_meta (file_id, meta_name, meta_value)
|
||||
VALUES(?, ?, ?);`,
|
||||
VALUES (?, ?, ?);`,
|
||||
[ fileId, name, value ],
|
||||
cb
|
||||
);
|
||||
@@ -243,6 +262,23 @@ module.exports = class FileEntry {
|
||||
);
|
||||
}
|
||||
|
||||
loadRating(cb) {
|
||||
fileDb.get(
|
||||
`SELECT AVG(fur.rating) AS avg_rating
|
||||
FROM file_user_rating fur
|
||||
INNER JOIN file f
|
||||
ON f.file_id = fur.file_id
|
||||
AND f.file_id = ?`,
|
||||
[ this.fileId ],
|
||||
(err, result) => {
|
||||
if(result) {
|
||||
this.userRating = result.avg_rating;
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setHashTags(hashTags) {
|
||||
if(_.isString(hashTags)) {
|
||||
this.hashTags = new Set(hashTags.split(/[\s,]+/));
|
||||
@@ -264,7 +300,7 @@ module.exports = class FileEntry {
|
||||
const sqlOrderDir = 'ascending' === filter.order ? 'ASC' : 'DESC';
|
||||
|
||||
function getOrderByWithCast(ob) {
|
||||
if( [ 'dl_count', 'user_rating', 'est_release_year', 'byte_size' ].indexOf(filter.sort) > -1 ) {
|
||||
if( [ 'dl_count', 'est_release_year', 'byte_size' ].indexOf(filter.sort) > -1 ) {
|
||||
return `ORDER BY CAST(${ob} AS INTEGER)`;
|
||||
}
|
||||
|
||||
@@ -290,11 +326,24 @@ module.exports = class FileEntry {
|
||||
|
||||
sqlOrderBy = `${getOrderByWithCast('m.meta_value')} ${sqlOrderDir}`;
|
||||
} else {
|
||||
sql =
|
||||
`SELECT f.file_id, f.${filter.sort}
|
||||
FROM file f`;
|
||||
// additional special treatment for user ratings: we need to average them
|
||||
if('user_rating' === filter.sort) {
|
||||
sql =
|
||||
`SELECT f.file_id,
|
||||
(SELECT IFNULL(AVG(rating), 0) rating
|
||||
FROM file_user_rating
|
||||
WHERE file_id = f.file_id)
|
||||
AS avg_rating
|
||||
FROM file f`;
|
||||
|
||||
sqlOrderBy = `ORDER BY avg_rating ${sqlOrderDir}`;
|
||||
} else {
|
||||
sql =
|
||||
`SELECT f.file_id, f.${filter.sort}
|
||||
FROM file f`;
|
||||
|
||||
sqlOrderBy = getOrderByWithCast(`f.${filter.sort}`) + ' ' + sqlOrderDir;
|
||||
sqlOrderBy = getOrderByWithCast(`f.${filter.sort}`) + ' ' + sqlOrderDir;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sql =
|
||||
|
||||
@@ -166,7 +166,8 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
||||
}
|
||||
|
||||
getMenuResult() {
|
||||
// nothing in base
|
||||
// default to the formData that was provided @ a submit, if any
|
||||
return this.submitFormData;
|
||||
}
|
||||
|
||||
nextMenu(cb) {
|
||||
@@ -345,22 +346,42 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
||||
);
|
||||
}
|
||||
|
||||
pausePrompt(position, cb) {
|
||||
if(!cb && _.isFunction(position)) {
|
||||
cb = position;
|
||||
position = null;
|
||||
}
|
||||
|
||||
optionalMoveToPosition(position) {
|
||||
if(position) {
|
||||
position.x = position.row || position.x || 1;
|
||||
position.y = position.col || position.y || 1;
|
||||
|
||||
this.client.term.rawWrite(ansi.goto(position.x, position.y));
|
||||
}
|
||||
|
||||
return theme.displayThemedPause( { client : this.client }, cb);
|
||||
}
|
||||
|
||||
pausePrompt(position, cb) {
|
||||
if(!cb && _.isFunction(position)) {
|
||||
cb = position;
|
||||
position = null;
|
||||
}
|
||||
|
||||
this.optionalMoveToPosition(position);
|
||||
|
||||
return theme.displayThemedPause(this.client, cb);
|
||||
}
|
||||
|
||||
/*
|
||||
:TODO: this needs quite a bit of work - but would be nice: promptForInput(..., (err, formData) => ... )
|
||||
promptForInput(formName, name, options, cb) {
|
||||
if(!cb && _.isFunction(options)) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.viewController = this.viewControllers[formName];
|
||||
|
||||
this.optionalMoveToPosition(options.position);
|
||||
|
||||
return theme.displayThemedPrompt(name, this.client, options, cb);
|
||||
}
|
||||
*/
|
||||
|
||||
setViewText(formName, mciId, text, appendMultiLine) {
|
||||
const view = this.viewControllers[formName].getView(mciId);
|
||||
if(!view) {
|
||||
|
||||
@@ -301,13 +301,17 @@ function renderStringLength(s) {
|
||||
const SIZE_ABBRS = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ]; // :)
|
||||
|
||||
function formatByteSizeAbbr(byteSize) {
|
||||
if(0 === byteSize) {
|
||||
return SIZE_ABBRS[0]; // B
|
||||
}
|
||||
|
||||
return SIZE_ABBRS[Math.floor(Math.log(byteSize) / Math.log(1024))];
|
||||
}
|
||||
|
||||
function formatByteSize(byteSize, withAbbr, decimals) {
|
||||
withAbbr = withAbbr || false;
|
||||
decimals = decimals || 3;
|
||||
const i = Math.floor(Math.log(byteSize) / Math.log(1024));
|
||||
const i = 0 === byteSize ? byteSize : Math.floor(Math.log(byteSize) / Math.log(1024));
|
||||
let result = parseFloat((byteSize / Math.pow(1024, i)).toFixed(decimals));
|
||||
if(withAbbr) {
|
||||
result += ` ${SIZE_ABBRS[i]}`;
|
||||
|
||||
@@ -63,9 +63,15 @@ function logoff(callingMenu, formData, extraArgs, cb) {
|
||||
}
|
||||
|
||||
function prevMenu(callingMenu, formData, extraArgs, cb) {
|
||||
|
||||
// :TODO: this is a pretty big hack -- need the whole key map concep there like other places
|
||||
if(formData.key && 'return' === formData.key.name) {
|
||||
callingMenu.submitFormData = formData;
|
||||
}
|
||||
|
||||
callingMenu.prevMenu( err => {
|
||||
if(err) {
|
||||
callingMenu.client.log.error( { error : err.toString() }, 'Error attempting to fallback!');
|
||||
callingMenu.client.log.error( { error : err.message }, 'Error attempting to fallback!');
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
@@ -74,7 +80,7 @@ function prevMenu(callingMenu, formData, extraArgs, cb) {
|
||||
function nextMenu(callingMenu, formData, extraArgs, cb) {
|
||||
callingMenu.nextMenu( err => {
|
||||
if(err) {
|
||||
callingMenu.client.log.error( { error : err.toString() }, 'Error attempting to go to next menu!');
|
||||
callingMenu.client.log.error( { error : err.message}, 'Error attempting to go to next menu!');
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
|
||||
271
core/theme.js
271
core/theme.js
@@ -9,6 +9,7 @@ const configCache = require('./config_cache.js');
|
||||
const getFullConfig = require('./config_util.js').getFullConfig;
|
||||
const asset = require('./asset.js');
|
||||
const ViewController = require('./view_controller.js').ViewController;
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
|
||||
const fs = require('fs');
|
||||
const paths = require('path');
|
||||
@@ -23,6 +24,7 @@ exports.setClientTheme = setClientTheme;
|
||||
exports.initAvailableThemes = initAvailableThemes;
|
||||
exports.displayThemeArt = displayThemeArt;
|
||||
exports.displayThemedPause = displayThemedPause;
|
||||
exports.displayThemedPrompt = displayThemedPrompt;
|
||||
exports.displayThemedAsset = displayThemedAsset;
|
||||
|
||||
function refreshThemeHelpers(theme) {
|
||||
@@ -484,110 +486,187 @@ 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) {
|
||||
|
||||
const useTempViewController = _.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(options.clearScreen) {
|
||||
client.term.rawWrite(ansi.clearScreen());
|
||||
}
|
||||
|
||||
//
|
||||
// If we did *not* clear the screen, don't let the font change
|
||||
// as it will mess with the output of the existing art displayed in a terminal
|
||||
//
|
||||
const dispOptions = Object.assign( {}, promptConfig.options );
|
||||
if(!options.clearScreen) {
|
||||
dispOptions.font = 'not_really_a_font!'; // kludge :)
|
||||
}
|
||||
|
||||
displayThemedAsset(
|
||||
promptConfig.art,
|
||||
client,
|
||||
dispOptions,
|
||||
(err, artInfo) => {
|
||||
return callback(err, promptConfig, artInfo);
|
||||
}
|
||||
);
|
||||
},
|
||||
function discoverCursorPosition(promptConfig, artInfo, callback) {
|
||||
if(!options.clearPrompt) {
|
||||
// no need to query cursor - we're not gonna use it
|
||||
return callback(null, promptConfig, artInfo);
|
||||
}
|
||||
|
||||
client.once('cursor position report', pos => {
|
||||
artInfo.startRow = pos[0] - artInfo.height;
|
||||
return callback(null, promptConfig, artInfo);
|
||||
});
|
||||
|
||||
client.term.rawWrite(ansi.queryPos());
|
||||
},
|
||||
function createMCIViews(promptConfig, artInfo, callback) {
|
||||
const tempViewController = useTempViewController ? new ViewController( { client : client } ) : options.viewController;
|
||||
|
||||
const loadOpts = {
|
||||
promptName : name,
|
||||
mciMap : artInfo.mciMap,
|
||||
config : promptConfig,
|
||||
};
|
||||
|
||||
tempViewController.loadFromPromptConfig(loadOpts, () => {
|
||||
return callback(null, artInfo, tempViewController);
|
||||
});
|
||||
},
|
||||
function pauseForUserInput(artInfo, tempViewController, callback) {
|
||||
if(!options.pause) {
|
||||
return callback(null, artInfo, tempViewController);
|
||||
}
|
||||
|
||||
client.waitForKeyPress( () => {
|
||||
return callback(null, artInfo, tempViewController);
|
||||
});
|
||||
},
|
||||
function clearPauseArt(artInfo, tempViewController, callback) {
|
||||
if(options.clearPrompt) {
|
||||
if(artInfo.startRow && artInfo.height) {
|
||||
client.term.rawWrite(ansi.goto(artInfo.startRow, 1));
|
||||
|
||||
// Note: Does not work properly in NetRunner < 2.0b17:
|
||||
client.term.rawWrite(ansi.deleteLine(artInfo.height));
|
||||
} else {
|
||||
client.term.rawWrite(ansi.eraseLine(1));
|
||||
}
|
||||
}
|
||||
|
||||
return callback(null, tempViewController);
|
||||
}
|
||||
],
|
||||
(err, tempViewController) => {
|
||||
if(err) {
|
||||
client.log.warn( { error : err.message }, `Failed displaying "${name}" prompt` );
|
||||
}
|
||||
|
||||
if(tempViewController && useTempViewController) {
|
||||
tempViewController.detachClientEvents();
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Pause prompts are a special prompt by the name 'pause'.
|
||||
//
|
||||
function displayThemedPause(options, cb) {
|
||||
//
|
||||
// options.client
|
||||
// options clearPrompt
|
||||
//
|
||||
assert(_.isObject(options.client));
|
||||
function displayThemedPause(client, options, cb) {
|
||||
|
||||
if(!cb && _.isFunction(options)) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if(!_.isBoolean(options.clearPrompt)) {
|
||||
options.clearPrompt = true;
|
||||
}
|
||||
|
||||
// :TODO: Support animated pause prompts. Probably via MCI with AnimatedView
|
||||
|
||||
var artInfo;
|
||||
var vc;
|
||||
var promptConfig;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadPromptJSON(callback) {
|
||||
configCache.getModConfig('prompt.hjson', function loaded(err, promptJson) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
} else {
|
||||
if(_.has(promptJson, [ 'prompts', 'pause' ] )) {
|
||||
promptConfig = promptJson.prompts.pause;
|
||||
callback(_.isObject(promptConfig) ? null : new Error('Invalid prompt config block!'));
|
||||
} else {
|
||||
callback(new Error('Missing standard \'pause\' prompt'));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function displayPausePrompt(callback) {
|
||||
//
|
||||
// Override .font so it doesn't change from current setting
|
||||
//
|
||||
var dispOptions = promptConfig.options;
|
||||
dispOptions.font = 'not_really_a_font!';
|
||||
|
||||
displayThemedAsset(
|
||||
promptConfig.art,
|
||||
options.client,
|
||||
dispOptions,
|
||||
function displayed(err, artData) {
|
||||
artInfo = artData;
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
function discoverCursorPosition(callback) {
|
||||
options.client.once('cursor position report', function cpr(pos) {
|
||||
artInfo.startRow = pos[0] - artInfo.height;
|
||||
callback(null);
|
||||
});
|
||||
options.client.term.rawWrite(ansi.queryPos());
|
||||
},
|
||||
function createMCIViews(callback) {
|
||||
vc = new ViewController( { client : options.client, noInput : true } );
|
||||
vc.loadFromPromptConfig( { promptName : 'pause', mciMap : artInfo.mciMap, config : promptConfig }, function loaded(err) {
|
||||
callback(null);
|
||||
});
|
||||
},
|
||||
function pauseForUserInput(callback) {
|
||||
options.client.waitForKeyPress(function keyPressed() {
|
||||
callback(null);
|
||||
});
|
||||
},
|
||||
function clearPauseArt(callback) {
|
||||
if(options.clearPrompt) {
|
||||
if(artInfo.startRow && artInfo.height) {
|
||||
options.client.term.rawWrite(ansi.goto(artInfo.startRow, 1));
|
||||
|
||||
// Note: Does not work properly in NetRunner < 2.0b17:
|
||||
options.client.term.rawWrite(ansi.deleteLine(artInfo.height));
|
||||
} else {
|
||||
options.client.term.rawWrite(ansi.eraseLine(1))
|
||||
}
|
||||
}
|
||||
callback(null);
|
||||
}
|
||||
/*
|
||||
, function debugPause(callback) {
|
||||
setTimeout(function to() {
|
||||
callback(null);
|
||||
}, 4000);
|
||||
}
|
||||
*/
|
||||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
Log.error(err);
|
||||
}
|
||||
|
||||
if(vc) {
|
||||
vc.detachClientEvents();
|
||||
}
|
||||
|
||||
cb();
|
||||
}
|
||||
);
|
||||
const promptOptions = Object.assign( {}, options, { pause : true } );
|
||||
return displayThemedPrompt('pause', client, promptOptions, cb);
|
||||
}
|
||||
|
||||
function displayThemedAsset(assetSpec, client, options, cb) {
|
||||
|
||||
@@ -184,52 +184,52 @@ function ViewController(options) {
|
||||
propAsset = asset.getViewPropertyAsset(conf[propName]);
|
||||
if(propAsset) {
|
||||
switch(propAsset.type) {
|
||||
case 'config' :
|
||||
propValue = asset.resolveConfigAsset(conf[propName]);
|
||||
break;
|
||||
|
||||
case 'sysStat' :
|
||||
propValue = asset.resolveSystemStatAsset(conf[propName]);
|
||||
break;
|
||||
case 'config' :
|
||||
propValue = asset.resolveConfigAsset(conf[propName]);
|
||||
break;
|
||||
|
||||
case 'sysStat' :
|
||||
propValue = asset.resolveSystemStatAsset(conf[propName]);
|
||||
break;
|
||||
|
||||
// :TODO: handle @art (e.g. text : @art ...)
|
||||
// :TODO: handle @art (e.g. text : @art ...)
|
||||
|
||||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
if('validate' === propName) {
|
||||
// :TODO: handle propAsset.location for @method script specification
|
||||
if('systemMethod' === propAsset.type) {
|
||||
// :TODO: implementation validation @systemMethod handling!
|
||||
var methodModule = require(paths.join(__dirname, 'system_view_validate.js'));
|
||||
if(_.isFunction(methodModule[propAsset.asset])) {
|
||||
propValue = methodModule[propAsset.asset];
|
||||
}
|
||||
} else {
|
||||
if(_.isFunction(self.client.currentMenuModule.menuMethods[propAsset.asset])) {
|
||||
propValue = self.client.currentMenuModule.menuMethods[propAsset.asset];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(_.isString(propAsset.location)) {
|
||||
|
||||
} else {
|
||||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
if('validate' === propName) {
|
||||
// :TODO: handle propAsset.location for @method script specification
|
||||
if('systemMethod' === propAsset.type) {
|
||||
// :TODO:
|
||||
// :TODO: implementation validation @systemMethod handling!
|
||||
var methodModule = require(paths.join(__dirname, 'system_view_validate.js'));
|
||||
if(_.isFunction(methodModule[propAsset.asset])) {
|
||||
propValue = methodModule[propAsset.asset];
|
||||
}
|
||||
} else {
|
||||
// local to current module
|
||||
var currentModule = self.client.currentMenuModule;
|
||||
if(_.isFunction(currentModule.menuMethods[propAsset.asset])) {
|
||||
// :TODO: Fix formData & extraArgs... this all needs general processing
|
||||
propValue = currentModule.menuMethods[propAsset.asset]({}, {});//formData, conf.extraArgs);
|
||||
if(_.isFunction(self.client.currentMenuModule.menuMethods[propAsset.asset])) {
|
||||
propValue = self.client.currentMenuModule.menuMethods[propAsset.asset];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(_.isString(propAsset.location)) {
|
||||
|
||||
} else {
|
||||
if('systemMethod' === propAsset.type) {
|
||||
// :TODO:
|
||||
} else {
|
||||
// local to current module
|
||||
var currentModule = self.client.currentMenuModule;
|
||||
if(_.isFunction(currentModule.menuMethods[propAsset.asset])) {
|
||||
// :TODO: Fix formData & extraArgs... this all needs general processing
|
||||
propValue = currentModule.menuMethods[propAsset.asset]({}, {});//formData, conf.extraArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
default :
|
||||
propValue = propValue = conf[propName];
|
||||
break;
|
||||
default :
|
||||
propValue = propValue = conf[propName];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
propValue = conf[propName];
|
||||
@@ -601,6 +601,33 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) {
|
||||
|
||||
callback(null);
|
||||
},
|
||||
function loadActionKeys(callback) {
|
||||
if(!_.isObject(promptConfig) || !_.isArray(promptConfig.actionKeys)) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
promptConfig.actionKeys.forEach(ak => {
|
||||
//
|
||||
// * 'keys' must be present and be an array of key names
|
||||
// * If 'viewId' is present, key(s) will focus & submit on behalf
|
||||
// of the specified view.
|
||||
// * If 'action' is present, that action will be procesed when
|
||||
// triggered by key(s)
|
||||
//
|
||||
// Ultimately, create a map of key -> { action block }
|
||||
//
|
||||
if(!_.isArray(ak.keys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ak.keys.forEach(kn => {
|
||||
self.actionKeyMap[kn] = ak;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function drawAllViews(callback) {
|
||||
self.redrawAll(initialFocusId);
|
||||
callback(null);
|
||||
|
||||
Reference in New Issue
Block a user