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 |