UNPKG

text-mode

Version:

Character based display for the DOM

267 lines (191 loc) 6.43 kB
'use strict'; var template = require( './string-inject' ); var charSize = require( './char-size' ); function TextMode( target, options ){ if( !( this instanceof TextMode )){ return new TextMode( target, options ); } var tm = this; var parent; if( typeof target === 'string' ){ parent = document.querySelector( target ); } else if( target instanceof HTMLElement ){ parent = target; } else { parent = document.body; if( !options && target && Object.keys( target ).some( function( key ){ return key in TextMode.defaults; })){ options = target; }; }; tm.viewport = document.createElement( 'div' ); parent.appendChild( tm.viewport ); options = options || TextMode.defaults; Object.keys( TextMode.defaults ).forEach( function( key ){ tm[ key ] = key in options ? options[ key ] : TextMode.defaults[ key ]; }); tm.id = randomId(); tm.viewport.id = 'tm-' + tm.id; tm.sheet = newSheet(); tm.charSize = charSize( tm.font ); tm.size = { width: tm.columns * tm.charSize.width, height: tm.rows * tm.charSize.height }; cache[ tm.id ] = { tiles: [], textNodes: [], fore: [], back: [], className: [], classMap: {}, nextIndex: 0 }; var viewportStyle = templates.viewport( tm ); var tilesStyle = templates.tiles( tm.charSize ); newRule( tm.sheet, '#' + tm.viewport.id, viewportStyle ); newRule( tm.sheet, '#' + tm.viewport.id + ' span', tilesStyle ); initTiles( tm ); } TextMode.prototype.set = function( column, row, text, fore, back, className ){ var tm = this; if( Array.isArray( column ) ){ var buffer = column; tm.each( function( x, y, i ){ tm.set( x, y, buffer[ i ].text, buffer[ i ].fore, buffer[ i ].back, buffer[ i ].className ); }); return; } var i = row * tm.columns + column; cache[ tm.id ].textNodes[ i ].nodeValue = text; if( fore || back ){ tm.setColor( column, row, fore, back ); } if( className ){ tm.setClassName( column, row, className ); } }; TextMode.prototype.setColor = function( column, row, fore, back ){ var tm = this; var i = row * tm.columns + column; if( fore === cache[ tm.id ].fore[ i ] && back === cache[ tm.id ].back[ i ] ) return; fore = fore || cache[ tm.id ].fore[ i ] || tm.fore; back = back || cache[ tm.id ].back[ i ] || tm.back; tm.fore = cache[ tm.id ].fore[ i ] = fore; tm.back = cache[ tm.id ].back[ i ] = back; var key = fore + ',' + back; if( !( key in cache[ tm.id ].classMap ) ){ tm.addColor( fore, back ); } var colorClass = cache[ tm.id ].classMap[ key ]; cache[ tm.id ].tiles[ i ].className = colorClass + ' ' + cache[ tm.id ].className[ i ]; }; TextMode.prototype.setClassName = function( column, row, className ){ var tm = this; var i = row * tm.columns + column; if( cache[ tm.id ].className[ i ] === className ) return; cache[ tm.id ].className[ i ] = className; var colorClass = getColorClass( tm, i ); cache[ tm.id ].tiles[ i ].className = colorClass + ' ' + className; }; TextMode.prototype.get = function( column, row ){ var tm = this; if( typeof column !== 'number' ){ var buffer = []; tm.each( function( x, y, i ){ buffer[ i ] = tm.get( x, y ); }); return buffer; } var i = row * tm.columns + column; return { fore: cache[ tm.id ].fore[ i ], back: cache[ tm.id ].back[ i ], text: cache[ tm.id ].textNodes[ i ].nodeValue, className: cache[ tm.id ].className[ i ] }; }; TextMode.prototype.addColor = function( fore, back ){ var tm = this; var key = fore + ',' + back; if( key in cache[ tm.id ].classMap ) return; var className = 'color-' + tm.id + '-' + nextIndex( tm.id ); cache[ tm.id ].classMap[ key ] = className; newRule( tm.sheet, '.' + className, templates.color({ fore: fore, back: back })); }; TextMode.prototype.each = function( callback ){ var tm = this; for( var i = 0; i < tm.columns * tm.rows; i++ ){ var x = i % tm.columns; var y = Math.floor( i / tm.columns ); callback( x, y, i ); } }; TextMode.defaults = { columns: 80, rows: 25, fore: '#ddd', back: '#222', font: '16px monospace' }; var cache = {}; var templates = { style: template( '<%=selector%> { <%=rule%> }' ), color: template( 'background: <%=back%>; color: <%=fore%>;' ), viewport: template( 'position: relative; width: <%=size.width%>px; height: <%=size.height%>px; font: <%=font%>' ), tiles: template( 'position: absolute; line-height: <%=height%>px; width: <%=width%>px; height: <%=height%>px;' ), tile: template( 'top: <%=top%>px; left: <%=left%>px;' ) }; function newSheet(){ var style = document.createElement( 'style' ); document.head.appendChild( style ); return style.sheet; } function newRule( sheet, selector, rule ){ var style = templates.style({ selector: selector, rule: rule }); sheet.insertRule( style, sheet.cssRules.length ); } function randomId( prefix ){ var h = prefix || ''; for( var i = 0; i < 8; i++ ){ h += Math.floor( Math.random() * 16 ).toString( 16 ); } return h; } function nextIndex( id ){ return cache[ id ].nextIndex++; } function initTiles( tm ){ tm.each( function( x, y, i ){ var span = document.createElement( 'span' ); var content = document.createTextNode( '' ); span.appendChild( content ); tm.viewport.appendChild( span ); cache[ tm.id ].tiles[ i ] = span; cache[ tm.id ].textNodes[ i ] = content; cache[ tm.id ].className[ i ] = ''; updatePosition( tm, x, y, i ); tm.setColor( x, y, tm.fore, tm.back ); }); } function updatePosition( tm, x, y, i ){ var span = cache[ tm.id ].tiles[ i ]; var position = { left: x * tm.charSize.width, top: y * tm.charSize.height }; span.style.cssText = templates.tile( position ); } function getColorClass( tm, i ){ var fore = cache[ tm.id ].fore[ i ]; var back = cache[ tm.id ].back[ i ]; var key = fore + ',' + back; return cache[ tm.id ].classMap[ key ]; } module.exports = TextMode;