sbbs/load/defs.js
2020-03-27 16:14:33 +11:00

345 lines
8.8 KiB
JavaScript

/**
* ANSItex definitions
*/
var ACTION_RELOAD =1; /* Reload the current frame */
var ACTION_GOTO =2; /* Goto a specific frame */
var ACTION_BACKUP =3; /* Goto previous frame */
var ACTION_NEXT =4; /* Goto next frame */
var ACTION_TERMINATE =5; /* Terminate the session */
var ACTION_SUBMITRF =6; /* Submit form contents */
var ACTION_STAR =7; /* Star command entry */
var MODE_BL =1; /* Typing * command on baseline */
var FRAME_LENGTH =22; /* Length of a frame */
var FRAME_WIDTH =80; /* Width of a frame */
var FRAME_HEADER =56; /* Size of page owner (length) */
var FRAME_PAGENUM =12; /* Size of page number (length with a-z) */
var FRAME_COST = 9; /* Size of cost (length without unit) */
var FRAME_COSTUNIT ='c'; /* Unit of cost */
var FRAME_TYPE_INFO ='i';
var FRAME_TYPE_TERMINATE ='t';
var FRAME_TYPE_EXTERNAL ='x';
var FRAME_TYPE_RESPONSE ='r';
var FRAME_TYPE_LOGIN ='l';
var ERR_NOT_IMPLEMENTED ='\1RNOT IMPLEMENTED YET?';
var ERR_ROUTE ='\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08';
var NO_HISTORY_FRAMES =['98b'];
// Our frame object
function Frame() {
this.version=1;
this.frame=null;
this.index=null;
this.owner=''; // @todo
this.cost=0; // @todo
this.content='';
this.isPublic=false; // @todo
this.isAccessible=false; // @todo
this.type = FRAME_TYPE_INFO;
this.key=[ null,null,null,null,null,null,null,null,null,null ];
// Initialise frame array
this.frame_data = {};
this.frame_content = {};
for(x=0;x<FRAME_LENGTH;x++)
{
this.frame_data[x] = {};
this.frame_content[x] = {};
}
this.frame_fields = [];
this.raw=function() {
return base64_decode(this.content).replace(/(\r\n|\n|\r)/gm,'');
};
this.render=function(withHeader) {
owner = base64_decode(this.owner);
header = '\n\r';
if (this.type != FRAME_TYPE_LOGIN) {
header = '\1n'+owner+' '.repeat(FRAME_HEADER-console.strlen(owner))+'\1n '+
'\1W\1H'+this.page+' '.repeat(FRAME_PAGENUM-this.page.length)+' '+
'\1G\1H'+' '.repeat(FRAME_COST-this.cost.toString().length)+this.cost+FRAME_COSTUNIT+
(console.screen_columns > 80 ? '\n\r' : '');
}
if ((this.type == FRAME_TYPE_LOGIN) || (this.type == FRAME_TYPE_RESPONSE)) {
return header+this.parse(this.content);
} else {
return header+this.content;
}
};
Object.defineProperty(this,'page', {
get: function() {
if (this.frame == null || this.index == null) return null;
return this.frame+this.index;
}
});
Object.defineProperty(this,'fields', {
get: function() {
return this.frame_fields;
}
});
}
// Load a frame from disk (.tex file)
Frame.prototype.load = function(filename) {
log(LOG_DEBUG,'Loading frame from: '+filename);
f = new File(system.mods_dir+'ansitex/text/'+filename+'.tex');
if (! f.exists || ! f.open('r')) {
return null;
}
try {
load = JSON.parse(f.read());
for (property in load) {
this[property] = load[property];
}
this.content = base64_decode(this.content);
} catch (error) {
log(LOG_ERROR,'Frame error: '+error);
return null;
}
log(LOG_DEBUG,'Loaded frame: ['+this.frame+']['+this.index+'] ('+this.page+')');
};
/**
* Parse the page text, and return the frame as 2 arrays:
* + First array is all the characters and the position on the frame
* + Second array is the array of the control codes that changes the color of the character
*
* The purpose of this function is to convert any special char sequences there are interpreted directly by Ansitex
* Currently they are:
* + ESC _ <value>[;value] ESC \
*
* Additionally, for response frames, if the cursor is moved to a field, its to determine what attributes (eg: color)
* should apply for that field.
*
* @todo This function could be advanced by looking for the next KEY_ESC char, instead of progressing 1 char at a time
* @param text
*/
Frame.prototype.parse = function(text) {
var c = 1; // column
var r = 1; // row
var output = '';
// Default Attributes
f = 39;
b = 49;
i = 0;
for(p=0;p<text.length;p++) {
// If the frame is not big enough, fill it with spaces.
byte = text.charAt(p);
advance = 0;
switch (byte) {
// Carriage Return
case '\r':
log(LOG_DEBUG,'CR');
c=1;
break;
// New line
case '\n':
log(LOG_DEBUG,'LF');
r++;
break;
// ESC
case KEY_ESC:
advance = 1;
// Is the next byte something we know about
nextbyte = text.charAt(p+advance);
log(LOG_DEBUG,'ESC ['+r+'x'+c+'] NEXT: '+nextbyte);
switch (nextbyte) {
// CSI
case '[':
advance++;
chars = '';
// Find our end CSI param in the next 50 chars
matches = text.substring(p+advance,p+advance+50).match(/([0-9]+[;]?)+([a-zA-Z])/);
log(LOG_DEBUG,'CSI ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+matches+', STRING: '+text.substring(p+advance,p+advance+50));
if (! matches) {
chars += nextbyte;
break;
}
advance += matches[0].length-1;
chars += nextbyte+matches[0];
log(LOG_DEBUG,'CSI ['+r+'x'+c+'] ADVANCE: '+advance+', CHARS: '+chars+', CHARSLEN: '+chars.length);
switch (matches[2]) {
// Color CSIs
case 'm':
log(LOG_DEBUG,'CSI m ['+r+'x'+c+'] MATCHES: '+matches[0]+', LENGTH : '+matches[0].length);
csi = matches[0].substr(0,matches[0].length-1).split(';');
var num = null;
for (num in csi) {
log(LOG_DEBUG,'CSI m ['+r+'x'+c+'] NUM: '+num+', CSI: '+csi[num]);
// Intensity
if (csi[num] >= 0 && csi[num] <= 8) {
i = csi[num];
f = 39;
b = 49;
// Forground Color
} else if (csi[num] >= 30 && csi[num] <= 39) {
f = csi[num];
// Background Color
} else if (csi[num] >= 40 && csi[num] <= 49) {
b = num;
}
}
break;
// Advance characters
case 'C':
log(LOG_DEBUG,'CSI C ['+r+'x'+c+'] CHARS: '+matches[1]);
c += parseInt(matches[1]); // Advance our position
break;
default:
dump('? CSI: '.matches[2]);
}
break;
case ' ':
log(LOG_DEBUG,'LOOSE ESC? ['+r+'x'+c+'] '+advance);
break;
// SOS
case '_':
log(LOG_DEBUG,'SOS ['+r+'x'+c+'] '+advance);
advance++;
// Find our end ST param in the next 50 chars
matches = text.substring(p+advance,p+advance+50).match(/(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/);
log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+matches+', LENGTH: '+matches[0].length+', STRING: '+text.substring(p+advance,p+advance+50));
if (! matches) {
chars += nextbyte;
break;
}
advance += matches[0].length-1;
// The last 2 chars of matches[0] are the ESC \
sos = matches[0].substr(0,matches[0].length-2).split(';');
log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', SOS: '+sos);
var num = null;
var fieldlen = null;
for (num in sos) {
log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM: '+num+', SOS: '+sos[num]);
switch (num) {
// First value is the field name
case '0':
field = sos[num];
break;
// Second value is the length/type of the field
case '1':
x = sos[num].match(/([0-9]+)([a-z])/);
if (! x) {
log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]);
break;
}
log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]);
fieldlen = x[1];
fieldtype = x[2];
break;
// Third field is the char to to use
case '2':
fieldchar = sos[num];
break;
default:
log(LOG_ERROR,'IGNORING ADITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]);
}
}
if (fieldlen) {
chars = fieldchar.repeat(fieldlen);
}
byte = '';
this.frame_fields.push({
'type': fieldtype,
'length': fieldlen,
'r': r,
'c': c,
});
log(LOG_DEBUG,'SOS Field found at ['+r+'x'+(c-1)+'], Type: '+fieldtype+', Length: '+fieldlen);
break;
default:
log(LOG_DEBUG,'DEFAULT ['+r+'x'+c+'] '+advance);
}
break;
default:
this.frame_data[r][c] = {i:i,f:f,b:b};
this.frame_content[r][c] = byte;
log(LOG_DEBUG,'ADD OUTPUT ['+r+'x'+c+'] for: '+byte);
c++;
}
output += byte;
if (advance) {
log(LOG_DEBUG,'ADVANCE P ['+r+'x'+c+'] '+advance+', NEXT CHAR: '+text.charAt(p+advance)+' ('+text.charCodeAt(p+advance)+')');
output += chars;
p += advance;
}
if (c>FRAME_WIDTH) {
c = 1;
r++;
}
// @todo - If we are longer than FRAME_LENGTH, move the output into the next frame.
if (r>FRAME_LENGTH) {
break;
}
// Debugging
if (false && r > 3) {
console.write(output);
bbs.hangup();
exit(1);
}
}
return output;
};