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 |