blob: 65d66201191468afb3b91ba6a161c619e261d45c [file] [log] [blame]
// 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.ui;
import 'dart:html';
import 'dart:async' show
scheduleMicrotask;
import 'cache.dart' show
onLoad,
updateCacheStatus;
import 'editor.dart' show
onKeyUp,
onMutation;
import 'run.dart' show
makeOutputFrame;
import 'themes.dart' show
THEMES,
Theme;
import 'samples.dart' show
EXAMPLE_FIBONACCI,
EXAMPLE_FIBONACCI_HTML,
EXAMPLE_HELLO,
EXAMPLE_HELLO_HTML,
EXAMPLE_SUNFLOWER;
DivElement inputPre;
PreElement outputDiv;
DivElement hackDiv;
IFrameElement outputFrame;
MutationObserver observer;
SpanElement 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';
final String rawCodeFont = window.localStorage['codeFont'];
String codeFont = rawCodeFont == null ? '' : rawCodeFont;
String currentSample = window.localStorage['currentSample'];
Theme currentTheme = Theme.named(window.localStorage['theme']);
bool applyingSettings = false;
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);
// TODO(ahe): Restore benchmarks.
// 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';
LinkElement link = query('link[rel="benchmark-DeltaBlue"]');
String deltaBlueUri = link.href;
link = query('link[rel="benchmark-base"]');
String benchmarkBaseUri = link.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';
LinkElement link = query('link[rel="benchmark-Richards"]');
String richardsUri = link.href;
link = query('link[rel="benchmark-base"]');
String benchmarkBaseUri = link.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);
scheduleMicrotask(() {
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) {
TextInputElement target = e.target;
codeFont = 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()
// TODO(ahe): Used to be ..defaultChecked = defaultValue
..checked = defaultValue
..onChange.listen(action);
return new LabelElement()
..classes.add('checkbox')
..append(checkBox)
..appendText(' $text');
}
bool isChecked(CheckboxInputElement checkBox) => checkBox.checked;
// TODO(ahe): Build abstraction for flags/options.
fieldSet.append(
buildCheckBox(
'Always run in Worker thread.', alwaysRunInWorker,
(Event e) { alwaysRunInWorker = isChecked(e.target); }));
fieldSet.append(
buildCheckBox(
'Verbose compiler output.', verboseCompiler,
(Event e) { verboseCompiler = isChecked(e.target); }));
fieldSet.append(
buildCheckBox(
'Generate compact (minified) JavaScript.', minified,
(Event e) { minified = isChecked(e.target); }));
fieldSet.append(
buildCheckBox(
'Only analyze program.', onlyAnalyze,
(Event e) { onlyAnalyze = isChecked(e.target); }));
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);
}