blob: 9db0438e7ab6f2a68a79316d0eb7ae780567ee8d [file] [log] [blame] [edit]
// Copyright (c) 2015, 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.
/// This library displays [MessageSummary]s from the Dart Dev Compiler.
import 'dart:convert';
import 'dart:html';
import 'package:dev_compiler/src/summary.dart';
import 'package:source_span/source_span.dart';
main() async {
await window.animationFrame;
displayMessages(await HttpRequest.getString('messages.json'));
}
void displayMessages(String data) {
var summary = GlobalSummary.parse(JSON.decode(data));
var messagesByLevel = _getMessagesByLevel(summary);
if (messagesByLevel.isEmpty) return;
// Build the wrapper, menu, and content divs.
var menuWrapper = new DivElement()..classes.add('menu');
var contentWrapper = new DivElement()..classes.add('content');
var wrapperDiv = new DivElement()
..classes.add('dev-compiler-messages')
..append(menuWrapper)
..append(contentWrapper);
var selectedMenu = new _Selection();
var selectedContent = new _Selection();
// Add a menu item, content section, and messages for each log level.
messagesByLevel.forEach((level, messages) {
var contentItem = new DivElement()..classes.add(level);
var menuItem = new Element.html('<div class="$level">'
'$level <span class="num">(${messages.length})</span>'
'</div>');
menuWrapper.append(menuItem);
contentWrapper.append(contentItem);
menuItem.onClick.listen((_) {
selectedMenu.select(menuItem);
selectedContent.select(contentItem);
});
// TODO(sigmund): add permanent links to error messages like in polymer.
for (var m in messages) {
var message = _hyperlinkUrls(_escape(m.message));
var span = m.span;
var sb = new StringBuffer();
sb.write('<div class="message"><div class="text $level">$message</div>');
if (span != null) {
sb.write('<div class="location">'
' <span class="location">${span.start.toolString}</span></div>'
' <span class="text">');
if (span is SourceSpanWithContext) {
sb.write(_escape(span.context.substring(0, span.start.column)));
sb.write('<span class="$level">');
sb.write(_escape(span.text));
sb.write('</span>');
var end = span.start.column + span.text.length;
sb.write(_escape(span.context.substring(end)));
} else {
sb.write(_escape(span.text));
}
sb.write('</span></div></div>');
}
sb.write('</div>');
var logElement = new Element.html('$sb',
validator: new NodeValidatorBuilder.common()
..allowNavigation(new _OpenUriPolicy()));
contentItem.append(logElement);
var messageElement = logElement.querySelector('div.text');
messageElement.onClick.listen((e) {
if (e.target == messageElement) {
messageElement.classes.toggle('expanded');
}
});
}
});
document.body.append(wrapperDiv);
}
/// Toggles classes to match which item of a list of items is selected.
class _Selection {
Element _selected;
select(Element newItem) {
if (_selected == newItem) {
_selected = null;
} else {
if (_selected != null) {
_selected.classes.remove('active');
}
_selected = newItem;
}
newItem.classes.toggle('active');
}
}
final _urlRegex = new RegExp('http://[^ ]*');
final _escaper = new HtmlEscape();
String _hyperlinkUrls(String text) => text.replaceAllMapped(_urlRegex,
(m) => '<a href="${m.group(0)}" target="blank">${m.group(0)}</a>');
String _escape(String text) => _escaper.convert(text);
class _OpenUriPolicy implements UriPolicy {
bool allowsUri(String uri) => true;
}
Map<String, List<MessageSummary>> _getMessagesByLevel(GlobalSummary messages) {
var visitor = new _Visitor();
messages.accept(visitor);
return visitor.messagesByLevel;
}
class _Visitor extends RecursiveSummaryVisitor {
final Map<String, List<MessageSummary>> messagesByLevel = {};
@override
void visitMessage(MessageSummary message) {
var level = message.level.toLowerCase();
messagesByLevel.putIfAbsent(level, () => []);
messagesByLevel[level].add(message);
}
}