* 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:
parent
5f929b3d63
commit
f0db0e3c94
|
@ -214,6 +214,7 @@ const DB_INIT_TABLE = {
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// :TODO: need SQL to ensure cleaned up if delete from message?
|
// :TODO: need SQL to ensure cleaned up if delete from message?
|
||||||
/*
|
/*
|
||||||
dbs.message.run(
|
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(
|
dbs.file.run(
|
||||||
`CREATE TABLE IF NOT EXISTS file_web_serve (
|
`CREATE TABLE IF NOT EXISTS file_web_serve (
|
||||||
hash_id VARCHAR NOT NULL PRIMARY KEY,
|
hash_id VARCHAR NOT NULL PRIMARY KEY,
|
||||||
|
|
|
@ -12,7 +12,7 @@ module.exports = class FileBaseFilters {
|
||||||
}
|
}
|
||||||
|
|
||||||
static get OrderByValues() {
|
static get OrderByValues() {
|
||||||
return [ 'ascending', 'descending' ];
|
return [ 'descending', 'ascending' ];
|
||||||
}
|
}
|
||||||
|
|
||||||
static get SortByValues() {
|
static get SortByValues() {
|
||||||
|
@ -116,7 +116,7 @@ module.exports = class FileBaseFilters {
|
||||||
areaTag : '', // all
|
areaTag : '', // all
|
||||||
terms : '', // *
|
terms : '', // *
|
||||||
tags : '', // *
|
tags : '', // *
|
||||||
order : 'ascending',
|
order : 'descending',
|
||||||
sort : 'upload_timestamp',
|
sort : 'upload_timestamp',
|
||||||
uuid : uuid,
|
uuid : uuid,
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,6 @@ const FILE_WELL_KNOWN_META = {
|
||||||
est_release_year : (y) => parseInt(y) || new Date().getFullYear(),
|
est_release_year : (y) => parseInt(y) || new Date().getFullYear(),
|
||||||
dl_count : (d) => parseInt(d) || 0,
|
dl_count : (d) => parseInt(d) || 0,
|
||||||
byte_size : (b) => parseInt(b) || 0,
|
byte_size : (b) => parseInt(b) || 0,
|
||||||
user_rating : (r) => Math.min(parseInt(r) || 0, 5),
|
|
||||||
archive_type : null,
|
archive_type : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,50 +37,61 @@ module.exports = class FileEntry {
|
||||||
this.areaTag = options.areaTag || '';
|
this.areaTag = options.areaTag || '';
|
||||||
this.meta = options.meta || {
|
this.meta = options.meta || {
|
||||||
// values we always want
|
// values we always want
|
||||||
user_rating : 0,
|
|
||||||
dl_count : 0,
|
dl_count : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.hashTags = options.hashTags || new Set();
|
this.hashTags = options.hashTags || new Set();
|
||||||
this.fileName = options.fileName;
|
this.fileName = options.fileName;
|
||||||
this.storageTag = options.storageTag;
|
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) {
|
load(fileId, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadBasicEntry(callback) {
|
function loadBasicEntry(callback) {
|
||||||
fileDb.get(
|
FileEntry.loadBasicEntry(fileId, self, callback);
|
||||||
`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);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
function loadMeta(callback) {
|
function loadMeta(callback) {
|
||||||
return self.loadMeta(callback);
|
return self.loadMeta(callback);
|
||||||
},
|
},
|
||||||
function loadHashTags(callback) {
|
function loadHashTags(callback) {
|
||||||
return self.loadHashTags(callback);
|
return self.loadHashTags(callback);
|
||||||
|
},
|
||||||
|
function loadUserRating(callback) {
|
||||||
|
return self.loadRating(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
|
@ -156,10 +166,19 @@ module.exports = class FileEntry {
|
||||||
return paths.join(storageDir, this.fileName);
|
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) {
|
static persistMetaValue(fileId, name, value, cb) {
|
||||||
fileDb.run(
|
return fileDb.run(
|
||||||
`REPLACE INTO file_meta (file_id, meta_name, meta_value)
|
`REPLACE INTO file_meta (file_id, meta_name, meta_value)
|
||||||
VALUES(?, ?, ?);`,
|
VALUES (?, ?, ?);`,
|
||||||
[ fileId, name, value ],
|
[ fileId, name, value ],
|
||||||
cb
|
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) {
|
setHashTags(hashTags) {
|
||||||
if(_.isString(hashTags)) {
|
if(_.isString(hashTags)) {
|
||||||
this.hashTags = new Set(hashTags.split(/[\s,]+/));
|
this.hashTags = new Set(hashTags.split(/[\s,]+/));
|
||||||
|
@ -264,7 +300,7 @@ module.exports = class FileEntry {
|
||||||
const sqlOrderDir = 'ascending' === filter.order ? 'ASC' : 'DESC';
|
const sqlOrderDir = 'ascending' === filter.order ? 'ASC' : 'DESC';
|
||||||
|
|
||||||
function getOrderByWithCast(ob) {
|
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)`;
|
return `ORDER BY CAST(${ob} AS INTEGER)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,11 +326,24 @@ module.exports = class FileEntry {
|
||||||
|
|
||||||
sqlOrderBy = `${getOrderByWithCast('m.meta_value')} ${sqlOrderDir}`;
|
sqlOrderBy = `${getOrderByWithCast('m.meta_value')} ${sqlOrderDir}`;
|
||||||
} else {
|
} else {
|
||||||
sql =
|
// additional special treatment for user ratings: we need to average them
|
||||||
`SELECT f.file_id, f.${filter.sort}
|
if('user_rating' === filter.sort) {
|
||||||
FROM file f`;
|
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 {
|
} else {
|
||||||
sql =
|
sql =
|
||||||
|
|
|
@ -166,7 +166,8 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuResult() {
|
getMenuResult() {
|
||||||
// nothing in base
|
// default to the formData that was provided @ a submit, if any
|
||||||
|
return this.submitFormData;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextMenu(cb) {
|
nextMenu(cb) {
|
||||||
|
@ -345,22 +346,42 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pausePrompt(position, cb) {
|
optionalMoveToPosition(position) {
|
||||||
if(!cb && _.isFunction(position)) {
|
|
||||||
cb = position;
|
|
||||||
position = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(position) {
|
if(position) {
|
||||||
position.x = position.row || position.x || 1;
|
position.x = position.row || position.x || 1;
|
||||||
position.y = position.col || position.y || 1;
|
position.y = position.col || position.y || 1;
|
||||||
|
|
||||||
this.client.term.rawWrite(ansi.goto(position.x, position.y));
|
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) {
|
setViewText(formName, mciId, text, appendMultiLine) {
|
||||||
const view = this.viewControllers[formName].getView(mciId);
|
const view = this.viewControllers[formName].getView(mciId);
|
||||||
if(!view) {
|
if(!view) {
|
||||||
|
|
|
@ -301,13 +301,17 @@ function renderStringLength(s) {
|
||||||
const SIZE_ABBRS = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ]; // :)
|
const SIZE_ABBRS = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ]; // :)
|
||||||
|
|
||||||
function formatByteSizeAbbr(byteSize) {
|
function formatByteSizeAbbr(byteSize) {
|
||||||
|
if(0 === byteSize) {
|
||||||
|
return SIZE_ABBRS[0]; // B
|
||||||
|
}
|
||||||
|
|
||||||
return SIZE_ABBRS[Math.floor(Math.log(byteSize) / Math.log(1024))];
|
return SIZE_ABBRS[Math.floor(Math.log(byteSize) / Math.log(1024))];
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatByteSize(byteSize, withAbbr, decimals) {
|
function formatByteSize(byteSize, withAbbr, decimals) {
|
||||||
withAbbr = withAbbr || false;
|
withAbbr = withAbbr || false;
|
||||||
decimals = decimals || 3;
|
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));
|
let result = parseFloat((byteSize / Math.pow(1024, i)).toFixed(decimals));
|
||||||
if(withAbbr) {
|
if(withAbbr) {
|
||||||
result += ` ${SIZE_ABBRS[i]}`;
|
result += ` ${SIZE_ABBRS[i]}`;
|
||||||
|
|
|
@ -63,9 +63,15 @@ function logoff(callingMenu, formData, extraArgs, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevMenu(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 => {
|
callingMenu.prevMenu( err => {
|
||||||
if(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);
|
return cb(err);
|
||||||
});
|
});
|
||||||
|
@ -74,7 +80,7 @@ function prevMenu(callingMenu, formData, extraArgs, cb) {
|
||||||
function nextMenu(callingMenu, formData, extraArgs, cb) {
|
function nextMenu(callingMenu, formData, extraArgs, cb) {
|
||||||
callingMenu.nextMenu( err => {
|
callingMenu.nextMenu( err => {
|
||||||
if(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);
|
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 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 fs = require('fs');
|
const fs = require('fs');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
|
@ -23,6 +24,7 @@ exports.setClientTheme = setClientTheme;
|
||||||
exports.initAvailableThemes = initAvailableThemes;
|
exports.initAvailableThemes = initAvailableThemes;
|
||||||
exports.displayThemeArt = displayThemeArt;
|
exports.displayThemeArt = displayThemeArt;
|
||||||
exports.displayThemedPause = displayThemedPause;
|
exports.displayThemedPause = displayThemedPause;
|
||||||
|
exports.displayThemedPrompt = displayThemedPrompt;
|
||||||
exports.displayThemedAsset = displayThemedAsset;
|
exports.displayThemedAsset = displayThemedAsset;
|
||||||
|
|
||||||
function refreshThemeHelpers(theme) {
|
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'.
|
// Pause prompts are a special prompt by the name 'pause'.
|
||||||
//
|
//
|
||||||
function displayThemedPause(options, cb) {
|
function displayThemedPause(client, options, cb) {
|
||||||
//
|
|
||||||
// options.client
|
if(!cb && _.isFunction(options)) {
|
||||||
// options clearPrompt
|
cb = options;
|
||||||
//
|
options = {};
|
||||||
assert(_.isObject(options.client));
|
}
|
||||||
|
|
||||||
if(!_.isBoolean(options.clearPrompt)) {
|
if(!_.isBoolean(options.clearPrompt)) {
|
||||||
options.clearPrompt = true;
|
options.clearPrompt = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: Support animated pause prompts. Probably via MCI with AnimatedView
|
const promptOptions = Object.assign( {}, options, { pause : true } );
|
||||||
|
return displayThemedPrompt('pause', client, promptOptions, cb);
|
||||||
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();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayThemedAsset(assetSpec, client, options, cb) {
|
function displayThemedAsset(assetSpec, client, options, cb) {
|
||||||
|
|
|
@ -184,52 +184,52 @@ function ViewController(options) {
|
||||||
propAsset = asset.getViewPropertyAsset(conf[propName]);
|
propAsset = asset.getViewPropertyAsset(conf[propName]);
|
||||||
if(propAsset) {
|
if(propAsset) {
|
||||||
switch(propAsset.type) {
|
switch(propAsset.type) {
|
||||||
case 'config' :
|
case 'config' :
|
||||||
propValue = asset.resolveConfigAsset(conf[propName]);
|
propValue = asset.resolveConfigAsset(conf[propName]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'sysStat' :
|
case 'sysStat' :
|
||||||
propValue = asset.resolveSystemStatAsset(conf[propName]);
|
propValue = asset.resolveSystemStatAsset(conf[propName]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// :TODO: handle @art (e.g. text : @art ...)
|
// :TODO: handle @art (e.g. text : @art ...)
|
||||||
|
|
||||||
case 'method' :
|
case 'method' :
|
||||||
case 'systemMethod' :
|
case 'systemMethod' :
|
||||||
if('validate' === propName) {
|
if('validate' === propName) {
|
||||||
// :TODO: handle propAsset.location for @method script specification
|
// :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 {
|
|
||||||
if('systemMethod' === propAsset.type) {
|
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 {
|
} else {
|
||||||
// local to current module
|
if(_.isFunction(self.client.currentMenuModule.menuMethods[propAsset.asset])) {
|
||||||
var currentModule = self.client.currentMenuModule;
|
propValue = self.client.currentMenuModule.menuMethods[propAsset.asset];
|
||||||
if(_.isFunction(currentModule.menuMethods[propAsset.asset])) {
|
}
|
||||||
// :TODO: Fix formData & extraArgs... this all needs general processing
|
}
|
||||||
propValue = currentModule.menuMethods[propAsset.asset]({}, {});//formData, conf.extraArgs);
|
} 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 :
|
default :
|
||||||
propValue = propValue = conf[propName];
|
propValue = propValue = conf[propName];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
propValue = conf[propName];
|
propValue = conf[propName];
|
||||||
|
@ -601,6 +601,33 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) {
|
||||||
|
|
||||||
callback(null);
|
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) {
|
function drawAllViews(callback) {
|
||||||
self.redrawAll(initialFocusId);
|
self.redrawAll(initialFocusId);
|
||||||
callback(null);
|
callback(null);
|
||||||
|
|
|
@ -99,8 +99,7 @@ exports.getModule = class AbracadabraModule extends MenuModule {
|
||||||
|
|
||||||
if(_.isString(self.config.tooManyArt)) {
|
if(_.isString(self.config.tooManyArt)) {
|
||||||
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() {
|
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() {
|
||||||
// :TODO: Use MenuModule.pausePrompt()
|
self.pausePrompt( () => {
|
||||||
theme.displayThemedPause( { client : self.client }, function keyPressed() {
|
|
||||||
callback(new Error('Too many active instances'));
|
callback(new Error('Too many active instances'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -108,7 +107,7 @@ exports.getModule = class AbracadabraModule extends MenuModule {
|
||||||
self.client.term.write('\nToo many active instances. Try again later.\n');
|
self.client.term.write('\nToo many active instances. Try again later.\n');
|
||||||
|
|
||||||
// :TODO: Use MenuModule.pausePrompt()
|
// :TODO: Use MenuModule.pausePrompt()
|
||||||
theme.displayThemedPause( { client : self.client }, function keyPressed() {
|
self.pausePrompt( () => {
|
||||||
callback(new Error('Too many active instances'));
|
callback(new Error('Too many active instances'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
||||||
},
|
},
|
||||||
newFilter : (formData, extraArgs, cb) => {
|
newFilter : (formData, extraArgs, cb) => {
|
||||||
this.currentFilterIndex = this.filtersArray.length; // next avail slot
|
this.currentFilterIndex = this.filtersArray.length; // next avail slot
|
||||||
this.clearForm(true); // true=reset focus
|
this.clearForm(MciViewIds.editor.searchTerms);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
deleteFilter : (formData, extraArgs, cb) => {
|
deleteFilter : (formData, extraArgs, cb) => {
|
||||||
|
@ -115,10 +115,12 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update UI
|
// update UI
|
||||||
|
this.updateActiveLabel();
|
||||||
|
|
||||||
if(this.filtersArray.length > 0) {
|
if(this.filtersArray.length > 0) {
|
||||||
this.loadDataForFilter(this.currentFilterIndex);
|
this.loadDataForFilter(this.currentFilterIndex);
|
||||||
} else {
|
} else {
|
||||||
this.clearForm(true); // true=reset focus
|
this.clearForm();
|
||||||
}
|
}
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
|
@ -203,7 +205,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearForm(setFocus) {
|
clearForm(newFocusId) {
|
||||||
[ MciViewIds.editor.searchTerms, MciViewIds.editor.tags, MciViewIds.editor.filterName ].forEach(mciId => {
|
[ MciViewIds.editor.searchTerms, MciViewIds.editor.tags, MciViewIds.editor.filterName ].forEach(mciId => {
|
||||||
this.setText(mciId, '');
|
this.setText(mciId, '');
|
||||||
});
|
});
|
||||||
|
@ -212,7 +214,9 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
||||||
this.setFocusItemIndex(mciId, 0);
|
this.setFocusItemIndex(mciId, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(setFocus) {
|
if(newFocusId) {
|
||||||
|
this.viewControllers.editor.switchFocus(newFocusId);
|
||||||
|
} else {
|
||||||
this.viewControllers.editor.resetInitialFocus();
|
this.viewControllers.editor.resetInitialFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,9 +78,17 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
|
|
||||||
this.dlQueue = new DownloadQueue(this.client);
|
this.dlQueue = new DownloadQueue(this.client);
|
||||||
|
|
||||||
this.filterCriteria = this.filterCriteria || {
|
if(!this.filterCriteria) {
|
||||||
// :TODO: set area tag - all in current area by default
|
this.filterCriteria = FileBaseFilters.getActiveFilter(this.client);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
if(_.isString(this.filterCriteria)) {
|
||||||
|
this.filterCriteria = JSON.parse(this.filterCriteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_.has(options, 'lastMenuResult.value')) {
|
||||||
|
this.lastMenuResultValue = options.lastMenuResult.value;
|
||||||
|
}
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
nextFile : (formData, extraArgs, cb) => {
|
nextFile : (formData, extraArgs, cb) => {
|
||||||
|
@ -116,7 +124,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
},
|
},
|
||||||
showWebDownloadLink : (formData, extraArgs, cb) => {
|
showWebDownloadLink : (formData, extraArgs, cb) => {
|
||||||
return this.fetchAndDisplayWebDownloadLink(cb);
|
return this.fetchAndDisplayWebDownloadLink(cb);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,11 +136,46 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
super.leave();
|
super.leave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSaveState() {
|
||||||
|
return {
|
||||||
|
fileList : this.fileList,
|
||||||
|
fileListPosition : this.fileListPosition,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreSavedState(savedState) {
|
||||||
|
if(savedState) {
|
||||||
|
this.fileList = savedState.fileList;
|
||||||
|
this.fileListPosition = savedState.fileListPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFileEntryWithMenuResult(cb) {
|
||||||
|
if(!this.lastMenuResultValue) {
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_.isNumber(this.lastMenuResultValue.rating)) {
|
||||||
|
const fileId = this.fileList[this.fileListPosition];
|
||||||
|
FileEntry.persistUserRating(fileId, this.client.user.userId, this.lastMenuResultValue.rating, err => {
|
||||||
|
if(err) {
|
||||||
|
this.client.log.warn( { error : err.message, fileId : fileId }, 'Failed to persist file rating' );
|
||||||
|
}
|
||||||
|
return cb(null);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
|
function preInit(callback) {
|
||||||
|
return self.updateFileEntryWithMenuResult(callback);
|
||||||
|
},
|
||||||
function beforeArt(callback) {
|
function beforeArt(callback) {
|
||||||
return self.beforeArt(callback);
|
return self.beforeArt(callback);
|
||||||
},
|
},
|
||||||
|
@ -165,11 +208,12 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
fileName : currEntry.fileName,
|
fileName : currEntry.fileName,
|
||||||
desc : currEntry.desc || '',
|
desc : currEntry.desc || '',
|
||||||
descLong : currEntry.descLong || '',
|
descLong : currEntry.descLong || '',
|
||||||
|
userRating : currEntry.userRating,
|
||||||
uploadTimestamp : moment(currEntry.uploadTimestamp).format(uploadTimestampFormat),
|
uploadTimestamp : moment(currEntry.uploadTimestamp).format(uploadTimestampFormat),
|
||||||
hashTags : Array.from(currEntry.hashTags).join(hashTagsSep),
|
hashTags : Array.from(currEntry.hashTags).join(hashTagsSep),
|
||||||
isQueued : this.dlQueue.isQueued(this.currentFileEntry) ? isQueuedIndicator : isNotQueuedIndicator,
|
isQueued : this.dlQueue.isQueued(this.currentFileEntry) ? isQueuedIndicator : isNotQueuedIndicator,
|
||||||
webDlLink : '', // :TODO: fetch web any existing web d/l link
|
webDlLink : '', // :TODO: fetch web any existing web d/l link
|
||||||
webDlExpire : '', // :TODO: fetch web d/l link expire time
|
webDlExpire : '', // :TODO: fetch web d/l link expire time
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -196,7 +240,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
// create a rating string, e.g. "**---"
|
// create a rating string, e.g. "**---"
|
||||||
const userRatingTicked = config.userRatingTicked || '*';
|
const userRatingTicked = config.userRatingTicked || '*';
|
||||||
const userRatingUnticked = config.userRatingUnticked || '';
|
const userRatingUnticked = config.userRatingUnticked || '';
|
||||||
entryInfo.userRating = entryInfo.userRating || 0; // be safe!
|
entryInfo.userRating = ~~Math.round(entryInfo.userRating) || 0; // be safe!
|
||||||
entryInfo.userRatingString = new Array(entryInfo.userRating + 1).join(userRatingTicked);
|
entryInfo.userRatingString = new Array(entryInfo.userRating + 1).join(userRatingTicked);
|
||||||
if(entryInfo.userRating < 5) {
|
if(entryInfo.userRating < 5) {
|
||||||
entryInfo.userRatingString += new Array( (5 - entryInfo.userRating) + 1).join(userRatingUnticked);
|
entryInfo.userRatingString += new Array( (5 - entryInfo.userRating) + 1).join(userRatingUnticked);
|
||||||
|
@ -297,7 +341,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
if(self.fileList) {
|
if(self.fileList) {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
return self.loadFileIds(callback);
|
return self.loadFileIds(false, callback); // false=do not force
|
||||||
},
|
},
|
||||||
function loadCurrentFileInfo(callback) {
|
function loadCurrentFileInfo(callback) {
|
||||||
self.currentFileEntry = new FileEntry();
|
self.currentFileEntry = new FileEntry();
|
||||||
|
@ -566,14 +610,13 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFileIds(cb) {
|
loadFileIds(force, cb) {
|
||||||
this.fileListPosition = 0;
|
if(force || (_.isUndefined(this.fileList) || _.isUndefined(this.fileListPosition))) {
|
||||||
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
this.fileListPosition = 0;
|
||||||
|
FileEntry.findFiles(this.filterCriteria, (err, fileIds) => {
|
||||||
FileEntry.findFiles(activeFilter, (err, fileIds) => {
|
this.fileList = fileIds;
|
||||||
this.fileList = fileIds;
|
return cb(err);
|
||||||
return cb(err);
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
/* jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ENiGMA½
|
||||||
|
const MenuModule = require('../core/menu_module.js').MenuModule;
|
||||||
|
const ViewController = require('../core/view_controller.js').ViewController;
|
||||||
|
const getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas;
|
||||||
|
const FileBaseFilters = require('../core/file_base_filter.js');
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const async = require('async');
|
||||||
|
|
||||||
|
exports.moduleInfo = {
|
||||||
|
name : 'File Base Search',
|
||||||
|
desc : 'Module for quickly searching the file base',
|
||||||
|
author : 'NuSkooler',
|
||||||
|
};
|
||||||
|
|
||||||
|
const MciViewIds = {
|
||||||
|
search : {
|
||||||
|
searchTerms : 1,
|
||||||
|
search : 2,
|
||||||
|
tags : 3,
|
||||||
|
area : 4,
|
||||||
|
orderBy : 5,
|
||||||
|
sort : 6,
|
||||||
|
advSearch : 7,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getModule = class FileBaseSearch extends MenuModule {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.menuMethods = {
|
||||||
|
search : (formData, extraArgs, cb) => {
|
||||||
|
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
||||||
|
return this.searchNow(formData, isAdvanced, cb);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mciReady(mciData, cb) {
|
||||||
|
super.mciReady(mciData, err => {
|
||||||
|
if(err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
const vc = self.addViewController( 'search', new ViewController( { client : this.client } ) );
|
||||||
|
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
function loadFromConfig(callback) {
|
||||||
|
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
||||||
|
},
|
||||||
|
function populateAreas(callback) {
|
||||||
|
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
||||||
|
|
||||||
|
const areasView = vc.getView(MciViewIds.search.area);
|
||||||
|
if(areasView) {
|
||||||
|
areasView.setItems( self.availAreas.map( a => a.name ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(null);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedAreaTag(index) {
|
||||||
|
if(0 === index) {
|
||||||
|
return ''; // -ALL-
|
||||||
|
}
|
||||||
|
const area = this.availAreas[index];
|
||||||
|
if(!area) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return area.areaTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrderBy(index) {
|
||||||
|
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSortBy(index) {
|
||||||
|
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilterValuesFromFormData(formData, isAdvanced) {
|
||||||
|
const areaIndex = isAdvanced ? formData.value.areaIndex : 0;
|
||||||
|
const orderByIndex = isAdvanced ? formData.value.orderByIndex : 0;
|
||||||
|
const sortByIndex = isAdvanced ? formData.value.sortByIndex : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
areaTag : this.getSelectedAreaTag(areaIndex),
|
||||||
|
terms : formData.value.searchTerms,
|
||||||
|
tags : isAdvanced ? formData.value.tags : '',
|
||||||
|
order : this.getOrderBy(orderByIndex),
|
||||||
|
sort : this.getSortBy(sortByIndex),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
searchNow(formData, isAdvanced, cb) {
|
||||||
|
const filterCriteria = this.getFilterValuesFromFormData(formData, isAdvanced);
|
||||||
|
|
||||||
|
const menuOpts = {
|
||||||
|
extraArgs : {
|
||||||
|
filterCriteria : filterCriteria,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb);
|
||||||
|
}
|
||||||
|
};
|
|
@ -6,7 +6,6 @@ const MenuModule = require('../core/menu_module.js').MenuModule;
|
||||||
const ViewController = require('../core/view_controller.js').ViewController;
|
const ViewController = require('../core/view_controller.js').ViewController;
|
||||||
const messageArea = require('../core/message_area.js');
|
const messageArea = require('../core/message_area.js');
|
||||||
const displayThemeArt = require('../core/theme.js').displayThemeArt;
|
const displayThemeArt = require('../core/theme.js').displayThemeArt;
|
||||||
const displayThemedPause = require('../core/theme.js').displayThemedPause;
|
|
||||||
const resetScreen = require('../core/ansi_term.js').resetScreen;
|
const resetScreen = require('../core/ansi_term.js').resetScreen;
|
||||||
const stringFormat = require('../core/string_format.js');
|
const stringFormat = require('../core/string_format.js');
|
||||||
|
|
||||||
|
@ -76,8 +75,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
||||||
if(_.has(area, 'options.pause') && false === area.options.pause) {
|
if(_.has(area, 'options.pause') && false === area.options.pause) {
|
||||||
return self.prevMenuOnTimeout(1000, cb);
|
return self.prevMenuOnTimeout(1000, cb);
|
||||||
} else {
|
} else {
|
||||||
// :TODO: Use MenuModule.pausePrompt()
|
self.pausePrompt( () => {
|
||||||
displayThemedPause( { client : self.client }, () => {
|
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ const MenuModule = require('../core/menu_module.js').MenuModule;
|
||||||
const ViewController = require('../core/view_controller.js').ViewController;
|
const ViewController = require('../core/view_controller.js').ViewController;
|
||||||
const messageArea = require('../core/message_area.js');
|
const messageArea = require('../core/message_area.js');
|
||||||
const displayThemeArt = require('../core/theme.js').displayThemeArt;
|
const displayThemeArt = require('../core/theme.js').displayThemeArt;
|
||||||
const displayThemedPause = require('../core/theme.js').displayThemedPause;
|
|
||||||
const resetScreen = require('../core/ansi_term.js').resetScreen;
|
const resetScreen = require('../core/ansi_term.js').resetScreen;
|
||||||
const stringFormat = require('../core/string_format.js');
|
const stringFormat = require('../core/string_format.js');
|
||||||
|
|
||||||
|
@ -63,8 +62,7 @@ exports.getModule = class MessageConfListModule extends MenuModule {
|
||||||
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
|
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
|
||||||
return self.prevMenuOnTimeout(1000, cb);
|
return self.prevMenuOnTimeout(1000, cb);
|
||||||
} else {
|
} else {
|
||||||
// :TODO: Use MenuModule.pausePrompt()
|
self.pausePrompt( () => {
|
||||||
displayThemedPause( { client : self.client }, () => {
|
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,34 @@
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
./\/\.' ENiGMA½ Prompt Configuration -/--/-------- - -- -
|
||||||
|
|
||||||
|
_____________________ _____ ____________________ __________\_ /
|
||||||
|
\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp!
|
||||||
|
// __|___// | \// |// | \// | | \// \ /___ /_____
|
||||||
|
/____ _____| __________ ___|__| ____| \ / _____ \
|
||||||
|
---- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/
|
||||||
|
/__ _\
|
||||||
|
<*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
This configuration is in HJSON (http://hjson.org/) format. Strict to-spec
|
||||||
|
JSON is also perfectly valid. Use 'hjson' from npm to convert to/from JSON.
|
||||||
|
|
||||||
|
See http://hjson.org/ for more information and syntax.
|
||||||
|
|
||||||
|
|
||||||
|
If you haven't yet, copy the conents of this file to something like
|
||||||
|
sick_board_prompt.hjson. Point to it via config.hjson using the
|
||||||
|
'general.promptFile' key:
|
||||||
|
|
||||||
|
general: { promptFile: "sick_board_prompt.hjson" }
|
||||||
|
|
||||||
|
*/
|
||||||
// :TODO: this entire file needs cleaned up a LOT
|
// :TODO: this entire file needs cleaned up a LOT
|
||||||
// :TODO: Convert all of this to HJSON
|
// :TODO: Convert all of this to HJSON
|
||||||
"prompts" : {
|
prompts: {
|
||||||
"userCredentials" : {
|
userCredentials: {
|
||||||
"art" : "usercred",
|
"art" : "usercred",
|
||||||
"mci" : {
|
"mci" : {
|
||||||
"ET1" : {
|
"ET1" : {
|
||||||
|
@ -106,6 +132,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// File Base Related
|
||||||
|
fileBaseRateEntryPrompt: {
|
||||||
|
art: RATEFILE
|
||||||
|
mci: {
|
||||||
|
SM1: {
|
||||||
|
argName: rating
|
||||||
|
items: [ "-----", "*----", "**---", "***--", "****-", "*****" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actionKeys: [
|
||||||
|
{
|
||||||
|
keys: [ "escape" ]
|
||||||
|
action: @systemMethod:prevMenu
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////
|
||||||
// Standard / Required
|
// Standard / Required
|
||||||
///////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
|
117
mods/upload.js
117
mods/upload.js
|
@ -7,11 +7,14 @@ const stringFormat = require('../core/string_format.js');
|
||||||
const getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas;
|
const getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas;
|
||||||
const getAreaDefaultStorageDirectory = require('../core/file_base_area.js').getAreaDefaultStorageDirectory;
|
const getAreaDefaultStorageDirectory = require('../core/file_base_area.js').getAreaDefaultStorageDirectory;
|
||||||
const scanFile = require('../core/file_base_area.js').scanFile;
|
const scanFile = require('../core/file_base_area.js').scanFile;
|
||||||
|
const getFileAreaByTag = require('../core/file_base_area.js').getFileAreaByTag;
|
||||||
const ansiGoto = require('../core/ansi_term.js').goto;
|
const ansiGoto = require('../core/ansi_term.js').goto;
|
||||||
const moveFileWithCollisionHandling = require('../core/file_util.js').moveFileWithCollisionHandling;
|
const moveFileWithCollisionHandling = require('../core/file_util.js').moveFileWithCollisionHandling;
|
||||||
const pathWithTerminatingSeparator = require('../core/file_util.js').pathWithTerminatingSeparator;
|
const pathWithTerminatingSeparator = require('../core/file_util.js').pathWithTerminatingSeparator;
|
||||||
const Log = require('../core/logger.js').log;
|
const Log = require('../core/logger.js').log;
|
||||||
const Errors = require('../core/enig_error.js').Errors;
|
const Errors = require('../core/enig_error.js').Errors;
|
||||||
|
const FileEntry = require('../core/file_entry.js');
|
||||||
|
const enigmaToAnsi = require('../core/color_codes.js').enigmaToAnsi;
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
@ -30,7 +33,7 @@ const FormIds = {
|
||||||
options : 0,
|
options : 0,
|
||||||
processing : 1,
|
processing : 1,
|
||||||
fileDetails : 2,
|
fileDetails : 2,
|
||||||
|
dupes : 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
|
@ -56,6 +59,10 @@ const MciViewIds = {
|
||||||
estYear : 3,
|
estYear : 3,
|
||||||
accept : 4, // accept fields & continue
|
accept : 4, // accept fields & continue
|
||||||
customRangeStart : 10, // 10+ = customs
|
customRangeStart : 10, // 10+ = customs
|
||||||
|
},
|
||||||
|
|
||||||
|
dupes : {
|
||||||
|
dupeList : 1,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,17 +168,6 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leave() {
|
|
||||||
// remove any temp files - only do this when
|
|
||||||
if(this.isFileTransferComplete()) {
|
|
||||||
temptmp.cleanup( paths => {
|
|
||||||
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' );
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
super.leave();
|
|
||||||
}
|
|
||||||
|
|
||||||
performUpload(cb) {
|
performUpload(cb) {
|
||||||
temptmp.mkdir( { prefix : 'enigul-' }, (err, tempRecvDirectory) => {
|
temptmp.mkdir( { prefix : 'enigul-' }, (err, tempRecvDirectory) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
|
@ -341,6 +337,12 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanupTempFiles() {
|
||||||
|
temptmp.cleanup( paths => {
|
||||||
|
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
moveAndPersistUploadsToDatabase(newEntries) {
|
moveAndPersistUploadsToDatabase(newEntries) {
|
||||||
|
|
||||||
const areaStorageDir = getAreaDefaultStorageDirectory(this.areaInfo);
|
const areaStorageDir = getAreaDefaultStorageDirectory(this.areaInfo);
|
||||||
|
@ -370,6 +372,11 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
return nextEntry(null); // still try next file
|
return nextEntry(null); // still try next file
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}, () => {
|
||||||
|
//
|
||||||
|
// Finally, we can remove any temp files that we may have created
|
||||||
|
//
|
||||||
|
self.cleanupTempFiles();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,6 +408,75 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayDupesPage(dupes, cb) {
|
||||||
|
//
|
||||||
|
// If we have custom art to show, use it - else just dump basic info.
|
||||||
|
// Pause at the end in either case.
|
||||||
|
//
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
function prepArtAndViewController(callback) {
|
||||||
|
self.prepViewControllerWithArt(
|
||||||
|
'dupes',
|
||||||
|
FormIds.dupes,
|
||||||
|
{ clearScreen : true, trailingLF : false },
|
||||||
|
err => {
|
||||||
|
if(err) {
|
||||||
|
self.client.term.pipeWrite('|00|07Duplicate upload(s) found:\n');
|
||||||
|
return callback(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dupeListView = self.viewControllers.dupes.getView(MciViewIds.dupes.dupeList);
|
||||||
|
return callback(null, dupeListView);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function prepDupeObjects(dupeListView, callback) {
|
||||||
|
// update dupe objects with additional info that can be used for formatString() and the like
|
||||||
|
async.each(dupes, (dupe, nextDupe) => {
|
||||||
|
FileEntry.loadBasicEntry(dupe.fileId, dupe, err => {
|
||||||
|
if(err) {
|
||||||
|
return nextDupe(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const areaInfo = getFileAreaByTag(dupe.areaTag);
|
||||||
|
if(areaInfo) {
|
||||||
|
dupe.areaName = areaInfo.name;
|
||||||
|
dupe.areaDesc = areaInfo.desc;
|
||||||
|
}
|
||||||
|
return nextDupe(null);
|
||||||
|
});
|
||||||
|
}, err => {
|
||||||
|
return callback(err, dupeListView);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function populateDupeInfo(dupeListView, callback) {
|
||||||
|
const dupeInfoFormat = self.menuConfig.config.dupeInfoFormat || '{fileName} @ {areaName}';
|
||||||
|
|
||||||
|
dupes.forEach(dupe => {
|
||||||
|
const formatted = stringFormat(dupeInfoFormat, dupe);
|
||||||
|
if(dupeListView) {
|
||||||
|
// dupesInfoFormatX
|
||||||
|
dupeListView.addText(enigmaToAnsi(formatted));
|
||||||
|
} else {
|
||||||
|
self.client.term.pipeWrite(`${formatted}\n`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return callback(null);
|
||||||
|
},
|
||||||
|
function pause(callback) {
|
||||||
|
return self.pausePrompt( { row : self.client.term.termHeight }, callback);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
processUploadedFiles() {
|
processUploadedFiles() {
|
||||||
//
|
//
|
||||||
// For each file uploaded, we need to process & gather information
|
// For each file uploaded, we need to process & gather information
|
||||||
|
@ -437,15 +513,16 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
|
|
||||||
self.pausePrompt( () => {
|
self.pausePrompt( () => {
|
||||||
return callback(null, scanResults);
|
return callback(null, scanResults);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function displayDupes(scanResults, callback) {
|
function displayDupes(scanResults, callback) {
|
||||||
if(0 === scanResults.dupes.length) {
|
if(0 === scanResults.dupes.length) {
|
||||||
return callback(null, scanResults);
|
return callback(null, scanResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: display dupe info
|
return self.displayDupesPage(scanResults.dupes, () => {
|
||||||
return callback(null, scanResults);
|
return callback(null, scanResults);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
function prepDetails(scanResults, callback) {
|
function prepDetails(scanResults, callback) {
|
||||||
return self.prepDetailsForUpload(scanResults, callback);
|
return self.prepDetailsForUpload(scanResults, callback);
|
||||||
|
@ -463,6 +540,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn('File upload error encountered', { error : err.message } );
|
self.client.log.warn('File upload error encountered', { error : err.message } );
|
||||||
|
self.cleanupTempFiles(); // normally called after moveAndPersistUploadsToDatabase() is completed.
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.prevMenu();
|
return self.prevMenu();
|
||||||
|
@ -565,13 +643,16 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
|
|
||||||
tagsView.setText( Array.from(fileEntry.hashTags).join(',') ); // :TODO: optional 'hashTagsSep' like file list/browse
|
tagsView.setText( Array.from(fileEntry.hashTags).join(',') ); // :TODO: optional 'hashTagsSep' like file list/browse
|
||||||
yearView.setText(fileEntry.meta.est_release_year || '');
|
yearView.setText(fileEntry.meta.est_release_year || '');
|
||||||
|
|
||||||
if(self.fileEntryHasDetectedDesc(fileEntry)) {
|
if(self.fileEntryHasDetectedDesc(fileEntry)) {
|
||||||
descView.setText(fileEntry.desc);
|
|
||||||
descView.setPropertyValue('mode', 'preview');
|
descView.setPropertyValue('mode', 'preview');
|
||||||
self.viewControllers.fileDetails.switchFocus(MciViewIds.fileDetails.tags);
|
descView.setText(fileEntry.desc);
|
||||||
descView.acceptsFocus = false;
|
descView.acceptsFocus = false;
|
||||||
|
self.viewControllers.fileDetails.switchFocus(MciViewIds.fileDetails.tags);
|
||||||
} else {
|
} else {
|
||||||
|
descView.setPropertyValue('mode', 'edit');
|
||||||
|
descView.setText('');
|
||||||
|
descView.acceptsFocus = true;
|
||||||
self.viewControllers.fileDetails.switchFocus(MciViewIds.fileDetails.desc);
|
self.viewControllers.fileDetails.switchFocus(MciViewIds.fileDetails.desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue