1 2window.addEventListener('DOMContentLoaded', function() { 3 4 var $ = function(s) { return document.querySelector(s); }; 5 6 $('#lb_files').selectedIndex = 0; 7 8 var frame = $('#frame'); 9 10 var keyboard = frame; 11 if (/iPod|iPad|iPhone/.test(navigator.platform)) { 12 keyboard = document.createElement('input'); 13 keyboard.type = 'text'; 14 keyboard.style.width = keyboard.style.height = '1px'; 15 keyboard.style.border = 'none'; 16 keyboard.style.position = 'absolute'; 17 keyboard.style.left = keyboard.style.top = '-100px'; 18 frame.removeAttribute('tabIndex'); 19 frame.addEventListener('click', function() { keyboard.focus(); }); 20 frame.parentNode.insertBefore(keyboard, frame); 21 } 22 23 var tty = new TTY($('#screen'), keyboard); 24 (function() { 25 // Install output hook for bell 26 var b = new Bell(/^.*\/|/.exec(window.location)[0]); 27 var orig = tty.writeChar; 28 tty.writeChar = function index_writeChar(c) { 29 if (c.charCodeAt(0) === 7) 30 b.play(); 31 else 32 orig(c); 33 }; 34 }()); 35 var dos = new DOS(tty); 36 37 var lores = new LoRes($('#lores'), 40, 48); 38 var hires = new HiRes($('#hires'), 280, 192); 39 var hires2 = new HiRes($('#hires2'), 280, 192); 40 var display = { 41 state: { graphics: false, full: true, page1: true, lores: true }, 42 setState: function(state, value /* ... */) { 43 var args = Array.prototype.slice.call(arguments); 44 while (args.length) { 45 state = args.shift(); 46 value = args.shift(); 47 this.state[state] = value; 48 } 49 50 if (this.state.graphics) { 51 lores.show(this.state.lores); 52 hires.show(!this.state.lores && this.state.page1); 53 hires2.show(!this.state.lores && !this.state.page1); 54 tty.splitScreen(tty.getScreenSize().height - (this.state.full ? 0 : 4)); 55 } else { 56 lores.show(false); 57 hires.show(false); 58 hires2.show(false); 59 tty.splitScreen(0); 60 } 61 }, 62 getState: function() { 63 return Object.assign({}, this.state); 64 } 65 }; 66 var pdl = [0, 0, 0, 0]; 67 68 // Lexical highlighting, if available 69 var editor; 70 if (typeof CodeMirror === 'function') { 71 editor = new CodeMirror($('#editorframe'), { 72 mode: 'basic', 73 tabMode: 'default', 74 content: $('#source').value, 75 height: '100%' 76 }); 77 } else { 78 editor = (function() { 79 var textArea = document.createElement('textarea'); 80 $('#editorframe').appendChild(textArea); 81 textArea.style.width = '598px'; 82 textArea.style.height = '384px'; 83 return { 84 getValue: function() { 85 return textArea.value; 86 }, 87 setValue: function(value) { 88 textArea.value = value; 89 }, 90 setCursor: function(line, column) { 91 // TODO: Implement me! 92 } 93 }; 94 }()); 95 } 96 97 $('#btn_share').onclick = function(e) { 98 // Load up the hidden text area with the current source 99 $('#source').value = getSource(); 100 }; 101 102 function getSource() { 103 return editor.getValue(); 104 } 105 106 function setSource(source) { 107 editor.setValue(source); 108 } 109 110 var program; 111 $('#btn_run').addEventListener('click', function(e) { 112 e.preventDefault(); 113 114 dos.reset(); 115 tty.reset(); 116 tty.autoScroll = true; 117 118 try { 119 program = basic.compile(getSource()); 120 } catch (e) { 121 if (e instanceof basic.ParseError) { 122 editor.setCursor({ line: e.line - 1, ch: e.column - 1 }); 123 console.log(e.message + 124 ' (source line:' + e.line + ', column:' + e.column + ')'); 125 } 126 alert(e); 127 return; 128 } 129 130 stopped = false; 131 updateUI(); 132 $('#btn_stop').focus(); 133 134 program.init({ 135 tty: tty, 136 hires: hires, 137 hires2: hires2, 138 lores: lores, 139 display: display, 140 paddle: function(n) { return pdl[n]; } 141 }); 142 setTimeout(driver, 0); 143 }); 144 145 $('#btn_stop').addEventListener('click', function(e) { 146 e.preventDefault(); 147 148 tty.reset(); // cancel any blocking input 149 stopped = true; 150 updateUI(); 151 }); 152 153 $('#lb_files').addEventListener('change', function() { 154 var sel = $('#lb_files'); 155 loadFile('samples/' + sel.value + ".txt", setSource); 156 }); 157 158 var current_file_name; 159 $('#btn_save').addEventListener('click', function(e) { 160 e.preventDefault(); 161 var a = document.createElement('a'); 162 a.download = current_file_name || 'basic_program.txt';
163 a.href = 'data:text/plain;base64,' + window.btoa(getSource()); 164 document.body.appendChild(a); 165 a.click(); 166 a.parentElement.removeChild(a); 167 }); 168 $('#btn_load').addEventListener('click', function(e) { 169 e.preventDefault(); 170 var input = document.createElement('input'); 171 input.type = 'file'; 172 input.addEventListener('change', function(e) { 173 var file = e.target.files[0]; 174 current_file_name = file.name; 175 var reader = new FileReader(); 176 reader.addEventListener('load', function() { 177 setSource(reader.result); 178 }); 179 reader.readAsText(file); 180 }); 181 document.body.appendChild(input); 182 input.click(); 183 input.parentElement.removeChild(input); 184 }); 185 186 // Add a "printer" on demand 187 var printer = null; 188 var paper = $('#paper'); 189 $('#show_paper').addEventListener('click', function(e) { 190 e.preventDefault(); 191 document.body.classList.add('printout'); 192 printer = new Printer(tty, paper); 193 }); 194 $('#hide_paper').addEventListener('click', function(e) { 195 e.preventDefault(); 196 document.body.classList.remove('printout'); 197 printer.close(); 198 printer = null; 199 }); 200 201 // Mouse-as-Joystick 202 var wrapper = $('#screen-wrapper'); 203 wrapper.addEventListener('mousemove', function(e) { 204 var rect = wrapper.getBoundingClientRect(), 205 x = e.clientX - rect.left, y = e.clientY - rect.top; 206 function clamp(n, min, max) { return n < min ? min : n > max ? max : n; } 207 pdl[0] = clamp(x / (rect.width - 1), 0, 1); 208 pdl[1] = clamp(y / (rect.height - 1), 0, 1); 209 }); 210 211 var stopped = true; 212 function updateUI() { 213 var btnFocus = (document.activeElement === $("#btn_run") || 214 document.activeElement === $("#btn_stop")); 215 $("#btn_stop").disabled = stopped ? "disabled" : ""; 216 $("#btn_run").disabled = stopped ? "" : "disabled"; 217 $("#lb_files").disabled = stopped ? "" : "disabled"; 218 219 if (btnFocus || stopped) { 220 $(stopped ? "#btn_run" : "#btn_stop").focus(); 221 } else { 222 tty.focus(); 223 } 224 } 225 226 227 // TODO: Expose a RESPONSIVE |---+--------| FAST slider in the UI 228 // Number of steps to execute before yielding execution 229 // (Use a prime to minimize risk of phasing with loops) 230 var NUM_SYNCHRONOUS_STEPS = 37; 231 232 function driver() { 233 var state = basic.STATE_RUNNING; 234 var statements = NUM_SYNCHRONOUS_STEPS; 235 236 while (!stopped && state === basic.STATE_RUNNING && statements > 0) { 237 238 try { 239 state = program.step(driver); 240 } catch (e) { 241 console.log(e); 242 alert(e.message ? e.message : e); 243 stopped = true; 244 updateUI(); 245 return; 246 } 247 248 statements -= 1; 249 } 250 251 if (state === basic.STATE_STOPPED || stopped) { 252 stopped = true; 253 updateUI(); 254 } else if (state === basic.STATE_BLOCKED) { 255 // Fall out 256 } else { // state === basic.STATE_RUNNING 257 setTimeout(driver, 0); // Keep going 258 } 259 } 260 261 function parseQueryParams() { 262 var params = {}; 263 var query = document.location.search.substring(1); 264 query.split(/&/g).forEach(function(pair) { 265 pair = pair.replace(/\+/g, " ").split(/=/).map(decodeURIComponent); 266 params[pair[0]] = pair.length === 1 ? pair[0] : pair[1]; 267 }); 268 return params; 269 } 270 271 function loadFile(filename, callback) { 272 var req = new XMLHttpRequest(); 273 var url = encodeURI(filename); // not encodeURIComponent, so paths can be specified 274 var async = true; 275 req.open("GET", url, async); 276 req.onreadystatechange = function() { 277 if (req.readyState === XMLHttpRequest.DONE) { 278 if (req.status === 200 || req.status === 0) { 279 callback(req.responseText); 280 } 281 } 282 }; 283 req.send(null); 284 } 285 286 // load default 287 var params = parseQueryParams(); 288 if ('source' in params) { 289 setSource(params.source); 290 } else { 291 loadFile('samples/sample.default.txt', setSource); 292 } 293});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.