Added support for multiple pages
This commit is contained in:
parent
3c71f88cb8
commit
4fe55e1e1b
|
@ -21,8 +21,15 @@ function FullMenuView(options) {
|
||||||
|
|
||||||
MenuView.call(this, options);
|
MenuView.call(this, options);
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize paging
|
||||||
|
this.pages = [];
|
||||||
|
this.currentPage = 0;
|
||||||
|
|
||||||
this.initDefaultWidth();
|
this.initDefaultWidth();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
// we want page up/page down by default
|
// we want page up/page down by default
|
||||||
|
@ -39,48 +46,111 @@ function FullMenuView(options) {
|
||||||
this.dimens.height = Math.min(this.dimens.height, this.client.term.termHeight - this.position.row);
|
this.dimens.height = Math.min(this.dimens.height, this.client.term.termHeight - this.position.row);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate number of items visible after adjusting height
|
this.positionCacheExpired = true;
|
||||||
this.itemsPerRow = Math.floor(this.dimens.height / (this.itemSpacing + 1));
|
|
||||||
// handle case where one can fit at the end
|
|
||||||
if (this.dimens.height > (this.itemsPerRow * (this.itemSpacing + 1))) {
|
|
||||||
this.itemsPerRow++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final check to make sure we don't try to display more than we have
|
|
||||||
if (this.itemsPerRow > this.items.length) {
|
|
||||||
this.itemsPerRow = this.items.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.autoAdjustHeightIfEnabled();
|
this.autoAdjustHeightIfEnabled();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.getSpacer = function() {
|
this.getSpacer = function() {
|
||||||
return new Array(self.itemHorizSpacing + 1).join(this.fillChar);
|
return new Array(self.itemHorizSpacing + 1).join(this.fillChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.clearPage = function() {
|
||||||
|
for (var i = 0; i < this.itemsPerRow; i++) {
|
||||||
|
let text = `${strUtil.pad(' ', this.dimens.width, this.fillChar, 'left')}`;
|
||||||
|
self.client.term.write(`${ansi.goto(this.position.row + i, this.position.col)}${text}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.cachePositions = function() {
|
this.cachePositions = function() {
|
||||||
if (this.positionCacheExpired) {
|
if (this.positionCacheExpired) {
|
||||||
this.autoAdjustHeightIfEnabled();
|
this.autoAdjustHeightIfEnabled();
|
||||||
|
|
||||||
var col = self.position.col;
|
this.pages = []; // reset
|
||||||
var row = self.position.row;
|
|
||||||
var spacer = self.getSpacer();
|
// Calculate number of items visible per column
|
||||||
|
this.itemsPerRow = Math.floor(this.dimens.height / (this.itemSpacing + 1));
|
||||||
|
// handle case where one can fit at the end
|
||||||
|
if (this.dimens.height > (this.itemsPerRow * (this.itemSpacing + 1))) {
|
||||||
|
this.itemsPerRow++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final check to make sure we don't try to display more than we have
|
||||||
|
if (this.itemsPerRow > this.items.length) {
|
||||||
|
this.itemsPerRow = this.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
var col = this.position.col;
|
||||||
|
var row = this.position.row;
|
||||||
|
var spacer = this.getSpacer();
|
||||||
|
|
||||||
var itemInRow = 0;
|
var itemInRow = 0;
|
||||||
|
|
||||||
for (var i = 0; i < self.items.length; ++i) {
|
this.viewWindow = {
|
||||||
|
start: this.focusedItemIndex,
|
||||||
|
end: this.items.length - 1, // this may be adjusted later
|
||||||
|
};
|
||||||
|
|
||||||
|
var pageStart = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < this.items.length; ++i) {
|
||||||
itemInRow++;
|
itemInRow++;
|
||||||
self.items[i].row = row;
|
this.items[i].row = row;
|
||||||
self.items[i].col = col;
|
this.items[i].col = col;
|
||||||
|
|
||||||
row += this.itemSpacing + 1;
|
row += this.itemSpacing + 1;
|
||||||
|
|
||||||
// handle going to next column
|
// have to calculate the max length on the last entry
|
||||||
if (itemInRow == this.itemsPerRow) {
|
if (i == this.items.length - 1) {
|
||||||
|
var maxLength = 0;
|
||||||
|
for (var j = 0; j < this.itemsPerRow; j++) {
|
||||||
|
if (this.items[i - j].col != this.items[i].col) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var itemLength = this.items[i - j].text.length;
|
||||||
|
if (itemLength > maxLength) {
|
||||||
|
maxLength = itemLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set length on each item in the column
|
||||||
|
for (var j = 0; j < this.itemsPerRow; j++) {
|
||||||
|
if (this.items[i - j].col != this.items[i].col) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.items[i - j].fixedLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check if we have room for this column
|
||||||
|
if (col + maxLength + spacer.length + 1 > this.position.col + this.dimens.width) {
|
||||||
|
// save previous page
|
||||||
|
this.pages.push({ start: pageStart, end: i - this.itemsPerRow });
|
||||||
|
|
||||||
|
// fix the last column processed
|
||||||
|
for (var j = 0; j < this.itemsPerRow; j++) {
|
||||||
|
if (this.items[i - j].col != col) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.items[i - j].col = this.position.col;
|
||||||
|
pageStart = i - j;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since this is the last page, save the current page as well
|
||||||
|
this.pages.push({ start: pageStart, end: i });
|
||||||
|
|
||||||
|
}
|
||||||
|
// also handle going to next column
|
||||||
|
else if (itemInRow == this.itemsPerRow) {
|
||||||
itemInRow = 0;
|
itemInRow = 0;
|
||||||
|
|
||||||
row = self.position.row;
|
// restart row for next column
|
||||||
|
row = this.position.row;
|
||||||
var maxLength = 0;
|
var maxLength = 0;
|
||||||
for (var j = 0; j < this.itemsPerRow; j++) {
|
for (var j = 0; j < this.itemsPerRow; j++) {
|
||||||
// TODO: handle complex items
|
// TODO: handle complex items
|
||||||
|
@ -92,34 +162,37 @@ function FullMenuView(options) {
|
||||||
|
|
||||||
// set length on each item in the column
|
// set length on each item in the column
|
||||||
for (var j = 0; j < this.itemsPerRow; j++) {
|
for (var j = 0; j < this.itemsPerRow; j++) {
|
||||||
self.items[i - j].fixedLength = maxLength;
|
this.items[i - j].fixedLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have room for this column in the current page
|
||||||
|
if (col + maxLength > this.position.col + this.dimens.width) {
|
||||||
|
// save previous page
|
||||||
|
this.pages.push({ start: pageStart, end: i - this.itemsPerRow });
|
||||||
|
|
||||||
|
// restart page start for next page
|
||||||
|
pageStart = i - this.itemsPerRow + 1;
|
||||||
|
|
||||||
|
// reset
|
||||||
|
col = this.position.col;
|
||||||
|
itemInRow = 0;
|
||||||
|
|
||||||
|
// fix the last column processed
|
||||||
|
for (var j = 0; j < this.itemsPerRow; j++) {
|
||||||
|
this.items[i - j].col = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// increment the column
|
// increment the column
|
||||||
col += maxLength + spacer.length + 1;
|
col += maxLength + spacer.length + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// also have to calculate the max length on the last column
|
|
||||||
else if (i == self.items.length - 1) {
|
|
||||||
var maxLength = 0;
|
|
||||||
for (var j = 0; j < this.itemsPerRow; j++) {
|
|
||||||
if (self.items[i - j].col != self.items[i].col) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var itemLength = this.items[i - j].text.length;
|
|
||||||
if (itemLength > maxLength) {
|
|
||||||
maxLength = itemLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set length on each item in the column
|
|
||||||
for (var j = 0; j < this.itemsPerRow; j++) {
|
|
||||||
if (self.items[i - j].col != self.items[i].col) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
self.items[i - j].fixedLength = maxLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Set the current page if the current item is focused.
|
||||||
|
if (this.focusedItemIndex === i) {
|
||||||
|
this.currentPage = this.pages.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,32 +201,32 @@ function FullMenuView(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.drawItem = function(index) {
|
this.drawItem = function(index) {
|
||||||
const item = self.items[index];
|
const item = this.items[index];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cached = this.getRenderCacheItem(index, item.focused);
|
const cached = this.getRenderCacheItem(index, item.focused);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return self.client.term.write(`${ansi.goto(item.row, item.col)}${cached}`);
|
return this.client.term.write(`${ansi.goto(item.row, item.col)}${cached}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text;
|
let text;
|
||||||
let sgr;
|
let sgr;
|
||||||
if (item.focused && self.hasFocusItems()) {
|
if (item.focused && this.hasFocusItems()) {
|
||||||
const focusItem = self.focusItems[index];
|
const focusItem = this.focusItems[index];
|
||||||
text = focusItem ? focusItem.text : item.text;
|
text = focusItem ? focusItem.text : item.text;
|
||||||
sgr = '';
|
sgr = '';
|
||||||
} else if (this.complexItems) {
|
} else if (this.complexItems) {
|
||||||
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item));
|
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item));
|
||||||
sgr = this.focusItemFormat ? '' : (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
sgr = this.focusItemFormat ? '' : (index === this.focusedItemIndex ? this.getFocusSGR() : this.getSGR());
|
||||||
} else {
|
} else {
|
||||||
text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle);
|
text = strUtil.stylizeString(item.text, item.focused ? this.focusTextStyle : this.textStyle);
|
||||||
sgr = (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
sgr = (index === this.focusedItemIndex ? this.getFocusSGR() : this.getSGR());
|
||||||
}
|
}
|
||||||
|
|
||||||
text = `${sgr}${strUtil.pad(text, this.dimens.width, this.fillChar, this.justify)}`;
|
text = `${sgr}${strUtil.pad(text, this.fixedLength, this.fillChar, this.justify)}`;
|
||||||
self.client.term.write(`${ansi.goto(item.row, item.col)}${text}`);
|
this.client.term.write(`${ansi.goto(item.row, item.col)}${text}`);
|
||||||
this.setRenderCacheItem(index, text, item.focused);
|
this.setRenderCacheItem(index, text, item.focused);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -166,11 +239,6 @@ FullMenuView.prototype.redraw = function() {
|
||||||
|
|
||||||
this.cachePositions();
|
this.cachePositions();
|
||||||
|
|
||||||
// :TODO: rename positionCacheExpired to something that makese sense; combine methods for such
|
|
||||||
if (this.positionCacheExpired) {
|
|
||||||
this.autoAdjustHeightIfEnabled();
|
|
||||||
this.positionCacheExpired = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// erase old items
|
// erase old items
|
||||||
// :TODO: optimize this: only needed if a item is removed or new max width < old.
|
// :TODO: optimize this: only needed if a item is removed or new max width < old.
|
||||||
|
@ -189,7 +257,7 @@ FullMenuView.prototype.redraw = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.items.length) {
|
if (this.items.length) {
|
||||||
for (let i = 0; i < this.items.length; ++i) {
|
for (let i = this.pages[this.currentPage].start; i <= this.pages[this.currentPage].end; ++i) {
|
||||||
this.items[i].focused = this.focusedItemIndex === i;
|
this.items[i].focused = this.focusedItemIndex === i;
|
||||||
this.drawItem(i);
|
this.drawItem(i);
|
||||||
}
|
}
|
||||||
|
@ -203,6 +271,12 @@ FullMenuView.prototype.setHeight = function(height) {
|
||||||
this.autoAdjustHeight = false;
|
this.autoAdjustHeight = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FullMenuView.prototype.setWidth = function(width) {
|
||||||
|
FullMenuView.super_.prototype.setWidth.call(this, width);
|
||||||
|
|
||||||
|
this.positionCacheExpired = true;
|
||||||
|
};
|
||||||
|
|
||||||
FullMenuView.prototype.setPosition = function(pos) {
|
FullMenuView.prototype.setPosition = function(pos) {
|
||||||
FullMenuView.super_.prototype.setPosition.call(this, pos);
|
FullMenuView.super_.prototype.setPosition.call(this, pos);
|
||||||
|
|
||||||
|
@ -273,11 +347,16 @@ FullMenuView.prototype.removeItem = function(index) {
|
||||||
|
|
||||||
FullMenuView.prototype.focusNext = function() {
|
FullMenuView.prototype.focusNext = function() {
|
||||||
if (this.items.length - 1 === this.focusedItemIndex) {
|
if (this.items.length - 1 === this.focusedItemIndex) {
|
||||||
|
this.clearPage();
|
||||||
this.focusedItemIndex = 0;
|
this.focusedItemIndex = 0;
|
||||||
|
this.currentPage = 0;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
this.focusedItemIndex++;
|
this.focusedItemIndex++;
|
||||||
|
if (this.focusedItemIndex > this.pages[this.currentPage].end) {
|
||||||
|
this.clearPage();
|
||||||
|
this.currentPage++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
|
@ -288,10 +367,14 @@ FullMenuView.prototype.focusNext = function() {
|
||||||
FullMenuView.prototype.focusPrevious = function() {
|
FullMenuView.prototype.focusPrevious = function() {
|
||||||
if (0 === this.focusedItemIndex) {
|
if (0 === this.focusedItemIndex) {
|
||||||
this.focusedItemIndex = this.items.length - 1;
|
this.focusedItemIndex = this.items.length - 1;
|
||||||
|
this.currentPage = this.pages.length - 1;
|
||||||
|
}
|
||||||
} else {
|
else {
|
||||||
|
this.clearPage();
|
||||||
this.focusedItemIndex--;
|
this.focusedItemIndex--;
|
||||||
|
if (this.focusedItemIndex < this.pages[this.currentPage].start) {
|
||||||
|
this.currentPage--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
|
@ -303,8 +386,17 @@ FullMenuView.prototype.focusPreviousColumn = function() {
|
||||||
|
|
||||||
this.focusedItemIndex = this.focusedItemIndex - this.itemsPerRow;
|
this.focusedItemIndex = this.focusedItemIndex - this.itemsPerRow;
|
||||||
if (this.focusedItemIndex < 0) {
|
if (this.focusedItemIndex < 0) {
|
||||||
|
this.clearPage();
|
||||||
// add the negative index to the end of the list
|
// add the negative index to the end of the list
|
||||||
this.focusedItemIndex = this.items.length + this.focusedItemIndex;
|
this.focusedItemIndex = this.items.length + this.focusedItemIndex;
|
||||||
|
// set to last page
|
||||||
|
this.currentPage = this.pages.length - 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.focusedItemIndex < this.pages[this.currentPage].start) {
|
||||||
|
this.clearPage();
|
||||||
|
this.currentPage--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
|
@ -319,6 +411,12 @@ FullMenuView.prototype.focusNextColumn = function() {
|
||||||
if (this.focusedItemIndex > this.items.length - 1) {
|
if (this.focusedItemIndex > this.items.length - 1) {
|
||||||
// add the overflow to the beginning of the list
|
// add the overflow to the beginning of the list
|
||||||
this.focusedItemIndex = this.focusedItemIndex - this.items.length;
|
this.focusedItemIndex = this.focusedItemIndex - this.items.length;
|
||||||
|
this.currentPage = 0;
|
||||||
|
this.clearPage();
|
||||||
|
}
|
||||||
|
else if (this.focusedItemIndex > this.pages[this.currentPage].end) {
|
||||||
|
this.clearPage();
|
||||||
|
this.currentPage++;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
|
|
|
@ -2,298 +2,300 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const View = require('./view.js').View;
|
const View = require('./view.js').View;
|
||||||
const miscUtil = require('./misc_util.js');
|
const miscUtil = require('./misc_util.js');
|
||||||
const pipeToAnsi = require('./color_codes.js').pipeToAnsi;
|
const pipeToAnsi = require('./color_codes.js').pipeToAnsi;
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.MenuView = MenuView;
|
exports.MenuView = MenuView;
|
||||||
|
|
||||||
function MenuView(options) {
|
function MenuView(options) {
|
||||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||||
|
|
||||||
View.call(this, options);
|
View.call(this, options);
|
||||||
|
|
||||||
this.disablePipe = options.disablePipe || false;
|
this.disablePipe = options.disablePipe || false;
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if(options.items) {
|
if (options.items) {
|
||||||
this.setItems(options.items);
|
this.setItems(options.items);
|
||||||
} else {
|
} else {
|
||||||
this.items = [];
|
this.items = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderCache = {};
|
||||||
|
|
||||||
|
this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(options.caseInsensitiveHotKeys, true);
|
||||||
|
|
||||||
|
this.setHotKeys(options.hotKeys);
|
||||||
|
|
||||||
|
this.focusedItemIndex = options.focusedItemIndex || 0;
|
||||||
|
this.focusedItemIndex = this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0;
|
||||||
|
|
||||||
|
this.itemSpacing = _.isNumber(options.itemSpacing) ? options.itemSpacing : 0;
|
||||||
|
this.itemHorizSpacing = _.isNumber(options.itemHorizSpacing) ? options.itemHorizSpacing : 0;
|
||||||
|
|
||||||
|
// :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.justify = options.justify || 'none';
|
||||||
|
|
||||||
|
this.hasFocusItems = function() {
|
||||||
|
return !_.isUndefined(self.focusItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getHotKeyItemIndex = function(ch) {
|
||||||
|
if (ch && self.hotKeys) {
|
||||||
|
const keyIndex = self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch];
|
||||||
|
if (_.isNumber(keyIndex)) {
|
||||||
|
return keyIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
this.renderCache = {};
|
this.emitIndexUpdate = function() {
|
||||||
|
self.emit('index update', self.focusedItemIndex);
|
||||||
this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(options.caseInsensitiveHotKeys, true);
|
};
|
||||||
|
|
||||||
this.setHotKeys(options.hotKeys);
|
|
||||||
|
|
||||||
this.focusedItemIndex = options.focusedItemIndex || 0;
|
|
||||||
this.focusedItemIndex = this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0;
|
|
||||||
|
|
||||||
this.itemSpacing = _.isNumber(options.itemSpacing) ? options.itemSpacing : 0;
|
|
||||||
this.itemHorizSpacing = _.isNumber(options.itemHorizSpacing) ? options.itemHorizSpacing : 0;
|
|
||||||
|
|
||||||
// :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.justify = options.justify || 'none';
|
|
||||||
|
|
||||||
this.hasFocusItems = function() {
|
|
||||||
return !_.isUndefined(self.focusItems);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getHotKeyItemIndex = function(ch) {
|
|
||||||
if(ch && self.hotKeys) {
|
|
||||||
const keyIndex = self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch];
|
|
||||||
if(_.isNumber(keyIndex)) {
|
|
||||||
return keyIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.emitIndexUpdate = function() {
|
|
||||||
self.emit('index update', self.focusedItemIndex);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(MenuView, View);
|
util.inherits(MenuView, View);
|
||||||
|
|
||||||
MenuView.prototype.setItems = function(items) {
|
MenuView.prototype.setItems = function(items) {
|
||||||
if(Array.isArray(items)) {
|
if (Array.isArray(items)) {
|
||||||
this.sorted = false;
|
this.sorted = false;
|
||||||
this.renderCache = {};
|
this.renderCache = {};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Items can be an array of strings or an array of objects.
|
// Items can be an array of strings or an array of objects.
|
||||||
//
|
//
|
||||||
// In the case of objects, items are considered complex and
|
// In the case of objects, items are considered complex and
|
||||||
// may have one or more members that can later be formatted
|
// may have one or more members that can later be formatted
|
||||||
// against. The default member is 'text'. The member 'data'
|
// against. The default member is 'text'. The member 'data'
|
||||||
// may be overridden to provide a form value other than the
|
// may be overridden to provide a form value other than the
|
||||||
// item's index.
|
// item's index.
|
||||||
//
|
//
|
||||||
// Items can be formatted with 'itemFormat' and 'focusItemFormat'
|
// Items can be formatted with 'itemFormat' and 'focusItemFormat'
|
||||||
//
|
//
|
||||||
let text;
|
let text;
|
||||||
let stringItem;
|
let stringItem;
|
||||||
this.items = items.map(item => {
|
this.items = items.map(item => {
|
||||||
stringItem = _.isString(item);
|
stringItem = _.isString(item);
|
||||||
if(stringItem) {
|
if (stringItem) {
|
||||||
text = item;
|
text = item;
|
||||||
} else {
|
} else {
|
||||||
text = item.text || '';
|
text = item.text || '';
|
||||||
this.complexItems = true;
|
this.complexItems = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
text = this.disablePipe ? text : pipeToAnsi(text, this.client);
|
text = this.disablePipe ? text : pipeToAnsi(text, this.client);
|
||||||
return Object.assign({ }, { text }, stringItem ? {} : item); // ensure we have a text member, plus any others
|
return Object.assign({}, { text }, stringItem ? {} : item); // ensure we have a text member, plus any others
|
||||||
});
|
});
|
||||||
|
|
||||||
if(this.complexItems) {
|
if (this.complexItems) {
|
||||||
this.itemFormat = this.itemFormat || '{text}';
|
this.itemFormat = this.itemFormat || '{text}';
|
||||||
}
|
|
||||||
|
|
||||||
this.invalidateRenderCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.invalidateRenderCache();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.getRenderCacheItem = function(index, focusItem = false) {
|
MenuView.prototype.getRenderCacheItem = function(index, focusItem = false) {
|
||||||
const item = this.renderCache[index];
|
const item = this.renderCache[index];
|
||||||
return item && item[focusItem ? 'focus' : 'standard'];
|
return item && item[focusItem ? 'focus' : 'standard'];
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.removeRenderCacheItem = function(index) {
|
MenuView.prototype.removeRenderCacheItem = function(index) {
|
||||||
delete this.renderCache[index];
|
delete this.renderCache[index];
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setRenderCacheItem = function(index, rendered, focusItem = false) {
|
MenuView.prototype.setRenderCacheItem = function(index, rendered, focusItem = false) {
|
||||||
this.renderCache[index] = this.renderCache[index] || {};
|
this.renderCache[index] = this.renderCache[index] || {};
|
||||||
this.renderCache[index][focusItem ? 'focus' : 'standard'] = rendered;
|
this.renderCache[index][focusItem ? 'focus' : 'standard'] = rendered;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.invalidateRenderCache = function() {
|
MenuView.prototype.invalidateRenderCache = function() {
|
||||||
this.renderCache = {};
|
this.renderCache = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setSort = function(sort) {
|
MenuView.prototype.setSort = function(sort) {
|
||||||
if(this.sorted || !Array.isArray(this.items) || 0 === this.items.length) {
|
if (this.sorted || !Array.isArray(this.items) || 0 === this.items.length) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = true === sort ? 'text' : sort;
|
||||||
|
if ('text' !== sort && !this.complexItems) {
|
||||||
|
return; // need a valid sort key
|
||||||
|
}
|
||||||
|
|
||||||
|
this.items.sort((a, b) => {
|
||||||
|
const a1 = a[key];
|
||||||
|
const b1 = b[key];
|
||||||
|
if (!a1) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (!b1) {
|
||||||
const key = true === sort ? 'text' : sort;
|
return 1;
|
||||||
if('text' !== sort && !this.complexItems) {
|
|
||||||
return; // need a valid sort key
|
|
||||||
}
|
}
|
||||||
|
return a1.localeCompare(b1, { sensitivity: false, numeric: true });
|
||||||
|
});
|
||||||
|
|
||||||
this.items.sort( (a, b) => {
|
this.sorted = true;
|
||||||
const a1 = a[key];
|
|
||||||
const b1 = b[key];
|
|
||||||
if(!a1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if(!b1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return a1.localeCompare( b1, { sensitivity : false, numeric : true } );
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sorted = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.removeItem = function(index) {
|
MenuView.prototype.removeItem = function(index) {
|
||||||
this.sorted = false;
|
this.sorted = false;
|
||||||
this.items.splice(index, 1);
|
this.items.splice(index, 1);
|
||||||
|
|
||||||
if(this.focusItems) {
|
if (this.focusItems) {
|
||||||
this.focusItems.splice(index, 1);
|
this.focusItems.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.focusedItemIndex >= index) {
|
if (this.focusedItemIndex >= index) {
|
||||||
this.focusedItemIndex = Math.max(this.focusedItemIndex - 1, 0);
|
this.focusedItemIndex = Math.max(this.focusedItemIndex - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.removeRenderCacheItem(index);
|
this.removeRenderCacheItem(index);
|
||||||
|
|
||||||
this.positionCacheExpired = true;
|
this.positionCacheExpired = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.getCount = function() {
|
MenuView.prototype.getCount = function() {
|
||||||
return this.items.length;
|
return this.items.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.getItems = function() {
|
MenuView.prototype.getItems = function() {
|
||||||
if(this.complexItems) {
|
if (this.complexItems) {
|
||||||
return this.items;
|
return this.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.items.map( item => {
|
return this.items.map(item => {
|
||||||
return item.text;
|
return item.text;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.getItem = function(index) {
|
MenuView.prototype.getItem = function(index) {
|
||||||
if(this.complexItems) {
|
if (this.complexItems) {
|
||||||
return this.items[index];
|
return this.items[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.items[index].text;
|
return this.items[index].text;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusNext = function() {
|
MenuView.prototype.focusNext = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusPrevious = function() {
|
MenuView.prototype.focusPrevious = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusNextPageItem = function() {
|
MenuView.prototype.focusNextPageItem = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusPreviousPageItem = function() {
|
MenuView.prototype.focusPreviousPageItem = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusFirst = function() {
|
MenuView.prototype.focusFirst = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusLast = function() {
|
MenuView.prototype.focusLast = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setFocusItemIndex = function(index) {
|
MenuView.prototype.setFocusItemIndex = function(index) {
|
||||||
this.focusedItemIndex = index;
|
this.focusedItemIndex = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.onKeyPress = function(ch, key) {
|
MenuView.prototype.onKeyPress = function(ch, key) {
|
||||||
const itemIndex = this.getHotKeyItemIndex(ch);
|
const itemIndex = this.getHotKeyItemIndex(ch);
|
||||||
if(itemIndex >= 0) {
|
if (itemIndex >= 0) {
|
||||||
this.setFocusItemIndex(itemIndex);
|
this.setFocusItemIndex(itemIndex);
|
||||||
|
|
||||||
if(true === this.hotKeySubmit) {
|
if (true === this.hotKeySubmit) {
|
||||||
this.emit('action', 'accept');
|
this.emit('action', 'accept');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MenuView.super_.prototype.onKeyPress.call(this, ch, key);
|
MenuView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setFocusItems = function(items) {
|
MenuView.prototype.setFocusItems = function(items) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if(items) {
|
if (items) {
|
||||||
this.focusItems = [];
|
this.focusItems = [];
|
||||||
items.forEach( itemText => {
|
items.forEach(itemText => {
|
||||||
this.focusItems.push(
|
this.focusItems.push(
|
||||||
{
|
{
|
||||||
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
|
text: self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setItemSpacing = function(itemSpacing) {
|
MenuView.prototype.setItemSpacing = function(itemSpacing) {
|
||||||
itemSpacing = parseInt(itemSpacing);
|
itemSpacing = parseInt(itemSpacing);
|
||||||
assert(_.isNumber(itemSpacing));
|
assert(_.isNumber(itemSpacing));
|
||||||
|
|
||||||
this.itemSpacing = itemSpacing;
|
this.itemSpacing = itemSpacing;
|
||||||
this.positionCacheExpired = true;
|
this.positionCacheExpired = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setItemHorizSpacing = function(itemHorizSpacing) {
|
MenuView.prototype.setItemHorizSpacing = function(itemHorizSpacing) {
|
||||||
itemSpacing = parseInt(itemHorizSpacing);
|
itemSpacing = parseInt(itemHorizSpacing);
|
||||||
assert(_.isNumber(itemHorizSpacing));
|
assert(_.isNumber(itemHorizSpacing));
|
||||||
|
|
||||||
this.itemHorizSpacing = itemHorizSpacing;
|
this.itemHorizSpacing = itemHorizSpacing;
|
||||||
this.positionCacheExpired = true;
|
this.positionCacheExpired = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setPropertyValue = function(propName, value) {
|
MenuView.prototype.setPropertyValue = function(propName, value) {
|
||||||
switch(propName) {
|
switch (propName) {
|
||||||
case 'itemSpacing' : this.setItemSpacing(value); break;
|
case 'itemSpacing': this.setItemSpacing(value); break;
|
||||||
case 'itemHorizSpacing' : this.setItemHorizSpacing(value); break;
|
case 'itemHorizSpacing': this.setItemHorizSpacing(value); break;
|
||||||
case 'items' : this.setItems(value); break;
|
case 'items': this.setItems(value); break;
|
||||||
case 'focusItems' : this.setFocusItems(value); break;
|
case 'focusItems': this.setFocusItems(value); break;
|
||||||
case 'hotKeys' : this.setHotKeys(value); break;
|
case 'hotKeys': this.setHotKeys(value); break;
|
||||||
case 'hotKeySubmit' : this.hotKeySubmit = value; break;
|
case 'hotKeySubmit': this.hotKeySubmit = value; break;
|
||||||
case 'justify' : this.justify = value; break;
|
case 'justify': this.justify = value; break;
|
||||||
case 'focusItemIndex' : this.focusedItemIndex = value; break;
|
case 'focusItemIndex': this.focusedItemIndex = value; break;
|
||||||
|
|
||||||
case 'itemFormat' :
|
case 'itemFormat':
|
||||||
case 'focusItemFormat' :
|
case 'focusItemFormat':
|
||||||
this[propName] = value;
|
this[propName] = value;
|
||||||
break;
|
// if there is a cache currently, invalidate it
|
||||||
|
this.invalidateRenderCache();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'sort' : this.setSort(value); break;
|
case 'sort': this.setSort(value); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuView.super_.prototype.setPropertyValue.call(this, propName, value);
|
MenuView.super_.prototype.setPropertyValue.call(this, propName, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setHotKeys = function(hotKeys) {
|
MenuView.prototype.setHotKeys = function(hotKeys) {
|
||||||
if(_.isObject(hotKeys)) {
|
if (_.isObject(hotKeys)) {
|
||||||
if(this.caseInsensitiveHotKeys) {
|
if (this.caseInsensitiveHotKeys) {
|
||||||
this.hotKeys = {};
|
this.hotKeys = {};
|
||||||
for(var key in hotKeys) {
|
for (var key in hotKeys) {
|
||||||
this.hotKeys[key.toLowerCase()] = hotKeys[key];
|
this.hotKeys[key.toLowerCase()] = hotKeys[key];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.hotKeys = hotKeys;
|
this.hotKeys = hotKeys;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue