Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library trydart.main; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:html'; | |
| 9 import 'dart:isolate'; | |
| 10 import 'dart:uri'; | |
| 11 | |
| 12 import '../sdk/lib/_internal/compiler/implementation/scanner/scannerlib.dart' sh ow StringScanner, EOF_TOKEN; | |
|
kasperl
2014/01/07 07:18:43
Long lines.
ahe
2014/01/07 14:06:23
Done.
| |
| 13 import '../sdk/lib/_internal/compiler/implementation/scanner/scannerlib.dart' as scanner; | |
| 14 | |
| 15 import 'decoration.dart'; | |
| 16 import 'themes.dart'; | |
| 17 | |
| 18 @lazy import 'compiler_isolate.dart'; | |
| 19 | |
| 20 const lazy = const DeferredLibrary('compiler_isolate'); | |
| 21 | |
| 22 var inputPre; | |
| 23 var outputDiv; | |
| 24 var hackDiv; | |
| 25 var outputFrame; | |
| 26 var compilerTimer; | |
| 27 var compilerPort; | |
| 28 var observer; | |
| 29 var cacheStatusElement; | |
| 30 bool alwaysRunInWorker = window.localStorage['alwaysRunInWorker'] == 'true'; | |
| 31 bool verboseCompiler = window.localStorage['verboseCompiler'] == 'true'; | |
| 32 bool minified = window.localStorage['minified'] == 'true'; | |
| 33 bool onlyAnalyze = window.localStorage['onlyAnalyze'] == 'true'; | |
| 34 String codeFont = ((x) => x == null ? '' : x)(window.localStorage['codeFont']); | |
|
kasperl
2014/01/07 07:18:43
Maybe just add an extra rawCodeFont variable? This
ahe
2014/01/07 14:06:23
Done.
| |
| 35 String currentSample = window.localStorage['currentSample']; | |
| 36 Theme currentTheme = Theme.named(window.localStorage['theme']); | |
| 37 bool applyingSettings = false; | |
| 38 | |
| 39 const String INDENT = '\u{a0}\u{a0}'; | |
| 40 | |
| 41 onKeyUp(KeyboardEvent e) { | |
| 42 if (e.keyCode == 13) { | |
| 43 e.preventDefault(); | |
| 44 DomSelection selection = window.getSelection(); | |
| 45 if (selection.isCollapsed && selection.anchorNode is Text) { | |
| 46 Text text = selection.anchorNode; | |
| 47 int offset = selection.anchorOffset; | |
| 48 text.insertData(offset, '\n'); | |
| 49 selection.collapse(text, offset + 1); | |
| 50 } | |
| 51 } | |
| 52 // This is a hack to get Safari to send mutation events on contenteditable. | |
| 53 var newDiv = new DivElement(); | |
| 54 hackDiv.replaceWith(newDiv); | |
| 55 hackDiv = newDiv; | |
| 56 } | |
| 57 | |
| 58 bool isMalformedInput = false; | |
| 59 String currentSource = ""; | |
| 60 | |
| 61 onMutation(List<MutationRecord> mutations, MutationObserver observer) { | |
|
kasperl
2014/01/07 07:18:43
This method is very long. Maybe break it into a fe
ahe
2014/01/07 14:06:23
Totally agree. I'll add a TODO for now.
| |
| 62 scheduleCompilation(); | |
| 63 | |
| 64 for (Element element in inputPre.queryAll('a[class="diagnostic"]>span')) { | |
| 65 element.remove(); | |
| 66 } | |
| 67 // Discard clean-up mutations. | |
| 68 observer.takeRecords(); | |
| 69 | |
| 70 DomSelection selection = window.getSelection(); | |
| 71 | |
| 72 while (!mutations.isEmpty) { | |
| 73 for (MutationRecord record in mutations) { | |
| 74 String type = record.type; | |
| 75 switch (type) { | |
| 76 | |
| 77 case 'characterData': | |
|
kasperl
2014/01/07 07:18:43
Indent cases.
ahe
2014/01/07 14:06:23
OK. That was hard:
(setq my-dart-style
'((c
| |
| 78 | |
| 79 bool hasSelection = false; | |
| 80 int offset = selection.anchorOffset; | |
| 81 if (selection.isCollapsed && selection.anchorNode == record.target) { | |
| 82 hasSelection = true; | |
| 83 } | |
| 84 var parent = record.target.parentNode; | |
| 85 if (parent != inputPre) { | |
| 86 inlineChildren(parent); | |
| 87 } | |
| 88 if (hasSelection) { | |
| 89 selection.collapse(record.target, offset); | |
| 90 } | |
| 91 break; | |
| 92 | |
| 93 default: | |
| 94 if (!record.addedNodes.isEmpty) { | |
| 95 for (var node in record.addedNodes) { | |
| 96 | |
| 97 if (node.nodeType != Node.ELEMENT_NODE) continue; | |
| 98 | |
| 99 if (node is BRElement) { | |
| 100 if (selection.anchorNode != node) { | |
| 101 node.replaceWith(new Text('\n')); | |
| 102 } | |
| 103 } else { | |
| 104 var parent = node.parentNode; | |
| 105 if (parent == null) continue; | |
| 106 var nodes = new List.from(node.nodes); | |
| 107 var style = node.getComputedStyle(); | |
| 108 if (style.display != 'inline') { | |
| 109 var previous = node.previousNode; | |
| 110 if (previous is Text) { | |
| 111 previous.appendData('\n'); | |
| 112 } else { | |
| 113 parent.insertBefore(new Text('\n'), node); | |
| 114 } | |
| 115 } | |
| 116 for (Node child in nodes) { | |
| 117 child.remove(); | |
| 118 parent.insertBefore(child, node); | |
| 119 } | |
| 120 node.remove(); | |
| 121 } | |
| 122 } | |
| 123 } | |
| 124 } | |
| 125 } | |
| 126 mutations = observer.takeRecords(); | |
| 127 } | |
| 128 | |
| 129 if (!inputPre.nodes.isEmpty && inputPre.nodes.last is Text) { | |
| 130 Text text = inputPre.nodes.last; | |
| 131 if (!text.text.endsWith('\n')) { | |
| 132 text.appendData('\n'); | |
| 133 } | |
| 134 } | |
| 135 | |
| 136 int offset = 0; | |
| 137 int anchorOffset = 0; | |
| 138 bool hasSelection = false; | |
| 139 Node anchorNode = selection.anchorNode; | |
| 140 void walk4(Node node) { | |
| 141 // TODO(ahe): Use TreeWalker when that is exposed. | |
| 142 // function textNodesUnder(root){ | |
| 143 // var n, a=[], walk=document.createTreeWalker(root,NodeFilter.SHOW_TEXT,n ull,false); | |
|
kasperl
2014/01/07 07:18:43
Long line.
ahe
2014/01/07 14:06:23
Done.
| |
| 144 // while(n=walk.nextNode()) a.push(n); | |
| 145 // return a; | |
| 146 // } | |
| 147 int type = node.nodeType; | |
| 148 if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { | |
| 149 if (anchorNode == node) { | |
| 150 hasSelection = true; | |
| 151 anchorOffset = selection.anchorOffset + offset; | |
| 152 return; | |
| 153 } | |
| 154 offset += node.length; | |
| 155 } | |
| 156 | |
| 157 var child = node.$dom_firstChild; | |
| 158 while(child != null) { | |
|
kasperl
2014/01/07 07:18:43
DANGER. DANGER. You know the drill!
ahe
2014/01/07 14:06:23
Done.
| |
| 159 walk4(child); | |
| 160 if (hasSelection) return; | |
| 161 child = child.nextNode; | |
| 162 } | |
| 163 } | |
| 164 if (selection.isCollapsed) { | |
| 165 walk4(inputPre); | |
| 166 } | |
| 167 | |
| 168 currentSource = inputPre.text; | |
| 169 inputPre.nodes.clear(); | |
| 170 inputPre.appendText(currentSource); | |
| 171 if (hasSelection) { | |
| 172 selection.collapse(inputPre.$dom_firstChild, anchorOffset); | |
| 173 } | |
| 174 | |
| 175 isMalformedInput = false; | |
| 176 for (Node node in new List.from(inputPre.nodes)) { | |
| 177 if (node is! Text) continue; | |
| 178 String text = node.text; | |
| 179 | |
| 180 var token = new StringScanner(text, includeComments: true).tokenize(); | |
| 181 int offset = 0; | |
| 182 for (;token.kind != EOF_TOKEN; token = token.next) { | |
| 183 Decoration decoration = getDecoration(token); | |
| 184 if (decoration == null) continue; | |
| 185 bool hasSelection = false; | |
| 186 int selectionOffset = selection.anchorOffset; | |
| 187 | |
| 188 if (selection.isCollapsed && selection.anchorNode == node) { | |
| 189 hasSelection = true; | |
| 190 selectionOffset = selection.anchorOffset; | |
| 191 } | |
| 192 int splitPoint = token.charOffset - offset; | |
| 193 Text str = node.splitText(splitPoint); | |
| 194 Text after = str.splitText(token.slowCharCount); | |
| 195 offset += splitPoint + token.slowCharCount; | |
| 196 inputPre.insertBefore(after, node.nextNode); | |
| 197 inputPre.insertBefore(decoration.applyTo(str), after); | |
| 198 | |
| 199 if (hasSelection && selectionOffset > node.length) { | |
| 200 selectionOffset -= node.length; | |
| 201 if (selectionOffset > str.length) { | |
| 202 selectionOffset -= str.length; | |
| 203 selection.collapse(after, selectionOffset); | |
| 204 } else { | |
| 205 selection.collapse(str, selectionOffset); | |
| 206 } | |
| 207 } | |
| 208 node = after; | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 window.localStorage['currentSource'] = currentSource; | |
| 213 | |
| 214 // Discard highlighting mutations. | |
| 215 observer.takeRecords(); | |
| 216 } | |
| 217 | |
| 218 addDiagnostic(String kind, String message, int begin, int end) { | |
| 219 observer.disconnect(); | |
| 220 DomSelection selection = window.getSelection(); | |
| 221 int offset = 0; | |
| 222 int anchorOffset = 0; | |
| 223 bool hasSelection = false; | |
| 224 Node anchorNode = selection.anchorNode; | |
| 225 bool foundNode = false; | |
| 226 void walk4(Node node) { | |
|
kasperl
2014/01/07 07:18:43
Could this be refactored somehow? You have walk4 i
ahe
2014/01/07 14:06:23
This might be fixed by using TreeWalker.
| |
| 227 // TODO(ahe): Use TreeWalker when that is exposed. | |
| 228 int type = node.nodeType; | |
| 229 if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { | |
| 230 // print('walking: ${node.data}'); | |
| 231 if (anchorNode == node) { | |
| 232 hasSelection = true; | |
| 233 anchorOffset = selection.anchorOffset + offset; | |
| 234 } | |
| 235 int newOffset = offset + node.length; | |
| 236 if (offset <= begin && begin < newOffset) { | |
| 237 hasSelection = node == anchorNode; | |
| 238 anchorOffset = selection.anchorOffset; | |
| 239 Node marker = new Text(""); | |
| 240 node.replaceWith(marker); | |
| 241 // TODO(ahe): Don't highlight everything in the node. Find | |
| 242 // the relevant token. | |
| 243 if (kind == 'error') { | |
| 244 marker.replaceWith(diagnostic(node, error(message))); | |
| 245 } else if (kind == 'warning') { | |
| 246 marker.replaceWith(diagnostic(node, warning(message))); | |
| 247 } else { | |
| 248 marker.replaceWith(diagnostic(node, info(message))); | |
| 249 } | |
| 250 if (hasSelection) { | |
| 251 selection.collapse(node, anchorOffset); | |
| 252 } | |
| 253 foundNode = true; | |
| 254 return; | |
| 255 } | |
| 256 offset = newOffset; | |
| 257 } else if (type == Node.ELEMENT_NODE) { | |
| 258 if (node.classes.contains('alert')) return; | |
| 259 } | |
| 260 | |
| 261 var child = node.$dom_firstChild; | |
| 262 while(child != null && !foundNode) { | |
| 263 walk4(child); | |
| 264 child = child.nextNode; | |
| 265 } | |
| 266 } | |
| 267 walk4(inputPre); | |
| 268 | |
| 269 if (!foundNode) { | |
| 270 outputDiv.appendText('$message\n'); | |
| 271 } | |
| 272 | |
| 273 observer.takeRecords(); | |
| 274 observer.observe(inputPre, childList: true, characterData: true, subtree: true ); | |
|
kasperl
2014/01/07 07:18:43
Long line.
ahe
2014/01/07 14:06:23
Done.
| |
| 275 } | |
| 276 | |
| 277 void inlineChildren(Element element) { | |
| 278 if (element == null) return; | |
| 279 var parent = element.parentNode; | |
| 280 if (parent == null) return; | |
| 281 for (Node child in new List.from(element.nodes)) { | |
| 282 child.remove(); | |
| 283 parent.insertBefore(child, element); | |
| 284 } | |
| 285 element.remove(); | |
| 286 } | |
| 287 | |
| 288 int count = 0; | |
| 289 | |
| 290 void scheduleCompilation() { | |
| 291 if (applyingSettings) return; | |
| 292 if (compilerTimer != null) { | |
| 293 compilerTimer.cancel(); | |
| 294 compilerTimer = null; | |
| 295 } | |
| 296 compilerTimer = | |
| 297 new Timer(const Duration(milliseconds: 500), startCompilation); | |
| 298 } | |
| 299 | |
| 300 void startCompilation() { | |
| 301 if (compilerTimer != null) { | |
| 302 compilerTimer.cancel(); | |
| 303 compilerTimer = null; | |
| 304 } | |
| 305 | |
| 306 new CompilationProcess(currentSource, outputDiv).start(); | |
| 307 } | |
| 308 | |
| 309 class CompilationProcess { | |
| 310 final String source; | |
| 311 final Element console; | |
| 312 final ReceivePort receivePort = new ReceivePort(); | |
| 313 bool isCleared = false; | |
| 314 bool isDone = false; | |
| 315 bool usesDartHtml = false; | |
| 316 Worker worker; | |
| 317 List<String> objectUrls = <String>[]; | |
| 318 | |
| 319 static CompilationProcess current; | |
| 320 | |
| 321 CompilationProcess(this.source, this.console); | |
| 322 | |
| 323 static bool shouldStartCompilation() { | |
| 324 if (compilerPort == null) return false; | |
| 325 if (isMalformedInput) return false; | |
| 326 if (current != null) return current.isDone; | |
| 327 return true; | |
| 328 } | |
| 329 | |
| 330 void clear() { | |
| 331 if (verboseCompiler) return; | |
| 332 if (!isCleared) console.nodes.clear(); | |
| 333 isCleared = true; | |
| 334 } | |
| 335 | |
| 336 void start() { | |
| 337 if (!shouldStartCompilation()) { | |
| 338 receivePort.close(); | |
| 339 if (!isMalformedInput) scheduleCompilation(); | |
| 340 return; | |
| 341 } | |
| 342 if (current != null) current.dispose(); | |
| 343 current = this; | |
| 344 console.nodes.clear(); | |
| 345 var options = []; | |
| 346 if (verboseCompiler) options.add('--verbose'); | |
| 347 if (minified) options.add('--minify'); | |
| 348 if (onlyAnalyze) options.add('--analyze-only'); | |
| 349 compilerPort.send(['options', options], receivePort.toSendPort()); | |
| 350 console.appendHtml('<i class="icon-spinner icon-spin"></i>'); | |
| 351 console.appendText(' Compiling Dart program...\n'); | |
| 352 outputFrame.style.display = 'none'; | |
| 353 receivePort.receive(onMessage); | |
| 354 compilerPort.send(source, receivePort.toSendPort()); | |
| 355 } | |
| 356 | |
| 357 void dispose() { | |
| 358 if (worker != null) worker.terminate(); | |
| 359 objectUrls.forEach(Url.revokeObjectUrl); | |
| 360 } | |
| 361 | |
| 362 onMessage(message, _) { | |
| 363 String kind = message is String ? message : message[0]; | |
| 364 var data = (message is List && message.length == 2) ? message[1] : null; | |
| 365 switch (kind) { | |
| 366 case 'done': return onDone(data); | |
|
kasperl
2014/01/07 07:18:43
I'd indent all the cases.
ahe
2014/01/07 14:06:23
Done.
| |
| 367 case 'url': return onUrl(data); | |
| 368 case 'code': return onCode(data); | |
| 369 case 'diagnostic': return onDiagnostic(data); | |
| 370 case 'crash': return onCrash(data); | |
| 371 case 'failed': return onFail(data); | |
| 372 case 'dart:html': return onDartHtml(data); | |
| 373 default: | |
| 374 throw ['Unknown message kind', message]; | |
| 375 } | |
| 376 } | |
| 377 | |
| 378 onDartHtml(_) { | |
| 379 usesDartHtml = true; | |
| 380 } | |
| 381 | |
| 382 onFail(_) { | |
| 383 clear(); | |
| 384 consolePrint('Compilation failed'); | |
| 385 } | |
| 386 | |
| 387 onDone(_) { | |
| 388 isDone = true; | |
| 389 receivePort.close(); | |
| 390 } | |
| 391 | |
| 392 // This is called in browsers that support creating Object URLs in a | |
| 393 // web worker. For example, Chrome and Firefox 21. | |
| 394 onUrl(String url) { | |
| 395 objectUrls.add(url); | |
| 396 clear(); | |
| 397 String wrapper = | |
| 398 'function dartPrint(msg) { self.postMessage(msg); };' | |
| 399 'self.importScripts("$url");'; | |
| 400 var wrapperUrl = | |
| 401 Url.createObjectUrl(new Blob([wrapper], 'application/javascript')); | |
| 402 objectUrls.add(wrapperUrl); | |
| 403 void retryInIframe(_) { | |
| 404 var frame = makeOutputFrame(url); | |
| 405 outputFrame.replaceWith(frame); | |
| 406 outputFrame = frame; | |
| 407 } | |
| 408 void onError(String errorMessage) { | |
| 409 console.appendText(errorMessage); | |
| 410 console.appendText(' '); | |
| 411 console.append(buildButton('Try in iframe', retryInIframe)); | |
| 412 console.appendText('\n'); | |
| 413 } | |
| 414 if (usesDartHtml && !alwaysRunInWorker) { | |
| 415 retryInIframe(null); | |
| 416 } else { | |
| 417 runInWorker(wrapperUrl, onError); | |
| 418 } | |
| 419 } | |
| 420 | |
| 421 // This is called in browsers that do not support creating Object | |
| 422 // URLs in a web worker. For example, Safari and Firefox < 21. | |
| 423 onCode(String code) { | |
| 424 clear(); | |
| 425 | |
| 426 void retryInIframe(_) { | |
| 427 // The obvious thing would be to call [makeOutputFrame], but | |
| 428 // Safari doesn't support access to Object URLs in an iframe. | |
| 429 | |
| 430 var frame = new IFrameElement() | |
| 431 ..src = 'iframe.html' | |
| 432 ..style.width = '100%' | |
| 433 ..style.height = '0px' | |
| 434 ..seamless = false; | |
| 435 frame.onLoad.listen((_) { | |
| 436 frame.contentWindow.postMessage(['source', code], '*'); | |
| 437 }); | |
| 438 outputFrame.replaceWith(frame); | |
| 439 outputFrame = frame; | |
| 440 } | |
| 441 | |
| 442 void onError(String errorMessage) { | |
| 443 console.appendText(errorMessage); | |
| 444 console.appendText(' '); | |
| 445 console.append(buildButton('Try in iframe', retryInIframe)); | |
| 446 console.appendText('\n'); | |
| 447 } | |
| 448 | |
| 449 String codeWithPrint = | |
| 450 '$code\n' | |
| 451 'function dartPrint(msg) { postMessage(msg); }\n'; | |
| 452 var url = | |
| 453 Url.createObjectUrl( | |
| 454 new Blob([codeWithPrint], 'application/javascript')); | |
| 455 objectUrls.add(url); | |
| 456 | |
| 457 if (usesDartHtml && !alwaysRunInWorker) { | |
| 458 retryInIframe(null); | |
| 459 } else { | |
| 460 runInWorker(url, onError); | |
| 461 } | |
| 462 } | |
| 463 | |
| 464 void runInWorker(String url, void onError(String errorMessage)) { | |
| 465 worker = new Worker(url) | |
| 466 ..onMessage.listen((MessageEvent event) { | |
| 467 consolePrint(event.data); | |
| 468 }) | |
| 469 ..onError.listen((ErrorEvent event) { | |
| 470 worker.terminate(); | |
| 471 worker = null; | |
| 472 onError(event.message); | |
| 473 }); | |
| 474 } | |
| 475 | |
| 476 onDiagnostic(Map<String, dynamic> diagnostic) { | |
| 477 String kind = diagnostic['kind']; | |
| 478 String message = diagnostic['message']; | |
| 479 if (kind == 'verbose info') { | |
| 480 if (verboseCompiler) { | |
| 481 consolePrint(message); | |
| 482 } | |
| 483 return; | |
| 484 } | |
| 485 String uri = diagnostic['uri']; | |
| 486 if (uri == null) { | |
| 487 clear(); | |
| 488 consolePrint(message); | |
| 489 return; | |
| 490 } | |
| 491 if (uri != 'memory:/main.dart') return; | |
| 492 if (currentSource != source) return; | |
| 493 int begin = diagnostic['begin']; | |
| 494 int end = diagnostic['end']; | |
| 495 if (begin == null) return; | |
| 496 addDiagnostic(kind, message, begin, end); | |
| 497 } | |
| 498 | |
| 499 onCrash(data) { | |
| 500 consolePrint(data); | |
| 501 } | |
| 502 | |
| 503 void consolePrint(message) { | |
| 504 console.appendText('$message\n'); | |
| 505 } | |
| 506 } | |
| 507 | |
| 508 Decoration getDecoration(scanner.Token token) { | |
| 509 String tokenValue = token.slowToString(); | |
| 510 String tokenInfo = token.info.value.slowToString(); | |
| 511 if (tokenInfo == 'string') return currentTheme.string; | |
| 512 // if (tokenInfo == 'identifier') return identifier; | |
| 513 if (tokenInfo == 'keyword') return currentTheme.keyword; | |
| 514 if (tokenInfo == 'comment') return currentTheme.singleLineComment; | |
| 515 if (tokenInfo == 'malformed input') { | |
| 516 isMalformedInput = true; | |
| 517 return new DiagnosticDecoration('error', tokenValue); | |
| 518 } | |
| 519 return null; | |
| 520 } | |
| 521 | |
| 522 diagnostic(text, tip) { | |
| 523 if (text is String) { | |
| 524 text = new Text(text); | |
| 525 } | |
| 526 return new AnchorElement() | |
| 527 ..classes.add('diagnostic') | |
| 528 ..append(text) | |
| 529 ..append(tip); | |
| 530 } | |
| 531 | |
| 532 img(src, width, height, alt) { | |
| 533 return new ImageElement(src: src, width: width, height: height)..alt = alt; | |
| 534 } | |
| 535 | |
| 536 makeOutputFrame(String scriptUrl) { | |
| 537 final String outputHtml = ''' | |
| 538 <!DOCTYPE html> | |
| 539 <html lang="en"> | |
| 540 <head> | |
| 541 <title>JavaScript output</title> | |
| 542 <meta http-equiv="Content-type" content="text/html;charset=UTF-8"> | |
| 543 </head> | |
| 544 <body> | |
| 545 <script type="application/javascript" src="$outputHelper"></script> | |
| 546 <script type="application/javascript" src="$scriptUrl"></script> | |
| 547 </body> | |
| 548 </html> | |
| 549 '''; | |
| 550 | |
| 551 return new IFrameElement() | |
| 552 ..src = Url.createObjectUrl(new Blob([outputHtml], "text/html")) | |
| 553 ..style.width = '100%' | |
| 554 ..style.height = '0px' | |
| 555 ..seamless = false; | |
| 556 } | |
| 557 | |
| 558 const String HAS_NON_DOM_HTTP_REQUEST = 'spawnFunction supports HttpRequest'; | |
| 559 const String NO_NON_DOM_HTTP_REQUEST = | |
| 560 'spawnFunction does not support HttpRequest'; | |
| 561 | |
| 562 | |
| 563 checkHttpRequest() { | |
| 564 port.receive((String uri, SendPort replyTo) { | |
| 565 try { | |
| 566 new HttpRequest(); | |
| 567 replyTo.send(HAS_NON_DOM_HTTP_REQUEST); | |
| 568 } catch (e, trace) { | |
| 569 replyTo.send(NO_NON_DOM_HTTP_REQUEST); | |
| 570 } | |
| 571 port.close(); | |
| 572 }); | |
| 573 } | |
| 574 | |
| 575 main() { | |
| 576 if (window.localStorage['currentSource'] == null) { | |
| 577 window.localStorage['currentSource'] = EXAMPLE_HELLO; | |
| 578 } | |
| 579 | |
| 580 buildUI(); | |
| 581 spawnFunction(checkHttpRequest).call('').then((reply) { | |
| 582 var compilerFuture; | |
| 583 if (reply == HAS_NON_DOM_HTTP_REQUEST) { | |
| 584 compilerFuture = spawnFunction(compilerIsolate); | |
| 585 } else { | |
| 586 compilerFuture = spawnDomFunction(compilerIsolate); | |
| 587 } | |
| 588 if (compilerFuture is! Future) { | |
| 589 compilerFuture = new Future.value(compilerFuture); | |
| 590 } | |
| 591 compilerFuture.then((port) { | |
| 592 String sdk = query('link[rel="dart-sdk"]').href; | |
| 593 print('Using Dart SDK: $sdk'); | |
| 594 port.call(sdk).then((_) { | |
| 595 compilerPort = port; | |
| 596 onMutation([], observer); | |
| 597 }); | |
| 598 }); | |
| 599 }); | |
| 600 } | |
| 601 | |
| 602 buildButton(message, action) { | |
| 603 if (message is String) { | |
| 604 message = new Text(message); | |
| 605 } | |
| 606 return new ButtonElement() | |
| 607 ..onClick.listen(action) | |
| 608 ..append(message); | |
| 609 } | |
| 610 | |
| 611 buildTab(message, id, action) { | |
| 612 if (message is String) { | |
| 613 message = new Text(message); | |
| 614 } | |
| 615 | |
| 616 onClick(MouseEvent event) { | |
| 617 event.preventDefault(); | |
| 618 Element e = event.target; | |
| 619 LIElement parent = e.parent; | |
| 620 parent.parent.query('li[class="active"]').classes.remove('active'); | |
| 621 parent.classes.add('active'); | |
| 622 action(event); | |
| 623 } | |
| 624 | |
| 625 inspirationCallbacks[id] = action; | |
| 626 | |
| 627 return new OptionElement()..append(message)..id = id; | |
| 628 } | |
| 629 | |
| 630 Map<String, Function> inspirationCallbacks = new Map<String, Function>(); | |
| 631 | |
| 632 void onInspirationChange(Event event) { | |
| 633 SelectElement select = event.target; | |
| 634 String id = select.queryAll('option')[select.selectedIndex].id; | |
| 635 Function action = inspirationCallbacks[id]; | |
| 636 if (action != null) action(event); | |
| 637 outputFrame.style.display = 'none'; | |
| 638 } | |
| 639 | |
| 640 buildUI() { | |
| 641 window.localStorage['currentSample'] = '$currentSample'; | |
| 642 | |
| 643 var inspirationTabs = document.getElementById('inspiration'); | |
| 644 var htmlGroup = new OptGroupElement()..label = 'HTML'; | |
| 645 var benchmarkGroup = new OptGroupElement()..label = 'Benchmarks'; | |
| 646 inspirationTabs.append(new OptionElement()..appendText('Pick an example')); | |
| 647 inspirationTabs.onChange.listen(onInspirationChange); | |
| 648 // inspirationTabs.classes.addAll(['nav', 'nav-tabs']); | |
| 649 inspirationTabs.append(buildTab('Hello, World!', 'EXAMPLE_HELLO', (_) { | |
| 650 inputPre | |
| 651 ..nodes.clear() | |
| 652 ..appendText(EXAMPLE_HELLO); | |
| 653 })); | |
| 654 inspirationTabs.append(buildTab('Fibonacci', 'EXAMPLE_FIBONACCI', (_) { | |
| 655 inputPre | |
| 656 ..nodes.clear() | |
| 657 ..appendText(EXAMPLE_FIBONACCI); | |
| 658 })); | |
| 659 inspirationTabs.append(htmlGroup); | |
| 660 inspirationTabs.append(benchmarkGroup); | |
| 661 | |
| 662 htmlGroup.append( | |
| 663 buildTab('Hello, World!', 'EXAMPLE_HELLO_HTML', (_) { | |
| 664 inputPre | |
| 665 ..nodes.clear() | |
| 666 ..appendText(EXAMPLE_HELLO_HTML); | |
| 667 })); | |
| 668 htmlGroup.append( | |
| 669 buildTab('Fibonacci', 'EXAMPLE_FIBONACCI_HTML', (_) { | |
| 670 inputPre | |
| 671 ..nodes.clear() | |
| 672 ..appendText(EXAMPLE_FIBONACCI_HTML); | |
| 673 })); | |
| 674 htmlGroup.append(buildTab('Sunflower', 'EXAMPLE_SUNFLOWER', (_) { | |
| 675 inputPre | |
| 676 ..nodes.clear() | |
| 677 ..appendText(EXAMPLE_SUNFLOWER); | |
| 678 })); | |
| 679 | |
| 680 benchmarkGroup.append(buildTab('DeltaBlue', 'BENCHMARK_DELTA_BLUE', (_) { | |
| 681 inputPre.contentEditable = 'false'; | |
| 682 String deltaBlueUri = query('link[rel="benchmark-DeltaBlue"]').href; | |
| 683 String benchmarkBaseUri = query('link[rel="benchmark-base"]').href; | |
| 684 HttpRequest.getString(benchmarkBaseUri).then((String benchmarkBase) { | |
| 685 HttpRequest.getString(deltaBlueUri).then((String deltaBlue) { | |
| 686 benchmarkBase = benchmarkBase.replaceFirst( | |
| 687 'part of benchmark_harness;', '// part of benchmark_harness;'); | |
| 688 deltaBlue = deltaBlue.replaceFirst( | |
| 689 "import 'package:benchmark_harness/benchmark_harness.dart';", | |
| 690 benchmarkBase); | |
| 691 inputPre | |
| 692 ..nodes.clear() | |
| 693 ..appendText(deltaBlue) | |
| 694 ..contentEditable = 'true'; | |
| 695 }); | |
| 696 }); | |
| 697 })); | |
| 698 | |
| 699 benchmarkGroup.append(buildTab('Richards', 'BENCHMARK_RICHARDS', (_) { | |
| 700 inputPre.contentEditable = 'false'; | |
| 701 String richardsUri = query('link[rel="benchmark-Richards"]').href; | |
| 702 String benchmarkBaseUri = query('link[rel="benchmark-base"]').href; | |
| 703 HttpRequest.getString(benchmarkBaseUri).then((String benchmarkBase) { | |
| 704 HttpRequest.getString(richardsUri).then((String richards) { | |
| 705 benchmarkBase = benchmarkBase.replaceFirst( | |
| 706 'part of benchmark_harness;', '// part of benchmark_harness;'); | |
| 707 richards = richards.replaceFirst( | |
| 708 "import 'package:benchmark_harness/benchmark_harness.dart';", | |
| 709 benchmarkBase); | |
| 710 inputPre | |
| 711 ..nodes.clear() | |
| 712 ..appendText(richards) | |
| 713 ..contentEditable = 'true'; | |
| 714 }); | |
| 715 }); | |
| 716 })); | |
| 717 | |
| 718 // TODO(ahe): Update currentSample. Or try switching to a drop-down menu. | |
| 719 var active = inspirationTabs.query('[id="$currentSample"]'); | |
| 720 if (active == null) { | |
| 721 // inspirationTabs.query('li').classes.add('active'); | |
| 722 } | |
| 723 | |
| 724 (inputPre = new DivElement()) | |
| 725 ..classes.add('well') | |
| 726 ..style.backgroundColor = currentTheme.background.color | |
| 727 ..style.color = currentTheme.foreground.color | |
| 728 ..style.overflow = 'auto' | |
| 729 ..style.whiteSpace = 'pre' | |
| 730 ..style.font = codeFont | |
| 731 ..spellcheck = false; | |
| 732 | |
| 733 inputPre.contentEditable = 'true'; | |
| 734 inputPre.onKeyDown.listen(onKeyUp); | |
| 735 | |
| 736 var inputWrapper = new DivElement() | |
| 737 ..append(inputPre) | |
| 738 ..style.position = 'relative'; | |
| 739 | |
| 740 var inputHeader = new DivElement()..appendText('Code'); | |
| 741 | |
| 742 inputHeader.style | |
| 743 ..right = '3px' | |
| 744 ..top = '0px' | |
| 745 ..position = 'absolute'; | |
| 746 inputWrapper.append(inputHeader); | |
| 747 | |
| 748 outputFrame = | |
| 749 makeOutputFrame( | |
| 750 Url.createObjectUrl(new Blob([''], 'application/javascript'))); | |
| 751 | |
| 752 outputDiv = new PreElement(); | |
| 753 outputDiv.style | |
| 754 ..backgroundColor = currentTheme.background.color | |
| 755 ..color = currentTheme.foreground.color | |
| 756 ..overflow = 'auto' | |
| 757 ..padding = '1em' | |
| 758 ..minHeight = '10em' | |
| 759 ..whiteSpace = 'pre-wrap'; | |
| 760 | |
| 761 var outputWrapper = new DivElement() | |
| 762 ..append(outputDiv) | |
| 763 ..style.position = 'relative'; | |
| 764 | |
| 765 var consoleHeader = new DivElement()..appendText('Console'); | |
| 766 | |
| 767 consoleHeader.style | |
| 768 ..right = '3px' | |
| 769 ..top = '0px' | |
| 770 ..position = 'absolute'; | |
| 771 outputWrapper.append(consoleHeader); | |
| 772 | |
| 773 hackDiv = new DivElement(); | |
| 774 | |
| 775 var saveButton = new ButtonElement() | |
| 776 ..onClick.listen((_) { | |
| 777 var blobUrl = | |
| 778 Url.createObjectUrl(new Blob([inputPre.text], 'text/plain')); | |
| 779 var save = new AnchorElement(href: blobUrl); | |
| 780 save.target = '_blank'; | |
| 781 save.download = 'untitled.dart'; | |
| 782 save.dispatchEvent(new Event.eventType('Event', 'click')); | |
| 783 }) | |
| 784 ..style.position = 'absolute' | |
| 785 ..style.right = '0px' | |
| 786 ..appendText('Save'); | |
| 787 | |
| 788 cacheStatusElement = document.getElementById('appcache-status'); | |
| 789 updateCacheStatus(); | |
| 790 | |
| 791 // TODO(ahe): Switch to two column layout so the console is on the right. | |
| 792 var section = document.query('article[class="homepage"]>section'); | |
| 793 | |
| 794 DivElement tryColumn = document.getElementById('try-dart-column'); | |
| 795 DivElement runColumn = document.getElementById('run-dart-column'); | |
| 796 | |
| 797 tryColumn.append(inputWrapper); | |
| 798 outputFrame.style.display = 'none'; | |
| 799 runColumn.append(outputFrame); | |
| 800 runColumn.append(outputWrapper); | |
| 801 runColumn.append(hackDiv); | |
| 802 | |
| 803 var settingsElement = document.getElementById('settings'); | |
| 804 settingsElement.onClick.listen(openSettings); | |
| 805 | |
| 806 window.onMessage.listen((MessageEvent event) { | |
| 807 if (event.data is List) { | |
| 808 List message = event.data; | |
| 809 if (message.length > 0) { | |
| 810 switch (message[0]) { | |
| 811 case 'error': | |
| 812 Map diagnostics = message[1]; | |
| 813 String url = diagnostics['url']; | |
| 814 outputDiv.appendText('${diagnostics["message"]}\n'); | |
| 815 return; | |
| 816 case 'scrollHeight': | |
| 817 int scrollHeight = message[1]; | |
| 818 if (scrollHeight > 0) { | |
| 819 outputFrame.style.height = '${scrollHeight}px'; | |
| 820 } | |
| 821 return; | |
| 822 } | |
| 823 } | |
| 824 } | |
| 825 outputDiv.appendText('${event.data}\n'); | |
| 826 }); | |
| 827 | |
| 828 observer = new MutationObserver(onMutation) | |
| 829 ..observe(inputPre, childList: true, characterData: true, subtree: true); | |
| 830 | |
| 831 window.setImmediate(() { | |
| 832 inputPre.appendText(window.localStorage['currentSource']); | |
| 833 }); | |
| 834 | |
| 835 // You cannot install event handlers on window.applicationCache | |
| 836 // until the window has loaded. In dartium, that's later than this | |
| 837 // method is called. | |
| 838 window.onLoad.listen(onLoad); | |
| 839 | |
| 840 // However, in dart2js, the window has already loaded, and onLoad is | |
| 841 // never called. | |
| 842 onLoad(null); | |
| 843 } | |
| 844 | |
| 845 void openSettings(MouseEvent event) { | |
| 846 event.preventDefault(); | |
| 847 | |
| 848 var backdrop = new DivElement()..classes.add('modal-backdrop'); | |
| 849 document.body.append(backdrop); | |
| 850 | |
| 851 void updateCodeFont(Event e) { | |
| 852 codeFont = e.target.value; | |
| 853 inputPre.style.font = codeFont; | |
| 854 backdrop.style.opacity = '0.0'; | |
| 855 } | |
| 856 | |
| 857 void updateTheme(Event e) { | |
| 858 var select = e.target; | |
| 859 String theme = select.queryAll('option')[select.selectedIndex].text; | |
| 860 window.localStorage['theme'] = theme; | |
| 861 currentTheme = Theme.named(theme); | |
| 862 | |
| 863 inputPre.style | |
| 864 ..backgroundColor = currentTheme.background.color | |
| 865 ..color = currentTheme.foreground.color; | |
| 866 | |
| 867 outputDiv.style | |
| 868 ..backgroundColor = currentTheme.background.color | |
| 869 ..color = currentTheme.foreground.color; | |
| 870 | |
| 871 backdrop.style.opacity = '0.0'; | |
| 872 | |
| 873 applyingSettings = true; | |
| 874 onMutation([], observer); | |
| 875 applyingSettings = false; | |
| 876 } | |
| 877 | |
| 878 | |
| 879 var body = document.getElementById('settings-body'); | |
| 880 | |
| 881 body.nodes.clear(); | |
| 882 | |
| 883 var form = new FormElement(); | |
| 884 var fieldSet = new FieldSetElement(); | |
| 885 body.append(form); | |
| 886 form.append(fieldSet); | |
| 887 | |
| 888 buildCheckBox(String text, bool defaultValue, void action(Event e)) { | |
| 889 var checkBox = new CheckboxInputElement() | |
| 890 ..defaultChecked = defaultValue | |
| 891 ..onChange.listen(action); | |
| 892 return new LabelElement() | |
| 893 ..classes.add('checkbox') | |
| 894 ..append(checkBox) | |
| 895 ..appendText(' $text'); | |
| 896 } | |
| 897 | |
| 898 fieldSet.append( | |
|
kasperl
2014/01/07 07:18:43
Maybe it would make sense to have an abstraction o
ahe
2014/01/07 14:06:23
Added TODO.
| |
| 899 buildCheckBox( | |
| 900 'Always run in Worker thread.', alwaysRunInWorker, | |
| 901 (Event e) { alwaysRunInWorker = e.target.checked; })); | |
| 902 | |
| 903 fieldSet.append( | |
| 904 buildCheckBox( | |
| 905 'Verbose compiler output.', verboseCompiler, | |
| 906 (Event e) { verboseCompiler = e.target.checked; })); | |
| 907 | |
| 908 fieldSet.append( | |
| 909 buildCheckBox( | |
| 910 'Generate compact (minified) JavaScript.', minified, | |
| 911 (Event e) { minified = e.target.checked; })); | |
| 912 | |
| 913 fieldSet.append( | |
| 914 buildCheckBox( | |
| 915 'Only analyze program.', onlyAnalyze, | |
| 916 (Event e) { onlyAnalyze = e.target.checked; })); | |
| 917 | |
| 918 fieldSet.append(new LabelElement()..appendText('Code font:')); | |
| 919 var textInput = new TextInputElement(); | |
| 920 textInput.classes.add('input-block-level'); | |
| 921 if (codeFont != null && codeFont != '') { | |
| 922 textInput.value = codeFont; | |
| 923 } | |
| 924 textInput.placeholder = 'Enter a size and font, for example, 11pt monospace'; | |
| 925 textInput.onChange.listen(updateCodeFont); | |
| 926 fieldSet.append(textInput); | |
| 927 | |
| 928 fieldSet.append(new LabelElement()..appendText('Theme:')); | |
| 929 var themeSelector = new SelectElement(); | |
| 930 themeSelector.classes.add('input-block-level'); | |
| 931 for (Theme theme in THEMES) { | |
| 932 OptionElement option = new OptionElement()..appendText(theme.name); | |
| 933 if (theme == currentTheme) option.selected = true; | |
| 934 themeSelector.append(option); | |
| 935 } | |
| 936 themeSelector.onChange.listen(updateTheme); | |
| 937 fieldSet.append(themeSelector); | |
| 938 | |
| 939 var dialog = document.getElementById('settings-dialog'); | |
| 940 | |
| 941 dialog.style.display = 'block'; | |
| 942 dialog.classes.add('in'); | |
| 943 | |
| 944 onSubmit(Event event) { | |
| 945 event.preventDefault(); | |
| 946 | |
| 947 window.localStorage['alwaysRunInWorker'] = '$alwaysRunInWorker'; | |
| 948 window.localStorage['verboseCompiler'] = '$verboseCompiler'; | |
| 949 window.localStorage['minified'] = '$minified'; | |
| 950 window.localStorage['onlyAnalyze'] = '$onlyAnalyze'; | |
| 951 window.localStorage['codeFont'] = '$codeFont'; | |
| 952 | |
| 953 dialog.style.display = 'none'; | |
| 954 dialog.classes.remove('in'); | |
| 955 backdrop.remove(); | |
| 956 } | |
| 957 form.onSubmit.listen(onSubmit); | |
| 958 | |
| 959 var doneButton = document.getElementById('settings-done'); | |
| 960 doneButton.onClick.listen(onSubmit); | |
| 961 } | |
| 962 | |
| 963 /// Called when the window has finished loading. | |
| 964 void onLoad(Event event) { | |
| 965 window.applicationCache.onUpdateReady.listen((_) => updateCacheStatus()); | |
| 966 window.applicationCache.onCached.listen((_) => updateCacheStatus()); | |
| 967 window.applicationCache.onChecking.listen((_) => updateCacheStatus()); | |
| 968 window.applicationCache.onDownloading.listen((_) => updateCacheStatus()); | |
| 969 window.applicationCache.onError.listen((_) => updateCacheStatus()); | |
| 970 window.applicationCache.onNoUpdate.listen((_) => updateCacheStatus()); | |
| 971 window.applicationCache.onObsolete.listen((_) => updateCacheStatus()); | |
| 972 window.applicationCache.onProgress.listen(onCacheProgress); | |
| 973 } | |
| 974 | |
| 975 onCacheProgress(ProgressEvent event) { | |
| 976 if (!event.lengthComputable) { | |
| 977 updateCacheStatus(); | |
| 978 return; | |
| 979 } | |
| 980 cacheStatusElement.nodes.clear(); | |
| 981 cacheStatusElement.appendText('Downloading SDK '); | |
| 982 var progress = '${event.loaded} of ${event.total}'; | |
| 983 if (MeterElement.supported) { | |
| 984 cacheStatusElement.append( | |
| 985 new MeterElement() | |
| 986 ..appendText(progress) | |
| 987 ..min = 0 | |
| 988 ..max = event.total | |
| 989 ..value = event.loaded); | |
| 990 } else { | |
| 991 cacheStatusElement.appendText(progress); | |
| 992 } | |
| 993 } | |
| 994 | |
| 995 String cacheStatus() { | |
| 996 if (!ApplicationCache.supported) return 'offline not supported'; | |
| 997 int status = window.applicationCache.status; | |
| 998 if (status == ApplicationCache.CHECKING) return 'Checking for updates'; | |
| 999 if (status == ApplicationCache.DOWNLOADING) return 'Downloading SDK'; | |
| 1000 if (status == ApplicationCache.IDLE) return 'Try Dart! works offline'; | |
| 1001 if (status == ApplicationCache.OBSOLETE) return 'OBSOLETE'; | |
| 1002 if (status == ApplicationCache.UNCACHED) return 'offline not available'; | |
| 1003 if (status == ApplicationCache.UPDATEREADY) return 'SDK downloaded'; | |
| 1004 return '?'; | |
| 1005 } | |
| 1006 | |
| 1007 void updateCacheStatus() { | |
| 1008 cacheStatusElement.nodes.clear(); | |
| 1009 String status = window.applicationCache.status; | |
| 1010 if (status == ApplicationCache.UPDATEREADY) { | |
| 1011 cacheStatusElement.appendText('New version of Try Dart! ready: '); | |
| 1012 cacheStatusElement.append( | |
| 1013 new AnchorElement(href: '#') | |
| 1014 ..appendText('Load') | |
| 1015 ..onClick.listen((event) { | |
| 1016 event.preventDefault(); | |
| 1017 window.applicationCache.swapCache(); | |
| 1018 window.location.reload(); | |
| 1019 })); | |
| 1020 } else if (status == ApplicationCache.IDLE) { | |
| 1021 cacheStatusElement.appendText(cacheStatus()); | |
| 1022 cacheStatusElement.classes.add('offlineyay'); | |
| 1023 new Timer(const Duration(seconds: 10), () { | |
| 1024 cacheStatusElement.style.display = 'none'; | |
| 1025 }); | |
| 1026 } else { | |
| 1027 cacheStatusElement.appendText(cacheStatus()); | |
| 1028 } | |
| 1029 } | |
| 1030 | |
| 1031 void compilerIsolate() { | |
| 1032 lazy.load().then((_) => port.receive(compile)); | |
| 1033 } | |
| 1034 | |
| 1035 final String outputHelper = | |
| 1036 Url.createObjectUrl(new Blob([OUTPUT_HELPER], 'application/javascript')); | |
| 1037 | |
| 1038 const String EXAMPLE_HELLO = r''' | |
| 1039 // Go ahead and modify this example. | |
| 1040 | |
| 1041 var greeting = "Hello, World!"; | |
| 1042 | |
| 1043 // Prints a greeting. | |
| 1044 void main() { | |
| 1045 // The [print] function displays a message in the "Console" box. | |
| 1046 // Try modifying the greeting above and watch the "Console" box change. | |
| 1047 print(greeting); | |
| 1048 } | |
| 1049 '''; | |
| 1050 | |
| 1051 const String EXAMPLE_HELLO_HTML = r''' | |
| 1052 // Go ahead and modify this example. | |
| 1053 | |
| 1054 import "dart:html"; | |
| 1055 | |
| 1056 var greeting = "Hello, World!"; | |
| 1057 | |
| 1058 // Displays a greeting. | |
| 1059 void main() { | |
| 1060 // This example uses HTML to display the greeting and it will appear | |
| 1061 // in a nested HTML frame (an iframe). | |
| 1062 document.body.append(new HeadingElement.h1()..appendText(greeting)); | |
| 1063 } | |
| 1064 '''; | |
| 1065 | |
| 1066 const String EXAMPLE_FIBONACCI = r''' | |
| 1067 // Go ahead and modify this example. | |
| 1068 | |
| 1069 // Computes the nth Fibonacci number. | |
| 1070 int fibonacci(int n) { | |
| 1071 if (n < 2) return n; | |
| 1072 return fibonacci(n - 1) + fibonacci(n - 2); | |
| 1073 } | |
| 1074 | |
| 1075 // Prints a Fibonacci number. | |
| 1076 void main() { | |
| 1077 int i = 20; | |
| 1078 String message = "fibonacci($i) = ${fibonacci(i)}"; | |
| 1079 // Print the result in the "Console" box. | |
| 1080 print(message); | |
| 1081 } | |
| 1082 '''; | |
| 1083 | |
| 1084 const String EXAMPLE_FIBONACCI_HTML = r''' | |
| 1085 // Go ahead and modify this example. | |
| 1086 | |
| 1087 import "dart:html"; | |
| 1088 | |
| 1089 // Computes the nth Fibonacci number. | |
| 1090 int fibonacci(int n) { | |
| 1091 if (n < 2) return n; | |
| 1092 return fibonacci(n - 1) + fibonacci(n - 2); | |
| 1093 } | |
| 1094 | |
| 1095 // Displays a Fibonacci number. | |
| 1096 void main() { | |
| 1097 int i = 20; | |
| 1098 String message = "fibonacci($i) = ${fibonacci(i)}"; | |
| 1099 | |
| 1100 // This example uses HTML to display the result and it will appear | |
| 1101 // in a nested HTML frame (an iframe). | |
| 1102 document.body.append(new HeadingElement.h1()..appendText(message)); | |
| 1103 } | |
| 1104 '''; | |
| 1105 | |
| 1106 const String OUTPUT_HELPER = r''' | |
| 1107 function dartPrint(msg) { | |
| 1108 window.parent.postMessage(String(msg), "*"); | |
| 1109 } | |
| 1110 | |
| 1111 function dartMainRunner(main) { | |
| 1112 main(); | |
| 1113 } | |
| 1114 | |
| 1115 window.onerror = function (message, url, lineNumber) { | |
| 1116 window.parent.postMessage( | |
| 1117 ["error", {message: message, url: url, lineNumber: lineNumber}], "*"); | |
| 1118 }; | |
| 1119 | |
| 1120 (function () { | |
| 1121 | |
| 1122 function postScrollHeight() { | |
| 1123 window.parent.postMessage(["scrollHeight", document.documentElement.scrollHeig ht], "*"); | |
| 1124 } | |
| 1125 | |
| 1126 var observer = new (window.MutationObserver||window.WebKitMutationObserver||wind ow.MozMutationObserver)(function(mutations) { | |
| 1127 postScrollHeight() | |
| 1128 window.setTimeout(postScrollHeight, 500); | |
| 1129 }); | |
| 1130 | |
| 1131 observer.observe( | |
| 1132 document.body, | |
| 1133 { attributes: true, | |
| 1134 childList: true, | |
| 1135 characterData: true, | |
| 1136 subtree: true }); | |
| 1137 })(); | |
| 1138 '''; | |
| 1139 | |
| 1140 const String EXAMPLE_SUNFLOWER = ''' | |
| 1141 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 1142 // for details. All rights reserved. Use of this source code is governed by a | |
| 1143 // BSD-style license that can be found in the LICENSE file. | |
| 1144 | |
| 1145 library sunflower; | |
| 1146 | |
| 1147 import "dart:html"; | |
| 1148 import "dart:math"; | |
| 1149 | |
| 1150 const String ORANGE = "orange"; | |
| 1151 const int SEED_RADIUS = 2; | |
| 1152 const int SCALE_FACTOR = 4; | |
| 1153 const num TAU = PI * 2; | |
| 1154 const int MAX_D = 300; | |
| 1155 const num centerX = MAX_D / 2; | |
| 1156 const num centerY = centerX; | |
| 1157 | |
| 1158 final InputElement slider = query("#slider"); | |
| 1159 final Element notes = query("#notes"); | |
| 1160 final num PHI = (sqrt(5) + 1) / 2; | |
| 1161 int seeds = 0; | |
| 1162 final CanvasRenderingContext2D context = | |
| 1163 (query("#canvas") as CanvasElement).context2D; | |
| 1164 | |
| 1165 void main() { | |
| 1166 document.head.append(new StyleElement()..appendText(STYLE)); | |
| 1167 document.body.innerHtml = BODY; | |
| 1168 slider.onChange.listen((e) => draw()); | |
| 1169 draw(); | |
| 1170 } | |
| 1171 | |
| 1172 /// Draw the complete figure for the current number of seeds. | |
| 1173 void draw() { | |
| 1174 seeds = int.parse(slider.value); | |
| 1175 context.clearRect(0, 0, MAX_D, MAX_D); | |
| 1176 for (var i = 0; i < seeds; i++) { | |
| 1177 final num theta = i * TAU / PHI; | |
| 1178 final num r = sqrt(i) * SCALE_FACTOR; | |
| 1179 drawSeed(centerX + r * cos(theta), centerY - r * sin(theta)); | |
| 1180 } | |
| 1181 notes.text = "\${seeds} seeds"; | |
| 1182 } | |
| 1183 | |
| 1184 /// Draw a small circle representing a seed centered at (x,y). | |
| 1185 void drawSeed(num x, num y) { | |
| 1186 context..beginPath() | |
| 1187 ..lineWidth = 2 | |
| 1188 ..fillStyle = ORANGE | |
| 1189 ..strokeStyle = ORANGE | |
| 1190 ..arc(x, y, SEED_RADIUS, 0, TAU, false) | |
| 1191 ..fill() | |
| 1192 ..closePath() | |
| 1193 ..stroke(); | |
| 1194 } | |
| 1195 | |
| 1196 const String MATH_PNG = | |
| 1197 "https://dart.googlecode.com/svn/trunk/dart/samples/sunflower/web/math.png"; | |
| 1198 const String BODY = """ | |
| 1199 <h1>drfibonacci\'s Sunflower Spectacular</h1> | |
| 1200 | |
| 1201 <p>A canvas 2D demo.</p> | |
| 1202 | |
| 1203 <div id="container"> | |
| 1204 <canvas id="canvas" width="300" height="300" class="center"></canvas> | |
| 1205 <form class="center"> | |
| 1206 <input id="slider" type="range" max="1000" value="500"/> | |
| 1207 </form> | |
| 1208 <br/> | |
| 1209 <img src="\$MATH_PNG" width="350px" height="42px" class="center"> | |
| 1210 </div> | |
| 1211 | |
| 1212 <footer> | |
| 1213 <p id="summary"> </p> | |
| 1214 <p id="notes"> </p> | |
| 1215 </footer> | |
| 1216 """; | |
| 1217 | |
| 1218 const String STYLE = r""" | |
| 1219 body { | |
| 1220 background-color: #F8F8F8; | |
| 1221 font-family: 'Open Sans', sans-serif; | |
| 1222 font-size: 14px; | |
| 1223 font-weight: normal; | |
| 1224 line-height: 1.2em; | |
| 1225 margin: 15px; | |
| 1226 } | |
| 1227 | |
| 1228 p { | |
| 1229 color: #333; | |
| 1230 } | |
| 1231 | |
| 1232 #container { | |
| 1233 width: 100%; | |
| 1234 height: 400px; | |
| 1235 position: relative; | |
| 1236 border: 1px solid #ccc; | |
| 1237 background-color: #fff; | |
| 1238 } | |
| 1239 | |
| 1240 #summary { | |
| 1241 float: left; | |
| 1242 } | |
| 1243 | |
| 1244 #notes { | |
| 1245 float: right; | |
| 1246 width: 120px; | |
| 1247 text-align: right; | |
| 1248 } | |
| 1249 | |
| 1250 .error { | |
| 1251 font-style: italic; | |
| 1252 color: red; | |
| 1253 } | |
| 1254 | |
| 1255 img { | |
| 1256 border: 1px solid #ccc; | |
| 1257 margin: auto; | |
| 1258 } | |
| 1259 | |
| 1260 .center { | |
| 1261 display: block; | |
| 1262 margin: 0px auto; | |
| 1263 text-align: center; | |
| 1264 } | |
| 1265 """; | |
| 1266 | |
| 1267 '''; | |
| OLD | NEW |