Chromium Code Reviews| Index: dart/site/try/leap.dart |
| diff --git a/dart/site/try/leap.dart b/dart/site/try/leap.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1828aa873d57e273f334bc11054bd005f97ddb95 |
| --- /dev/null |
| +++ b/dart/site/try/leap.dart |
| @@ -0,0 +1,1267 @@ |
| +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +library trydart.main; |
| + |
| +import 'dart:async'; |
| +import 'dart:html'; |
| +import 'dart:isolate'; |
| +import 'dart:uri'; |
| + |
| +import '../sdk/lib/_internal/compiler/implementation/scanner/scannerlib.dart' show StringScanner, EOF_TOKEN; |
|
kasperl
2014/01/07 07:18:43
Long lines.
ahe
2014/01/07 14:06:23
Done.
|
| +import '../sdk/lib/_internal/compiler/implementation/scanner/scannerlib.dart' as scanner; |
| + |
| +import 'decoration.dart'; |
| +import 'themes.dart'; |
| + |
| +@lazy import 'compiler_isolate.dart'; |
| + |
| +const lazy = const DeferredLibrary('compiler_isolate'); |
| + |
| +var inputPre; |
| +var outputDiv; |
| +var hackDiv; |
| +var outputFrame; |
| +var compilerTimer; |
| +var compilerPort; |
| +var observer; |
| +var cacheStatusElement; |
| +bool alwaysRunInWorker = window.localStorage['alwaysRunInWorker'] == 'true'; |
| +bool verboseCompiler = window.localStorage['verboseCompiler'] == 'true'; |
| +bool minified = window.localStorage['minified'] == 'true'; |
| +bool onlyAnalyze = window.localStorage['onlyAnalyze'] == 'true'; |
| +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.
|
| +String currentSample = window.localStorage['currentSample']; |
| +Theme currentTheme = Theme.named(window.localStorage['theme']); |
| +bool applyingSettings = false; |
| + |
| +const String INDENT = '\u{a0}\u{a0}'; |
| + |
| +onKeyUp(KeyboardEvent e) { |
| + if (e.keyCode == 13) { |
| + e.preventDefault(); |
| + DomSelection selection = window.getSelection(); |
| + if (selection.isCollapsed && selection.anchorNode is Text) { |
| + Text text = selection.anchorNode; |
| + int offset = selection.anchorOffset; |
| + text.insertData(offset, '\n'); |
| + selection.collapse(text, offset + 1); |
| + } |
| + } |
| + // This is a hack to get Safari to send mutation events on contenteditable. |
| + var newDiv = new DivElement(); |
| + hackDiv.replaceWith(newDiv); |
| + hackDiv = newDiv; |
| +} |
| + |
| +bool isMalformedInput = false; |
| +String currentSource = ""; |
| + |
| +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.
|
| + scheduleCompilation(); |
| + |
| + for (Element element in inputPre.queryAll('a[class="diagnostic"]>span')) { |
| + element.remove(); |
| + } |
| + // Discard clean-up mutations. |
| + observer.takeRecords(); |
| + |
| + DomSelection selection = window.getSelection(); |
| + |
| + while (!mutations.isEmpty) { |
| + for (MutationRecord record in mutations) { |
| + String type = record.type; |
| + switch (type) { |
| + |
| + 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
|
| + |
| + bool hasSelection = false; |
| + int offset = selection.anchorOffset; |
| + if (selection.isCollapsed && selection.anchorNode == record.target) { |
| + hasSelection = true; |
| + } |
| + var parent = record.target.parentNode; |
| + if (parent != inputPre) { |
| + inlineChildren(parent); |
| + } |
| + if (hasSelection) { |
| + selection.collapse(record.target, offset); |
| + } |
| + break; |
| + |
| + default: |
| + if (!record.addedNodes.isEmpty) { |
| + for (var node in record.addedNodes) { |
| + |
| + if (node.nodeType != Node.ELEMENT_NODE) continue; |
| + |
| + if (node is BRElement) { |
| + if (selection.anchorNode != node) { |
| + node.replaceWith(new Text('\n')); |
| + } |
| + } else { |
| + var parent = node.parentNode; |
| + if (parent == null) continue; |
| + var nodes = new List.from(node.nodes); |
| + var style = node.getComputedStyle(); |
| + if (style.display != 'inline') { |
| + var previous = node.previousNode; |
| + if (previous is Text) { |
| + previous.appendData('\n'); |
| + } else { |
| + parent.insertBefore(new Text('\n'), node); |
| + } |
| + } |
| + for (Node child in nodes) { |
| + child.remove(); |
| + parent.insertBefore(child, node); |
| + } |
| + node.remove(); |
| + } |
| + } |
| + } |
| + } |
| + } |
| + mutations = observer.takeRecords(); |
| + } |
| + |
| + if (!inputPre.nodes.isEmpty && inputPre.nodes.last is Text) { |
| + Text text = inputPre.nodes.last; |
| + if (!text.text.endsWith('\n')) { |
| + text.appendData('\n'); |
| + } |
| + } |
| + |
| + int offset = 0; |
| + int anchorOffset = 0; |
| + bool hasSelection = false; |
| + Node anchorNode = selection.anchorNode; |
| + void walk4(Node node) { |
| + // TODO(ahe): Use TreeWalker when that is exposed. |
| + // function textNodesUnder(root){ |
| + // var n, a=[], walk=document.createTreeWalker(root,NodeFilter.SHOW_TEXT,null,false); |
|
kasperl
2014/01/07 07:18:43
Long line.
ahe
2014/01/07 14:06:23
Done.
|
| + // while(n=walk.nextNode()) a.push(n); |
| + // return a; |
| + // } |
| + int type = node.nodeType; |
| + if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { |
| + if (anchorNode == node) { |
| + hasSelection = true; |
| + anchorOffset = selection.anchorOffset + offset; |
| + return; |
| + } |
| + offset += node.length; |
| + } |
| + |
| + var child = node.$dom_firstChild; |
| + while(child != null) { |
|
kasperl
2014/01/07 07:18:43
DANGER. DANGER. You know the drill!
ahe
2014/01/07 14:06:23
Done.
|
| + walk4(child); |
| + if (hasSelection) return; |
| + child = child.nextNode; |
| + } |
| + } |
| + if (selection.isCollapsed) { |
| + walk4(inputPre); |
| + } |
| + |
| + currentSource = inputPre.text; |
| + inputPre.nodes.clear(); |
| + inputPre.appendText(currentSource); |
| + if (hasSelection) { |
| + selection.collapse(inputPre.$dom_firstChild, anchorOffset); |
| + } |
| + |
| + isMalformedInput = false; |
| + for (Node node in new List.from(inputPre.nodes)) { |
| + if (node is! Text) continue; |
| + String text = node.text; |
| + |
| + var token = new StringScanner(text, includeComments: true).tokenize(); |
| + int offset = 0; |
| + for (;token.kind != EOF_TOKEN; token = token.next) { |
| + Decoration decoration = getDecoration(token); |
| + if (decoration == null) continue; |
| + bool hasSelection = false; |
| + int selectionOffset = selection.anchorOffset; |
| + |
| + if (selection.isCollapsed && selection.anchorNode == node) { |
| + hasSelection = true; |
| + selectionOffset = selection.anchorOffset; |
| + } |
| + int splitPoint = token.charOffset - offset; |
| + Text str = node.splitText(splitPoint); |
| + Text after = str.splitText(token.slowCharCount); |
| + offset += splitPoint + token.slowCharCount; |
| + inputPre.insertBefore(after, node.nextNode); |
| + inputPre.insertBefore(decoration.applyTo(str), after); |
| + |
| + if (hasSelection && selectionOffset > node.length) { |
| + selectionOffset -= node.length; |
| + if (selectionOffset > str.length) { |
| + selectionOffset -= str.length; |
| + selection.collapse(after, selectionOffset); |
| + } else { |
| + selection.collapse(str, selectionOffset); |
| + } |
| + } |
| + node = after; |
| + } |
| + } |
| + |
| + window.localStorage['currentSource'] = currentSource; |
| + |
| + // Discard highlighting mutations. |
| + observer.takeRecords(); |
| +} |
| + |
| +addDiagnostic(String kind, String message, int begin, int end) { |
| + observer.disconnect(); |
| + DomSelection selection = window.getSelection(); |
| + int offset = 0; |
| + int anchorOffset = 0; |
| + bool hasSelection = false; |
| + Node anchorNode = selection.anchorNode; |
| + bool foundNode = false; |
| + 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.
|
| + // TODO(ahe): Use TreeWalker when that is exposed. |
| + int type = node.nodeType; |
| + if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { |
| + // print('walking: ${node.data}'); |
| + if (anchorNode == node) { |
| + hasSelection = true; |
| + anchorOffset = selection.anchorOffset + offset; |
| + } |
| + int newOffset = offset + node.length; |
| + if (offset <= begin && begin < newOffset) { |
| + hasSelection = node == anchorNode; |
| + anchorOffset = selection.anchorOffset; |
| + Node marker = new Text(""); |
| + node.replaceWith(marker); |
| + // TODO(ahe): Don't highlight everything in the node. Find |
| + // the relevant token. |
| + if (kind == 'error') { |
| + marker.replaceWith(diagnostic(node, error(message))); |
| + } else if (kind == 'warning') { |
| + marker.replaceWith(diagnostic(node, warning(message))); |
| + } else { |
| + marker.replaceWith(diagnostic(node, info(message))); |
| + } |
| + if (hasSelection) { |
| + selection.collapse(node, anchorOffset); |
| + } |
| + foundNode = true; |
| + return; |
| + } |
| + offset = newOffset; |
| + } else if (type == Node.ELEMENT_NODE) { |
| + if (node.classes.contains('alert')) return; |
| + } |
| + |
| + var child = node.$dom_firstChild; |
| + while(child != null && !foundNode) { |
| + walk4(child); |
| + child = child.nextNode; |
| + } |
| + } |
| + walk4(inputPre); |
| + |
| + if (!foundNode) { |
| + outputDiv.appendText('$message\n'); |
| + } |
| + |
| + observer.takeRecords(); |
| + 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.
|
| +} |
| + |
| +void inlineChildren(Element element) { |
| + if (element == null) return; |
| + var parent = element.parentNode; |
| + if (parent == null) return; |
| + for (Node child in new List.from(element.nodes)) { |
| + child.remove(); |
| + parent.insertBefore(child, element); |
| + } |
| + element.remove(); |
| +} |
| + |
| +int count = 0; |
| + |
| +void scheduleCompilation() { |
| + if (applyingSettings) return; |
| + if (compilerTimer != null) { |
| + compilerTimer.cancel(); |
| + compilerTimer = null; |
| + } |
| + compilerTimer = |
| + new Timer(const Duration(milliseconds: 500), startCompilation); |
| +} |
| + |
| +void startCompilation() { |
| + if (compilerTimer != null) { |
| + compilerTimer.cancel(); |
| + compilerTimer = null; |
| + } |
| + |
| + new CompilationProcess(currentSource, outputDiv).start(); |
| +} |
| + |
| +class CompilationProcess { |
| + final String source; |
| + final Element console; |
| + final ReceivePort receivePort = new ReceivePort(); |
| + bool isCleared = false; |
| + bool isDone = false; |
| + bool usesDartHtml = false; |
| + Worker worker; |
| + List<String> objectUrls = <String>[]; |
| + |
| + static CompilationProcess current; |
| + |
| + CompilationProcess(this.source, this.console); |
| + |
| + static bool shouldStartCompilation() { |
| + if (compilerPort == null) return false; |
| + if (isMalformedInput) return false; |
| + if (current != null) return current.isDone; |
| + return true; |
| + } |
| + |
| + void clear() { |
| + if (verboseCompiler) return; |
| + if (!isCleared) console.nodes.clear(); |
| + isCleared = true; |
| + } |
| + |
| + void start() { |
| + if (!shouldStartCompilation()) { |
| + receivePort.close(); |
| + if (!isMalformedInput) scheduleCompilation(); |
| + return; |
| + } |
| + if (current != null) current.dispose(); |
| + current = this; |
| + console.nodes.clear(); |
| + var options = []; |
| + if (verboseCompiler) options.add('--verbose'); |
| + if (minified) options.add('--minify'); |
| + if (onlyAnalyze) options.add('--analyze-only'); |
| + compilerPort.send(['options', options], receivePort.toSendPort()); |
| + console.appendHtml('<i class="icon-spinner icon-spin"></i>'); |
| + console.appendText(' Compiling Dart program...\n'); |
| + outputFrame.style.display = 'none'; |
| + receivePort.receive(onMessage); |
| + compilerPort.send(source, receivePort.toSendPort()); |
| + } |
| + |
| + void dispose() { |
| + if (worker != null) worker.terminate(); |
| + objectUrls.forEach(Url.revokeObjectUrl); |
| + } |
| + |
| + onMessage(message, _) { |
| + String kind = message is String ? message : message[0]; |
| + var data = (message is List && message.length == 2) ? message[1] : null; |
| + switch (kind) { |
| + 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.
|
| + case 'url': return onUrl(data); |
| + case 'code': return onCode(data); |
| + case 'diagnostic': return onDiagnostic(data); |
| + case 'crash': return onCrash(data); |
| + case 'failed': return onFail(data); |
| + case 'dart:html': return onDartHtml(data); |
| + default: |
| + throw ['Unknown message kind', message]; |
| + } |
| + } |
| + |
| + onDartHtml(_) { |
| + usesDartHtml = true; |
| + } |
| + |
| + onFail(_) { |
| + clear(); |
| + consolePrint('Compilation failed'); |
| + } |
| + |
| + onDone(_) { |
| + isDone = true; |
| + receivePort.close(); |
| + } |
| + |
| + // This is called in browsers that support creating Object URLs in a |
| + // web worker. For example, Chrome and Firefox 21. |
| + onUrl(String url) { |
| + objectUrls.add(url); |
| + clear(); |
| + String wrapper = |
| + 'function dartPrint(msg) { self.postMessage(msg); };' |
| + 'self.importScripts("$url");'; |
| + var wrapperUrl = |
| + Url.createObjectUrl(new Blob([wrapper], 'application/javascript')); |
| + objectUrls.add(wrapperUrl); |
| + void retryInIframe(_) { |
| + var frame = makeOutputFrame(url); |
| + outputFrame.replaceWith(frame); |
| + outputFrame = frame; |
| + } |
| + void onError(String errorMessage) { |
| + console.appendText(errorMessage); |
| + console.appendText(' '); |
| + console.append(buildButton('Try in iframe', retryInIframe)); |
| + console.appendText('\n'); |
| + } |
| + if (usesDartHtml && !alwaysRunInWorker) { |
| + retryInIframe(null); |
| + } else { |
| + runInWorker(wrapperUrl, onError); |
| + } |
| + } |
| + |
| + // This is called in browsers that do not support creating Object |
| + // URLs in a web worker. For example, Safari and Firefox < 21. |
| + onCode(String code) { |
| + clear(); |
| + |
| + void retryInIframe(_) { |
| + // The obvious thing would be to call [makeOutputFrame], but |
| + // Safari doesn't support access to Object URLs in an iframe. |
| + |
| + var frame = new IFrameElement() |
| + ..src = 'iframe.html' |
| + ..style.width = '100%' |
| + ..style.height = '0px' |
| + ..seamless = false; |
| + frame.onLoad.listen((_) { |
| + frame.contentWindow.postMessage(['source', code], '*'); |
| + }); |
| + outputFrame.replaceWith(frame); |
| + outputFrame = frame; |
| + } |
| + |
| + void onError(String errorMessage) { |
| + console.appendText(errorMessage); |
| + console.appendText(' '); |
| + console.append(buildButton('Try in iframe', retryInIframe)); |
| + console.appendText('\n'); |
| + } |
| + |
| + String codeWithPrint = |
| + '$code\n' |
| + 'function dartPrint(msg) { postMessage(msg); }\n'; |
| + var url = |
| + Url.createObjectUrl( |
| + new Blob([codeWithPrint], 'application/javascript')); |
| + objectUrls.add(url); |
| + |
| + if (usesDartHtml && !alwaysRunInWorker) { |
| + retryInIframe(null); |
| + } else { |
| + runInWorker(url, onError); |
| + } |
| + } |
| + |
| + void runInWorker(String url, void onError(String errorMessage)) { |
| + worker = new Worker(url) |
| + ..onMessage.listen((MessageEvent event) { |
| + consolePrint(event.data); |
| + }) |
| + ..onError.listen((ErrorEvent event) { |
| + worker.terminate(); |
| + worker = null; |
| + onError(event.message); |
| + }); |
| + } |
| + |
| + onDiagnostic(Map<String, dynamic> diagnostic) { |
| + String kind = diagnostic['kind']; |
| + String message = diagnostic['message']; |
| + if (kind == 'verbose info') { |
| + if (verboseCompiler) { |
| + consolePrint(message); |
| + } |
| + return; |
| + } |
| + String uri = diagnostic['uri']; |
| + if (uri == null) { |
| + clear(); |
| + consolePrint(message); |
| + return; |
| + } |
| + if (uri != 'memory:/main.dart') return; |
| + if (currentSource != source) return; |
| + int begin = diagnostic['begin']; |
| + int end = diagnostic['end']; |
| + if (begin == null) return; |
| + addDiagnostic(kind, message, begin, end); |
| + } |
| + |
| + onCrash(data) { |
| + consolePrint(data); |
| + } |
| + |
| + void consolePrint(message) { |
| + console.appendText('$message\n'); |
| + } |
| +} |
| + |
| +Decoration getDecoration(scanner.Token token) { |
| + String tokenValue = token.slowToString(); |
| + String tokenInfo = token.info.value.slowToString(); |
| + if (tokenInfo == 'string') return currentTheme.string; |
| + // if (tokenInfo == 'identifier') return identifier; |
| + if (tokenInfo == 'keyword') return currentTheme.keyword; |
| + if (tokenInfo == 'comment') return currentTheme.singleLineComment; |
| + if (tokenInfo == 'malformed input') { |
| + isMalformedInput = true; |
| + return new DiagnosticDecoration('error', tokenValue); |
| + } |
| + return null; |
| +} |
| + |
| +diagnostic(text, tip) { |
| + if (text is String) { |
| + text = new Text(text); |
| + } |
| + return new AnchorElement() |
| + ..classes.add('diagnostic') |
| + ..append(text) |
| + ..append(tip); |
| +} |
| + |
| +img(src, width, height, alt) { |
| + return new ImageElement(src: src, width: width, height: height)..alt = alt; |
| +} |
| + |
| +makeOutputFrame(String scriptUrl) { |
| + final String outputHtml = ''' |
| +<!DOCTYPE html> |
| +<html lang="en"> |
| +<head> |
| +<title>JavaScript output</title> |
| +<meta http-equiv="Content-type" content="text/html;charset=UTF-8"> |
| +</head> |
| +<body> |
| +<script type="application/javascript" src="$outputHelper"></script> |
| +<script type="application/javascript" src="$scriptUrl"></script> |
| +</body> |
| +</html> |
| +'''; |
| + |
| + return new IFrameElement() |
| + ..src = Url.createObjectUrl(new Blob([outputHtml], "text/html")) |
| + ..style.width = '100%' |
| + ..style.height = '0px' |
| + ..seamless = false; |
| +} |
| + |
| +const String HAS_NON_DOM_HTTP_REQUEST = 'spawnFunction supports HttpRequest'; |
| +const String NO_NON_DOM_HTTP_REQUEST = |
| + 'spawnFunction does not support HttpRequest'; |
| + |
| + |
| +checkHttpRequest() { |
| + port.receive((String uri, SendPort replyTo) { |
| + try { |
| + new HttpRequest(); |
| + replyTo.send(HAS_NON_DOM_HTTP_REQUEST); |
| + } catch (e, trace) { |
| + replyTo.send(NO_NON_DOM_HTTP_REQUEST); |
| + } |
| + port.close(); |
| + }); |
| +} |
| + |
| +main() { |
| + if (window.localStorage['currentSource'] == null) { |
| + window.localStorage['currentSource'] = EXAMPLE_HELLO; |
| + } |
| + |
| + buildUI(); |
| + spawnFunction(checkHttpRequest).call('').then((reply) { |
| + var compilerFuture; |
| + if (reply == HAS_NON_DOM_HTTP_REQUEST) { |
| + compilerFuture = spawnFunction(compilerIsolate); |
| + } else { |
| + compilerFuture = spawnDomFunction(compilerIsolate); |
| + } |
| + if (compilerFuture is! Future) { |
| + compilerFuture = new Future.value(compilerFuture); |
| + } |
| + compilerFuture.then((port) { |
| + String sdk = query('link[rel="dart-sdk"]').href; |
| + print('Using Dart SDK: $sdk'); |
| + port.call(sdk).then((_) { |
| + compilerPort = port; |
| + onMutation([], observer); |
| + }); |
| + }); |
| + }); |
| +} |
| + |
| +buildButton(message, action) { |
| + if (message is String) { |
| + message = new Text(message); |
| + } |
| + return new ButtonElement() |
| + ..onClick.listen(action) |
| + ..append(message); |
| +} |
| + |
| +buildTab(message, id, action) { |
| + if (message is String) { |
| + message = new Text(message); |
| + } |
| + |
| + onClick(MouseEvent event) { |
| + event.preventDefault(); |
| + Element e = event.target; |
| + LIElement parent = e.parent; |
| + parent.parent.query('li[class="active"]').classes.remove('active'); |
| + parent.classes.add('active'); |
| + action(event); |
| + } |
| + |
| + inspirationCallbacks[id] = action; |
| + |
| + return new OptionElement()..append(message)..id = id; |
| +} |
| + |
| +Map<String, Function> inspirationCallbacks = new Map<String, Function>(); |
| + |
| +void onInspirationChange(Event event) { |
| + SelectElement select = event.target; |
| + String id = select.queryAll('option')[select.selectedIndex].id; |
| + Function action = inspirationCallbacks[id]; |
| + if (action != null) action(event); |
| + outputFrame.style.display = 'none'; |
| +} |
| + |
| +buildUI() { |
| + window.localStorage['currentSample'] = '$currentSample'; |
| + |
| + var inspirationTabs = document.getElementById('inspiration'); |
| + var htmlGroup = new OptGroupElement()..label = 'HTML'; |
| + var benchmarkGroup = new OptGroupElement()..label = 'Benchmarks'; |
| + inspirationTabs.append(new OptionElement()..appendText('Pick an example')); |
| + inspirationTabs.onChange.listen(onInspirationChange); |
| + // inspirationTabs.classes.addAll(['nav', 'nav-tabs']); |
| + inspirationTabs.append(buildTab('Hello, World!', 'EXAMPLE_HELLO', (_) { |
| + inputPre |
| + ..nodes.clear() |
| + ..appendText(EXAMPLE_HELLO); |
| + })); |
| + inspirationTabs.append(buildTab('Fibonacci', 'EXAMPLE_FIBONACCI', (_) { |
| + inputPre |
| + ..nodes.clear() |
| + ..appendText(EXAMPLE_FIBONACCI); |
| + })); |
| + inspirationTabs.append(htmlGroup); |
| + inspirationTabs.append(benchmarkGroup); |
| + |
| + htmlGroup.append( |
| + buildTab('Hello, World!', 'EXAMPLE_HELLO_HTML', (_) { |
| + inputPre |
| + ..nodes.clear() |
| + ..appendText(EXAMPLE_HELLO_HTML); |
| + })); |
| + htmlGroup.append( |
| + buildTab('Fibonacci', 'EXAMPLE_FIBONACCI_HTML', (_) { |
| + inputPre |
| + ..nodes.clear() |
| + ..appendText(EXAMPLE_FIBONACCI_HTML); |
| + })); |
| + htmlGroup.append(buildTab('Sunflower', 'EXAMPLE_SUNFLOWER', (_) { |
| + inputPre |
| + ..nodes.clear() |
| + ..appendText(EXAMPLE_SUNFLOWER); |
| + })); |
| + |
| + benchmarkGroup.append(buildTab('DeltaBlue', 'BENCHMARK_DELTA_BLUE', (_) { |
| + inputPre.contentEditable = 'false'; |
| + String deltaBlueUri = query('link[rel="benchmark-DeltaBlue"]').href; |
| + String benchmarkBaseUri = query('link[rel="benchmark-base"]').href; |
| + HttpRequest.getString(benchmarkBaseUri).then((String benchmarkBase) { |
| + HttpRequest.getString(deltaBlueUri).then((String deltaBlue) { |
| + benchmarkBase = benchmarkBase.replaceFirst( |
| + 'part of benchmark_harness;', '// part of benchmark_harness;'); |
| + deltaBlue = deltaBlue.replaceFirst( |
| + "import 'package:benchmark_harness/benchmark_harness.dart';", |
| + benchmarkBase); |
| + inputPre |
| + ..nodes.clear() |
| + ..appendText(deltaBlue) |
| + ..contentEditable = 'true'; |
| + }); |
| + }); |
| + })); |
| + |
| + benchmarkGroup.append(buildTab('Richards', 'BENCHMARK_RICHARDS', (_) { |
| + inputPre.contentEditable = 'false'; |
| + String richardsUri = query('link[rel="benchmark-Richards"]').href; |
| + String benchmarkBaseUri = query('link[rel="benchmark-base"]').href; |
| + HttpRequest.getString(benchmarkBaseUri).then((String benchmarkBase) { |
| + HttpRequest.getString(richardsUri).then((String richards) { |
| + benchmarkBase = benchmarkBase.replaceFirst( |
| + 'part of benchmark_harness;', '// part of benchmark_harness;'); |
| + richards = richards.replaceFirst( |
| + "import 'package:benchmark_harness/benchmark_harness.dart';", |
| + benchmarkBase); |
| + inputPre |
| + ..nodes.clear() |
| + ..appendText(richards) |
| + ..contentEditable = 'true'; |
| + }); |
| + }); |
| + })); |
| + |
| + // TODO(ahe): Update currentSample. Or try switching to a drop-down menu. |
| + var active = inspirationTabs.query('[id="$currentSample"]'); |
| + if (active == null) { |
| + // inspirationTabs.query('li').classes.add('active'); |
| + } |
| + |
| + (inputPre = new DivElement()) |
| + ..classes.add('well') |
| + ..style.backgroundColor = currentTheme.background.color |
| + ..style.color = currentTheme.foreground.color |
| + ..style.overflow = 'auto' |
| + ..style.whiteSpace = 'pre' |
| + ..style.font = codeFont |
| + ..spellcheck = false; |
| + |
| + inputPre.contentEditable = 'true'; |
| + inputPre.onKeyDown.listen(onKeyUp); |
| + |
| + var inputWrapper = new DivElement() |
| + ..append(inputPre) |
| + ..style.position = 'relative'; |
| + |
| + var inputHeader = new DivElement()..appendText('Code'); |
| + |
| + inputHeader.style |
| + ..right = '3px' |
| + ..top = '0px' |
| + ..position = 'absolute'; |
| + inputWrapper.append(inputHeader); |
| + |
| + outputFrame = |
| + makeOutputFrame( |
| + Url.createObjectUrl(new Blob([''], 'application/javascript'))); |
| + |
| + outputDiv = new PreElement(); |
| + outputDiv.style |
| + ..backgroundColor = currentTheme.background.color |
| + ..color = currentTheme.foreground.color |
| + ..overflow = 'auto' |
| + ..padding = '1em' |
| + ..minHeight = '10em' |
| + ..whiteSpace = 'pre-wrap'; |
| + |
| + var outputWrapper = new DivElement() |
| + ..append(outputDiv) |
| + ..style.position = 'relative'; |
| + |
| + var consoleHeader = new DivElement()..appendText('Console'); |
| + |
| + consoleHeader.style |
| + ..right = '3px' |
| + ..top = '0px' |
| + ..position = 'absolute'; |
| + outputWrapper.append(consoleHeader); |
| + |
| + hackDiv = new DivElement(); |
| + |
| + var saveButton = new ButtonElement() |
| + ..onClick.listen((_) { |
| + var blobUrl = |
| + Url.createObjectUrl(new Blob([inputPre.text], 'text/plain')); |
| + var save = new AnchorElement(href: blobUrl); |
| + save.target = '_blank'; |
| + save.download = 'untitled.dart'; |
| + save.dispatchEvent(new Event.eventType('Event', 'click')); |
| + }) |
| + ..style.position = 'absolute' |
| + ..style.right = '0px' |
| + ..appendText('Save'); |
| + |
| + cacheStatusElement = document.getElementById('appcache-status'); |
| + updateCacheStatus(); |
| + |
| + // TODO(ahe): Switch to two column layout so the console is on the right. |
| + var section = document.query('article[class="homepage"]>section'); |
| + |
| + DivElement tryColumn = document.getElementById('try-dart-column'); |
| + DivElement runColumn = document.getElementById('run-dart-column'); |
| + |
| + tryColumn.append(inputWrapper); |
| + outputFrame.style.display = 'none'; |
| + runColumn.append(outputFrame); |
| + runColumn.append(outputWrapper); |
| + runColumn.append(hackDiv); |
| + |
| + var settingsElement = document.getElementById('settings'); |
| + settingsElement.onClick.listen(openSettings); |
| + |
| + window.onMessage.listen((MessageEvent event) { |
| + if (event.data is List) { |
| + List message = event.data; |
| + if (message.length > 0) { |
| + switch (message[0]) { |
| + case 'error': |
| + Map diagnostics = message[1]; |
| + String url = diagnostics['url']; |
| + outputDiv.appendText('${diagnostics["message"]}\n'); |
| + return; |
| + case 'scrollHeight': |
| + int scrollHeight = message[1]; |
| + if (scrollHeight > 0) { |
| + outputFrame.style.height = '${scrollHeight}px'; |
| + } |
| + return; |
| + } |
| + } |
| + } |
| + outputDiv.appendText('${event.data}\n'); |
| + }); |
| + |
| + observer = new MutationObserver(onMutation) |
| + ..observe(inputPre, childList: true, characterData: true, subtree: true); |
| + |
| + window.setImmediate(() { |
| + inputPre.appendText(window.localStorage['currentSource']); |
| + }); |
| + |
| + // You cannot install event handlers on window.applicationCache |
| + // until the window has loaded. In dartium, that's later than this |
| + // method is called. |
| + window.onLoad.listen(onLoad); |
| + |
| + // However, in dart2js, the window has already loaded, and onLoad is |
| + // never called. |
| + onLoad(null); |
| +} |
| + |
| +void openSettings(MouseEvent event) { |
| + event.preventDefault(); |
| + |
| + var backdrop = new DivElement()..classes.add('modal-backdrop'); |
| + document.body.append(backdrop); |
| + |
| + void updateCodeFont(Event e) { |
| + codeFont = e.target.value; |
| + inputPre.style.font = codeFont; |
| + backdrop.style.opacity = '0.0'; |
| + } |
| + |
| + void updateTheme(Event e) { |
| + var select = e.target; |
| + String theme = select.queryAll('option')[select.selectedIndex].text; |
| + window.localStorage['theme'] = theme; |
| + currentTheme = Theme.named(theme); |
| + |
| + inputPre.style |
| + ..backgroundColor = currentTheme.background.color |
| + ..color = currentTheme.foreground.color; |
| + |
| + outputDiv.style |
| + ..backgroundColor = currentTheme.background.color |
| + ..color = currentTheme.foreground.color; |
| + |
| + backdrop.style.opacity = '0.0'; |
| + |
| + applyingSettings = true; |
| + onMutation([], observer); |
| + applyingSettings = false; |
| + } |
| + |
| + |
| + var body = document.getElementById('settings-body'); |
| + |
| + body.nodes.clear(); |
| + |
| + var form = new FormElement(); |
| + var fieldSet = new FieldSetElement(); |
| + body.append(form); |
| + form.append(fieldSet); |
| + |
| + buildCheckBox(String text, bool defaultValue, void action(Event e)) { |
| + var checkBox = new CheckboxInputElement() |
| + ..defaultChecked = defaultValue |
| + ..onChange.listen(action); |
| + return new LabelElement() |
| + ..classes.add('checkbox') |
| + ..append(checkBox) |
| + ..appendText(' $text'); |
| + } |
| + |
| + 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.
|
| + buildCheckBox( |
| + 'Always run in Worker thread.', alwaysRunInWorker, |
| + (Event e) { alwaysRunInWorker = e.target.checked; })); |
| + |
| + fieldSet.append( |
| + buildCheckBox( |
| + 'Verbose compiler output.', verboseCompiler, |
| + (Event e) { verboseCompiler = e.target.checked; })); |
| + |
| + fieldSet.append( |
| + buildCheckBox( |
| + 'Generate compact (minified) JavaScript.', minified, |
| + (Event e) { minified = e.target.checked; })); |
| + |
| + fieldSet.append( |
| + buildCheckBox( |
| + 'Only analyze program.', onlyAnalyze, |
| + (Event e) { onlyAnalyze = e.target.checked; })); |
| + |
| + fieldSet.append(new LabelElement()..appendText('Code font:')); |
| + var textInput = new TextInputElement(); |
| + textInput.classes.add('input-block-level'); |
| + if (codeFont != null && codeFont != '') { |
| + textInput.value = codeFont; |
| + } |
| + textInput.placeholder = 'Enter a size and font, for example, 11pt monospace'; |
| + textInput.onChange.listen(updateCodeFont); |
| + fieldSet.append(textInput); |
| + |
| + fieldSet.append(new LabelElement()..appendText('Theme:')); |
| + var themeSelector = new SelectElement(); |
| + themeSelector.classes.add('input-block-level'); |
| + for (Theme theme in THEMES) { |
| + OptionElement option = new OptionElement()..appendText(theme.name); |
| + if (theme == currentTheme) option.selected = true; |
| + themeSelector.append(option); |
| + } |
| + themeSelector.onChange.listen(updateTheme); |
| + fieldSet.append(themeSelector); |
| + |
| + var dialog = document.getElementById('settings-dialog'); |
| + |
| + dialog.style.display = 'block'; |
| + dialog.classes.add('in'); |
| + |
| + onSubmit(Event event) { |
| + event.preventDefault(); |
| + |
| + window.localStorage['alwaysRunInWorker'] = '$alwaysRunInWorker'; |
| + window.localStorage['verboseCompiler'] = '$verboseCompiler'; |
| + window.localStorage['minified'] = '$minified'; |
| + window.localStorage['onlyAnalyze'] = '$onlyAnalyze'; |
| + window.localStorage['codeFont'] = '$codeFont'; |
| + |
| + dialog.style.display = 'none'; |
| + dialog.classes.remove('in'); |
| + backdrop.remove(); |
| + } |
| + form.onSubmit.listen(onSubmit); |
| + |
| + var doneButton = document.getElementById('settings-done'); |
| + doneButton.onClick.listen(onSubmit); |
| +} |
| + |
| +/// Called when the window has finished loading. |
| +void onLoad(Event event) { |
| + window.applicationCache.onUpdateReady.listen((_) => updateCacheStatus()); |
| + window.applicationCache.onCached.listen((_) => updateCacheStatus()); |
| + window.applicationCache.onChecking.listen((_) => updateCacheStatus()); |
| + window.applicationCache.onDownloading.listen((_) => updateCacheStatus()); |
| + window.applicationCache.onError.listen((_) => updateCacheStatus()); |
| + window.applicationCache.onNoUpdate.listen((_) => updateCacheStatus()); |
| + window.applicationCache.onObsolete.listen((_) => updateCacheStatus()); |
| + window.applicationCache.onProgress.listen(onCacheProgress); |
| +} |
| + |
| +onCacheProgress(ProgressEvent event) { |
| + if (!event.lengthComputable) { |
| + updateCacheStatus(); |
| + return; |
| + } |
| + cacheStatusElement.nodes.clear(); |
| + cacheStatusElement.appendText('Downloading SDK '); |
| + var progress = '${event.loaded} of ${event.total}'; |
| + if (MeterElement.supported) { |
| + cacheStatusElement.append( |
| + new MeterElement() |
| + ..appendText(progress) |
| + ..min = 0 |
| + ..max = event.total |
| + ..value = event.loaded); |
| + } else { |
| + cacheStatusElement.appendText(progress); |
| + } |
| +} |
| + |
| +String cacheStatus() { |
| + if (!ApplicationCache.supported) return 'offline not supported'; |
| + int status = window.applicationCache.status; |
| + if (status == ApplicationCache.CHECKING) return 'Checking for updates'; |
| + if (status == ApplicationCache.DOWNLOADING) return 'Downloading SDK'; |
| + if (status == ApplicationCache.IDLE) return 'Try Dart! works offline'; |
| + if (status == ApplicationCache.OBSOLETE) return 'OBSOLETE'; |
| + if (status == ApplicationCache.UNCACHED) return 'offline not available'; |
| + if (status == ApplicationCache.UPDATEREADY) return 'SDK downloaded'; |
| + return '?'; |
| +} |
| + |
| +void updateCacheStatus() { |
| + cacheStatusElement.nodes.clear(); |
| + String status = window.applicationCache.status; |
| + if (status == ApplicationCache.UPDATEREADY) { |
| + cacheStatusElement.appendText('New version of Try Dart! ready: '); |
| + cacheStatusElement.append( |
| + new AnchorElement(href: '#') |
| + ..appendText('Load') |
| + ..onClick.listen((event) { |
| + event.preventDefault(); |
| + window.applicationCache.swapCache(); |
| + window.location.reload(); |
| + })); |
| + } else if (status == ApplicationCache.IDLE) { |
| + cacheStatusElement.appendText(cacheStatus()); |
| + cacheStatusElement.classes.add('offlineyay'); |
| + new Timer(const Duration(seconds: 10), () { |
| + cacheStatusElement.style.display = 'none'; |
| + }); |
| + } else { |
| + cacheStatusElement.appendText(cacheStatus()); |
| + } |
| +} |
| + |
| +void compilerIsolate() { |
| + lazy.load().then((_) => port.receive(compile)); |
| +} |
| + |
| +final String outputHelper = |
| + Url.createObjectUrl(new Blob([OUTPUT_HELPER], 'application/javascript')); |
| + |
| +const String EXAMPLE_HELLO = r''' |
| +// Go ahead and modify this example. |
| + |
| +var greeting = "Hello, World!"; |
| + |
| +// Prints a greeting. |
| +void main() { |
| + // The [print] function displays a message in the "Console" box. |
| + // Try modifying the greeting above and watch the "Console" box change. |
| + print(greeting); |
| +} |
| +'''; |
| + |
| +const String EXAMPLE_HELLO_HTML = r''' |
| +// Go ahead and modify this example. |
| + |
| +import "dart:html"; |
| + |
| +var greeting = "Hello, World!"; |
| + |
| +// Displays a greeting. |
| +void main() { |
| + // This example uses HTML to display the greeting and it will appear |
| + // in a nested HTML frame (an iframe). |
| + document.body.append(new HeadingElement.h1()..appendText(greeting)); |
| +} |
| +'''; |
| + |
| +const String EXAMPLE_FIBONACCI = r''' |
| +// Go ahead and modify this example. |
| + |
| +// Computes the nth Fibonacci number. |
| +int fibonacci(int n) { |
| + if (n < 2) return n; |
| + return fibonacci(n - 1) + fibonacci(n - 2); |
| +} |
| + |
| +// Prints a Fibonacci number. |
| +void main() { |
| + int i = 20; |
| + String message = "fibonacci($i) = ${fibonacci(i)}"; |
| + // Print the result in the "Console" box. |
| + print(message); |
| +} |
| +'''; |
| + |
| +const String EXAMPLE_FIBONACCI_HTML = r''' |
| +// Go ahead and modify this example. |
| + |
| +import "dart:html"; |
| + |
| +// Computes the nth Fibonacci number. |
| +int fibonacci(int n) { |
| + if (n < 2) return n; |
| + return fibonacci(n - 1) + fibonacci(n - 2); |
| +} |
| + |
| +// Displays a Fibonacci number. |
| +void main() { |
| + int i = 20; |
| + String message = "fibonacci($i) = ${fibonacci(i)}"; |
| + |
| + // This example uses HTML to display the result and it will appear |
| + // in a nested HTML frame (an iframe). |
| + document.body.append(new HeadingElement.h1()..appendText(message)); |
| +} |
| +'''; |
| + |
| +const String OUTPUT_HELPER = r''' |
| +function dartPrint(msg) { |
| + window.parent.postMessage(String(msg), "*"); |
| +} |
| + |
| +function dartMainRunner(main) { |
| + main(); |
| +} |
| + |
| +window.onerror = function (message, url, lineNumber) { |
| + window.parent.postMessage( |
| + ["error", {message: message, url: url, lineNumber: lineNumber}], "*"); |
| +}; |
| + |
| +(function () { |
| + |
| +function postScrollHeight() { |
| + window.parent.postMessage(["scrollHeight", document.documentElement.scrollHeight], "*"); |
| +} |
| + |
| +var observer = new (window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver)(function(mutations) { |
| + postScrollHeight() |
| + window.setTimeout(postScrollHeight, 500); |
| +}); |
| + |
| +observer.observe( |
| + document.body, |
| + { attributes: true, |
| + childList: true, |
| + characterData: true, |
| + subtree: true }); |
| +})(); |
| +'''; |
| + |
| +const String EXAMPLE_SUNFLOWER = ''' |
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +library sunflower; |
| + |
| +import "dart:html"; |
| +import "dart:math"; |
| + |
| +const String ORANGE = "orange"; |
| +const int SEED_RADIUS = 2; |
| +const int SCALE_FACTOR = 4; |
| +const num TAU = PI * 2; |
| +const int MAX_D = 300; |
| +const num centerX = MAX_D / 2; |
| +const num centerY = centerX; |
| + |
| +final InputElement slider = query("#slider"); |
| +final Element notes = query("#notes"); |
| +final num PHI = (sqrt(5) + 1) / 2; |
| +int seeds = 0; |
| +final CanvasRenderingContext2D context = |
| + (query("#canvas") as CanvasElement).context2D; |
| + |
| +void main() { |
| + document.head.append(new StyleElement()..appendText(STYLE)); |
| + document.body.innerHtml = BODY; |
| + slider.onChange.listen((e) => draw()); |
| + draw(); |
| +} |
| + |
| +/// Draw the complete figure for the current number of seeds. |
| +void draw() { |
| + seeds = int.parse(slider.value); |
| + context.clearRect(0, 0, MAX_D, MAX_D); |
| + for (var i = 0; i < seeds; i++) { |
| + final num theta = i * TAU / PHI; |
| + final num r = sqrt(i) * SCALE_FACTOR; |
| + drawSeed(centerX + r * cos(theta), centerY - r * sin(theta)); |
| + } |
| + notes.text = "\${seeds} seeds"; |
| +} |
| + |
| +/// Draw a small circle representing a seed centered at (x,y). |
| +void drawSeed(num x, num y) { |
| + context..beginPath() |
| + ..lineWidth = 2 |
| + ..fillStyle = ORANGE |
| + ..strokeStyle = ORANGE |
| + ..arc(x, y, SEED_RADIUS, 0, TAU, false) |
| + ..fill() |
| + ..closePath() |
| + ..stroke(); |
| +} |
| + |
| +const String MATH_PNG = |
| + "https://dart.googlecode.com/svn/trunk/dart/samples/sunflower/web/math.png"; |
| +const String BODY = """ |
| + <h1>drfibonacci\'s Sunflower Spectacular</h1> |
| + |
| + <p>A canvas 2D demo.</p> |
| + |
| + <div id="container"> |
| + <canvas id="canvas" width="300" height="300" class="center"></canvas> |
| + <form class="center"> |
| + <input id="slider" type="range" max="1000" value="500"/> |
| + </form> |
| + <br/> |
| + <img src="\$MATH_PNG" width="350px" height="42px" class="center"> |
| + </div> |
| + |
| + <footer> |
| + <p id="summary"> </p> |
| + <p id="notes"> </p> |
| + </footer> |
| +"""; |
| + |
| +const String STYLE = r""" |
| +body { |
| + background-color: #F8F8F8; |
| + font-family: 'Open Sans', sans-serif; |
| + font-size: 14px; |
| + font-weight: normal; |
| + line-height: 1.2em; |
| + margin: 15px; |
| +} |
| + |
| +p { |
| + color: #333; |
| +} |
| + |
| +#container { |
| + width: 100%; |
| + height: 400px; |
| + position: relative; |
| + border: 1px solid #ccc; |
| + background-color: #fff; |
| +} |
| + |
| +#summary { |
| + float: left; |
| +} |
| + |
| +#notes { |
| + float: right; |
| + width: 120px; |
| + text-align: right; |
| +} |
| + |
| +.error { |
| + font-style: italic; |
| + color: red; |
| +} |
| + |
| +img { |
| + border: 1px solid #ccc; |
| + margin: auto; |
| +} |
| + |
| +.center { |
| + display: block; |
| + margin: 0px auto; |
| + text-align: center; |
| +} |
| +"""; |
| + |
| +'''; |