'use strict'; /** PAGE.js handles ANSItex and ViewData page frames It also handles windows, horizontal/vertical scrolling and content paging. This is inspired by frame.js provided by Synchronet Objects: + Page - our display page, build with Windows - Line 1 - Title (Fixed) - Line 2..23 - Content (Scrollable) - Line 24 - Status/Command Input (Fixed) = @todo When scrolling is disabled, and the canvas is greater than the window, then "nextpage" returns the next frame = Pageable windows cannot have children [b-z frames], only "CONTENT" is paged = @todo Pageable windows are pagable when scrolling is false and window canvas.height > window.height and canvas.width = window.width + Window - size W x H, where W/H can be larger than the Screen - Window holds all the content to be shown - x,y - attributes define the position of the window in it's parent [1..] - z - determines which layer the window is on, higher z is shown [0..] - width/height - determines the physical size of the window (cannot be larger than it's parent) - canvas width/height - determines the logical size of the window, which if larger than physical enables scrolling - ox/oy - determines the start of the canvas that is shown, relative to canvas width/height - service - Current supported are ANSItex (80x24) and ViewData (40x24) - content - array of Chars height/width order - visible - determines if the window (and it's children) are renderable = Windows can be have children, and the z determines the layer shown relative to its parent = Swapping z values determines which windows are hidden by others + Char - object holding each character, and it's color when rendered = Rendering - ANSItex + Each attribute can have it's own color (colors take up no positional space) + We only change render control colors to change attributes when it actually changes, otherwise we render just the character - ViewData + An attribute Foreground or Background or Special Function takes up a character + Character must be set to NULL when it's a control character = EXAMPLE: a = new Page() // root frame 80 x 24 for ANSItex b = new Window(1,1,40,22,a.content) // b frame 40 x 22 - starting at 1,1 c = new Window(41,1,40,22,a.content) // c frame 40 x 22 - starting at 41,1 (child of a) d = new Window(1,1,21,10,c) // d frame 20 x 11 - starting at 1,1 of c e = new Window(25,12,10,5,c) // e frame 10 x 5 - starting at 25,12 of c f = new Window(15,8,13,7,c) // f frame 13 x 7 - starting at 15,8 of c --:____.____|____.____|____.____|____.____|____.____|____.____|____.____|____.____| 01:TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT 02:22222222222222222222222222222222222222224444444444444444444443333333333333333333 03:2 24 43 3 04:2 24 43 3 05:2 24 43 3 06:2 24 43 3 07:2 24 43 3 08:2 24 444444443333333 3 09:2 24 466666666666663 3 10:2 24 46 63 3 11:2 2444444444444446 63 3 12:2 2333333333333336 633333333 3 13:2 23 36 665555553 3 14:2 23 36 65 53 3 15:2 23 366666666666665 53 3 16:2 23 333333333335555 53 3 17:2 23 355555555553 3 18:2 23 333333333333 3 19:2 23 3 20:2 23 3 21:2 23 3 22:2 23 3 23:22222222222222222222222222222222222222223333333333333333333333333333333333333333 24:PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP --:____.____|____.____|____.____|____.____|____.____|____.____|____.____|____.____| */ load('ansitex/load/windows.js'); // Our supporting window class load('ansitex/load/msgbases.js'); // To read/write to message bases require('sbbsdefs.js','SS_USERON'); // Need for our ANSI colors eg: BG_* /** * This object represents a full page that interacts with the user * * @param service - Type of page (tex=ANSI, vtx=VIEWDATA) * @param debug - Whether to dump some debug information. This is an int, and will start debugging on line debug * @constructor * * Pages have the following attributes: * - dimensions - (string) representation of their width x height * - dynamic_fields - (array) Location of fields that are dynamically filled * - height - (Number) page height * - input_fields - (array) Location of fields that take input * - page - (string) Full page number (frame+index) * - width - (Number) page width * * Pages have the following settings: * - cost - (int) Cost to view the page * - page - (object) Frame/index of the page * - provider - (string) Name of the frame provider * - showHeader - (boolean) Whether to show the header when rendering the frame * - type - (TEX/VTX) Type of frame * * Pages have the following public functions * - build - Compile the frame for rendering * - display - Display the compiled frame * - import - Load a frame from a file source * - save - Save the frame to the msgbase * - load - Load the frame from the msgbase */ function Page(debug) { this.__window__ = { layout: undefined, // Window - Full page content header: undefined, // Window - Page Title provider: undefined, // Page provider (*) pagenum: undefined, // Our page number (*) cost: undefined, // Page cost (*) body: undefined, // Window - Page body }; this.__properties__ = { name: new PageObject, //type: undefined, // Frame type (deprecated, see attrs) attrs: 0, // (Type 0, CUG 0 (public), Hidden=false) input_fields: [], // Array of our input fields dynamic_fields: [], // Array of our dynamic fields //isAccessible: undefined, // Is this page visible to all users (deprecated, see attrs) // If FALSE, only the SP can view/edit the frame //isPublic: undefined, // Is this page visible to public (not CUG) users (deprecated, see attrs) // If FALSE user must be a member of the CUG to view the frame // All users, including unauthenticated, are members of 'system' (owner = 0) // 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) key: [], // Key actions raw: {}, // Page raw content for each session type date: undefined, // Date of frame }; this.__defaults__ = { attr: BG_BLACK|LIGHTGRAY, }; this.__compiled__ = { build: undefined, // Our page compiled content }; /* this.__settings__ = { pageable: false, // If the virtual window is larger that height (and width is the same) next page is the next content contenttitle: undefined, // Template (window) for 1st page (a) contentsubtitle: undefined, // Template (window) for subsequent pages (b..z) } */ /** * @todo borders for each window * @param service * @param debug */ function init(debug) { log(LOG_DEBUG,'- PAGE::init(): type ['+SESSION_EXT+']'); this.__window__.layout = new Window(1,1,FRAME_WIDTH,FRAME_HEIGHT+1,'LAYOUT',this,debug); this.__window__.body = new Window(1,2,FRAME_WIDTH,FRAME_HEIGHT,'CONTENT',this.__window__.layout,debug); this.__window__.header = new Window(1,1,FRAME_WIDTH,1,'HEADER',this.__window__.layout,debug); this.__window__.provider = new Window(1,1,FRAME_PROVIDER_LENGTH,1,'PROVIDER',this.__window__.header,debug); switch (SESSION_EXT) { case 'tex': require('ansitex/load/session/ansitex.js','SESSION_ANSITEX'); this.__window__.pagenum = new Window(57,1,FRAME_PAGE_LENGTH,1,'#',this.__window__.header,debug); this.__window__.cost = new Window(71,1,FRAME_COST_LENGTH,1,'$',this.__window__.header,debug); break; case 'vtx': require('ansitex/load/session/viewdata.js','SESSION_VIEWDATA'); this.__window__.pagenum = new Window(24,1,FRAME_PAGE_LENGTH,1,'#',this.__window__.header,debug); this.__window__.cost = new Window(35,1,FRAME_COST_LENGTH,1,'$',this.__window__.header,debug); break; default: throw new Error('INVALID Page Service: '+SESSION_EXT); } } /** * Determine if this frame is accessible to the current user */ Object.defineProperty(this,'accessible',{ get: function() { log(LOG_DEBUG,'- Checking if user ['+user.number+'] can access frame: '+this.name.toString()); log(LOG_DEBUG,'|* Frame Owner: '+this.owner+', System Frame: '+this.isSystemPage); log(LOG_DEBUG,'|* Accessible: '+this.isAccessible); log(LOG_DEBUG,'|* Public: '+this.isPublic); log(LOG_DEBUG,'|* Member: '+this.isMember); // user.number 0 is unidentified user. if (user.number) { return ( (this.isAccessible && this.isSystemPage && ! this.isPublic) || (this.isAccessible && this.isPublic) || (this.isAccessible && ! this.isPublic && this.isMember) || (pageEditor(this.name.frame)) ); } else { return this.isAccessible && this.isSystemPage && this.isPublic; } } }); // The page attributes, which determine accessible, public, cug and frame type Object.defineProperty(this,'attrs',{ get: function() { return this.__properties__.attrs; }, /* Frame attributes are bit items, bits: * 1-4 TYPE * 5-7 CUG * 8 accessible */ set: function(int) { if (int === undefined) int = 0; this.__properties__.attrs = int; } }); Object.defineProperty(this,'cost',{ get: function() { return Number(this.__properties__.cost); }, set: function(int) { if (int === undefined) int = 0; if (typeof int !== 'number') throw new Error('Cost must be a number'); this.__properties__.cost = int; if ((''+int).length > FRAME_COST_LENGTH-1-FRAME_ATTR_LENGTH) throw new Error('Cost too large'); // Populate the cost window switch (SESSION_EXT) { case 'tex': this.__window__.cost.__properties__.content = rawtoattrs(ESC+'[1;32m'+padright(int,FRAME_COST_LENGTH-1-FRAME_ATTR_LENGTH,' ')+FRAME_COSTUNIT).content; break; case 'vtx': this.__window__.cost.__properties__.content = rawtoattrs(VIEWDATA_BIN_GREEN+padright(int,FRAME_COST_LENGTH-1-FRAME_ATTR_LENGTH,' ')+FRAME_COSTUNIT).content; break; default: throw new Error(SESSION_EXT+' type not implemented'); } } }); Object.defineProperty(this,'cug',{ get: function() { return ((this.__properties__.attrs >> 4) & 0x7); } }); Object.defineProperty(this,'date',{ get: function() { if (this.__properties__.date !== undefined) return strftime("%a, %d %b %Y %H:%M:%S %z",this.__properties__.date); }, set: function(int) { if (typeof int !== 'number') throw new Error('DATE must be a epoch'); return this.__properties__.date = int; } }); Page.prototype.__defineGetter__('dimensions',function() { return this.__properties__.width+' X '+this.__properties__.height; }); Page.prototype.__defineGetter__('dynamic_fields',function() { return this.__properties__.dynamic_fields === undefined ? [] : this.__properties__.dynamic_fields; }); Page.prototype.__defineSetter__('dynamic_fields',function(array) { this.__properties__.dynamic_fields = array; }); Page.prototype.__defineGetter__('height',function() { return Number(this.__window__.layout.height); }); Page.prototype.__defineGetter__('input_fields',function() { return this.__properties__.input_fields; }); Page.prototype.__defineSetter__('input_fields',function(array) { this.__properties__.input_fields = array; }); Object.defineProperty(this,'isAccessible',{ get: function() { return (this.__properties__.attrs >> 7 !== 1); } }); /** * Check if the user is already a member of the CUG */ Object.defineProperty(this,'isMember',{ get: function() { // @todo Implement CUGs, this would be "=== SERVICE_PROVIDER" and user is a member of SERVICE_PROVIDER return (user.number > 0) && this.isSystemPage; } }); // Is the page public or CUG Object.defineProperty(this,'isPublic',{ get: function() { return (this.cug === 0); } }); // Is this a system page Object.defineProperty(this,'isSystemPage',{ get: function() { return (this.owner === SYSTEM_OWNER); } }); // Key Array Page.prototype.__defineGetter__('key',function() { return this.__properties__.key; }); Page.prototype.__defineSetter__('key',function(array) { if (typeof array !== 'object') throw new Error('key must be an array :'+typeof array); if (array.length !== 10) throw new Error('key must contain 10 items :'+array); return this.__properties__.key = array; }); Page.prototype.__defineGetter__('name',function() { return this.__properties__.name; }); Page.prototype.__defineSetter__('name',function(object) { if (!(object instanceof PageObject)) throw new Error('Page must be PageObject'); this.__properties__.name = object; if ((''+this.__properties__.name.frame).length > FRAME_PAGE_LENGTH-1-FRAME_ATTR_LENGTH) throw new Error('Pagenum too large - '+(''+this.__properties__.name.frame).length+'|'+(FRAME_PAGE_LENGTH-1-FRAME_ATTR_LENGTH)); switch (SESSION_EXT) { case 'tex': this.__window__.pagenum.__properties__.content = rawtoattrs(ESC+'[1;37m'+this.__properties__.name.toString()).content; break; case 'vtx': this.__window__.pagenum.__properties__.content = rawtoattrs(VIEWDATA_BIN_WHITE+this.__properties__.name.toString()).content; break; default: throw new Error(SESSION_EXT+' type not implemented'); } }); // Display who owns the page Object.defineProperty(this,'owner',{ get: function() { return pageOwner(this.__properties__.name.frame).prefix; }, }); Page.prototype.__defineGetter__('pagenext',function() { return this.__properties__.name.next; }); /** * Determine who the owner of a page is * @deprecated use owner */ Page.prototype.__defineGetter__('pageowner',function() { return pageOwner(this.__properties__.name.frame).prefix; }); Page.prototype.__defineSetter__('provider',function(ansi) { var provider; switch (SESSION_EXT) { case 'tex': provider = rawtoattrs(ansi+ESC+'[0m').content; if (provider[1].filter(function(child) { return child.ch; }).length-1 > FRAME_PROVIDER_LENGTH) throw new Error('Provider too large'); break; case 'vtx': provider = rawtoattrs(ansi).content; if (provider[1].length-1 > FRAME_PROVIDER_LENGTH) throw new Error('Provider too large'); break; default: throw new Error(SESSION_EXT+' not implemented'); } this.__window__.provider.__properties__.content = provider; }); Object.defineProperty(this,'raw',{ get: function() { return this.__properties__.raw }, set: function(value) { this.__properties__.raw[SESSION_EXT] = value; } }); Page.prototype.__defineSetter__('showHeader',function(bool) { if (typeof bool !== 'boolean') throw new Error('showHeader expected a true/false'); this.__window__.header.visible = bool; }); Object.defineProperty(this,'type',{ get: function() { return this.__properties__.attrs & 0xf; }, }); Page.prototype.__defineGetter__('type',function() { return this.__properties__.type; }); Page.prototype.__defineSetter__('type',function(string) { if (typeof string !== 'string') throw new Error('type must be an string :'+typeof string); return this.__properties__.type = string; }); Page.prototype.__defineGetter__('width',function() { return Number(this.__window__.layout.width); }); /** * Build the screen layout * * @returns {*} */ this.build = function(force) { log(LOG_DEBUG,'* Building frame...'); if (this.__compiled__.build && ! force) throw new Error('Refusing to build without force.'); this.build_system_fields(); this.__compiled__.build = this.__window__.layout.build(1,1,false); // Add our dynamic values var fields = this.dynamic_fields.filter(function(item) { return item.value !== undefined; }); log(LOG_DEBUG,'|* We have DF fields:'+fields.length); if (fields.length) insert_fields(fields,this.__compiled__.build); // Add our dynamic values fields = this.input_fields.filter(function(item) { return item.value !== undefined; }); log(LOG_DEBUG,'|* We have INPUT fields:'+fields.length); if (fields.length) insert_fields(fields,this.__compiled__.build); // Insert our *_field data (if it is set) function insert_fields(fields,build) { for (var i in fields) { log(LOG_DEBUG,'|-- Adding:'+fields[i].name+', with value:'+fields[i].value); var content = fields[i].value.split(''); for (var x=fields[i].x;x 0) throw new Error('Dynamic fields ['+df.length+'] without values:'+(df.map(function(item) { return item.name; }).join('|'))); // Render the display for (var y=1;y<=this.height;y++) { var line = ''; if (new_line) last = new_line; if (debug) writeln('============== ['+y+'] ==============='); for (var x=1;x<=this.width;x++) { if (debug) log(LOG_DEBUG,'* CELL : y:'+y+', x:'+x); // The current char value var char = (display[y] !== undefined && display[y][x] !== undefined) ? display[y][x] : undefined; if (debug) log(LOG_DEBUG,' - CHAR : '+(char !== undefined ? char.ch : 'undefined')+', ATTR:'+(char !== undefined ? char.attr : 'undefined')+', LAST:'+last); if (debug) { writeln(); writeln('-------- ['+x+'] ------'); writeln('y:'+y+',x:'+x+', attr:'+(char !== undefined ? char.attr : 'undefined')); } if ((color === undefined) || color) { // Only write a new attribute if it has changed (and not Videotex) if ((SESSION_EXT === 'vtx') || (last === undefined) || (last !== char.attr)) { // The current attribute for this character attr = (char === undefined) ? undefined : char.attribute(last,SESSION_EXT,debug); switch (SESSION_EXT) { case 'tex': // If the attribute is null, we'll write our default attribute if (attr === null) line += this.attr else line += (attr !== undefined) ? attr : ''; break; case 'vtx': // If the attribute is null, we'll ignore it since we are drawing a character if ((attr !== undefined) && (attr !== null)) { if (debug) log(LOG_DEBUG,' = SEND ATTR :'+attr+', attr length:'+attr.length+', last:'+last); line += attr; } break; default: throw new Error('service type:'+SESSION_EXT+' hasnt been implemented.'); } } // For no-color output and ViewData, we'll render a character } else { if ((SESSION_EXT === 'vtx') && char.attr) line += '^'; } if (char.ch !== undefined) { if (debug) log(LOG_DEBUG,' = SEND CHAR :'+char.ch+', attr:'+char.attr+', last:'+last); line += (char.ch !== null) ? char.ch : ''; } else { if (debug) log(LOG_DEBUG,' = CHAR UNDEFINED'); line += ' '; } last = (char.attr === undefined) ? undefined : char.attr; } result.push(line); if (debug && (y > debug)) exit(1); } return result; } /** * Dump a page in an axis grid to view that it renders correctly * * @param last - (int) The current cursor color * @param color - (bool) Optionally show color * @param debug - (int) Debugging mode starting at line * * @note When drawing a Char: * * | CH | ATTR | RESULT | * |------------|------------|--------------------------------------| * | undefined | undefined | no output (cursor advances 1) | NOOP * | null | undefined | invalid | * | defined | undefined | invalid | * | undefined | null | invalid | * | null | null | invalid | * | defined | null | render ch only (cursor advances 1) | Viewdata * | undefined | defined | render attr only (no cursor move) | ANSItex (used to close the edge of a window) * | null | defined | render attr only (cursor advances 1) | Viewdata * | defined | defined | render attr + ch (cursor advances 1) | ANSItex * |------------|------------|--------------------------------------| * * + for ANSItex, attribute(s) dont advance the cursor, clear screen sets the default to BG_BLACK|LIGHTGRAY * + for ViewData, an attribute does advance the cursor, and each attribute advances the cursor, also each new line starts with a default BG_BLACK|WHITE */ this.dump = function(last,color,debug) { if (! this.__compiled__.build) this.build(); // Our built display var display = this.__compiled__.build; color = (color === undefined) || (color === '1') || (color === true); writeln('Dumping Page:'+this.name.toString()); writeln('= Size :'+this.dimensions); writeln('- Last :'+last); writeln('- Color:'+color); writeln('- Debug:'+debug); if (last === undefined) last = new_screen; if (debug) { writeln('==== content dump ===='); var yy = 1; for (var y in display) { write(padright(yy,2,0)+':'); var xx = 1; for (var x in display[y]) { if (debug && (y === debug)) { writeln(JSON.stringify(display[y][x])); writeln() } write('['); if (display[y][x].attr === undefined) { // NOOP } else if (display[y][x].attr === null) { // NOOP } else { try { write((last === display[y][x].attr) ? '' : display[y][x].attr); } catch (e) { writeln(); writeln('error:'+e); writeln(' y:'+y); writeln(' x:'+x); writeln(JSON.stringify(display[y][x].attr)); exit(1); } } write(':'); if (display[y][x].ch === undefined) { // NOOP - No window filled a character at this location write((display[y][x].attr === undefined) ? '--' : ''); } else if (display[y][x].ch === null) { // NOOP } else { write('_'+display[y][x].ch); } write(']'); last = display[y][x].attr; xx++; } writeln('|'+padright(xx-1,2,0)); xx = 0; yy++; } // Detail dump when debug is a line number if (debug && (y > debug)) { writeln('=========================='); for (var y in display) { writeln ('------ ['+y+'] -------'); var xx = 1; for (var x in display[y]) { var attr = display[y][x].attr; writeln('X:'+(xx++)+'|'+attr+':'+display[y][x].ch+'|'+display[y][x].attribute(last,SESSION_EXT,debug)); // Only write a new attribute if it has changed if ((this.last === undefined) || (this.last !== attr)) { this.last = attr; } } } } writeln('==== END content dump ===='); } // Dump Header write('--:'); for (var x=0;x b.number else return (a.when_imported_time > b.when_imported_time); }) .filter(function(item) { return (item.from === 'SYSTEM') && (item.to === page); }).pop(); if (msg === undefined) { log(LOG_DEBUG,'|- Frame not found: ['+page.toString()+'] in ['+FRAMES_MSG_BASE+']'); return false; } else { log(LOG_DEBUG,'|- Loading frame: ['+page.toString()+'] from msgbase ['+msg.number+'] ['+SESSION_EXT+']'); var contents = mb.getContent(msg.number); if ((contents === undefined) || (contents.content[SESSION_EXT] === undefined)) return false; contents.content = contents.content[SESSION_EXT]; log(LOG_DEBUG,'|/ Content:'+contents.content); return this.preload(contents,SESSION_EXT); } return false; } /** * After page load routines */ this.loadcomplete = function() { var po = pageOwner(this.name.frame); switch (SESSION_EXT) { case 'tex': this.__window__.pagenum.__properties__.content = rawtoattrs(ESC+'[1;37m'+this.name.toString()).content; this.provider = base64_decode(po.logoans); break; case 'vtx': this.__window__.pagenum.__properties__.content = rawtoattrs(VIEWDATA_BIN_WHITE+this.name.toString()).content; this.provider = base64_decode(po.logovtx); break; default: throw new Error(SESSION_EXT+' hasnt been implemented'); } // Dont show header on un-authed login frames if (! user.number) this.showHeader = false; } /** * Process a loaded frame from either a file or message base * * @param contents * @param ext * @param width * @param height * @returns {boolean|null} */ this.preload = function(contents,ext) { switch (ext) { // Messages case 'txt': log(LOG_DEBUG,'Processing txt'); var page = rawtoattrs(contents,this.width,this.__window__.body.y,this.__window__.body.x,debug); this.__window__.body.__properties__.content = page.content; return contents; // ANSI files case 'ans': // ViewData files case 'bin': log(LOG_DEBUG,'Processing ANSI/VIEWDATA file'); var page = rawtoattrs(contents,this.width,this.__window__.body.y,this.__window__.body.x,debug); this.__window__.body.__properties__.content = page.content; this.dynamic_fields = page.dynamic_fields; // Our fields are sorted in x descending order this.input_fields = page.input_fields.sort(function(a,b) { return a.x < b.x ? 1 : -1; }); this.raw = contents; break; // ANSItex files case 'tex': case 'vtx': log(LOG_DEBUG,'|-- Processing FRAME file. V:'+contents.version); switch (contents.version) { case 1: try { for (var index in contents) { if (FRAME_SAVE_ATTRS.indexOf(index) === -1) { log(LOG_ERROR,'|-! Unknown index ['+index+'] in input.'); continue; } log(LOG_DEBUG,'|-* Processing ['+index+'] with value ['+JSON.stringify(contents[index])+'].'); switch (index) { case 'content': //if (ext === 'tex') // var page = rawtoattrs(base64_decode(contents[index]).replace("\x0a\x0d\x0a\x0d","\x0a\x0d"),this.width,this.__window__.body.y,this.__window__.body.x); //else if (ext === 'vtx') var page = rawtoattrs(base64_decode(contents[index]),this.width,this.__window__.body.y,this.__window__.body.x); this.__window__.body.__properties__.content = page.content; this.dynamic_fields = page.dynamic_fields; // Our fields are sorted in x descending order if (page.input_fields.length) this.input_fields = page.input_fields.sort(function(a,b) { return a.x < b.x ? 1 : -1; }); this.raw = base64_decode(contents[index]); break; case 'cost': this.cost = contents[index]; break; case 'date': log(LOG_INFO,'|-/ Frame date : '+contents[index]); break; case 'dynamic_fields': this.dynamic_fields = contents[index]; break; case 'frame': this.name.frame = ''+contents[index]; break; case 'index': this.name.index = contents[index]; break; case 'input_fields': this.input_fields = contents[index]; break; case 'isAccessible': this.isAccessible = ((contents[index] === 1) || (contents[index] === true)); break; case 'isPublic': this.isPublic = ((contents[index] === 1) || (contents[index] === true)); break; case 'key': this.key = contents[index]; break; case 'type': this.type = contents[index]; break; case 'version': log(LOG_INFO,'|-/ Frame version : '+contents[index]); break; case 'window': for (var y in contents[index]) { //log(LOG_DEBUG,' - Y: '+y+', '+JSON.stringify(contents[index][y])); if (contents[index][y] === null) continue; for (var x in contents[index][y]) { //log(LOG_DEBUG,' - X: '+x+', '+JSON.stringify(contents[index][y][x])); if (contents[index][y][x] === null) continue; this.__window__.body.__properties__.content[y][x] = new Char( contents[index][y][x].__properties__.ch, contents[index][y][x].__properties__.attr ); } } break; default: log(LOG_ERROR,'|-! Frame property not handled: '+index+', value:'+contents[index]); } } } catch (error) { log(LOG_ERROR,'|-! Frame error : '+error); // Load our system error frame. // @todo If our system error page errors, then we go into a loop this.get(new PageObject(FRAME_SYSTEM_ERROR)); } break; case 2: log(LOG_DEBUG,'|-- Type: '+SESSION_EXT); if ((SESSION_EXT === 'vtx') && contents.dynamic_fields) { log(LOG_DEBUG,'|--- Dynamic Fields: '+contents.dynamic_fields.length); this.dynamic_fields = contents.dynamic_fields; } var page = rawtoattrs(contents.content,this.width,this.__window__.body.y,this.__window__.body.x); this.__window__.body.__properties__.content = page.content; if ((SESSION_EXT === 'vtx') && contents.input_fields) { log(LOG_DEBUG, '|--- Input Fields: ' + contents.input_fields.length); this.input_fields = contents.input_fields; // Our fields are sorted in x descending order } else if (page.input_fields.length) this.input_fields = page.input_fields.sort(function(a,b) { return a.x < b.x ? 1 : -1; }); if ((SESSION_EXT === 'tex') && page.dynamic_fields) this.dynamic_fields = page.dynamic_fields; // Work out frame type this.attrs = contents.attrs; this.cost = contents.cost; this.key = contents.key; this.raw = contents.content; this.date = contents.date; break; default: log(LOG_ERROR,'|-! Unknown frame version : '+contents.version); this.get(new PageObject(FRAME_SYSTEM_ERROR)); return null; } this.loadcomplete(); log(LOG_DEBUG,'|= Frame complete : '+this.name.toString()); break; default: throw new Error('Unsupported filetype:'+ext); } // Successful load return true; } /** * Save the frame to the message base */ this.save = function() { var msgbase = new MsgBase(FRAMES_MSG_BASE); if (! msgbase.open()) { log(LOG_ERROR,'! Message Base cannot be opened (save): ['+msgbase.error+']'); return false; } var hdr = { to: this.name, from: 'SYSTEM', tags: this.name, date: this.date, subject: 'Content', }; var page = { 'version': 2, 'attrs': p.attrs, 'cost': p.cost, 'key': p.key, 'content': p.raw, }; var body = LZString.compressToBase64(JSON.stringify(page))+FRAMES_EOF_MARKER; if (! msgbase.save_msg(hdr,body)) log(LOG_ERROR,' ! Error saving frame: ['+this.name.toString()+']'); msgbase.close(); return true; } this.scroll = function(x,y) { this.__compiled__.build = null; // @todo Check that we can scroll and if we are out of bounds. this.__window__.body.scroll(x,y); } init.apply(this,arguments); } function PageObject(frame,index) { this.__properties__ = { frame: '0', // Frame number index: 'a', // Frame index } function init(frame,index) { if (typeof frame === 'object') { this.__properties__.frame = frame.frame.toString(); this.index = frame.index; } else if ((frame !== undefined) && (index === undefined)) { if (/^\d+[a-z]$/.test(frame)) { var split = frame.split(/(\d+)/); this.__properties__.frame = split[1]; this.index = split[2]; } else { this.__properties__.frame = frame; } } else if ((frame !== undefined) && (index !== undefined)) { this.__properties__.frame = frame; this.index = index; } } PageObject.prototype.__defineGetter__('frame',function() { return this.__properties__.frame; }); // @todo validate that string only has digits PageObject.prototype.__defineSetter__('frame',function(string) { if (typeof string !== 'string') throw new Error('Page.number must be a string'); this.__properties__.frame = string; }); PageObject.prototype.__defineGetter__('index',function() { return this.__properties__.index; }); PageObject.prototype.__defineSetter__('index',function(string) { if (typeof string !== 'string') throw new Error('Page.index must be a string'); if (string.length !== 1) throw new Error('Page.index can only be 1 char'); this.__properties__.index = string; }); PageObject.prototype.__defineGetter__('next',function() { var next = undefined; if (this.index !== 'z') { log(LOG_DEBUG,'page_next: Current page:'+this.frame+', current index:'+this.index); next = new PageObject(this.frame,String.fromCharCode(this.index.charCodeAt(0)+1)); } return next; }); PageObject.prototype.toString = function() { return (this.frame && this.index) ? this.frame+this.index : null; } init.apply(this,arguments); }