| 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.ui; | |
| 6 | |
| 7 import 'dart:html'; | |
| 8 | |
| 9 import 'dart:async' show | |
| 10 Future, | |
| 11 Timer, | |
| 12 scheduleMicrotask; | |
| 13 | |
| 14 import 'cache.dart' show | |
| 15 onLoad, | |
| 16 updateCacheStatus; | |
| 17 | |
| 18 import 'interaction_manager.dart' show InteractionManager; | |
| 19 | |
| 20 import 'run.dart' show | |
| 21 makeOutputFrame; | |
| 22 | |
| 23 import 'themes.dart' show | |
| 24 THEMES, | |
| 25 Theme; | |
| 26 | |
| 27 import 'samples.dart' show | |
| 28 EXAMPLE_FIBONACCI, | |
| 29 EXAMPLE_FIBONACCI_HTML, | |
| 30 EXAMPLE_HELLO, | |
| 31 EXAMPLE_HELLO_HTML, | |
| 32 EXAMPLE_SUNFLOWER; | |
| 33 | |
| 34 import 'settings.dart'; | |
| 35 | |
| 36 import 'user_option.dart'; | |
| 37 | |
| 38 import 'messages.dart' show messages; | |
| 39 | |
| 40 import 'compilation_unit.dart' show | |
| 41 CompilationUnit; | |
| 42 | |
| 43 import 'compilation.dart' show | |
| 44 currentSource; | |
| 45 | |
| 46 // TODO(ahe): Make internal to buildUI once all interactions have been moved to | |
| 47 // the manager. | |
| 48 InteractionManager interaction; | |
| 49 | |
| 50 DivElement mainEditorPane; | |
| 51 DivElement statusDiv; | |
| 52 PreElement outputDiv; | |
| 53 DivElement hackDiv; | |
| 54 IFrameElement outputFrame; | |
| 55 MutationObserver observer; | |
| 56 SpanElement cacheStatusElement; | |
| 57 Theme currentTheme = Theme.named(theme); | |
| 58 | |
| 59 buildButton(message, action) { | |
| 60 if (message is String) { | |
| 61 message = new Text(message); | |
| 62 } | |
| 63 return new ButtonElement() | |
| 64 ..onClick.listen(action) | |
| 65 ..append(message); | |
| 66 } | |
| 67 | |
| 68 buildTab(message, id, action) { | |
| 69 if (message is String) { | |
| 70 message = new Text(message); | |
| 71 } | |
| 72 | |
| 73 onClick(MouseEvent event) { | |
| 74 event.preventDefault(); | |
| 75 Element e = event.target; | |
| 76 LIElement parent = e.parent; | |
| 77 parent.parent.querySelector('li[class="active"]').classes.remove('active'); | |
| 78 parent.classes.add('active'); | |
| 79 action(event); | |
| 80 } | |
| 81 | |
| 82 codeCallbacks[id] = action; | |
| 83 | |
| 84 return new OptionElement()..append(message)..id = id; | |
| 85 } | |
| 86 | |
| 87 Map<String, Function> codeCallbacks = new Map<String, Function>(); | |
| 88 | |
| 89 void onCodeChange(Event event) { | |
| 90 SelectElement select = event.target; | |
| 91 String id = select.querySelectorAll('option')[select.selectedIndex].id; | |
| 92 Function action = codeCallbacks[id]; | |
| 93 if (action != null) action(event); | |
| 94 outputFrame.style.display = 'none'; | |
| 95 outputDiv.nodes.clear(); | |
| 96 } | |
| 97 | |
| 98 buildUI() { | |
| 99 interaction = new InteractionManager(); | |
| 100 | |
| 101 CompilationUnit.onChanged.listen(interaction.onCompilationUnitChanged); | |
| 102 | |
| 103 window.localStorage['currentSample'] = '$currentSample'; | |
| 104 | |
| 105 buildCode(interaction); | |
| 106 | |
| 107 (mainEditorPane = new DivElement()) | |
| 108 ..classes.addAll(['mainEditorPane']) | |
| 109 ..style.backgroundColor = currentTheme.background.color | |
| 110 ..style.color = currentTheme.foreground.color | |
| 111 ..style.font = codeFont | |
| 112 ..spellcheck = false; | |
| 113 | |
| 114 mainEditorPane | |
| 115 ..contentEditable = 'true' | |
| 116 ..onKeyDown.listen(interaction.onKeyUp) | |
| 117 ..onInput.listen(interaction.onInput); | |
| 118 | |
| 119 document.onSelectionChange.listen(interaction.onSelectionChange); | |
| 120 | |
| 121 var inputWrapper = new DivElement() | |
| 122 ..append(mainEditorPane) | |
| 123 ..classes.add('well') | |
| 124 ..style.padding = '0px' | |
| 125 ..style.overflowX = 'hidden' | |
| 126 ..style.overflowY = 'scroll' | |
| 127 ..style.position = 'relative' | |
| 128 ..style.maxHeight = '80vh'; | |
| 129 | |
| 130 var inputHeader = new DivElement()..appendText('Code'); | |
| 131 | |
| 132 inputHeader.style | |
| 133 ..right = '3px' | |
| 134 ..top = '0px' | |
| 135 ..position = 'absolute'; | |
| 136 inputWrapper.append(inputHeader); | |
| 137 | |
| 138 statusDiv = new DivElement(); | |
| 139 statusDiv.style | |
| 140 ..left = '0px' | |
| 141 ..top = '0px' | |
| 142 ..position = 'absolute'; | |
| 143 inputWrapper.append(statusDiv); | |
| 144 | |
| 145 outputFrame = | |
| 146 makeOutputFrame( | |
| 147 Url.createObjectUrl(new Blob([''], 'application/javascript'))); | |
| 148 | |
| 149 outputDiv = new PreElement(); | |
| 150 outputDiv.style | |
| 151 ..backgroundColor = currentTheme.background.color | |
| 152 ..color = currentTheme.foreground.color | |
| 153 ..overflow = 'auto' | |
| 154 ..padding = '1em' | |
| 155 ..minHeight = '10em' | |
| 156 ..whiteSpace = 'pre-wrap'; | |
| 157 | |
| 158 var outputWrapper = new DivElement() | |
| 159 ..append(outputDiv) | |
| 160 ..style.position = 'relative'; | |
| 161 | |
| 162 var consoleHeader = new DivElement()..appendText('Console'); | |
| 163 | |
| 164 consoleHeader.style | |
| 165 ..right = '3px' | |
| 166 ..top = '0px' | |
| 167 ..position = 'absolute'; | |
| 168 outputWrapper.append(consoleHeader); | |
| 169 | |
| 170 hackDiv = new DivElement(); | |
| 171 | |
| 172 var saveButton = new ButtonElement() | |
| 173 ..onClick.listen((_) { | |
| 174 var blobUrl = | |
| 175 Url.createObjectUrl(new Blob([mainEditorPane.text], 'text/plain')); | |
| 176 var save = new AnchorElement(href: blobUrl); | |
| 177 save.target = '_blank'; | |
| 178 save.download = 'untitled.dart'; | |
| 179 save.dispatchEvent(new Event.eventType('Event', 'click')); | |
| 180 }) | |
| 181 ..style.position = 'absolute' | |
| 182 ..style.right = '0px' | |
| 183 ..appendText('Save'); | |
| 184 | |
| 185 cacheStatusElement = document.getElementById('appcache-status'); | |
| 186 updateCacheStatus(null); | |
| 187 | |
| 188 var section = document.querySelector('article[class="homepage"]>section'); | |
| 189 | |
| 190 DivElement tryColumn = document.getElementById('try-dart-column'); | |
| 191 DivElement runColumn = document.getElementById('run-dart-column'); | |
| 192 | |
| 193 tryColumn.append(inputWrapper); | |
| 194 outputFrame.style.display = 'none'; | |
| 195 runColumn.append(outputFrame); | |
| 196 runColumn.append(outputWrapper); | |
| 197 runColumn.append(hackDiv); | |
| 198 | |
| 199 var settingsElement = document.getElementById('settings'); | |
| 200 settingsElement.onClick.listen(openSettings); | |
| 201 | |
| 202 window.onMessage.listen(interaction.onWindowMessage); | |
| 203 | |
| 204 observer = new MutationObserver(interaction.onMutation) | |
| 205 ..observe( | |
| 206 mainEditorPane, childList: true, characterData: true, subtree: true); | |
| 207 | |
| 208 scheduleMicrotask(() { | |
| 209 mainEditorPane.appendText(currentSource); | |
| 210 }); | |
| 211 | |
| 212 // You cannot install event handlers on window.applicationCache | |
| 213 // until the window has loaded. In dartium, that's later than this | |
| 214 // method is called. | |
| 215 window.onLoad.listen(onLoad); | |
| 216 | |
| 217 // However, in dart2js, the window has already loaded, and onLoad is | |
| 218 // never called. | |
| 219 onLoad(null); | |
| 220 } | |
| 221 | |
| 222 buildCode(InteractionManager interaction) { | |
| 223 var codePicker = | |
| 224 document.getElementById('code-picker') | |
| 225 ..style.visibility = 'hidden' | |
| 226 ..onChange.listen(onCodeChange); | |
| 227 var htmlGroup = new OptGroupElement()..label = 'HTML'; | |
| 228 var benchmarkGroup = new OptGroupElement()..label = 'Benchmarks'; | |
| 229 | |
| 230 interaction.projectFileNames().then((List<String> names) { | |
| 231 OptionElement none = new OptionElement() | |
| 232 ..appendText('--') | |
| 233 ..disabled = true; | |
| 234 codePicker | |
| 235 ..append(none) | |
| 236 ..style.visibility = 'visible' | |
| 237 ..selectedIndex = 0; | |
| 238 | |
| 239 for (String name in names) { | |
| 240 codePicker.append(buildTab(name, name, (event) { | |
| 241 interaction.onProjectFileSelected(name); | |
| 242 })); | |
| 243 } | |
| 244 }).catchError((error) { | |
| 245 codePicker.style.visibility = 'visible'; | |
| 246 OptionElement none = new OptionElement() | |
| 247 ..appendText('Pick an example') | |
| 248 ..disabled = true; | |
| 249 codePicker.append(none); | |
| 250 | |
| 251 // codePicker.classes.addAll(['nav', 'nav-tabs']); | |
| 252 codePicker.append(buildTab('Hello, World!', 'EXAMPLE_HELLO', (_) { | |
| 253 mainEditorPane | |
| 254 ..nodes.clear() | |
| 255 ..appendText(EXAMPLE_HELLO); | |
| 256 })); | |
| 257 codePicker.append(buildTab('Fibonacci', 'EXAMPLE_FIBONACCI', (_) { | |
| 258 mainEditorPane | |
| 259 ..nodes.clear() | |
| 260 ..appendText(EXAMPLE_FIBONACCI); | |
| 261 })); | |
| 262 codePicker.append(htmlGroup); | |
| 263 // TODO(ahe): Restore benchmarks. | |
| 264 // codePicker.append(benchmarkGroup); | |
| 265 | |
| 266 htmlGroup.append( | |
| 267 buildTab('Hello, World!', 'EXAMPLE_HELLO_HTML', (_) { | |
| 268 mainEditorPane | |
| 269 ..nodes.clear() | |
| 270 ..appendText(EXAMPLE_HELLO_HTML); | |
| 271 })); | |
| 272 htmlGroup.append( | |
| 273 buildTab('Fibonacci', 'EXAMPLE_FIBONACCI_HTML', (_) { | |
| 274 mainEditorPane | |
| 275 ..nodes.clear() | |
| 276 ..appendText(EXAMPLE_FIBONACCI_HTML); | |
| 277 })); | |
| 278 htmlGroup.append(buildTab('Sunflower', 'EXAMPLE_SUNFLOWER', (_) { | |
| 279 mainEditorPane | |
| 280 ..nodes.clear() | |
| 281 ..appendText(EXAMPLE_SUNFLOWER); | |
| 282 })); | |
| 283 | |
| 284 benchmarkGroup.append(buildTab('DeltaBlue', 'BENCHMARK_DELTA_BLUE', (_) { | |
| 285 mainEditorPane.contentEditable = 'false'; | |
| 286 LinkElement link = querySelector('link[rel="benchmark-DeltaBlue"]'); | |
| 287 String deltaBlueUri = link.href; | |
| 288 link = querySelector('link[rel="benchmark-base"]'); | |
| 289 String benchmarkBaseUri = link.href; | |
| 290 HttpRequest.getString(benchmarkBaseUri).then((String benchmarkBase) { | |
| 291 HttpRequest.getString(deltaBlueUri).then((String deltaBlue) { | |
| 292 benchmarkBase = benchmarkBase.replaceFirst( | |
| 293 'part of benchmark_harness;', '// part of benchmark_harness;'); | |
| 294 deltaBlue = deltaBlue.replaceFirst( | |
| 295 "import 'package:benchmark_harness/benchmark_harness.dart';", | |
| 296 benchmarkBase); | |
| 297 mainEditorPane | |
| 298 ..nodes.clear() | |
| 299 ..appendText(deltaBlue) | |
| 300 ..contentEditable = 'true'; | |
| 301 }); | |
| 302 }); | |
| 303 })); | |
| 304 | |
| 305 benchmarkGroup.append(buildTab('Richards', 'BENCHMARK_RICHARDS', (_) { | |
| 306 mainEditorPane.contentEditable = 'false'; | |
| 307 LinkElement link = querySelector('link[rel="benchmark-Richards"]'); | |
| 308 String richardsUri = link.href; | |
| 309 link = querySelector('link[rel="benchmark-base"]'); | |
| 310 String benchmarkBaseUri = link.href; | |
| 311 HttpRequest.getString(benchmarkBaseUri).then((String benchmarkBase) { | |
| 312 HttpRequest.getString(richardsUri).then((String richards) { | |
| 313 benchmarkBase = benchmarkBase.replaceFirst( | |
| 314 'part of benchmark_harness;', '// part of benchmark_harness;'); | |
| 315 richards = richards.replaceFirst( | |
| 316 "import 'package:benchmark_harness/benchmark_harness.dart';", | |
| 317 benchmarkBase); | |
| 318 mainEditorPane | |
| 319 ..nodes.clear() | |
| 320 ..appendText(richards) | |
| 321 ..contentEditable = 'true'; | |
| 322 }); | |
| 323 }); | |
| 324 })); | |
| 325 | |
| 326 codePicker.selectedIndex = 0; | |
| 327 }); | |
| 328 } | |
| 329 | |
| 330 num settingsHeight = 0; | |
| 331 | |
| 332 void openSettings(MouseEvent event) { | |
| 333 event.preventDefault(); | |
| 334 | |
| 335 if (settingsHeight != 0) { | |
| 336 var dialog = document.getElementById('settings-dialog'); | |
| 337 if (dialog.getBoundingClientRect().height > 0) { | |
| 338 dialog.style.height = '0px'; | |
| 339 } else { | |
| 340 dialog.style.height = '${settingsHeight}px'; | |
| 341 } | |
| 342 return; | |
| 343 } | |
| 344 | |
| 345 void updateCodeFont(Event e) { | |
| 346 TextInputElement target = e.target; | |
| 347 codeFont = target.value; | |
| 348 mainEditorPane.style.font = codeFont; | |
| 349 } | |
| 350 | |
| 351 void updateTheme(Event e) { | |
| 352 var select = e.target; | |
| 353 String theme = select.queryAll('option')[select.selectedIndex].text; | |
| 354 window.localStorage['theme'] = theme; | |
| 355 currentTheme = Theme.named(theme); | |
| 356 | |
| 357 mainEditorPane.style | |
| 358 ..backgroundColor = currentTheme.background.color | |
| 359 ..color = currentTheme.foreground.color; | |
| 360 | |
| 361 outputDiv.style | |
| 362 ..backgroundColor = currentTheme.background.color | |
| 363 ..color = currentTheme.foreground.color; | |
| 364 | |
| 365 bool oldCompilationPaused = compilationPaused; | |
| 366 compilationPaused = true; | |
| 367 interaction.onMutation([], observer); | |
| 368 compilationPaused = false; | |
| 369 } | |
| 370 | |
| 371 var body = document.getElementById('settings-body'); | |
| 372 | |
| 373 body.nodes.clear(); | |
| 374 | |
| 375 var form = new FormElement(); | |
| 376 var fieldSet = new FieldSetElement(); | |
| 377 body.append(form); | |
| 378 form.append(fieldSet); | |
| 379 | |
| 380 bool isChecked(CheckboxInputElement checkBox) => checkBox.checked; | |
| 381 | |
| 382 String messageFor(UserOption option) { | |
| 383 var message = messages[option.name]; | |
| 384 if (message is List) message = message[0]; | |
| 385 return (message == null) ? option.name : message; | |
| 386 } | |
| 387 | |
| 388 String placeHolderFor(UserOption option) { | |
| 389 var message = messages[option.name]; | |
| 390 if (message is! List) return ''; | |
| 391 message = message[1]; | |
| 392 return (message == null) ? '' : message; | |
| 393 } | |
| 394 | |
| 395 void addBooleanOption(BooleanUserOption option) { | |
| 396 CheckboxInputElement checkBox = new CheckboxInputElement() | |
| 397 ..checked = option.value | |
| 398 ..onChange.listen((Event e) { option.value = isChecked(e.target); }); | |
| 399 | |
| 400 LabelElement label = new LabelElement() | |
| 401 ..classes.add('checkbox') | |
| 402 ..append(checkBox) | |
| 403 ..appendText(' ${messageFor(option)}'); | |
| 404 | |
| 405 fieldSet.append(label); | |
| 406 } | |
| 407 | |
| 408 void addStringOption(StringUserOption option) { | |
| 409 fieldSet.append(new LabelElement()..appendText(messageFor(option))); | |
| 410 var textInput = new TextInputElement(); | |
| 411 textInput.classes.add('input-block-level'); | |
| 412 String value = option.value; | |
| 413 if (!value.isEmpty) { | |
| 414 textInput.value = value; | |
| 415 } | |
| 416 textInput.placeholder = placeHolderFor(option);; | |
| 417 textInput.onChange.listen(updateCodeFont); | |
| 418 fieldSet.append(textInput); | |
| 419 } | |
| 420 | |
| 421 void addThemeOption(StringUserOption option) { | |
| 422 fieldSet.append(new LabelElement()..appendText('Theme:')); | |
| 423 var themeSelector = new SelectElement(); | |
| 424 themeSelector.classes.add('input-block-level'); | |
| 425 for (Theme theme in THEMES) { | |
| 426 OptionElement option = new OptionElement()..appendText(theme.name); | |
| 427 if (theme == currentTheme) option.selected = true; | |
| 428 themeSelector.append(option); | |
| 429 } | |
| 430 themeSelector.onChange.listen(updateTheme); | |
| 431 fieldSet.append(themeSelector); | |
| 432 } | |
| 433 | |
| 434 for (UserOption option in options) { | |
| 435 if (option.isHidden) continue; | |
| 436 if (option.name == 'theme') { | |
| 437 addThemeOption(option); | |
| 438 } else if (option is BooleanUserOption) { | |
| 439 addBooleanOption(option); | |
| 440 } else if (option is StringUserOption) { | |
| 441 addStringOption(option); | |
| 442 } | |
| 443 } | |
| 444 | |
| 445 var dialog = document.getElementById('settings-dialog'); | |
| 446 | |
| 447 if (settingsHeight == 0) { | |
| 448 settingsHeight = dialog.getBoundingClientRect().height; | |
| 449 dialog.classes | |
| 450 ..add('slider') | |
| 451 ..remove('myhidden'); | |
| 452 Timer.run(() { | |
| 453 dialog.style.height = '${settingsHeight}px'; | |
| 454 }); | |
| 455 } else { | |
| 456 dialog.style.height = '${settingsHeight}px'; | |
| 457 } | |
| 458 | |
| 459 onSubmit(Event event) { | |
| 460 event.preventDefault(); | |
| 461 | |
| 462 window.localStorage['alwaysRunInWorker'] = '$alwaysRunInWorker'; | |
| 463 window.localStorage['verboseCompiler'] = '$verboseCompiler'; | |
| 464 window.localStorage['minified'] = '$minified'; | |
| 465 window.localStorage['onlyAnalyze'] = '$onlyAnalyze'; | |
| 466 window.localStorage['enableDartMind'] = '$enableDartMind'; | |
| 467 window.localStorage['compilationPaused'] = '$compilationPaused'; | |
| 468 window.localStorage['codeFont'] = '$codeFont'; | |
| 469 | |
| 470 dialog.style.height = '0px'; | |
| 471 } | |
| 472 form.onSubmit.listen(onSubmit); | |
| 473 | |
| 474 var doneButton = document.getElementById('settings-done'); | |
| 475 doneButton.onClick.listen(onSubmit); | |
| 476 } | |
| OLD | NEW |