enigma-bbs/core/menu_view.js

357 lines
9.3 KiB
JavaScript
Raw Normal View History

/* jslint node: true */
'use strict';
// ENiGMA½
const View = require('./view.js').View;
const miscUtil = require('./misc_util.js');
const pipeToAnsi = require('./color_codes.js').pipeToAnsi;
// deps
const util = require('util');
const assert = require('assert');
const _ = require('lodash');
exports.MenuView = MenuView;
function MenuView(options) {
2022-01-23 16:46:09 +00:00
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
2022-01-23 16:46:09 +00:00
View.call(this, options);
2022-01-23 16:46:09 +00:00
this.disablePipe = options.disablePipe || false;
2022-01-23 16:46:09 +00:00
const self = this;
if (options.items) {
2022-01-23 16:46:09 +00:00
this.setItems(options.items);
} else {
this.items = [];
}
2022-01-23 16:46:09 +00:00
this.renderCache = {};
this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(
options.caseInsensitiveHotKeys,
true
);
2022-01-23 16:46:09 +00:00
this.setHotKeys(options.hotKeys);
2022-01-23 16:46:09 +00:00
this.focusedItemIndex = options.focusedItemIndex || 0;
this.focusedItemIndex =
this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0;
2014-11-01 15:50:11 +00:00
this.itemSpacing = _.isNumber(options.itemSpacing) ? options.itemSpacing : 0;
this.itemHorizSpacing = _.isNumber(options.itemHorizSpacing)
? options.itemHorizSpacing
: 0;
2022-01-23 16:46:09 +00:00
// :TODO: probably just replace this with owner draw / pipe codes / etc. more control, less specialization
this.focusPrefix = options.focusPrefix || '';
this.focusSuffix = options.focusSuffix || '';
this.fillChar = miscUtil.valueWithDefault(options.fillChar, ' ').substr(0, 1);
this.hasFocusItems = function () {
2022-01-23 16:46:09 +00:00
return !_.isUndefined(self.focusItems);
};
2015-12-24 02:08:24 +00:00
this.getHotKeyItemIndex = function (ch) {
if (ch && self.hotKeys) {
const keyIndex =
self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch];
if (_.isNumber(keyIndex)) {
2022-01-23 16:46:09 +00:00
return keyIndex;
}
}
return -1;
};
2018-06-10 04:45:01 +00:00
this.emitIndexUpdate = function () {
2022-01-23 16:46:09 +00:00
self.emit('index update', self.focusedItemIndex);
};
}
util.inherits(MenuView, View);
MenuView.prototype.setTextOverflow = function (overflow) {
this.textOverflow = overflow;
this.invalidateRenderCache();
};
2022-01-19 19:07:02 +00:00
MenuView.prototype.hasTextOverflow = function () {
return this.textOverflow != undefined;
};
2022-01-19 19:07:02 +00:00
MenuView.prototype.setItems = function (items) {
if (Array.isArray(items)) {
2022-01-23 16:46:09 +00:00
this.sorted = false;
this.renderCache = {};
//
// Items can be an array of strings or an array of objects.
//
// In the case of objects, items are considered complex and
// may have one or more members that can later be formatted
// against. The default member is 'text'. The member 'data'
// may be overridden to provide a form value other than the
// item's index.
//
// Items can be formatted with 'itemFormat' and 'focusItemFormat'
//
let text;
let stringItem;
this.items = items.map(item => {
stringItem = _.isString(item);
if (stringItem) {
2022-01-23 16:46:09 +00:00
text = item;
} else {
text = item.text || '';
this.complexItems = true;
}
text = this.disablePipe ? text : pipeToAnsi(text, this.client);
return Object.assign({}, { text }, stringItem ? {} : item); // ensure we have a text member, plus any others
2022-01-23 16:46:09 +00:00
});
if (this.complexItems) {
2022-01-23 16:46:09 +00:00
this.itemFormat = this.itemFormat || '{text}';
}
2022-01-16 18:12:41 +00:00
2022-01-23 16:46:09 +00:00
this.invalidateRenderCache();
}
};
MenuView.prototype.getRenderCacheItem = function (index, focusItem = false) {
2022-01-23 16:46:09 +00:00
const item = this.renderCache[index];
return item && item[focusItem ? 'focus' : 'standard'];
};
MenuView.prototype.removeRenderCacheItem = function (index) {
2022-01-23 16:46:09 +00:00
delete this.renderCache[index];
2018-07-05 00:48:35 +00:00
};
MenuView.prototype.setRenderCacheItem = function (index, rendered, focusItem = false) {
2022-01-23 16:46:09 +00:00
this.renderCache[index] = this.renderCache[index] || {};
this.renderCache[index][focusItem ? 'focus' : 'standard'] = rendered;
};
MenuView.prototype.invalidateRenderCache = function () {
2022-01-23 16:46:09 +00:00
this.renderCache = {};
2018-07-05 00:48:35 +00:00
};
MenuView.prototype.setSort = function (sort) {
if (this.sorted || !Array.isArray(this.items) || 0 === this.items.length) {
2022-01-23 16:46:09 +00:00
return;
}
2022-01-23 16:46:09 +00:00
const key = true === sort ? 'text' : sort;
if ('text' !== sort && !this.complexItems) {
2022-01-23 16:46:09 +00:00
return; // need a valid sort key
}
this.items.sort((a, b) => {
2022-01-23 16:46:09 +00:00
const a1 = a[key];
const b1 = b[key];
if (!a1) {
2022-01-23 16:46:09 +00:00
return -1;
}
if (!b1) {
2022-01-23 16:46:09 +00:00
return 1;
}
return a1.localeCompare(b1, { sensitivity: false, numeric: true });
2022-01-23 16:46:09 +00:00
});
this.sorted = true;
2018-01-28 20:03:11 +00:00
};
MenuView.prototype.removeItem = function (index) {
2022-01-23 16:46:09 +00:00
this.sorted = false;
this.items.splice(index, 1);
if (this.focusItems) {
2022-01-23 16:46:09 +00:00
this.focusItems.splice(index, 1);
}
if (this.focusedItemIndex >= index) {
2022-01-23 16:46:09 +00:00
this.focusedItemIndex = Math.max(this.focusedItemIndex - 1, 0);
}
2022-01-23 16:46:09 +00:00
this.removeRenderCacheItem(index);
2018-07-05 00:48:35 +00:00
2022-01-23 16:46:09 +00:00
this.positionCacheExpired = true;
};
MenuView.prototype.getCount = function () {
2022-01-23 16:46:09 +00:00
return this.items.length;
2016-01-05 06:32:43 +00:00
};
MenuView.prototype.getItems = function () {
if (this.complexItems) {
2022-01-23 16:46:09 +00:00
return this.items;
}
return this.items.map(item => {
2022-01-23 16:46:09 +00:00
return item.text;
});
2016-01-05 06:32:43 +00:00
};
MenuView.prototype.getItem = function (index) {
if (index > this.items.length - 1) {
return null;
}
if (this.complexItems) {
2022-01-23 16:46:09 +00:00
return this.items[index];
}
2022-01-23 16:46:09 +00:00
return this.items[index].text;
};
MenuView.prototype.focusNext = function () {
2022-01-23 16:46:09 +00:00
this.emitIndexUpdate();
};
MenuView.prototype.focusPrevious = function () {
2022-01-23 16:46:09 +00:00
this.emitIndexUpdate();
};
MenuView.prototype.focusNextPageItem = function () {
2022-01-23 16:46:09 +00:00
this.emitIndexUpdate();
};
MenuView.prototype.focusPreviousPageItem = function () {
2022-01-23 16:46:09 +00:00
this.emitIndexUpdate();
2018-06-10 04:45:01 +00:00
};
MenuView.prototype.focusFirst = function () {
2022-01-23 16:46:09 +00:00
this.emitIndexUpdate();
2018-06-10 04:45:01 +00:00
};
MenuView.prototype.focusLast = function () {
2022-01-23 16:46:09 +00:00
this.emitIndexUpdate();
};
MenuView.prototype.setFocusItemIndex = function (index) {
2022-01-23 16:46:09 +00:00
this.focusedItemIndex = index;
2015-12-24 02:08:24 +00:00
};
MenuView.prototype.getFocusItemIndex = function () {
return this.focusedItemIndex;
};
MenuView.prototype.onKeyPress = function (ch, key) {
2022-01-23 16:46:09 +00:00
const itemIndex = this.getHotKeyItemIndex(ch);
if (itemIndex >= 0) {
2022-01-23 16:46:09 +00:00
this.setFocusItemIndex(itemIndex);
2015-12-24 02:08:24 +00:00
if (true === this.hotKeySubmit) {
2022-01-23 16:46:09 +00:00
this.emit('action', 'accept');
}
}
2015-12-24 02:08:24 +00:00
2022-01-23 16:46:09 +00:00
MenuView.super_.prototype.onKeyPress.call(this, ch, key);
2015-12-24 02:08:24 +00:00
};
MenuView.prototype.setFocusItems = function (items) {
2022-01-23 16:46:09 +00:00
const self = this;
if (items) {
2022-01-23 16:46:09 +00:00
this.focusItems = [];
items.forEach(itemText => {
this.focusItems.push({
text: self.disablePipe ? itemText : pipeToAnsi(itemText, self.client),
});
2022-01-23 16:46:09 +00:00
});
}
};
MenuView.prototype.setItemSpacing = function (itemSpacing) {
2022-01-23 16:46:09 +00:00
itemSpacing = parseInt(itemSpacing);
assert(_.isNumber(itemSpacing));
this.itemSpacing = itemSpacing;
this.positionCacheExpired = true;
};
MenuView.prototype.setItemHorizSpacing = function (itemHorizSpacing) {
2022-01-23 16:46:09 +00:00
itemHorizSpacing = parseInt(itemHorizSpacing);
assert(_.isNumber(itemHorizSpacing));
2022-01-12 16:38:47 +00:00
this.itemHorizSpacing = itemHorizSpacing;
this.positionCacheExpired = true;
2022-01-12 16:38:47 +00:00
};
MenuView.prototype.setPropertyValue = function (propName, value) {
switch (propName) {
case 'itemSpacing':
this.setItemSpacing(value);
break;
case 'itemHorizSpacing':
this.setItemHorizSpacing(value);
break;
case 'items':
this.setItems(value);
break;
case 'focusItems':
this.setFocusItems(value);
break;
case 'hotKeys':
this.setHotKeys(value);
break;
case 'textOverflow':
this.setTextOverflow(value);
break;
case 'hotKeySubmit':
this.hotKeySubmit = value;
break;
case 'justify':
this.setJustify(value);
break;
case 'fillChar':
this.setFillChar(value);
break;
case 'focusItemIndex':
this.focusedItemIndex = value;
break;
case 'itemFormat':
case 'focusItemFormat':
2022-01-23 16:46:09 +00:00
this[propName] = value;
// if there is a cache currently, invalidate it
this.invalidateRenderCache();
break;
case 'sort':
this.setSort(value);
break;
2022-01-23 16:46:09 +00:00
}
MenuView.super_.prototype.setPropertyValue.call(this, propName, value);
};
MenuView.prototype.setFillChar = function (fillChar) {
this.fillChar = miscUtil.valueWithDefault(fillChar, ' ').substr(0, 1);
this.invalidateRenderCache();
};
2022-01-19 21:11:33 +00:00
MenuView.prototype.setJustify = function (justify) {
this.justify = justify;
this.invalidateRenderCache();
this.positionCacheExpired = true;
};
2022-01-19 20:36:43 +00:00
MenuView.prototype.setHotKeys = function (hotKeys) {
if (_.isObject(hotKeys)) {
if (this.caseInsensitiveHotKeys) {
2022-01-23 16:46:09 +00:00
this.hotKeys = {};
for (var key in hotKeys) {
2022-01-23 16:46:09 +00:00
this.hotKeys[key.toLowerCase()] = hotKeys[key];
}
} else {
this.hotKeys = hotKeys;
}
}
};