trainingproject
Version:
A module to teach you how to module.
222 lines (196 loc) • 6.05 kB
JavaScript
var createCharm = require('charm_inheritance-fix');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var through = require('through2');
var duplexer = require('duplexer2');
var wcstring = require('wcstring');
module.exports = function (opts) {
return new Menu(opts || {});
}
inherits(Menu, EventEmitter);
function Menu (opts) {
var self = this;
self.width = opts.width || 50;
self.x = opts.x || 1;
self.y = opts.y || 1;
self.init = { x: self.x, y: self.y };
self.items = [];
self.lines = {};
self.selected = opts.selected || 0;
self.colors = {
fg: opts.fg || 'white',
bg: opts.bg || 'blue'
};
self.padding = opts.padding || {
left: 2,
right: 2,
top: 1,
bottom: 1
};
if (typeof self.padding === 'number') {
self.padding = {
left: self.padding,
right: self.padding,
top: self.padding,
bottom: self.padding
};
}
self.x += self.padding.left;
self.y += self.padding.top;
self.size = {
x: self.width + self.padding.left + self.padding.right
};
self._input = through(
function (buf, enc, next) {
self._ondata(buf);
next();
},
function () { self.emit('close') }
);
self._output = through();
self.charm = opts.charm || createCharm({
input: self._input
});
self.charm.on('error', function () {});
self.charm.pipe(self._output);
self.stream = self.charm.pipe(through());
try {
self.charm.display('reset');
self.charm.display('bright');
}
catch (e) {}
process.nextTick(function () {
self._ticked = true;
self.charm.cursor(false);
self._draw();
});
};
Menu.prototype.createStream = function () {
return duplexer(this._input, this._output);
};
Menu.prototype.add = function (label, cb) {
var index = this.items.length;
if (cb) {
this.on('select', function (x, ix) {
if (ix === index) cb(x, ix);
});
}
this.items.push({
x: this.x,
y: this.y,
label: label
});
this._fillLine(this.y);
this.y ++;
};
Menu.prototype._fillLine = function (y) {
if (!this.lines[y]) {
this.charm.position(this.init.x, y);
this.charm.write(Array(1 + this.size.x).join(' '));
this.lines[y] = true;
}
};
Menu.prototype.jump = function (name) {
var index = typeof name === 'number'
? name
: this.items
.map(function (item) { return item.label })
.indexOf(name)
;
if (index < 0) return;
var prev = this.selected;
this.selected = index;
if (this._ticked) {
this._drawRow(prev);
this._drawRow(index);
}
};
Menu.prototype.close = function () {
this._input.end();
this.charm.cursor(true);
this.charm.display('reset');
this.charm.position(1, this.y + 1);
this.charm.end();
};
Menu.prototype.reset = function () {
this.charm.reset();
};
Menu.prototype.write = function (msg) {
this.charm.background(this.colors.bg);
this.charm.foreground(this.colors.fg);
this._fillLine(this.y);
var parts = msg.split('\n');
for (var i = 0; i < parts.length; i++) {
if (parts[i].length) {
this.charm.position(this.x, this.y);
this.charm.write(parts[i]);
}
if (i !== parts.length - 1) {
this.x = this.init.x + this.padding.left;
this._fillLine(this.y);
this.y ++;
}
}
};
Menu.prototype._draw = function () {
for (var i = 0; i < this.padding.top; i++) {
this._fillLine(this.init.y + i);
}
for (var i = 0; i < this.items.length; i++) this._drawRow(i);
// reset foreground and background colors
this.charm.background(this.colors.bg);
this.charm.foreground(this.colors.fg);
for (var i = 0; i < this.padding.bottom; i++) {
this._fillLine(this.y + i);
}
};
Menu.prototype._drawRow = function (index) {
index = (index + this.items.length) % this.items.length;
var item = this.items[index];
this.charm.position(item.x, item.y);
if (this.selected === index) {
this.charm.background(this.colors.fg);
this.charm.foreground(this.colors.bg);
}
else {
this.charm.background(this.colors.bg);
this.charm.foreground(this.colors.fg);
}
var len = this.width - wcstring(item.label).size() + 1;
this.charm.write(item.label + Array(Math.max(0, len)).join(' '));
};
Menu.prototype._ondata = function ondata (buf) {
var bytes = [].slice.call(buf);
while (bytes.length) {
var codes = [].join.call(bytes, '.');
if (/^(27.91.65|27,79.65|107|16)\b/.test(codes)) { // up or k
this.selected = (this.selected - 1 + this.items.length)
% this.items.length
;
this._drawRow(this.selected + 1);
this._drawRow(this.selected);
if (/^107\b/.test(codes)) bytes.shift()
else bytes.splice(0, 3);
}
if (/^(27.91.66|27.79.66|106|14)\b/.test(codes)) { // down or j
this.selected = (this.selected + 1) % this.items.length;
this._drawRow(this.selected - 1);
this._drawRow(this.selected);
if (/^106\b/.test(codes)) bytes.shift()
else bytes.splice(0, 3);
}
else if (/^(3|113)/.test(codes)) { // ^C or q
this.charm.reset();
this._input.end();
this._output.end();
bytes.shift();
}
else if (/^(13|10)\b/.test(codes)) { // enter
this.charm.position(1, this.items[this.items.length-1].y + 2);
this.charm.display('reset');
this.emit('select', this.items[this.selected].label, this.selected);
bytes.shift();
}
else bytes.shift();
}
};