1 /** 2 * Common JS that talks XHR back to the server and runs the code and receives 3 * the results. 4 */ 5 6 7 /** 8 * All the functionality is wrapped up in this anonymous closure, but we need 9 * to be told if we are on the workspace page or a normal try page, so the 10 * workspaceName is passed into the closure, it must be set in the global 11 * namespace. If workspaceName is the empty string then we know we aren't 12 * running on a workspace page. 13 * 14 * If we are on a workspace page we also look for a 'history_' 15 * variable in the global namespace which contains the list of tries 16 * that are included in this workspace. That variable is used to 17 * populate the history list. 18 */ 19 (function() { 20 function onLoad() { 21 var run = document.getElementById('run'); 22 var permalink = document.getElementById('permalink'); 23 var embed = document.getElementById('embed'); 24 var embedButton = document.getElementById('embedButton'); 25 var code = document.getElementById('code'); 26 var output = document.getElementById('output'); 27 var stdout = document.getElementById('stdout'); 28 var img = document.getElementById('img'); 29 var tryHistory = document.getElementById('tryHistory'); 30 var parser = new DOMParser(); 31 var tryTemplate = document.getElementById('tryTemplate'); 32 var sourcesTemplate = document.getElementById('sourcesTemplate'); 33 34 var enableSource = document.getElementById('enableSource'); 35 var selectedSource = document.getElementById('selectedSource'); 36 var sourceCode = document.getElementById('sourceCode'); 37 var chooseSource = document.getElementById('chooseSource'); 38 var chooseList = document.getElementById('chooseList'); 39 40 // Id of the source image to use, 0 if no source image is used. 41 var sourceId = 0; 42 43 sourceId = parseInt(enableSource.getAttribute('data-id')); 44 if (sourceId) { 45 sourceSelectByID(sourceId); 46 } 47 48 49 function beginWait() { 50 document.body.classList.add('waiting'); 51 run.disabled = true; 52 } 53 54 55 function endWait() { 56 document.body.classList.remove('waiting'); 57 run.disabled = false; 58 } 59 60 61 function sourceSelectByID(id) { 62 sourceId = id; 63 if (id > 0) { 64 enableSource.checked = true; 65 selectedSource.innerHTML = '<img with=64 height=64 src="/i/image-'+sourceId+'.png" />'; 66 selectedSource.classList.add('show'); 67 sourceCode.classList.add('show'); 68 chooseSource.classList.remove('show'); 69 } else { 70 enableSource.checked = false; 71 selectedSource.classList.remove('show'); 72 sourceCode.classList.remove('show'); 73 } 74 } 75 76 77 /** 78 * A selection has been made in the choiceList. 79 */ 80 function sourceSelect() { 81 sourceSelectByID(parseInt(this.getAttribute('data-id'))); 82 } 83 84 85 /** 86 * Callback when the loading of the image sources is complete. 87 * 88 * Fills in the list of images from the data returned. 89 */ 90 function sourcesComplete(e) { 91 endWait(); 92 // The response is JSON of the form: 93 // [ 94 // {"id": 1}, 95 // {"id": 3}, 96 // ... 97 // ] 98 body = JSON.parse(e.target.response); 99 // Clear out the old list if present. 100 while (chooseList.firstChild) { 101 chooseList.removeChild(chooseList.firstChild); 102 } 103 body.forEach(function(source) { 104 var id = 'i'+source.id; 105 var imgsrc = '/i/image-'+source.id+'.png'; 106 var clone = sourcesTemplate.content.cloneNode(true); 107 clone.querySelector('img').src = imgsrc; 108 clone.querySelector('button').setAttribute('id', id); 109 clone.querySelector('button').setAttribute('data-id', source.id); 110 chooseList.insertBefore(clone, chooseList.firstChild); 111 chooseList.querySelector('#'+id).addEventListener('click', sourceSelect, true); 112 }); 113 chooseSource.classList.add('show'); 114 } 115 116 117 /** 118 * Toggle the use of a source image, or select a new source image. 119 * 120 * If enabling source images then load the list of available images via 121 * XHR. 122 */ 123 function sourceClick(e) { 124 selectedSource.classList.remove('show'); 125 sourceCode.classList.remove('show'); 126 if (enableSource.checked) { 127 beginWait(); 128 var req = new XMLHttpRequest(); 129 req.addEventListener('load', sourcesComplete); 130 req.addEventListener('error', xhrError); 131 req.overrideMimeType('application/json'); 132 req.open('GET', '/sources/', true); 133 req.send(); 134 } else { 135 sourceId = 0; 136 } 137 } 138 139 enableSource.addEventListener('click', sourceClick, true); 140 selectedSource.addEventListener('click', sourceClick, true); 141 142 143 var editor = CodeMirror.fromTextArea(code, { 144 theme: "default", 145 lineNumbers: true, 146 matchBrackets: true, 147 mode: "text/x-c++src", 148 indentUnit: 4, 149 }); 150 151 // Match the initial textarea size. 152 editor.setSize(editor.defaultCharWidth() * code.cols, 153 editor.defaultTextHeight() * code.rows); 154 155 156 /** 157 * Callback when there's an XHR error. 158 * @param e The callback event. 159 */ 160 function xhrError(e) { 161 endWait(); 162 alert('Something bad happened: ' + e); 163 } 164 165 function clearOutput() { 166 output.textContent = ""; 167 if (stdout) { 168 stdout.textContent = ""; 169 } 170 embed.style.display='none'; 171 } 172 173 /** 174 * Called when an image in the workspace history is clicked. 175 */ 176 function historyClick() { 177 beginWait(); 178 clearOutput(); 179 var req = new XMLHttpRequest(); 180 req.addEventListener('load', historyComplete); 181 req.addEventListener('error', xhrError); 182 req.overrideMimeType('application/json'); 183 req.open('GET', this.getAttribute('data-try'), true); 184 req.send(); 185 } 186 187 188 /** 189 * Callback for when the XHR kicked off in historyClick() returns. 190 */ 191 function historyComplete(e) { 192 // The response is JSON of the form: 193 // { 194 // "hash": "unique id for a try", 195 // "code": "source code for try" 196 // } 197 endWait(); 198 body = JSON.parse(e.target.response); 199 code.value = body.code; 200 editor.setValue(body.code); 201 img.src = '/i/'+body.hash+'.png'; 202 sourceSelectByID(body.source); 203 if (permalink) { 204 permalink.href = '/c/' + body.hash; 205 } 206 } 207 208 209 /** 210 * Add the given try image to the history of a workspace. 211 */ 212 function addToHistory(hash, imgUrl) { 213 var clone = tryTemplate.content.cloneNode(true); 214 clone.querySelector('img').src = imgUrl; 215 clone.querySelector('.tries').setAttribute('data-try', '/json/' + hash); 216 tryHistory.insertBefore(clone, tryHistory.firstChild); 217 tryHistory.querySelector('.tries').addEventListener('click', historyClick, true); 218 } 219 220 221 /** 222 * Callback for when the XHR returns after attempting to run the code. 223 * @param e The callback event. 224 */ 225 function codeComplete(e) { 226 // The response is JSON of the form: 227 // { 228 // "message": "you had an error...", 229 // "img": "<base64 encoded image but only on success>" 230 // } 231 // 232 // The img is optional and only appears if there is a valid 233 // image to display. 234 endWait(); 235 console.log(e.target.response); 236 body = JSON.parse(e.target.response); 237 output.textContent = body.message; 238 if (stdout) { 239 stdout.textContent = body.stdout; 240 } 241 if (body.hasOwnProperty('img')) { 242 img.src = 'data:image/png;base64,' + body.img; 243 } else { 244 img.src = ''; 245 } 246 // Add the image to the history if we are on a workspace page. 247 if (tryHistory) { 248 addToHistory(body.hash, 'data:image/png;base64,' + body.img); 249 } else { 250 window.history.pushState(null, null, '/c/' + body.hash); 251 } 252 if (permalink) { 253 permalink.href = '/c/' + body.hash; 254 } 255 if (embed) { 256 var url = document.URL; 257 url = url.replace('/c/', '/iframe/'); 258 embed.value = '<iframe src="' + url + '" width="740" height="550" style="border: solid #00a 5px; border-radius: 5px;"/>' 259 } 260 if (embedButton && embedButton.hasAttribute('disabled')) { 261 embedButton.removeAttribute('disabled'); 262 } 263 } 264 265 266 function onSubmitCode() { 267 beginWait(); 268 clearOutput(); 269 var req = new XMLHttpRequest(); 270 req.addEventListener('load', codeComplete); 271 req.addEventListener('error', xhrError); 272 req.overrideMimeType('application/json'); 273 req.open('POST', '/', true); 274 req.setRequestHeader('content-type', 'application/json'); 275 req.send(JSON.stringify({'code': editor.getValue(), 'name': workspaceName, 'source': sourceId})); 276 } 277 run.addEventListener('click', onSubmitCode); 278 279 280 function onEmbedClick() { 281 embed.style.display='inline'; 282 } 283 284 if (embedButton) { 285 embedButton.addEventListener('click', onEmbedClick); 286 } 287 288 // Add the images to the history if we are on a workspace page. 289 if (tryHistory && history_) { 290 for (var i=0; i<history_.length; i++) { 291 addToHistory(history_[i].hash, '/i/'+history_[i].hash+'.png'); 292 } 293 } 294 } 295 296 // If loaded via HTML Imports then DOMContentLoaded will be long done. 297 if (document.readyState != "loading") { 298 onLoad(); 299 } else { 300 this.addEventListener('load', onLoad); 301 } 302 303 })(); 304