diff --git a/core/asset.js b/core/asset.js index 3a496b0c..8f76dee9 100644 --- a/core/asset.js +++ b/core/asset.js @@ -8,6 +8,7 @@ var assert = require('assert'); exports.parseAsset = parseAsset; exports.getArtAsset = getArtAsset; +exports.resolveConfigAsset = resolveConfigAsset; var ALL_ASSETS = [ 'art', @@ -15,6 +16,7 @@ var ALL_ASSETS = [ 'method', 'systemMethod', 'prompt', + 'config', ]; var ASSET_RE = new RegExp('\\@(' + ALL_ASSETS.join('|') + ')\\:([\\w\\d\\.]*)(?:\\/([\\w\\d\\_]+))*'); @@ -36,7 +38,7 @@ function parseAsset(s) { } } -function getArtAsset(art, cb) { +function getArtAsset(art) { if(!_.isString(art)) { return null; } @@ -53,3 +55,22 @@ function getArtAsset(art, cb) { }; } } + +function resolveConfigAsset(from) { + var asset = parseAsset(from); + if(asset) { + assert('config' === asset.type); + + var path = asset.asset.split('.'); + var conf = Config; + for(var i = 0; i < path.length; ++i) { + if(_.isUndefined(conf[path[i]])) { + return from; + } + conf = conf[path[i]]; + } + return conf; + } else { + return from; + } +} diff --git a/core/config.js b/core/config.js index 49f9d103..c08a0537 100644 --- a/core/config.js +++ b/core/config.js @@ -83,7 +83,7 @@ function getDefaultConfig() { usernameMax : 22, usernamePattern : '^[A-Za-z0-9~!@#$%^&*()\\-\\_+]+$', passwordMin : 6, - passwordMax : 256, + passwordMax : 128, requireActivation : true, // require SysOp activation? invalidUsernames : [], }, diff --git a/core/edit_text_view.js b/core/edit_text_view.js index 01a1b1ba..5a26976f 100644 --- a/core/edit_text_view.js +++ b/core/edit_text_view.js @@ -14,9 +14,11 @@ function EditTextView(options) { options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true); options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true); options.resizable = false; - + TextView.call(this, options); + this.cursorPos = { x : 0 }; + this.clientBackspace = function() { this.client.term.write( '\b' + this.getANSIColor(this.getColor()) + this.fillChar + '\b' + this.getANSIColor(this.getFocusColor())); @@ -33,15 +35,22 @@ EditTextView.prototype.onKeyPress = function(key, isSpecial) { assert(1 === key.length); // :TODO: how to handle justify left/center? - if(!_.isNumber(this.maxLength) || this.text.length < this.maxLength) { + if(this.text.length < this.maxLength) { key = strUtil.stylizeString(key, this.textStyle); this.text += key; - if(this.textMaskChar) { - this.client.term.write(this.textMaskChar); + if(this.text.length > this.dimens.width) { + // no shortcuts - redraw the view + this.redraw(); } else { - this.client.term.write(key); + this.cursorPos.x += 1; + + if(this.textMaskChar) { + this.client.term.write(this.textMaskChar); + } else { + this.client.term.write(key); + } } } @@ -54,9 +63,18 @@ EditTextView.prototype.onSpecialKeyPress = function(keyName) { if(this.isSpecialKeyMapped('backspace', keyName)) { if(this.text.length > 0) { this.text = this.text.substr(0, this.text.length - 1); - this.clientBackspace(); + + if(this.text.length >= this.dimens.width) { + this.redraw(); + } else { + this.cursorPos.x -= 1; + if(this.cursorPos.x >= 0) { + this.clientBackspace(); + } + } } } + EditTextView.super_.prototype.onSpecialKeyPress.call(this, keyName); }; \ No newline at end of file diff --git a/core/mci_view_factory.js b/core/mci_view_factory.js index 4a3890b2..e5fe2bf9 100644 --- a/core/mci_view_factory.js +++ b/core/mci_view_factory.js @@ -70,6 +70,17 @@ MCIViewFactory.prototype.createFromMCI = function(mci) { return false; } + function setWidth(pos) { + if(mci.args.length > pos && mci.args[pos].length > 0) { + if(!_.isObject(options.dimens)) { + options.dimens = {}; + } + options.dimens.width = parseInt(mci.args[pos], 10); + return true; + } + return false; + } + function setFocusOption(pos, name) { if(mci.focusArgs && mci.focusArgs.length > pos && mci.focusArgs[pos].length > 0) { options[name] = mci.focusArgs[pos]; @@ -82,30 +93,31 @@ MCIViewFactory.prototype.createFromMCI = function(mci) { case 'TL' : setOption(0, 'textStyle'); setOption(1, 'justify'); + setWidth(2); + + + /* if(setOption(2, 'maxLength')) { options.maxLength = parseInt(options.maxLength, 10); options.dimens = { width : options.maxLength }; } + */ view = new TextView(options); break; // Edit Text case 'ET' : + setWidth(0); + /* if(setOption(0, 'maxLength')) { options.maxLength = parseInt(options.maxLength, 10); // ensure number options.dimens = { width : options.maxLength }; } + */ - setOption(1, 'textStyle'); - - if(options.textStyle === 'P') { - // Assign the proper password character / text mask - assert(_.isObject(this.client.currentThemeInfo)); - options.textMaskChar = this.client.currentThemeInfo.getPasswordChar(); - } - - setFocusOption(0, 'focusTextStyle'); + setOption(1, 'textStyle'); + setFocusOption(0, 'focusTextStyle'); view = new EditTextView(options); break; @@ -117,11 +129,14 @@ MCIViewFactory.prototype.createFromMCI = function(mci) { if(options.text) { setOption(1, 'textStyle'); setOption(2, 'justify'); + setWidth(3); + /* if(setOption(3, 'maxLength')) { options.maxLength = parseInt(options.maxLength, 10); options.dimens = { width : options.maxLength }; } + */ view = new TextView(options); } diff --git a/core/menu_util.js b/core/menu_util.js index 58a09977..e474d290 100644 --- a/core/menu_util.js +++ b/core/menu_util.js @@ -148,6 +148,7 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) { var formForId = menuConfig.form[formId]; var mciReqKey = _.sortBy(Object.keys(mciMap), String).join(''); + Log.trace( { mciKey : mciReqKey }, 'Looking for MCI configuration key'); if(_.isObject(formForId[mciReqKey])) { cb(null, formForId[mciReqKey]); return; diff --git a/core/text_view.js b/core/text_view.js index 2b37a810..59c9859f 100644 --- a/core/text_view.js +++ b/core/text_view.js @@ -5,8 +5,10 @@ var View = require('./view.js').View; var miscUtil = require('./misc_util.js'); var strUtil = require('./string_util.js'); var ansi = require('./ansi_term.js'); + var util = require('util'); var assert = require('assert'); +var _ = require('lodash'); exports.TextView = TextView; @@ -17,27 +19,52 @@ function TextView(options) { if(options.maxLength) { this.maxLength = options.maxLength; + } else { + this.maxLength = this.client.term.termWidth - this.position.x; } - this.multiLine = options.multiLine || false; - this.fillChar = miscUtil.valueWithDefault(options.fillChar, ' ').substr(0, 1); - this.justify = options.justify || 'right'; - this.resizable = miscUtil.valueWithDefault(options.resizable, true); - //this.inputType = options.inputType || 'normal'; + this.fillChar = miscUtil.valueWithDefault(options.fillChar, ' ').substr(0, 1); + this.justify = options.justify || 'right'; + this.resizable = miscUtil.valueWithDefault(options.resizable, true); + this.horizScroll = miscUtil.valueWithDefault(options.horizScroll, true); - this.isPasswordTextStyle = 'P' === this.textStyle || 'password' === this.textStyle; - - assert(!this.multiLine); // :TODO: not yet supported - - if(!this.multiLine) { - this.dimens.height = 1; + if(_.isString(options.textMaskChar) && 1 === options.textMaskChar.length) { + this.textMaskChar = options.textMaskChar; } - + + this.dimens.height = 1; this.drawText = function(s) { - var ansiColor = this.getANSIColor(this.hasFocus ? this.getFocusColor() : this.getColor()); - if(this.isPasswordTextStyle) { + // + // ABCDEFGHIJK + // |ABCDEFG| ^_ length + // ^-- dimens.width + // + + var textToDraw = _.isString(this.textMaskChar) ? + new Array(s.length + 1).join(this.textMaskChar) : + strUtil.stylizeString(s, this.hasFocus ? this.focusTextStyle : this.textStyle); + + if(textToDraw.length > this.dimens.width) { + // XXXXXXXXXXXXXXXXX + // this is the text but too long + // text but too long + if(this.horizScroll) { + textToDraw = textToDraw.substr(textToDraw.length - this.dimens.width, textToDraw.length);//0, this.dimens.width);//textToDraw.length - (this.dimens.width + 1)); + } + } + + var textAnsiColor = this.getANSIColor(this.hasFocus ? this.getFocusColor() : this.getColor()); + var fillAnsiColor = this.getANSIColor(this.getColor()); + + //console.log(textToDraw) + ///console.log(this.dimens.width + 1) + + this.client.term.write(strUtil.pad(textToDraw, this.dimens.width + 1, this.fillChar, this.justify, textAnsiColor, fillAnsiColor)); + +/* + if(_.isString(this.textMaskChar)) { this.client.term.write(strUtil.pad( new Array(s.length + 1).join(this.textMaskChar), this.dimens.width + 1, @@ -55,13 +82,10 @@ function TextView(options) { ansiColor, this.getANSIColor(this.getColor()))); } + */ }; this.setText(options.text || ''); - - if(this.isPasswordTextStyle) { - this.textMaskChar = miscUtil.valueWithDefault(options.textMaskChar, '*').substr(0, 1); - } } util.inherits(TextView, View); @@ -99,14 +123,8 @@ TextView.prototype.setText = function(text) { this.text = strUtil.stylizeString(this.text, this.hasFocus ? this.focusTextStyle : this.textStyle); - /*if(!this.multiLine && !this.dimens.width) { - this.dimens.width = this.text.length; - }*/ - - if(!this.multiLine) { - if(this.resizable) { - this.dimens.width = this.text.length + widthDelta; - } + if(this.resizable) { + this.dimens.width = this.text.length + widthDelta; } this.redraw(); diff --git a/core/view.js b/core/view.js index 576a9834..0db3c4f0 100644 --- a/core/view.js +++ b/core/view.js @@ -49,9 +49,6 @@ function View(options) { this.setPosition(options.position); } - -// this.isPasswordTextStyle = 'P' === this.textStyle || 'password' === this.textStyle; - // :TODO: Don't allow width/height > client.term if(options.dimens && options.dimens.height) { this.dimens.height = options.dimens.height; diff --git a/core/view_controller.js b/core/view_controller.js index eb68978d..5c7d08c0 100644 --- a/core/view_controller.js +++ b/core/view_controller.js @@ -120,6 +120,7 @@ function ViewController(options) { }; this.getLogFriendlyFormData = function(formData) { + // :TODO: these fields should be part of menu.json sensitiveMembers[] var safeFormData = _.cloneDeep(formData); if(safeFormData.value.password) { safeFormData.value.password = '*****'; @@ -149,8 +150,6 @@ function ViewController(options) { view.on('action', self.viewActionListener); self.addView(view); - - view.redraw(); // :TODO: fix double-redraw if this is the item we set focus to! } nextItem(null); @@ -162,21 +161,54 @@ function ViewController(options) { }; this.setViewPropertiesFromMCIConf = function(view, conf) { - if(_.isBoolean(conf.submit)) { - view.submit = conf.submit; - } else if(_.isArray(conf.submit) && conf.submit.length > 0) { - view.submit = true; + + function getViewProp(propName) { + if(conf[propName]) { + return asset.resolveConfigAsset(conf[propName]); + } + } + + function setSimpleViewProp(propName) { + var propValue = getViewProp(propName); + if(propValue) { + view[propName] = propValue; + } + } + + var value; + + value = getViewProp('items'); + if(value) { + view.setItems(value); + } + + value = getViewProp('text'); + if(value) { + view.setText(value); + } + + setSimpleViewProp('textStyle'); + setSimpleViewProp('focusTextStyle'); + setSimpleViewProp('fillChar'); + setSimpleViewProp('maxLength'); + + value = getViewProp('textMaskChar'); + if(_.isString(value)) { + view.textMaskChar = value.substr(0, 1); + } else if(value && true === value) { + // + // Option that should normally be used in order to + // get the password character from Config/theme + // + view.textMaskChar = self.client.currentThemeInfo.getPasswordChar(); + } + + + value = getViewProp('submit'); + if(_.isBoolean(value)) { + view.submit = value; } else { - view.submit = false; - } - //view.submit = conf.submit || false; - - if(_.isArray(conf.items)) { - view.setItems(conf.items); - } - - if(_.isString(conf.text)) { - view.setText(conf.text); + view.submit = _.isArray(value) && value.length > 0; } if(_.isString(conf.argName)) { @@ -196,6 +228,13 @@ function ViewController(options) { assert(!isNaN(viewId)); var view = self.getView(viewId); + + if(!view) { + Log.warn( { viewId : viewId }, 'Cannot find view'); + nextItem(null); + return; + } + var mciConf = config.mci[mci]; self.setViewPropertiesFromMCIConf(view, mciConf); @@ -328,6 +367,7 @@ ViewController.prototype.setViewOrder = function(order) { } }; +/* ViewController.prototype.loadFromMCIMap = function(mciMap) { var factory = new MCIViewFactory(this.client); var self = this; @@ -343,6 +383,7 @@ ViewController.prototype.loadFromMCIMap = function(mciMap) { } }); }; +*/ ViewController.prototype.loadFromPromptConfig = function(options, cb) { assert(_.isObject(options)); @@ -375,6 +416,15 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) { callback(null); }, + function drawAllViews(callback) { + for(var id in self.views) { + if(initialFocusId === id) { + continue; // will draw @ focus + } + self.views[id].redraw(); + } + callback(null); + }, function setInitialViewFocus(callback) { if(initialFocusId) { self.switchFocus(initialFocusId); @@ -501,6 +551,15 @@ ViewController.prototype.loadFromMenuConfig = function(options, cb) { callback(null); }, + function drawAllViews(callback) { + for(var id in self.views) { + if(initialFocusId === id) { + continue; // will draw @ focus + } + self.views[id].redraw(); + } + callback(null); + }, function setInitialViewFocus(callback) { if(initialFocusId) { self.switchFocus(initialFocusId); diff --git a/mods/menu.json b/mods/menu.json index 1e246e1b..6ac170de 100644 --- a/mods/menu.json +++ b/mods/menu.json @@ -99,20 +99,41 @@ "form" : { "0" : { "BT12BT13ET1ET10ET2ET3ET4ET5ET6ET7ET8ET9TL11" : { + // + // "beforeViewsDraw" : "@method:location.js/myBeforeViewsDraw" -> myBeforeViewsDraw(views) "mci" : { "ET1" : { "focus" : true, "argName" : "username" }, - "ET2" : { "argName" : "realName" }, + "ET2" : { + "argName" : "realName", + "maxLength" : "@config:users.usernameMax" + }, "ET3" : { "argName" : "age" }, "ET4" : { "argName" : "sex" }, "ET5" : { "argName" : "location" }, - "ET6" : { "argName" : "affils" }, - "ET7" : { "argName" : "email" }, - "ET8" : { "argName" : "web" }, - "ET9" : { "argName" : "password" }, - "ET10" : { "argName" : "passwordConfirm" }, + "ET6" : { + "argName" : "affils", + "maxLength" : 30 + }, + "ET7" : { + "argName" : "email", + "maxLength" : 255 + }, + "ET8" : { + "argName" : "web", + "maxLength" : 255 + }, + "ET9" : { + "argName" : "password", + "password" : true + }, + "ET10" : { + "argName" : "passwordConfirm", + "password" : true, + "maxLength" : "@config:users.passwordMax" + }, "BT12" : { "submit" : true, "text" : "Apply" @@ -162,7 +183,8 @@ "VM1" : { "mci" : { "VM1" : { - "items" : [ "Edit Text View", "Other" ] + "items" : [ "Edit Text View", "Other" ], + "focusTextStyle" : "U" } }, "submit" : { @@ -182,7 +204,17 @@ "options" : { "cls" : true }, "form" : { "0" : { - "CB4ET1ET2ET3ET4ET5" : { + "CB4ET1ET2ET3ET5" : { + "mci" : { + "ET1" : { "maxLength" : 1 }, + "ET2" : { "maxLength" : 1 }, + "ET3" : { "maxLength" : 1 }, + "ET5" : { + "password" : true, + "submit" : [ "esc" ], + "fillChar" : "#" + } + } } } } diff --git a/mods/prompt.json b/mods/prompt.json index cf873d5a..1ea11ddd 100644 --- a/mods/prompt.json +++ b/mods/prompt.json @@ -7,7 +7,8 @@ }, "ET2" : { "submit" : true, - "argName" : "password" + "argName" : "password", + "password" : true } } }