diff --git a/load/ansiframe.js b/load/ansiframe.js deleted file mode 100644 index 9277773..0000000 --- a/load/ansiframe.js +++ /dev/null @@ -1,578 +0,0 @@ -var FRAME_ANSI = (1<<1); - -var ANSI_FRAME_LENGTH = 22; /* Length of a frame */ -var ANSI_FRAME_WIDTH = 80; /* Width of a frame */ -var ANSI_FRAME_HEADER = 56; /* Size of page owner (length) */ -var ANSI_FRAME_PAGENUM = 12; /* Size of page number (length with a-z) */ -var ANSI_FRAME_COST = 9; /* Size of cost (length without unit) */ - -var ANSI_MSG_SENDORNOT = '\1n\1h\1GKEY 1 TO SEND, 2 NOT TO SEND'; -var ANSI_MSG_LOGON = '\1n\1h\1GKEY 1 TO LOGON, 2 TO RETURN'; -var ANSI_MSG_SENT = '\1n\1h\1GMESSAGE SENT - KEY # TO CONTINUE'; -var ANSI_MSG_NOTSENT = '\1n\1h\1GMESSAGE NOT SENT - KEY # TO CONTINUE'; -var ANSI_ERR_NO_PARENT = '\1n\1h\1RPARENT FRAME DOESNT EXIST'; -var ANSI_ERR_NOT_IMPLEMENTED = '\1n\1h\1RNOT IMPLEMENTED YET?'; -var ANSI_ERR_ROUTE = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; -var ANSI_ERR_METHOD_NOT_EXIST = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; -var ANSI_ACCESS_DENIED = '\1n\1h\1RACCESS DENIED. MISTAKE? TRY AGAIN OR TELL US *08'; -var ANSI_ALREADY_MEMBER = '\1n\1h\1RALREADY MEMBER OF CUG' -var ANSI_INACTIVITY = '\1n\1h\1RINACTIVITY ALERT, DISCONNECT PENDING...'; -var ANSI_INACTIVE = '\1n\1h\1RINACTIVITY DISCONNECT'; -var ANSI_NOACTION = '\1n\1h\1RNO ACTION PERFORMED'; -var ANSI_BASESTAR = '\1N\1G\1H*'; -var ANSI_INVALID_CODE = '\1n\1h\1RINVAID CODE, PLEASE TRY AGAIN *00'; -var ANSI_TOKEN_EMAIL = '\1n\1h\1RTOKEN EMAILED TO YOU...'; -var ANSI_TOKEN_SENT = '\1n\1h\1RTOKEN SENT, PLEASE ENTER TOKEN'; -var ANSI_INVALID_EMAIL = '\1n\1h\1RINVAID EMAIL, PLEASE TRY AGAIN *00'; -var ANSI_INVALID_UID = '\1n\1h\1RINVAID USER ID, PLEASE TRY AGAIN *00'; -var ANSI_CANNOT_SEND_TOKEN = '\1n\1h\1RCANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; -var ANSI_USER_EXISTS = '\1n\1h\1RERROR USER EXISTS, PLEASE TRY AGAIN *00'; -var ANSI_USER_CREATE_ERROR = '\1n\1h\1RERROR CREATING USER, PLEASE TRY AGAIN *00'; -var ANSI_LOGIN_ERROR = '\1n\1h\1RERROR LOGGING IN, PLEASE TRY AGAIN *00'; -var ANSI_CANCEL_MSG = '\1n\1h\1GPRESS 2 TO CANCEL'; - -// Our frame object -function ANSIFrame() { - this.version=1; // The version of this frame - in case we update functionality and we need to be - // backwards compatible - this.frame=null; // Frame Number [0-9]+ - this.index=null; // Frame Index [a-z] - this.owner=0; // The Service Provider owning the frame. - this.cost=0; // The cost to view the frame @TODO - this.content=''; // The frame content, base64 encoded - - // Frame's owned by the system where: - // isPublic is FALSE - the user must be logged in to view it - // isPublic is TRUE - can be viewed by non-logged in users - - // Frame's owned by Service Providers where: - // isPublic is FALSE - can only be viewed if a user is - // a member of the Service Providers CUG - // isPublic is TRUE - can be viewed by users (if logged in) - - this.isPublic=false; // Is this frame accessible to non CUG users - // If FALSE user must be a member of the CUG to view the frame - // All users, including unauthenticated, are members of 'system' (owner = 0) - this.isAccessible=false; // Is this frame available to be accessed - // If FALSE, only the SP can view/edit the frame - - this.type = FRAME_TYPE_INFO; // The frame type - see FRAME_TYPES above - this.key=[ null,null,null,null,null,null,null,null,null,null ]; // Key actions [0-9] - - /** - * Set the attribute at the current position - */ - this.attr=function(field) { - console.write(ascii(27)+'['+field.i+';'+field.f+';'+field.b+'m'); - } - - /** - * Turn off the cursor - */ - this.cursorOff=function() { - ansi.send('ext_mode','clear','cursor'); - this.gotoxy(0,24); - } - - this.cursorOn=function(x,y) { - ansi.send('ext_mode','set','cursor'); - this.gotoxy(x,y); - } - - // Field backspace, that leaves the field filler char - this.fieldbs=function(char) { - console.write(ascii(27)+'[D'+char+ascii(27)+'[D'); - } - - this.gotoxy=function(x,y) { - console.gotoxy(x,y); - } - - // Render the frame to the user - this.render=function(withHeader) { - log(LOG_DEBUG,'- ANSI FRAME'); - owner = base64_decode(this.owner); - - header = '\n\r'; - - //log(LOG_DEBUG,' - FRAME User: ['+JSON.stringify(user)+']'); - - // Dont show the page number on system login page - if (user.number || (this.type != FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) == -1)) { - log(LOG_DEBUG,' - Owner: ['+this.pageowner+']'); - - cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); - - header = '\1n'+this.pageownerlogo+' '.repeat(ANSI_FRAME_HEADER-console.strlen(this.pageownerlogo))+'\1n '+ - (this.isAccessible ? '\1W' : '\1R')+'\1H'+this.page+' '.repeat(ANSI_FRAME_PAGENUM-this.page.length)+' '+ - '\1G\1H'+' '.repeat(ANSI_FRAME_COST-cost.toString().length+1)+cost+'\1n'+ - (console.screen_columns > 80 ? '\n\r' : ''); - } - - console.clear(); - - return console.putmsg(header+this.parse(base64_decode(this.content))); - }; - - this.fieldValue=function(key) { - for each (var k in this.frame_fields) { - log(LOG_DEBUG,' - k:'+JSON.stringify(k)); - - if (k.fname == key) { - return k.fvalue; - } - } - - return null; - } - - // Load a frame from disk (.tex file) - this.load = function(filename) { - log(LOG_DEBUG,'Loading ANSI frame from: '+filename); - - f = new File(system.mods_dir+'ansitex/text/'+filename+'.tex'); - if (! f.exists || ! f.open('r')) { - return null; - } - - try { - var load = JSON.parse(f.read()); - - for (property in load) { - this[property] = load[property]; - } - - } catch (error) { - log(LOG_ERROR,'Frame error: '+error); - - // Load our system error frame. - this.load('998a'); - 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] 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. - * - * @param text - */ - this.parse = function(text) { - var c = 1; // column - var r = 2; // row (row 1 is the header) - var output = ''; - this.frame_fields = []; - - // If there is no text return - if (! text) - return output; - - // Default Attributes - f = 39; - b = 49; - i = 0; - - for(p=0;p= 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: - log(LOG_DEBUG,'? CSI: '+matches[2]); - } - - break; - - case ' ': - log(LOG_DEBUG,'LOOSE ESC? ['+r+'x'+c+'] '+advance); - - break; - - // X - dynamic fields, terminated with ESC - case 'X': - //log(LOG_DEBUG,'PU ['+r+'x'+c+'] '+advance); - advance++; - - // Find our end ST param in the next 50 chars - matches = text.substr(p+advance,50).match(/(([a-z]+;[0-9]+)([;]?.+)?)\x1b\\/); - //log(LOG_DEBUG,'PU ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+JSON.stringify(matches)+', LENGTH: '+(matches ? matches[0].length : 0)+', STRING: '+text.substr(p+advance,50)); - - if (! matches) { - chars += nextbyte; - - break; - } - - advance += matches[0].length-1; - - var pu = matches[0].substr(0,matches[0].length-2).split(';'); - //log(LOG_DEBUG,'PU ['+r+'x'+c+'] ADVANCE: '+advance+', PU: '+pu); - - chars = renderfield(pu[0],pu[1]); - byte = ''; - - log(LOG_DEBUG,'PU Field found at ['+r+'x'+(c-1)+'], Field: '+pu[0]+', Length: '+pu[1]+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); - - break; - - // _ - has our fields that take input - case '_': - //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] '+advance); - advance++; - - // Find our end ST param in the next 50 chars - matches = text.substr(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.substr(p+advance,50)); - - if (! matches) { - chars += nextbyte; - - break; - } - - advance += matches[0].length-1; - - // The last 2 chars of matches[0] are the ESC \ - var 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 field = 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 ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]); - } - } - - if (fieldlen) { - chars = fieldchar.repeat(fieldlen); - } - - byte = ''; - - this.frame_fields.push({ - ftype: fieldtype, - flength: fieldlen, - fchar: fieldchar, - fname: field, - r: r, - c: c, - attribute: {i:i,f:f,b:b}, - fvalue: '', - }); - - log(LOG_DEBUG,'SOS Field found at ['+r+'x'+(c-1)+'], Type: '+fieldtype+', Length: '+fieldlen+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); - - break; - - default: - log(LOG_DEBUG,'DEFAULT ['+r+'x'+c+'] '+advance); - } - - break; - - default: - 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>ANSI_FRAME_WIDTH) { - c = 1; - r++; - } - - /* - // @todo - If we are longer than ANSI_FRAME_LENGTH, move the output into the next frame. - if (r>ANSI_FRAME_LENGTH) { - break; - } - */ - } - - return output; - }; - - this.qrcode = function(qr,subframe) { - // SMALL Image - var full = ascii(0xdb); - var top = ascii(0xdf); - var bot = ascii(0xdc); - var blank = ' '; - - var qrcode = ''; - - /* - // Render the top line - var line = ascii(27)+'[1;37m'+bot; - for (var y = 0; y < qr.size; y++) { - line += bot; - } - qrcode += line+bot+bot+ascii(27)+'[0m'+"\r\n"; - */ - - // Render the body - for (var x = -1; x < qr.size; x=x+2) { - line = ascii(27)+'[1;37m'+full; - - for (var y = 0; y < qr.size; y++) { - // Top is white - if (((x==-1)? 0 : qr.getModule(x, y)) == 0) { - line += (qr.getModule(x+1, y)) ? top : full; - - // Top is black - } else { - line += (qr.getModule(x+1, y)) ? blank : bot; - } - } - - qrcode += line+full+ascii(27)+'[0m'+"\r\n"; - } - - // Render the bottom - line = ascii(27)+'[1;37m'+top; - for (var y = 0; y < qr.size; y++) { - line += top; - } - qrcode += line+top+ascii(27)+'[0m'+"\r\n"; - - ans2bin(fo.parse(qrcode),subframe); - subframe.open(); - subframe.cycle(); - }; - - this.save=function() { - file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; - w = new File(file); - if (! w.open('w')) { - log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); - exit(1); - } - - w.write(JSON.stringify(this)); - w.close(); - - log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); - } - - /** - * Send a message to the baseline. - * - * @param text - * @param reposition - */ - this.sendBaseline=function(text,reposition) { - eval('var msg = ANSI_'+text+';'); - console.pushxy(); - console.gotoxy(0,24); - console.print(msg); - console.cleartoeol(); - - if (! reposition) { - console.popxy(); - } - } - - this.clearBaseline=function(reposition) { - console.pushxy(); - console.gotoxy(0,24); - console.print(''); - console.cleartoeol(); - - if (! reposition) { - console.popxy(); - } - } - - Object.defineProperty(this,'accessible',{ - get: function() { - log(LOG_DEBUG,'- Checking if user can access frame: '+this.page); - log(LOG_DEBUG,' - User: '+JSON.stringify(user.number)); - log(LOG_DEBUG,' - Frame Owner: '+JSON.stringify(this.owner)+', System Frame: '+(this.pageowner == SYSTEM_OWNER)); - - // user.number 0 is unidentified user. - if (user.number) { - return ( - (this.isAccessible && this.pageowner == SYSTEM_OWNER && ! this.isPublic) || - (this.isAccessible && this.isPublic) || - (this.isAccessible && ! this.isPublic && this.isMember) || - (pageEditor(this.frame)) - ); - - } else { - return (this.isAccessible && this.pageowner == SYSTEM_OWNER && this.isPublic); - } - } - }); - - Object.defineProperty(this,'fields', { - get: function() { - return this.frame_fields; - } - }); - - // Check if the user is already a member of the CUG - Object.defineProperty(this,'isMember',{ - get: function() { - log(LOG_DEBUG,'- Checking if user is a member of frame: '+this.page); - - if (user.number) { - return ( - (this.pageowner == SYSTEM_OWNER) - ); - - } else { - return false; - } - } - }) - - Object.defineProperty(this,'page', { - get: function() { - if (this.frame == null || this.index == null) return null; - return this.frame.toString()+this.index; - } - }); - - Object.defineProperty(this,'pageowner', { - get: function() { - return pageOwner(this.frame).prefix; - } - }) - - Object.defineProperty(this,'pageownerlogo', { - get: function() { - return base64_decode(pageOwner(this.frame).logoans); - } - }) -} diff --git a/load/defs.js b/load/defs.js index 7fa45b5..981bdc2 100644 --- a/load/defs.js +++ b/load/defs.js @@ -89,4 +89,7 @@ const HOME_FRAME_AUTH ={frame: 98,index: 'b'}; /* Home page for initial connection */ const HOME_FRAME_CONNECT ={frame: 980,index: 'a'}; +/* Attributes saved/loaded from files */ +const SAVED_FRAME_ATTRS =['version','frame','index','owner','cost','content','isPublic','isAccessible','type','key']; + this; diff --git a/load/edit.js b/load/edit.js index cfa1eb3..e9bb4f2 100644 --- a/load/edit.js +++ b/load/edit.js @@ -30,8 +30,8 @@ function edit(fo) { const frame = new Frame(1,1,console.screen_columns,console.screen_rows,BG_BLACK|LIGHTGRAY); frame.gotoxy(1,1); - header = '\1n'+fo.pageownerlogo+' '.repeat(ANSI_FRAME_HEADER-console.strlen(fo.pageownerlogo))+'\1n '+ - '\1W\1H'+fo.page+' '.repeat(ANSI_FRAME_PAGENUM-fo.page.length)+' '+ + header = '\1n'+fo.pageownerlogo+' '.repeat(fo.FRAME_PAGENUM-console.strlen(fo.pageownerlogo))+'\1n '+ + '\1W\1H'+fo.page+' '.repeat(fo.FRAME_PAGENUM-fo.page.length)+' '+ '\1G\1H'+' Edit'; frame.putmsg(header); frame.open(); @@ -138,4 +138,4 @@ function edit(fo) { } } -this; \ No newline at end of file +this; diff --git a/load/frame-ansi.js b/load/frame-ansi.js new file mode 100644 index 0000000..de7e20f --- /dev/null +++ b/load/frame-ansi.js @@ -0,0 +1,195 @@ +var FRAME_ANSI = (1<<1); + +load('ansitex/load/frame-page.js'); + +// Our frame object +function FrameAnsi() { + PageFrame.apply(this,arguments); + + /* File Extension used for frames */ + this.settings.ext = 'tex'; + + /* Length of a frame */ + this.settings.FRAME_LENGTH = 22; + /* Width of a frame */ + this.settings.FRAME_WIDTH = 80; + /* Size of page owner (length) */ + this.settings.FRAME_HEADER = 56; + /* Size of page number (length with a-z) */ + this.settings.FRAME_PAGENUM = 12; + /* Size of cost (length without unit) */ + this.settings.FRAME_COST = 9; + + this.settings.MSG_SENDORNOT = '\1n\1h\1GKEY 1 TO SEND, 2 NOT TO SEND'; + this.settings.MSG_LOGON = '\1n\1h\1GKEY 1 TO LOGON, 2 TO RETURN'; + this.settings.MSG_SENT = '\1n\1h\1GMESSAGE SENT - KEY # TO CONTINUE'; + this.settings.MSG_NOTSENT = '\1n\1h\1GMESSAGE NOT SENT - KEY # TO CONTINUE'; + this.settings.ERR_NO_PARENT = '\1n\1h\1RPARENT FRAME DOESNT EXIST'; + this.settings.ERR_NOT_IMPLEMENTED = '\1n\1h\1RNOT IMPLEMENTED YET?'; + this.settings.ERR_ROUTE = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; + this.settings.ERR_METHOD_NOT_EXIST = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; + this.settings.ACCESS_DENIED = '\1n\1h\1RACCESS DENIED. MISTAKE? TRY AGAIN OR TELL US *08'; + this.settings.ALREADY_MEMBER = '\1n\1h\1RALREADY MEMBER OF CUG' + this.settings.INACTIVITY = '\1n\1h\1RINACTIVITY ALERT, DISCONNECT PENDING...'; + this.settings.INACTIVE = '\1n\1h\1RINACTIVITY DISCONNECT'; + this.settings.NOACTION = '\1n\1h\1RNO ACTION PERFORMED'; + this.settings.BASESTAR = '\1N\1G\1H*'; + this.settings.INVALID_CODE = '\1n\1h\1RINVAID CODE, PLEASE TRY AGAIN *00'; + this.settings.TOKEN_EMAIL = '\1n\1h\1RTOKEN EMAILED TO YOU...'; + this.settings.TOKEN_SENT = '\1n\1h\1RTOKEN SENT, PLEASE ENTER TOKEN'; + this.settings.INVALID_EMAIL = '\1n\1h\1RINVAID EMAIL, PLEASE TRY AGAIN *00'; + this.settings.INVALID_UID = '\1n\1h\1RINVAID USER ID, PLEASE TRY AGAIN *00'; + this.settings.CANNOT_SEND_TOKEN = '\1n\1h\1RCANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; + this.settings.USER_EXISTS = '\1n\1h\1RERROR USER EXISTS, PLEASE TRY AGAIN *00'; + this.settings.USER_CREATE_ERROR = '\1n\1h\1RERROR CREATING USER, PLEASE TRY AGAIN *00'; + this.settings.LOGIN_ERROR = '\1n\1h\1RERROR LOGGING IN, PLEASE TRY AGAIN *00'; + this.settings.CANCEL_MSG = '\1n\1h\1GPRESS 2 TO CANCEL'; + + /** + * Set the attribute at the current position + */ + this.attr=function(field) { + console.write(ascii(27)+'['+field.i+';'+field.f+';'+field.b+'m'); + } + + /** + * Turn off the cursor + */ + this.cursorOff=function() { + ansi.send('ext_mode','clear','cursor'); + this.gotoxy(0,24); + } + + this.cursorOn=function(x,y) { + ansi.send('ext_mode','set','cursor'); + this.gotoxy(x,y); + } + + // Field backspace, that leaves the field filler char + this.fieldbs=function(char) { + console.write(ascii(27)+'[D'+char+ascii(27)+'[D'); + } + + this.gotoxy=function(x,y) { + console.gotoxy(x,y); + } + + // Render the frame to the user + this.render=function(withHeader) { + log(LOG_DEBUG,'- ANSI FRAME'); + owner = base64_decode(this.owner); + + header = '\n\r'; + + //log(LOG_DEBUG,' - FRAME User: ['+JSON.stringify(user)+']'); + + // Dont show the page number on system login page + if (user.number || (this.type !== FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) === -1)) { + log(LOG_DEBUG,' - Owner: ['+this.pageowner+']'); + + cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); + + header = '\1n'+this.pageownerlogo+' '.repeat(this.settings.FRAME_HEADER-console.strlen(this.pageownerlogo))+'\1n '+ + (this.isAccessible ? '\1W' : '\1R')+'\1H'+this.page+' '.repeat(this.settings.FRAME_PAGENUM-this.page.length)+' '+ + '\1G\1H'+' '.repeat(this.settings.FRAME_COST-cost.toString().length+1)+cost+'\1n'+ + (console.screen_columns > 80 ? '\n\r' : ''); + } + + console.clear(); + + return console.putmsg(header+this.parse(base64_decode(this.content))); + }; + + this.qrcode = function(qr,subframe) { + // SMALL Image + var full = ascii(0xdb); + var top = ascii(0xdf); + var bot = ascii(0xdc); + var blank = ' '; + + var qrcode = ''; + + /* + // Render the top line + var line = ascii(27)+'[1;37m'+bot; + for (var y = 0; y < qr.size; y++) { + line += bot; + } + qrcode += line+bot+bot+ascii(27)+'[0m'+"\r\n"; + */ + + // Render the body + for (var x = -1; x < qr.size; x=x+2) { + line = ascii(27)+'[1;37m'+full; + + for (var y = 0; y < qr.size; y++) { + // Top is white + if (((x==-1)? 0 : qr.getModule(x, y)) == 0) { + line += (qr.getModule(x+1, y)) ? top : full; + + // Top is black + } else { + line += (qr.getModule(x+1, y)) ? blank : bot; + } + } + + qrcode += line+full+ascii(27)+'[0m'+"\r\n"; + } + + // Render the bottom + line = ascii(27)+'[1;37m'+top; + for (var y = 0; y < qr.size; y++) { + line += top; + } + qrcode += line+top+ascii(27)+'[0m'+"\r\n"; + + ans2bin(fo.parse(qrcode),subframe); + subframe.open(); + subframe.cycle(); + }; + + this.save=function() { + file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; + w = new File(file); + if (! w.open('w')) { + log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); + exit(1); + } + + w.write(JSON.stringify(this)); + w.close(); + + log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); + } + + /** + * Send a message to the baseline. + * + * @param text + * @param reposition + */ + this.sendBaseline=function(text,reposition) { + console.pushxy(); + console.gotoxy(0,24); + console.print(this.getMessage(text)); + console.cleartoeol(); + + if (! reposition) { + console.popxy(); + } + } + + this.clearBaseline=function(reposition) { + console.pushxy(); + console.gotoxy(0,24); + console.print(''); + console.cleartoeol(); + + if (! reposition) { + console.popxy(); + } + } +} + +FrameAnsi.prototype = PageFrame.prototype; +FrameAnsi.prototype.constructor = FrameAnsi; diff --git a/load/frame-page.js b/load/frame-page.js new file mode 100644 index 0000000..977ad95 --- /dev/null +++ b/load/frame-page.js @@ -0,0 +1,475 @@ +// Our frame object +function PageFrame() { + 'use strict'; + + /* Frame type settings */ + this.settings = {}; + + /* Frame Version */ + this.version = 1; + + /* Frame Number [0-9] */ + this.frame = null; + + /* Frame Index [a-z] */ + this.index = null; + + /* The Service Provider owning the frame. */ + this.owner = 0; + + /* The cost to view the frame @todo to implement */ + this.cost = 0; + + /* The frame content, base64 encoded */ + this.content = ''; + + // Frame's owned by the system where: + // isPublic is FALSE - the user must be logged in to view it + // isPublic is TRUE - can be viewed by non-logged in users + + // Frame's owned by Service Providers where: + // isPublic is FALSE - can only be viewed if a user is + // a member of the Service Providers CUG + // isPublic is TRUE - can be viewed by users (if logged in) + + this.isPublic = false; // Is this frame accessible to non CUG users + // If FALSE user must be a member of the CUG to view the frame + // All users, including unauthenticated, are members of 'system' (owner = 0) + this.isAccessible = false; // Is this frame available to be accessed + // If FALSE, only the SP can view/edit the frame + + this.type = FRAME_TYPE_INFO; // The frame type - see FRAME_TYPES above + this.key = [null,null,null,null,null,null,null,null,null,null]; // Key actions [0-9] + + /** + * Determine if this frame is accessible to the current user + */ + Object.defineProperty(this,'accessible',{ + get: function() { + log(LOG_DEBUG,'- Checking if user can access frame: '+this.page); + log(LOG_DEBUG,' - User: '+JSON.stringify(user.number)); + log(LOG_DEBUG,' - Frame Owner: '+JSON.stringify(this.owner)+', System Frame: '+(this.pageowner == SYSTEM_OWNER)); + + // user.number 0 is unidentified user. + if (user.number) { + return ( + (this.isAccessible && this.pageowner == SYSTEM_OWNER && ! this.isPublic) || + (this.isAccessible && this.isPublic) || + (this.isAccessible && ! this.isPublic && this.isMember) || + (pageEditor(this.frame)) + ); + + } else { + return (this.isAccessible && this.pageowner == SYSTEM_OWNER && this.isPublic); + } + } + }); + + /** + * Return the frames fields + */ + Object.defineProperty(this,'fields', { + get: function() { + return this.frame_fields; + } + }); + + /** + * Check if the user is already a member of the CUG + */ + Object.defineProperty(this,'isMember',{ + get: function() { + log(LOG_DEBUG,'- Checking if user is a member of frame: '+this.page); + + if (user.number) { + return ( + (this.pageowner === SYSTEM_OWNER) + ); + + } else { + return false; + } + } + }) + + /** + * Return the page number + */ + Object.defineProperty(this,'page', { + get: function() { + if (this.frame == null || this.index == null) return null; + return this.frame.toString()+this.index; + } + }); + + Object.defineProperty(this,'pageowner', { + get: function() { + return pageOwner(this.frame).prefix; + } + }) + + Object.defineProperty(this,'pageownerlogo', { + get: function() { + return base64_decode(pageOwner(this.frame).logoans); + } + }) +} + +/** + * Return the message for a index + * + * @param index + * @returns {string|*} + */ +PageFrame.prototype.getMessage = function(index) { + eval('var msg = this.settings.'+index); + return msg; +} + +/** + * Load a frame + * + * @param filename + * @returns {null} + */ +PageFrame.prototype.load = function(filename) { + log(LOG_DEBUG,'Loading FRAME from: '+filename+'.'+this.settings.ext); + + f = new File(system.mods_dir+'ansitex/text/'+filename+'.'+this.settings.ext); + if (! f.exists || ! f.open('r')) { + return null; + } + + try { + var load = JSON.parse(f.read()); + + for (property in SAVED_FRAME_ATTRS) { + this[SAVED_FRAME_ATTRS[property]] = load[SAVED_FRAME_ATTRS[property]]; + } + + } catch (error) { + log(LOG_ERROR,'Frame error: '+error); + + // Load our system error frame. + // @todo this should be a config item + this.load('998a'); + return null; + } + + log(LOG_DEBUG,'Loaded frame: ['+this.frame+']['+this.index+'] ('+this.page+')'); +}; + +/** + * Enable pulling out submitted value by its name + * + * @param key + * @returns {null|string|*} + */ +PageFrame.prototype.fieldValue = function(key) { + for each (var k in this.frame_fields) { + log(LOG_DEBUG,' - k:'+JSON.stringify(k)); + + if (k.fname == key) { + return k.fvalue; + } + } + + return null; +} + +/** + * 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] 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. + * + * @param text + */ +PageFrame.prototype.parse = function(text) { + /** Column + * @type {number} + */ + var c = 1; + /** Row - row 1 is the header + * @type {number} + */ + var r = 2; + /** + * The output to send to the screen + * @type {string} + */ + var output = ''; + /** + * Fields within the frame + * @type {{ + * ftype: fieldtype, + * flength: fieldlen, + * fchar: fieldchar, + * fname: field, + * r: r, + * c: c, + * attribute: {i:i,f:f,b:b}, + * fvalue: '', + * }} + */ + this.frame_fields = []; + + // If there is no text return + if (! text) + return output; + + // Default Attributes + // @note - this has no affect for viewdata frames + /* Foreground */ + var f = 39; + /* Background */ + var b = 49; + /* Intensity */ + var i = 0; + + for(var p=0;p= 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: + log(LOG_DEBUG,'? CSI: '+matches[2]); + } + + break; + + case ' ': + log(LOG_DEBUG,'LOOSE ESC? ['+r+'x'+c+'] '+advance); + + break; + + // X - dynamic fields, terminated with ESC + case 'X': + //log(LOG_DEBUG,'PU ['+r+'x'+c+'] '+advance); + advance++; + + // Find our end ST param in the next 50 chars + matches = text.substr(p+advance,50).match(/(([a-z]+;[0-9]+)([;]?.+)?)\x1b\\/); + //log(LOG_DEBUG,'PU ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+JSON.stringify(matches)+', LENGTH: '+(matches ? matches[0].length : 0)+', STRING: '+text.substr(p+advance,50)); + + if (! matches) { + chars += nextbyte; + + break; + } + + advance += matches[0].length-1; + + var pu = matches[0].substr(0,matches[0].length-2).split(';'); + //log(LOG_DEBUG,'PU ['+r+'x'+c+'] ADVANCE: '+advance+', PU: '+pu); + + chars = renderfield(pu[0],pu[1]); + byte = ''; + + log(LOG_DEBUG,'PU Field found at ['+r+'x'+(c-1)+'], Field: '+pu[0]+', Length: '+pu[1]+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); + + break; + + // _ - has our fields that take input + case '_': + //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] '+advance); + advance++; + + // Find our end ST param in the next 50 chars + matches = text.substr(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.substr(p+advance,50)); + + if (! matches) { + chars += nextbyte; + + break; + } + + advance += matches[0].length-1; + + // The last 2 chars of matches[0] are the ESC \ + var 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 field = 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 ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]); + } + } + + if (fieldlen) { + chars = fieldchar.repeat(fieldlen); + } + + byte = ''; + + this.frame_fields.push({ + ftype: fieldtype, + flength: fieldlen, + fchar: fieldchar, + fname: field, + r: r, + c: c, + attribute: {i:i,f:f,b:b}, + fvalue: '', + }); + + log(LOG_DEBUG,'SOS Field found at ['+r+'x'+(c-1)+'], Type: '+fieldtype+', Length: '+fieldlen+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); + + break; + + default: + log(LOG_DEBUG,'DEFAULT ['+r+'x'+c+'] '+advance); + } + + break; + + default: + 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>this.settings.FRAME_WIDTH) { + c = 1; + r++; + } + + /* + // @todo - If we are longer than FRAME_LENGTH, move the output into the next frame. + if (r>this.settings.FRAME_LENGTH) { + break; + } + */ + } + + return output; +}; diff --git a/load/frame-viewdata.js b/load/frame-viewdata.js new file mode 100644 index 0000000..44335b6 --- /dev/null +++ b/load/frame-viewdata.js @@ -0,0 +1,255 @@ +var FRAME_VIEWDATA = (1<<2); + +var VIEWDATA_LEFT = ascii(0x08); +var VIEWDATA_RIGHT = ascii(0x09); +var VIEWDATA_DOWN = ascii(0x0a); +var VIEWDATA_UP = ascii(0x0b); +var VIEWDATA_CLS = ascii(0x0c); +var VIEWDATA_CR = ascii(0x0d); +var VIEWDATA_CON = ascii(0x11); +var VIEWDATA_COFF = ascii(0x14); +var VIEWDATA_HOME = ascii(0x1e); +var VIEWDATA_MOSIAC_RED = ascii(27)+ascii(0x51); +var VIEWDATA_MOSIAC_GREEN = ascii(27)+ascii(0x52); +var VIEWDATA_MOSIAC_YELLOW = ascii(27)+ascii(0x53); +var VIEWDATA_MOSIAC_BLUE = ascii(27)+ascii(0x54); +var VIEWDATA_MOSIAC_MAGENTA = ascii(27)+ascii(0x55); +var VIEWDATA_MOSIAC_CYAN = ascii(27)+ascii(0x56); +var VIEWDATA_MOSIAC_WHITE = ascii(27)+ascii(0x57); + +load('ansitex/load/frame-page.js'); + +// Our frame object +function FrameViewdata() { + PageFrame.apply(this,arguments); + + /* File Extension used for frames */ + this.settings.ext = 'vtx'; + + /* Length of a frame */ + this.settings.FRAME_LENGTH = 22; + /* Width of a frame */ + this.settings.FRAME_WIDTH = 40; + /* Size of page owner (length) */ + this.settings.FRAME_HEADER = 23; + /* Size of page number (length with a-z) */ + this.settings.FRAME_PAGENUM = 11; + /* Size of cost (length without unit) */ + this.settings.FRAME_COST = 3; + + this.settings.MSG_SENDORNOT = ascii(27)+'BKEY 1 TO SEND, 2 NOT TO SEND'; + this.settings.MSG_LOGON = ascii(27)+'BKEY 1 TO LOGON, 2 TO RETURN'; + this.settings.MSG_SENT = ascii(27)+'BMESSAGE SENT - KEY _ TO CONTINUE'; + this.settings.MSG_NOTSENT = ascii(27)+'BMESSAGE NOT SENT - KEY _ TO CONTINUE'; + this.settings.ERR_NO_PARENT = ascii(27)+'APARENT FRAME DOESNT EXIST'; + this.settings.ERR_NOT_IMPLEMENTED = ascii(27)+'ANOT IMPLEMENTED YET?'; + this.settings.ERR_ROUTE = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; + this.settings.ERR_METHOD_NOT_EXIST = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; + this.settings.ACCESS_DENIED = ascii(27)+'ACCESS DENIED.'; + this.settings.ALREADY_MEMBER = ascii(27)+'AALREADY MEMBER OF CUG' + this.settings.INACTIVITY = ascii(27)+'AINACTIVITY ALERT, DISCONNECT PENDING...'; + this.settings.INACTIVE = ascii(27)+'AINACTIVITY DISCONNECT'; + this.settings.NOACTION = ascii(27)+'ANO ACTION PERFORMED'; + this.settings.BASESTAR = ascii(27)+'B*'; + this.settings.INVALID_CODE = ascii(27)+'AINVAID CODE, PLEASE TRY AGAIN *00'; + this.settings.TOKEN_EMAIL = ascii(27)+'ATOKEN EMAILED TO YOU...'; + this.settings.TOKEN_SENT = ascii(27)+'ATOKEN SENT, PLEASE ENTER TOKEN'; + this.settings.INVALID_EMAIL = ascii(27)+'AINVAID EMAIL, PLEASE TRY AGAIN *00'; + this.settings.INVALID_UID = ascii(27)+'AINVAID USER ID, PLEASE TRY AGAIN *00'; + this.settings.CANNOT_SEND_TOKEN = ascii(27)+'ACANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; + this.settings.USER_EXISTS = ascii(27)+'AERROR USER EXISTS, PLEASE TRY AGAIN *00'; + this.settings.USER_CREATE_ERROR = ascii(27)+'AERROR CREATING USER, PLEASE TRY AGAIN *00'; + this.settings.LOGIN_ERROR = ascii(27)+'AERROR LOGGING IN, PLEASE TRY AGAIN *00'; + this.settings.CANCEL_MSG = ascii(27)+'BPRESS 2 TO CANCEL'; + + var blp=0; // Length of data on the bottom line + + /** + * Set the attribute at the current position + */ + this.attr=function(field) { + //NOOP + } + + /** + * Turn off the cursor + */ + this.cursorOff=function() { + write_raw(VIEWDATA_COFF); + this.gotoxy(1,1); + } + + this.cursorOn=function(x,y) { + write_raw(VIEWDATA_CON); + this.gotoxy(x,y); + } + + // Field backspace, that leaves the field filler char + this.fieldbs=function(char) { + console.write(VIEWDATA_LEFT+char+VIEWDATA_LEFT); + } + + this.gotoxy=function(x,y) { + // @todo This could be optimised to go the shortest route + write_raw(VIEWDATA_HOME); + if (x>0) + write_raw(VIEWDATA_RIGHT.repeat(x)); + if (y>0) + write_raw(VIEWDATA_DOWN.repeat(y)); + } + + this.strlen=function(str) { + return str.replace(/\x1b/g,'').length; + }; + + // Render the frame to the user + this.render=function(withHeader) { + log(LOG_DEBUG,'- VIEWDATA FRAME'); + owner = base64_decode(this.owner); + + header = VIEWDATA_DOWN; + + //log(LOG_DEBUG,' - FRAME User: ['+JSON.stringify(user)+']'); + + // Dont show the page number on system login page + if (user.number || (this.type !== FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) === -1)) { + log(LOG_DEBUG,' - Owner: ['+this.pageowner+'] ('+this.strlen(videotex(this.pageownerlogo))+')'); + + cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); + + header = videotex(this.pageownerlogo)+' '.repeat(this.settings.FRAME_HEADER-this.strlen(videotex(this.pageownerlogo)))+ + (this.isAccessible ? ascii(27)+'G' : ascii(27)+'A')+this.page+' '.repeat(this.settings.FRAME_PAGENUM-this.page.length)+ + ascii(27)+'B'+' '.repeat(this.settings.FRAME_COST-cost.toString().length+1)+cost; + } + + //console.status |= CON_RAW_IN; + write_raw(VIEWDATA_CLS); + write_raw(header); + return write_raw(videotex(base64_decode(this.content))); + }; + + this.qrcode = function(qr) { + // Render the body + var qrcode = VIEWDATA_HOME+VIEWDATA_DOWN.repeat(5); + var offset = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-1; + + for (var x = -1; x < qr.size; x=x+3) { + var line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+VIEWDATA_MOSIAC_WHITE; + + for (var y = -1; y < qr.size; y=y+2) { + var char = 0; + + //TL + char |= ((x==-1) || (y==-1) || ! qr.getModule(x,y)) ? (1<<0) : (0<<0); + //TR + char |= ((x==-1) || (y == qr.size-1) || ! qr.getModule(x,y+1)) ? (1<<1) : (0<<1); + //ML + char |= ((y==-1) || ! qr.getModule(x+1,y)) ? (1<<2) : (0<<2); + //MR + char |= ((y == qr.size-1) || ! qr.getModule(x+1,y+1)) ? (1<<3) : (0<<3); + //BL + char |= ((x==qr.size-2) || (y==-1) || ! qr.getModule(x+2,y)) ? (1<<4) : (0<<4); + //BR + char |= ((x==qr.size-2) || (y == qr.size-1) || ! qr.getModule(x+2,y+1)) ? (1<<5) : (0<<5); + + char += 0x20; + if (char > 0x3f) + char += 0x20; + + line += ascii(char); + } + + // Render the right column + if (y%2) + line += ascii(0x35); + + repeat_count = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-offset-(offset ? 1 : 2)-(y%2 == 1 ? 0 : 1); + + qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); + + // To fix some terminals where moving right from col 40 doesnt advance to col 1 on the next line + qrcode +=VIEWDATA_LEFT+VIEWDATA_CR+VIEWDATA_DOWN; + } + + log(LOG_DEBUG,'WIDTH:'+this.settings.FRAME_WIDTH); + log(LOG_DEBUG,'QR :'+(Math.ceil(qr.size/2)+1)); + log(LOG_DEBUG,'OFF :'+offset); + log(LOG_DEBUG,'Y :'+(y%2 ? 0 : 1)); + log(LOG_DEBUG,'X :'+(x%3 ? 0 : 1)); + + // Render the bottom + if (x%3) { + line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+VIEWDATA_MOSIAC_WHITE; + + for (var y = 0; y < qr.size; y=y+2) { + line += ascii(0x23); + } + + // Render the right column + if (y%2 == 0) { + line += ascii(0x21); + } + + qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); + } + + write_raw(qrcode); + }; + + this.save=function() { + file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; + w = new File(file); + if (! w.open('w')) { + log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); + exit(1); + } + + w.write(JSON.stringify(this)); + w.close(); + + log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); + } + + /** + * Send a message to the baseline. + * + * @param text + * @param reposition + */ + this.sendBaseline=function(text,reposition) { + var msg = this.getMessage(text); + var x = this.strlen(msg); + + write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ + ((blp > x) + ? (' '.repeat(blp-x)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(x) : '')) + : '') + ); + + blp = x; + } + + this.clearBaseline=function(reposition) { + msg = ''; + + write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ + ((blp > msg.length) + ? (' '.repeat(blp-msg.length)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(msg.length) : '')) + : '') + ); + + blp = msg.length; + } +} + +function videotex(data) { + var output = ''; + //$output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte); + for (var i = 0; i < data.length; i++) { + output += (data.charCodeAt(i) < 32) ? "\x1b"+String.fromCharCode(data.charCodeAt(i)+64) : String.fromCharCode(data.charCodeAt(i)); + } + return output; +} + +FrameViewdata.prototype = PageFrame.prototype; +FrameViewdata.prototype.constructor = FrameViewdata; diff --git a/load/viewdataframe.js b/load/viewdataframe.js deleted file mode 100644 index 0d0efe7..0000000 --- a/load/viewdataframe.js +++ /dev/null @@ -1,608 +0,0 @@ -var FRAME_VIEWDATA = (1<<2); - -var VIEWDATA_FRAME_LENGTH = 22; /* Length of a frame */ -var VIEWDATA_FRAME_WIDTH = 40; /* Width of a frame */ -var VIEWDATA_FRAME_HEADER = 23; /* Size of page owner (length) */ -var VIEWDATA_FRAME_PAGENUM = 11; /* Size of page number (length with a-z) */ -var VIEWDATA_FRAME_COST = 3; /* Size of cost (length without unit) */ - -var VIEWDATA_LEFT = ascii(0x08); -var VIEWDATA_RIGHT = ascii(0x09); -var VIEWDATA_DOWN = ascii(0x0a); -var VIEWDATA_UP = ascii(0x0b); -var VIEWDATA_CLS = ascii(0x0c); -var VIEWDATA_CR = ascii(0x0d); -var VIEWDATA_CON = ascii(0x11); -var VIEWDATA_COFF = ascii(0x14); -var VIEWDATA_HOME = ascii(0x1e); -var VIEWDATA_MOSIAC_RED = ascii(27)+ascii(0x51); -var VIEWDATA_MOSIAC_GREEN = ascii(27)+ascii(0x52); -var VIEWDATA_MOSIAC_YELLOW = ascii(27)+ascii(0x53); -var VIEWDATA_MOSIAC_BLUE = ascii(27)+ascii(0x54); -var VIEWDATA_MOSIAC_MAGENTA = ascii(27)+ascii(0x55); -var VIEWDATA_MOSIAC_CYAN = ascii(27)+ascii(0x56); -var VIEWDATA_MOSIAC_WHITE = ascii(27)+ascii(0x57); - -var VIEWDATA_MSG_SENDORNOT = ascii(27)+'BKEY 1 TO SEND, 2 NOT TO SEND'; -var VIEWDATA_MSG_LOGON = ascii(27)+'BKEY 1 TO LOGON, 2 TO RETURN'; -var VIEWDATA_MSG_SENT = ascii(27)+'BMESSAGE SENT - KEY _ TO CONTINUE'; -var VIEWDATA_MSG_NOTSENT = ascii(27)+'BMESSAGE NOT SENT - KEY _ TO CONTINUE'; -var VIEWDATA_ERR_NO_PARENT = ascii(27)+'APARENT FRAME DOESNT EXIST'; -var VIEWDATA_ERR_NOT_IMPLEMENTED = ascii(27)+'ANOT IMPLEMENTED YET?'; -var VIEWDATA_ERR_ROUTE = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; -var VIEWDATA_ERR_METHOD_NOT_EXIST = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; -var VIEWDATA_ACCESS_DENIED = ascii(27)+'ACCESS DENIED.'; -var VIEWDATA_ALREADY_MEMBER = ascii(27)+'AALREADY MEMBER OF CUG' -var VIEWDATA_INACTIVITY = ascii(27)+'AINACTIVITY ALERT, DISCONNECT PENDING...'; -var VIEWDATA_INACTIVE = ascii(27)+'AINACTIVITY DISCONNECT'; -var VIEWDATA_NOACTION = ascii(27)+'ANO ACTION PERFORMED'; -var VIEWDATA_BASESTAR = ascii(27)+'B*'; -var VIEWDATA_INVALID_CODE = ascii(27)+'AINVAID CODE, PLEASE TRY AGAIN *00'; -var VIEWDATA_TOKEN_EMAIL = ascii(27)+'ATOKEN EMAILED TO YOU...'; -var VIEWDATA_TOKEN_SENT = ascii(27)+'ATOKEN SENT, PLEASE ENTER TOKEN'; -var VIEWDATA_INVALID_EMAIL = ascii(27)+'AINVAID EMAIL, PLEASE TRY AGAIN *00'; -var VIEWDATA_INVALID_UID = ascii(27)+'AINVAID USER ID, PLEASE TRY AGAIN *00'; -var VIEWDATA_CANNOT_SEND_TOKEN = ascii(27)+'ACANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; -var VIEWDATA_USER_EXISTS = ascii(27)+'AERROR USER EXISTS, PLEASE TRY AGAIN *00'; -var VIEWDATA_USER_CREATE_ERROR = ascii(27)+'AERROR CREATING USER, PLEASE TRY AGAIN *00'; -var VIEWDATA_LOGIN_ERROR = ascii(27)+'AERROR LOGGING IN, PLEASE TRY AGAIN *00'; -var VIEWDATA_CANCEL_MSG = ascii(27)+'BPRESS 2 TO CANCEL'; - -// Our frame object -function VIEWDATAFrame() { - this.version=1; // The version of this frame - in case we update functionality and we need to be - // backwards compatible - this.frame=null; // Frame Number [0-9]+ - this.index=null; // Frame Index [a-z] - this.owner=0; // The Service Provider owning the frame. - this.cost=0; // The cost to view the frame @TODO - this.content=''; // The frame content, base64 encoded - var blp=0; // Length of data on the bottom line - - // Frame's owned by the system where: - // isPublic is FALSE - the user must be logged in to view it - // isPublic is TRUE - can be viewed by non-logged in users - - // Frame's owned by Service Providers where: - // isPublic is FALSE - can only be viewed if a user is - // a member of the Service Providers CUG - // isPublic is TRUE - can be viewed by users (if logged in) - - this.isPublic=false; // Is this frame accessible to non CUG users - // If FALSE user must be a member of the CUG to view the frame - // All users, including unauthenticated, are members of 'system' (owner = 0) - this.isAccessible=false; // Is this frame available to be accessed - // If FALSE, only the SP can view/edit the frame - - this.type = FRAME_TYPE_INFO; // The frame type - see FRAME_TYPES above - this.key=[ null,null,null,null,null,null,null,null,null,null ]; // Key actions [0-9] - - /** - * Set the attribute at the current position - */ - this.attr=function(field) { - //NOOP - } - - /** - * Turn off the cursor - */ - this.cursorOff=function() { - write_raw(VIEWDATA_COFF); - this.gotoxy(1,1); - } - - this.cursorOn=function(x,y) { - write_raw(VIEWDATA_CON); - this.gotoxy(x,y); - } - - // Field backspace, that leaves the field filler char - this.fieldbs=function(char) { - console.write(VIEWDATA_LEFT+char+VIEWDATA_LEFT); - } - - this.gotoxy=function(x,y) { - // @todo This could be optimised to go the shortest route - write_raw(VIEWDATA_HOME); - if (x>0) - write_raw(VIEWDATA_RIGHT.repeat(x)); - if (y>0) - write_raw(VIEWDATA_DOWN.repeat(y)); - } - - this.strlen=function(str) { - return str.replace(/\x1b/g,'').length; - }; - - // Render the frame to the user - this.render=function(withHeader) { - log(LOG_DEBUG,'- VIEWDATA FRAME'); - owner = base64_decode(this.owner); - - header = VIEWDATA_DOWN; - - //log(LOG_DEBUG,' - FRAME User: ['+JSON.stringify(user)+']'); - - // Dont show the page number on system login page - if (user.number || (this.type != FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) == -1)) { - log(LOG_DEBUG,' - Owner: ['+this.pageowner+'] ('+this.strlen(videotex(this.pageownerlogo))+')'); - - cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); - - header = videotex(this.pageownerlogo)+' '.repeat(VIEWDATA_FRAME_HEADER-this.strlen(videotex(this.pageownerlogo)))+ - (this.isAccessible ? ascii(27)+'G' : ascii(27)+'A')+this.page+' '.repeat(VIEWDATA_FRAME_PAGENUM-this.page.length)+ - ascii(27)+'B'+' '.repeat(VIEWDATA_FRAME_COST-cost.toString().length+1)+cost; - } - - //console.status |= CON_RAW_IN; - write_raw(VIEWDATA_CLS); - write_raw(header); - return write_raw(videotex(base64_decode(this.content))); - }; - - this.fieldValue=function(key) { - for each (var k in this.frame_fields) { - log(LOG_DEBUG,' - k:'+JSON.stringify(k)); - - if (k.fname == key) { - return k.fvalue; - } - } - - return null; - } - - // Load a frame from disk (.tex file) - this.load = function(filename) { - log(LOG_DEBUG,'Loading VIEWDATA frame from: '+filename); - - f = new File(system.mods_dir+'ansitex/text/'+filename+'.vtx'); - if (! f.exists || ! f.open('r')) { - return null; - } - - try { - var load = JSON.parse(f.read()); - - for (property in load) { - this[property] = load[property]; - } - - } catch (error) { - log(LOG_ERROR,'Frame error: '+error); - - // Load our system error frame. - this.load('998a'); - 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] 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. - * - * @param text - */ - this.parse = function(text) { - var c = 1; // column - var r = 2; // row (row 1 is the header) - var output = ''; - this.frame_fields = []; - - // If there is no text return - if (! text) - return output; - - // Default Attributes - f = 39; - b = 49; - i = 0; - - for(p=0;p= 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: - log(LOG_DEBUG,'? 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.substr(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.substr(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 ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]); - } - } - - if (fieldlen) { - chars = fieldchar.repeat(fieldlen); - } - - byte = ''; - - this.frame_fields.push({ - ftype: fieldtype, - flength: fieldlen, - fchar: fieldchar, - fname: field, - r: r, - c: c, - attribute: {i:i,f:f,b:b}, - fvalue: '', - }); - - log(LOG_DEBUG,'SOS Field found at ['+r+'x'+(c-1)+'], Type: '+fieldtype+', Length: '+fieldlen+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); - - break; - - default: - log(LOG_DEBUG,'DEFAULT ['+r+'x'+c+'] '+advance); - } - - break; - - default: - 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>VIEWDATA_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; - } - */ - } - - return output; - }; - - this.qrcode = function(qr) { - // Render the body - var qrcode = VIEWDATA_HOME+VIEWDATA_DOWN.repeat(5); - var offset = VIEWDATA_FRAME_WIDTH-Math.ceil(qr.size/2)-1; - - for (var x = -1; x < qr.size; x=x+3) { - var line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+VIEWDATA_MOSIAC_WHITE; - - for (var y = -1; y < qr.size; y=y+2) { - var char = 0; - - //TL - char |= ((x==-1) || (y==-1) || ! qr.getModule(x,y)) ? (1<<0) : (0<<0); - //TR - char |= ((x==-1) || (y == qr.size-1) || ! qr.getModule(x,y+1)) ? (1<<1) : (0<<1); - //ML - char |= ((y==-1) || ! qr.getModule(x+1,y)) ? (1<<2) : (0<<2); - //MR - char |= ((y == qr.size-1) || ! qr.getModule(x+1,y+1)) ? (1<<3) : (0<<3); - //BL - char |= ((x==qr.size-2) || (y==-1) || ! qr.getModule(x+2,y)) ? (1<<4) : (0<<4); - //BR - char |= ((x==qr.size-2) || (y == qr.size-1) || ! qr.getModule(x+2,y+1)) ? (1<<5) : (0<<5); - - char += 0x20; - if (char > 0x3f) - char += 0x20; - - line += ascii(char); - } - - // Render the right column - if (y%2) - line += ascii(0x35); - - repeat_count = VIEWDATA_FRAME_WIDTH-Math.ceil(qr.size/2)-offset-(offset ? 1 : 2)-(y%2 == 1 ? 0 : 1); - - qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); - - // To fix some terminals where moving right from col 40 doesnt advance to col 1 on the next line - qrcode +=VIEWDATA_LEFT+VIEWDATA_CR+VIEWDATA_DOWN; - } - - log(LOG_DEBUG,'WIDTH:'+VIEWDATA_FRAME_WIDTH); - log(LOG_DEBUG,'QR :'+(Math.ceil(qr.size/2)+1)); - log(LOG_DEBUG,'OFF :'+offset); - log(LOG_DEBUG,'Y :'+(y%2 ? 0 : 1)); - log(LOG_DEBUG,'X :'+(x%3 ? 0 : 1)); - - // Render the bottom - if (x%3) { - line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+VIEWDATA_MOSIAC_WHITE; - - for (var y = 0; y < qr.size; y=y+2) { - line += ascii(0x23); - } - - // Render the right column - if (y%2 == 0) { - line += ascii(0x21); - } - - qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); - } - - write_raw(qrcode); - }; - - this.save=function() { - file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; - w = new File(file); - if (! w.open('w')) { - log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); - exit(1); - } - - w.write(JSON.stringify(this)); - w.close(); - - log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); - } - - /** - * Send a message to the baseline. - * - * @param text - * @param reposition - */ - this.sendBaseline=function(text,reposition) { - eval('var msg = VIEWDATA_'+text+';'); - var x = this.strlen(msg); - - write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ - ((blp > x) - ? (' '.repeat(blp-x)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(x) : '')) - : '') - ); - - blp = x; - } - - this.clearBaseline=function(reposition) { - msg = ''; - - write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ - ((blp > msg.length) - ? (' '.repeat(blp-msg.length)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(msg.length) : '')) - : '') - ); - - blp = msg.length; - } - - Object.defineProperty(this,'accessible',{ - get: function() { - log(LOG_DEBUG,'- Checking if user can access frame: '+this.page); - log(LOG_DEBUG,' - User: '+JSON.stringify(user.number)); - log(LOG_DEBUG,' - Frame Owner: '+JSON.stringify(this.owner)+', System Frame: '+(this.pageowner == SYSTEM_OWNER)); - - // user.number 0 is unidentified user. - if (user.number) { - return ( - (this.isAccessible && this.pageowner == SYSTEM_OWNER && ! this.isPublic) || - (this.isAccessible && this.isPublic) || - (this.isAccessible && ! this.isPublic && this.isMember) || - (pageEditor(this.frame)) - ); - - } else { - return (this.isAccessible && this.pageowner == SYSTEM_OWNER && this.isPublic); - } - } - }); - - Object.defineProperty(this,'fields', { - get: function() { - return this.frame_fields; - } - }); - - // Check if the user is already a member of the CUG - Object.defineProperty(this,'isMember',{ - get: function() { - log(LOG_DEBUG,'- Checking if user is a member of frame: '+this.page); - - if (user.number) { - return ( - (this.pageowner == SYSTEM_OWNER) - ); - - } else { - return false; - } - } - }) - - Object.defineProperty(this,'page', { - get: function() { - if (this.frame == null || this.index == null) return null; - return this.frame.toString()+this.index; - } - }); - - Object.defineProperty(this,'pageowner', { - get: function() { - return pageOwner(this.frame).prefix; - } - }) - - Object.defineProperty(this,'pageownerlogo', { - get: function() { - return base64_decode(pageOwner(this.frame).logovtx); - } - }) -} - -function videotex(data) { - var output = ''; - //$output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte); - for (var i = 0; i < data.length; i++) { - output += (data.charCodeAt(i) < 32) ? "\x1b"+String.fromCharCode(data.charCodeAt(i)+64) : String.fromCharCode(data.charCodeAt(i)); - } - return output; -} diff --git a/main.js b/main.js index 9eb823e..280900a 100644 --- a/main.js +++ b/main.js @@ -12,8 +12,8 @@ load('ansitex/load/funcs.js'); // Ansitex specific includes require('ansitex/load/defs.js','ACTION_EXIT'); -require('ansitex/load/ansiframe.js','FRAME_ANSI'); -require('ansitex/load/viewdataframe.js','FRAME_VIEWDATA'); +require('ansitex/load/frame-ansi.js','FRAME_ANSI'); +require('ansitex/load/frame-viewdata.js','FRAME_VIEWDATA'); // @todo Returning from chat should refresh the frame // @todo Suppress displays of telegrams @@ -53,9 +53,9 @@ while(bbs.online) { /** * Current Frame Object that is being displayed to the user - * - VIEWDATAFrame - for viewdata frames - * - ANSIFrame - for ANSItex frames - * @type {ANSIFrame|VIEWDATAFrame} + * - FrameViewdata - for viewdata frames + * - FrameAnsi - for ANSItex frames + * @type {FrameAnsi|FrameViewdata} */ var fo = null; @@ -834,7 +834,7 @@ while(bbs.online) { // If we are editing a specific frame, attempt to load it if (next_page) { var current = fo; - fo = viewdata ? new VIEWDATAFrame() : new ANSIFrame(); + fo = viewdata ? new FrameViewdata() : new FrameAnsi(); fo.load(pageStr(next_page)); // If the frame doesnt exist, check that the parent frame exists in case we are creating a new one @@ -843,7 +843,7 @@ while(bbs.online) { // We can always create an 'a' frame if (next_page.index !== 'a') { - fo = viewdata ? new VIEWDATAFrame() : new ANSIFrame(); + fo = viewdata ? new FrameViewdata() : new FrameAnsi(); fo.load(pageStr({frame: next_page.frame, index: String.fromCharCode(next_page.index.charCodeAt(0)-1)})); log(LOG_DEBUG,'- ACTION_EDIT: check index: '+JSON.stringify(fo)+' ('+String.fromCharCode(next_page.index.charCodeAt(0)-1)+')'); @@ -857,7 +857,7 @@ while(bbs.online) { } // New frame - fo = viewdata ? new VIEWDATAFrame() : new ANSIFrame(); + fo = viewdata ? new FrameViewdata() : new FrameAnsi(); fo.frame = next_page.frame; fo.index = next_page.index; fo.cost = 0; @@ -905,7 +905,7 @@ while(bbs.online) { if (next_page !== null) { current = fo; - fo = viewdata ? new VIEWDATAFrame() : new ANSIFrame(); + fo = viewdata ? new FrameViewdata() : new FrameAnsi(); fo.load(pageStr(next_page)); if (fo.page == null) { @@ -913,7 +913,7 @@ while(bbs.online) { // In case the frame doesnt exist if (fo == null) - fo = viewdata ? new VIEWDATAFrame() : new ANSIFrame(); + fo = viewdata ? new FrameViewdata() : new FrameAnsi(); // sendbaseline ERR_PAGE fo.sendBaseline('ERR_ROUTE',false);