| OLD | NEW |
| (Empty) |
| 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 outputWrapper = document.getElementById('output-wrapper'); | |
| 28 var gpu = document.getElementById('use-gpu'); | |
| 29 var raster = document.getElementById('use-raster'); | |
| 30 var pdf = document.getElementById('use-pdf'); | |
| 31 var rasterOutput = document.getElementById('raster-output'); | |
| 32 var rasterImg = document.getElementById('raster-img'); | |
| 33 var gpuOutput = document.getElementById('gpu-output'); | |
| 34 var gpuImg = document.getElementById('gpu-img'); | |
| 35 var imageWidth = document.getElementById('image-width'); | |
| 36 var imageHeight = document.getElementById('image-height'); | |
| 37 var tryHistory = document.getElementById('tryHistory'); | |
| 38 var parser = new DOMParser(); | |
| 39 var tryTemplate = document.getElementById('tryTemplate'); | |
| 40 var sourcesTemplate = document.getElementById('sourcesTemplate'); | |
| 41 | |
| 42 var enableSource = document.getElementById('enableSource'); | |
| 43 var selectedSource = document.getElementById('selectedSource'); | |
| 44 var sourceCode = document.getElementById('sourceCode'); | |
| 45 var chooseSource = document.getElementById('chooseSource'); | |
| 46 var chooseList = document.getElementById('chooseList'); | |
| 47 | |
| 48 // Id of the source image to use, 0 if no source image is used. | |
| 49 var sourceId = 0; | |
| 50 | |
| 51 sourceId = parseInt(enableSource.getAttribute('data-id')); | |
| 52 if (sourceId) { | |
| 53 sourceSelectByID(sourceId); | |
| 54 } | |
| 55 | |
| 56 function setIFrameURL() { | |
| 57 var url = document.URL; | |
| 58 url = url.replace('/c/', '/iframe/'); | |
| 59 embed.value = '<iframe src="' + url + '" width="740" height="550" style=
"border: solid #00a 5px; border-radius: 5px;"/>' | |
| 60 } | |
| 61 | |
| 62 function beginWait() { | |
| 63 document.body.classList.add('waiting'); | |
| 64 run.disabled = true; | |
| 65 } | |
| 66 | |
| 67 | |
| 68 function endWait() { | |
| 69 document.body.classList.remove('waiting'); | |
| 70 run.disabled = false; | |
| 71 } | |
| 72 | |
| 73 | |
| 74 function sourceSelectByID(id) { | |
| 75 sourceId = id; | |
| 76 if (id > 0) { | |
| 77 enableSource.checked = true; | |
| 78 selectedSource.innerHTML = '<img with=64 height=64 src="/i/image-'+sou
rceId+'.png" />'; | |
| 79 selectedSource.classList.add('show'); | |
| 80 sourceCode.classList.add('show'); | |
| 81 chooseSource.classList.remove('show'); | |
| 82 } else { | |
| 83 enableSource.checked = false; | |
| 84 selectedSource.classList.remove('show'); | |
| 85 sourceCode.classList.remove('show'); | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 | |
| 90 /** | |
| 91 * A selection has been made in the choiceList. | |
| 92 */ | |
| 93 function sourceSelect() { | |
| 94 sourceSelectByID(parseInt(this.getAttribute('data-id'))); | |
| 95 } | |
| 96 | |
| 97 | |
| 98 /** | |
| 99 * Callback when the loading of the image sources is complete. | |
| 100 * | |
| 101 * Fills in the list of images from the data returned. | |
| 102 */ | |
| 103 function sourcesComplete(e) { | |
| 104 endWait(); | |
| 105 // The response is JSON of the form: | |
| 106 // [ | |
| 107 // {"id": 1}, | |
| 108 // {"id": 3}, | |
| 109 // ... | |
| 110 // ] | |
| 111 body = JSON.parse(e.target.response); | |
| 112 // Clear out the old list if present. | |
| 113 while (chooseList.firstChild) { | |
| 114 chooseList.removeChild(chooseList.firstChild); | |
| 115 } | |
| 116 body.forEach(function(source) { | |
| 117 var id = 'i'+source.id; | |
| 118 var imgsrc = '/i/image-'+source.id+'.png'; | |
| 119 var clone = sourcesTemplate.content.cloneNode(true); | |
| 120 clone.querySelector('img').src = imgsrc; | |
| 121 clone.querySelector('button').setAttribute('id', id); | |
| 122 clone.querySelector('button').setAttribute('data-id', source.id); | |
| 123 chooseList.insertBefore(clone, chooseList.firstChild); | |
| 124 chooseList.querySelector('#'+id).addEventListener('click', sourceSelect
, true); | |
| 125 }); | |
| 126 chooseSource.classList.add('show'); | |
| 127 } | |
| 128 | |
| 129 | |
| 130 /** | |
| 131 * Toggle the use of a source image, or select a new source image. | |
| 132 * | |
| 133 * If enabling source images then load the list of available images via | |
| 134 * XHR. | |
| 135 */ | |
| 136 function sourceClick(e) { | |
| 137 selectedSource.classList.remove('show'); | |
| 138 sourceCode.classList.remove('show'); | |
| 139 if (enableSource.checked) { | |
| 140 beginWait(); | |
| 141 var req = new XMLHttpRequest(); | |
| 142 req.addEventListener('load', sourcesComplete); | |
| 143 req.addEventListener('error', xhrError); | |
| 144 req.overrideMimeType('application/json'); | |
| 145 req.open('GET', '/sources/', true); | |
| 146 req.send(); | |
| 147 } else { | |
| 148 sourceId = 0; | |
| 149 chooseSource.classList.remove('show'); | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 enableSource.addEventListener('click', sourceClick, true); | |
| 154 selectedSource.addEventListener('click', sourceClick, true); | |
| 155 | |
| 156 function configChange(e) { | |
| 157 if (!(gpu.checked || raster.checked || pdf.checked)) { | |
| 158 run.disabled = true; | |
| 159 run.innerHTML = "Choose a configuration" | |
| 160 } else { | |
| 161 run.disabled = false; | |
| 162 run.innerHTML = "Run" | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 gpu.addEventListener('change', configChange); | |
| 167 raster.addEventListener('change', configChange); | |
| 168 pdf.addEventListener('change', configChange); | |
| 169 | |
| 170 | |
| 171 var editor = CodeMirror.fromTextArea(code, { | |
| 172 theme: "default", | |
| 173 lineNumbers: true, | |
| 174 matchBrackets: true, | |
| 175 lineWrapping: true, | |
| 176 mode: "text/x-c++src", | |
| 177 indentUnit: 4, | |
| 178 }); | |
| 179 | |
| 180 // Match the initial textarea width, but leave the height alone | |
| 181 // The css will automatically resize the editor vertically. | |
| 182 editor.setSize(editor.defaultCharWidth() * code.cols, | |
| 183 null); | |
| 184 | |
| 185 editor.on('beforeChange', function(instance, changeObj) { | |
| 186 var startLine = changeObj.from.line; | |
| 187 var endLine = changeObj.to.line; | |
| 188 | |
| 189 for (var i = startLine ; i <= endLine ; i++) { | |
| 190 editor.removeLineClass( i, "wrap", "error" ); | |
| 191 } | |
| 192 }); | |
| 193 | |
| 194 /** | |
| 195 * Callback when there's an XHR error. | |
| 196 * @param e The callback event. | |
| 197 */ | |
| 198 function xhrError(e) { | |
| 199 endWait(); | |
| 200 alert('Something bad happened: ' + e); | |
| 201 } | |
| 202 | |
| 203 function clearOutput() { | |
| 204 output.textContent = ""; | |
| 205 outputWrapper.style.display='none'; | |
| 206 if (embed) { | |
| 207 embed.style.display='none'; | |
| 208 } | |
| 209 } | |
| 210 | |
| 211 /** | |
| 212 * Called when an image in the workspace history is clicked. | |
| 213 */ | |
| 214 function historyClick() { | |
| 215 beginWait(); | |
| 216 clearOutput(); | |
| 217 var req = new XMLHttpRequest(); | |
| 218 req.addEventListener('load', historyComplete); | |
| 219 req.addEventListener('error', xhrError); | |
| 220 req.overrideMimeType('application/json'); | |
| 221 req.open('GET', this.getAttribute('data-try'), true); | |
| 222 req.send(); | |
| 223 } | |
| 224 | |
| 225 | |
| 226 /** | |
| 227 * Callback for when the XHR kicked off in historyClick() returns. | |
| 228 */ | |
| 229 function historyComplete(e) { | |
| 230 // The response is JSON of the form: | |
| 231 // { | |
| 232 // "hash": "unique id for a try", | |
| 233 // "code": "source code for try" | |
| 234 // } | |
| 235 endWait(); | |
| 236 body = JSON.parse(e.target.response); | |
| 237 code.value = body.code; | |
| 238 editor.setValue(body.code); | |
| 239 rasterImg.src = '/i/'+body.hash+'_raster.png'; | |
| 240 gpuImg.src = '/i/'+body.hash+'_gpu.png'; | |
| 241 imageWidth.value = body.width; | |
| 242 imageHeight.value = body.height; | |
| 243 gpu.checked = body.gpu; | |
| 244 sourceSelectByID(body.source); | |
| 245 if (permalink) { | |
| 246 permalink.href = '/c/' + body.hash; | |
| 247 permalink.style.display='inline-block'; | |
| 248 } | |
| 249 } | |
| 250 | |
| 251 | |
| 252 /** | |
| 253 * Add the given try image to the history of a workspace. | |
| 254 */ | |
| 255 function addToHistory(hash, imgUrl) { | |
| 256 var clone = tryTemplate.content.cloneNode(true); | |
| 257 clone.querySelector('img').src = imgUrl; | |
| 258 clone.querySelector('.tries').setAttribute('data-try', '/json/' + hash); | |
| 259 tryHistory.insertBefore(clone, tryHistory.firstChild); | |
| 260 tryHistory.querySelector('.tries').addEventListener('click', historyClic
k, true); | |
| 261 } | |
| 262 | |
| 263 /** | |
| 264 * Callback for when the user clicks on a compile error message | |
| 265 * | |
| 266 */ | |
| 267 | |
| 268 function errorClick() { | |
| 269 var line = this.getAttribute('data-line'); | |
| 270 var col = this.getAttribute('data-col'); | |
| 271 | |
| 272 editor.setCursor(line-1,col-1); | |
| 273 editor.focus(); | |
| 274 } | |
| 275 | |
| 276 | |
| 277 /** | |
| 278 * Callback for when the XHR returns after attempting to run the code. | |
| 279 * @param e The callback event. | |
| 280 */ | |
| 281 function codeComplete(e) { | |
| 282 // The response is JSON of the form: | |
| 283 // { | |
| 284 // "message": "you had an error...", | |
| 285 // "img": "<base64 encoded image but only on success>" | |
| 286 // } | |
| 287 // | |
| 288 // The img is optional and only appears if there is a valid | |
| 289 // image to display. | |
| 290 endWait(); | |
| 291 body = JSON.parse(e.target.response); | |
| 292 if (null != body.compileErrors && body.compileErrors.length) { | |
| 293 html = ""; | |
| 294 for (i = 0 ; i < body.compileErrors.length ; i++) { | |
| 295 compileError = body.compileErrors[i]; | |
| 296 | |
| 297 err = document.createElement("div"); | |
| 298 err.className = "compile-error"; | |
| 299 | |
| 300 loc = document.createElement("span"); | |
| 301 loc.className = "error-location"; | |
| 302 loc.innerHTML = "Line " + compileError.line + ", col " + compileErro
r.column + ": "; | |
| 303 | |
| 304 errorMessage = document.createElement("span"); | |
| 305 errorMessage.className = "error-mesage"; | |
| 306 errorMessage.innerHTML = compileError.error; | |
| 307 | |
| 308 err.appendChild(loc); | |
| 309 err.appendChild(errorMessage); | |
| 310 | |
| 311 err.setAttribute('data-line', compileError.line); | |
| 312 err.setAttribute('data-col', compileError.column); | |
| 313 | |
| 314 output.appendChild(err); | |
| 315 | |
| 316 err.addEventListener('click', errorClick); | |
| 317 | |
| 318 editor.addLineClass( parseInt( compileError.line ) - 1, "wrap", "err
or" ); | |
| 319 } | |
| 320 outputWrapper.style.display = 'block'; | |
| 321 } else { | |
| 322 output.textContent = body.message; | |
| 323 if (body.message) { | |
| 324 outputWrapper.style.display = 'block'; | |
| 325 } | |
| 326 } | |
| 327 if (body.hasOwnProperty('rasterImg') && body.rasterImg != "") { | |
| 328 rasterImg.src = 'data:image/png;base64,' + body.rasterImg; | |
| 329 rasterOutput.style.display = "inline-block"; | |
| 330 } else { | |
| 331 rasterOutput.style.display = "none"; | |
| 332 rasterImg.src = ''; | |
| 333 } | |
| 334 if (body.hasOwnProperty('gpuImg') && body.gpuImg != "") { | |
| 335 gpuImg.src = 'data:image/png;base64,' + body.gpuImg; | |
| 336 gpuOutput.style.display = "inline-block"; | |
| 337 } else { | |
| 338 gpuImg.src = ''; | |
| 339 gpuOutput.style.display = "none"; | |
| 340 } | |
| 341 // Add the image to the history if we are on a workspace page. | |
| 342 if (tryHistory) { | |
| 343 addToHistory(body.hash, 'data:image/png;base64,' + body.rasterImg); | |
| 344 } else { | |
| 345 window.history.pushState(null, null, '/c/' + body.hash); | |
| 346 } | |
| 347 if (permalink) { | |
| 348 permalink.href = '/c/' + body.hash; | |
| 349 permalink.style.display = 'inline-block'; | |
| 350 } | |
| 351 if (embed) { | |
| 352 setIFrameURL(); | |
| 353 } | |
| 354 if (embedButton) { | |
| 355 embedButton.style.display = 'inline-block'; | |
| 356 } | |
| 357 } | |
| 358 | |
| 359 function onSubmitCode() { | |
| 360 beginWait(); | |
| 361 clearOutput(); | |
| 362 var req = new XMLHttpRequest(); | |
| 363 req.addEventListener('load', codeComplete); | |
| 364 req.addEventListener('error', xhrError); | |
| 365 req.overrideMimeType('application/json'); | |
| 366 req.open('POST', '/', true); | |
| 367 req.setRequestHeader('content-type', 'application/json'); | |
| 368 req.send(JSON.stringify({ | |
| 369 'code': editor.getValue(), | |
| 370 'width': parseInt(imageWidth.value), | |
| 371 'height': parseInt(imageHeight.value), | |
| 372 'name': workspaceName, | |
| 373 'source': sourceId, | |
| 374 'gpu': gpu.checked, | |
| 375 'raster': raster.checked, | |
| 376 'pdf': pdf.checked | |
| 377 })); | |
| 378 } | |
| 379 run.addEventListener('click', onSubmitCode); | |
| 380 | |
| 381 | |
| 382 function onEmbedClick() { | |
| 383 embed.style.display='inline'; | |
| 384 } | |
| 385 | |
| 386 if (embedButton) { | |
| 387 embedButton.addEventListener('click', onEmbedClick); | |
| 388 } | |
| 389 | |
| 390 setIFrameURL(); | |
| 391 | |
| 392 // Add the images to the history if we are on a workspace page. | |
| 393 if (tryHistory && history_) { | |
| 394 for (var i=0; i<history_.length; i++) { | |
| 395 addToHistory(history_[i].hash, '/i/'+history_[i].hash+'.png'); | |
| 396 } | |
| 397 } | |
| 398 } | |
| 399 | |
| 400 // If loaded via HTML Imports then DOMContentLoaded will be long done. | |
| 401 if (document.readyState != "loading") { | |
| 402 onLoad(); | |
| 403 } else { | |
| 404 this.addEventListener('load', onLoad); | |
| 405 } | |
| 406 | |
| 407 })(); | |
| 408 | |
| 409 // TODO (humper) -- move the following functions out of the global | |
| 410 // namespace as part of a web-components based fiddle frontend rewrite. | |
| 411 | |
| 412 function collectionHas(a, b) { //helper function (see below) | |
| 413 for(var i = 0, len = a.length; i < len; i ++) { | |
| 414 if(a[i] == b) return true; | |
| 415 } | |
| 416 return false; | |
| 417 } | |
| 418 function findParentBySelector(elm, selector) { | |
| 419 var all = document.querySelectorAll(selector); | |
| 420 var cur = elm.parentNode; | |
| 421 while(cur && !collectionHas(all, cur)) { //keep going up until you find a ma
tch | |
| 422 cur = cur.parentNode; //go up | |
| 423 } | |
| 424 return cur; //will return null if not found | |
| 425 } | |
| 426 | |
| 427 function onLoadImage(img) { | |
| 428 var wrapper = findParentBySelector(img, ".image-wrapper"); | |
| 429 wrapper.style.display = "inline-block"; | |
| 430 } | |
| OLD | NEW |