minitel.ts
Version:
A port to typescript and extension of https://github.com/cquest/pynitel
334 lines (333 loc) • 15.7 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
export default class MinitelTSRead {
constructor(minitel) {
this.minitel = minitel;
this.output = minitel.output;
this.currentInputLength = 0;
}
line(line, column, length) {
// Position the cursor at the start of the input field
this.minitel.output.pos(line, column);
let inputText = "";
this.currentInputLength = 0; // reset input length
this.cursor(true);
let funcBuffer = ""; // Buffer to catch function key sequences (start with \x13)
return new Promise((resolve) => {
const onData = (data) => {
// Convert using latin1 since Minitel uses ISO-8859-1
let str = data.toString("latin1");
for (let i = 0; i < str.length; i++) {
let char = str[i];
if (funcBuffer) {
// We're in a function key sequence; add the new char
funcBuffer += char;
if (funcBuffer.length === 2) {
if (funcBuffer === '\x13\x41') {
// ENVOI button: finish input
this.minitel.removeListener('data', onData);
this.cursor(false);
resolve(inputText);
}
else if (funcBuffer === '\x13\x45') {
// Annulation: clear the input area
inputText = "";
this.currentInputLength = 0;
this.output.pos(line, column);
this.output.print(" ".repeat(length));
this.output.pos(line, column);
}
else if (funcBuffer === '\x13\x47') {
// RETOUR: delete the last character
if (inputText.length > 0) {
inputText = inputText.slice(0, -1);
this.currentInputLength = inputText.length;
this.output.pos(line, column + this.currentInputLength);
this.output.print(" "); // blank the last character
this.output.pos(line, column + this.currentInputLength);
}
}
else {
// Unrecognized sequence, beep as needed
this.minitel.bip();
}
// Reset the function key buffer after processing a two-byte sequence
funcBuffer = "";
}
continue;
}
if (char === '\x13') {
// Start a function key sequence
funcBuffer = char;
}
else if (char === '\r' || char === '\n') {
// ENTER key finishes input
this.minitel.removeListener('data', onData);
this.cursor(false);
resolve(inputText);
}
else if (char >= ' ' && inputText.length < length) {
// Append normal characters; update the current input length for cursor positioning
inputText += char;
this.currentInputLength = inputText.length;
// Minitel echoes input automatically, so no manual printing is needed
}
else {
// For extra characters beyond the limit, beep
this.minitel.bip();
}
}
};
this.minitel.on('data', onData);
});
}
label(label) {
const output = this.minitel.output;
output.inverse(true);
output.print(label);
output.inverse(false);
output.print(": ");
}
here(label = undefined, quickCharacters = []) {
// Position the cursor at the start of the input field
let inputText = "";
this.currentInputLength = 0; // reset input length
const output = this.minitel.output;
if (label) {
this.label(label);
}
this.cursor(true);
let funcBuffer = ""; // Buffer to catch function key sequences (start with \x13)
return new Promise((resolve, reject) => {
const onData = (data) => {
// Convert using latin1 since Minitel uses ISO-8859-1
let str = data.toString("latin1");
for (let i = 0; i < str.length; i++) {
let char = str[i];
if (quickCharacters.includes(char)) {
this.minitel.removeListener('data', onData);
this.cursor(false);
resolve(char);
}
if (funcBuffer) {
// We're in a function key sequence; add the new char
funcBuffer += char;
if (funcBuffer.length === 2) {
if (funcBuffer === '\x13\x41') {
// ENVOI button: finish input
this.minitel.removeListener('data', onData);
this.cursor(false);
resolve(inputText);
}
else if (funcBuffer === '\x13F') {
reject(new Error('KEY:SOMMAIRE'));
}
else if (funcBuffer === '\x13H') {
reject(new Error('KEY:SUITE'));
}
else if (funcBuffer === '\x13D') {
reject(new Error('KEY:GUIDE'));
}
else if (funcBuffer === '\x13B') {
reject(new Error('KEY:RETOUR'));
}
else if (funcBuffer === '\x13C') {
reject(new Error('KEY:REPETITION'));
}
else if (funcBuffer === '\x13\x45') {
// Annulation: clear the input area
for (let i = 0; i < this.currentInputLength; i++) {
output.backspace();
output.print(" "); // blank the last character
output.backspace();
}
inputText = "";
this.currentInputLength = 0;
}
else if (funcBuffer === '\x13\x47') {
// RETOUR: delete the last character
if (inputText.length > 0) {
inputText = inputText.slice(0, -1);
this.currentInputLength = inputText.length;
output.backspace();
output.print(" "); // blank the last character
output.backspace();
}
}
else {
// Unrecognized sequence, beep as needed
console.log("funcBuffer", JSON.stringify(funcBuffer));
this.minitel.bip();
}
// Reset the function key buffer after processing a two-byte sequence
funcBuffer = "";
}
continue;
}
if (char === '\x13') {
// Start a function key sequence
funcBuffer = char;
}
else if (char === '\r' || char === '\n') {
// ENTER key finishes input
this.minitel.removeListener('data', onData);
this.cursor(false);
resolve(inputText);
}
else if (char >= ' ') { // && inputText.length < length
// Append normal characters; update the current input length for cursor positioning
inputText += char;
this.currentInputLength = inputText.length;
// Minitel echoes input automatically, so no manual printing is needed
}
else {
// For extra characters beyond the limit, beep
this.minitel.bip();
}
}
};
this.minitel.on('data', onData);
});
}
// herenow only supports 1 character and directyl returns
hereNow(label = undefined, allowedCharacters) {
// Position the cursor at the start of the input field
let inputText = "";
this.currentInputLength = 0; // reset input length
const output = this.minitel.output;
if (label) {
this.label(label);
}
this.cursor(true);
let funcBuffer = ""; // Buffer to catch function key sequences (start with \x13)
return new Promise((resolve, reject) => {
const onData = (data) => {
// Convert using latin1 since Minitel uses ISO-8859-1
let str = data.toString("latin1");
for (let i = 0; i < str.length; i++) {
let char = str[i];
if (allowedCharacters.includes(char)) {
this.minitel.removeListener('data', onData);
this.cursor(false);
resolve(char);
}
if (funcBuffer) {
// We're in a function key sequence; add the new char
funcBuffer += char;
if (funcBuffer.length === 2) {
if (funcBuffer === '\x13\x41') {
reject(new Error('KEY:ENVOI'));
}
else if (funcBuffer === '\x13F') {
reject(new Error('KEY:SOMMAIRE'));
}
else if (funcBuffer === '\x13H') {
reject(new Error('KEY:SUITE'));
}
else if (funcBuffer === '\x13D') {
reject(new Error('KEY:GUIDE'));
}
else if (funcBuffer === '\x13B') {
reject(new Error('KEY:RETOUR'));
}
else if (funcBuffer === '\x13C') {
reject(new Error('KEY:REPETITION'));
}
else if (funcBuffer === '\x13\x45') {
reject(new Error('KEY:ANNULATION'));
}
else if (funcBuffer === '\x13\x47') {
reject(new Error('KEY:RETOUR'));
}
else {
// Unrecognized sequence, beep as needed
this.minitel.bip();
}
// Reset the function key buffer after processing a two-byte sequence
funcBuffer = "";
}
continue;
}
else {
// For extra characters beyond the limit, beep
output.backspace();
output.print(" "); // blank the last character
output.backspace();
this.minitel.bip();
}
if (char === '\x13') {
// Start a function key sequence
funcBuffer = char;
}
}
};
this.minitel.on('data', onData);
});
}
;
multipleCoice(label, choices, allowManualAnswer = false) {
// Position the cursor at the start of the input field
let inputText = "";
this.currentInputLength = 0; // reset input length
const output = this.minitel.output;
output.print(label, this.minitel.colors.bleu);
output.newLine();
choices.forEach((choice, index) => {
output.inverse(true);
output.print(`[${index + 1}]`);
output.inverse(false);
output.print(` ${choice.label}\n`);
output.startLine();
});
output.newLine();
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
let choice;
try {
if (allowManualAnswer) {
choice = yield this.here(undefined, choices.map((c, i) => (i + 1).toString()));
}
else {
choice = yield this.hereNow(undefined, choices.map((c, i) => (i + 1).toString()));
}
}
catch (error) {
reject(error);
return;
}
// check if the choice is a number
if (isNaN(parseInt(choice, 10))) {
resolve({
value: choice,
label: choice
});
}
else {
// if it is a number, check if it is a valid index
const choiceIndex = parseInt(choice, 10) - 1;
if (choiceIndex >= 0 && choiceIndex < choices.length) {
resolve(choices[choiceIndex]);
}
else {
reject(new Error('Invalid choice'));
}
}
}));
}
waitForKey() {
return new Promise((resolve) => {
this.minitel.once('data', (data) => {
resolve(data.toString());
});
});
}
cursor(visible) {
this.minitel.sendChr(visible ? 17 : 20); // Show (17) or hide (20) blinking cursor
}
}