// 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 provides a single function called injectLogs which when called /// will request a logs json file and build a small widget out of them which /// groups the logs by level. library polymer.build.log_injector; import 'dart:async'; import 'dart:convert'; import 'dart:html'; import 'package:path/path.dart' as path; import 'package:source_span/source_span.dart'; import 'package:dev_compiler/src/summary.dart'; main() async => 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('
' '$level (${messages.length})' '
'); 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('
$message
'); if (span != null) { sb.write('
' ' ${span.start.toolString}
' ' '); if (span is SourceSpanWithContext) { var context = span.context; var text = span.text; sb.write(_escape(context.substring(0, span.start.column))); sb.write(''); sb.write(_escape(span.text)); sb.write(''); sb.write(_escape(context.substring(span.end.column))); } else { sb.write(_escape(span.text)); } sb.write('
'); } sb.write(''); 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) => '${m.group(0)}'); String _escape(String text) => _escaper.convert(text); class _OpenUriPolicy implements UriPolicy { bool allowsUri(String uri) => true; } Map> _getMessagesByLevel(GlobalSummary messages) { var visitor = new _Visitor(); messages.accept(visitor); return visitor.messagesByLevel; } class _Visitor extends RecursiveSummaryVisitor { final Map> messagesByLevel = {}; @override void visitMessage(MessageSummary message) { var level = message.level.toLowerCase(); messagesByLevel.putIfAbsent(level, () => []); messagesByLevel[level].add(message); } }