| Index: bin/inference/client.dart
|
| diff --git a/bin/inference/client.dart b/bin/inference/client.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7f7fcbd63b421c6b16dd131cf10b55c4ab351f3a
|
| --- /dev/null
|
| +++ b/bin/inference/client.dart
|
| @@ -0,0 +1,134 @@
|
| +// 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.
|
| +
|
| +/// Client component to display [GlobalResult]s as a web app.
|
| +library dart2js_info.bin.inference.client;
|
| +
|
| +import 'dart:html' hide Entry;
|
| +import 'dart:convert';
|
| +import 'package:dart2js_info/info.dart';
|
| +import 'package:dart2js_info/src/string_edit_buffer.dart';
|
| +import 'package:charcode/charcode.dart';
|
| +
|
| +AllInfo data;
|
| +main() async {
|
| + data =
|
| + AllInfo.parseFromJson(JSON.decode(await HttpRequest.getString('/data')));
|
| +
|
| + routeByHash();
|
| + window.onHashChange.listen((_) => routeByHash());
|
| +}
|
| +
|
| +/// Does basic routing for the client UI.
|
| +routeByHash() {
|
| + var hash = window.location.hash;
|
| + if (hash.isEmpty || hash == '#' || hash == '#!') {
|
| + handleHomePage();
|
| + } else if (hash.startsWith('#!')) {
|
| + handleFileView(hash.substring(2));
|
| + }
|
| +}
|
| +
|
| +/// Renders the home screen: a list of files with results.
|
| +handleHomePage() {
|
| + var files = UrlRetriever.run(data);
|
| + var html = new StringBuffer()..write('<ul>');
|
| + for (var file in files) {
|
| + html.write('<li> <a href="#!$file">$file</a></li>');
|
| + }
|
| + html.write('</ul>');
|
| + document.body.setInnerHtml('$html', treeSanitizer: NodeTreeSanitizer.trusted);
|
| +}
|
| +
|
| +/// Renders the results of a single file: the code with highlighting for each
|
| +/// send.
|
| +handleFileView(String path) async {
|
| + var contents = await HttpRequest.getString('file/$path');
|
| + var visitor = new SendHighlighter(path, contents);
|
| + data.accept(visitor);
|
| + var code = '${visitor.code}';
|
| + document.body.setInnerHtml(
|
| + '''
|
| + <div class="grid">
|
| + <div class="main code">$code</div>
|
| + <div id="selections" class="right code"></div>
|
| + </div>
|
| + ''',
|
| + treeSanitizer: NodeTreeSanitizer.trusted);
|
| +
|
| + var div = document.querySelector('#selections');
|
| + visitAllMetrics((metric, _) {
|
| + if (metric is GroupedMetric || metric.name == 'reachable functions') return;
|
| + var cssClassName = _classNameForMetric(metric);
|
| + var node = new Element.html('<div>'
|
| + '<span class="send $cssClassName inactive">${metric.name}</span>'
|
| + '</div>');
|
| + node.children[0].onClick.listen((_) {
|
| + document.querySelectorAll('.$cssClassName').classes.toggle('inactive');
|
| + });
|
| + div.append(node);
|
| + });
|
| +}
|
| +
|
| +/// Extracts urls for all files mentioned in the results.
|
| +class UrlRetriever extends RecursiveInfoVisitor {
|
| + List<String> _paths = [];
|
| +
|
| + static List<String> run(AllInfo results) {
|
| + var visitor = new UrlRetriever();
|
| + results.accept(visitor);
|
| + return visitor._paths;
|
| + }
|
| +
|
| + @override
|
| + visitLibrary(LibraryInfo info) {
|
| + _paths.add(info.uri.path);
|
| + super.visitLibrary(info);
|
| + }
|
| +
|
| + @override
|
| + visitFunction(FunctionInfo info) {
|
| + var path = info.measurements?.uri?.path;
|
| + if (path != null) _paths.add(path);
|
| + }
|
| +}
|
| +
|
| +/// Visitors that highlights every send in the text of a file using HTML
|
| +/// `<span>` tags.
|
| +class SendHighlighter extends RecursiveInfoVisitor {
|
| + final String path;
|
| + final StringEditBuffer code;
|
| +
|
| + SendHighlighter(this.path, String contents)
|
| + : code = new StringEditBuffer(contents) {
|
| + code.insert(0, '<span class="line">');
|
| + for (int i = 0; i < contents.length; i++) {
|
| + if (contents.codeUnitAt(i) == $lt) {
|
| + code.replace(i, i + 1, '<');
|
| + } else if (contents.codeUnitAt(i) == $gt) {
|
| + code.replace(i, i + 1, '>');
|
| + } else if (contents.codeUnitAt(i) == $lf) {
|
| + code.insert(i + 1, '</span><span class="line">');
|
| + }
|
| + }
|
| + code.insert(contents.length, '</span>');
|
| + }
|
| +
|
| + @override
|
| + visitFunction(FunctionInfo function) {
|
| + if (function.measurements?.uri?.path != path) return;
|
| + var entries = function.measurements.entries;
|
| + for (var metric in entries.keys) {
|
| + if (metric is GroupedMetric) continue;
|
| + var cssClassName = _classNameForMetric(metric);
|
| + for (var entry in entries[metric]) {
|
| + code.insert(entry.begin, '<span class="send ${cssClassName} inactive">',
|
| + -entry.end);
|
| + code.insert(entry.end, '</span>');
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +_classNameForMetric(Metric metric) => metric.name.replaceAll(' ', '-');
|
|
|