| OLD | NEW | 
|---|
| 1 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. | 
| 4 | 4 | 
| 5 library logging_page; | 5 library logging_page; | 
| 6 | 6 | 
| 7 import 'dart:async'; | 7 import 'dart:async'; | 
| 8 import 'dart:html'; | 8 import 'dart:html'; | 
| 9 import 'observatory_element.dart'; |  | 
| 10 import 'package:logging/logging.dart'; | 9 import 'package:logging/logging.dart'; | 
| 11 import 'package:observatory/service.dart'; | 10 import 'package:observatory/models.dart' as M; | 
| 12 import 'package:observatory/app.dart'; | 11 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; | 
| 13 import 'package:observatory/elements.dart'; | 12 import 'package:observatory/src/elements/helpers/tag.dart'; | 
| 14 import 'package:observatory/utils.dart'; | 13 import 'package:observatory/src/elements/logging_list.dart'; | 
| 15 import 'package:polymer/polymer.dart'; | 14 import 'package:observatory/src/elements/nav/bar.dart'; | 
|  | 15 import 'package:observatory/src/elements/nav/class_menu.dart'; | 
|  | 16 import 'package:observatory/src/elements/nav/isolate_menu.dart'; | 
|  | 17 import 'package:observatory/src/elements/nav/menu.dart'; | 
|  | 18 import 'package:observatory/src/elements/nav/notify.dart'; | 
|  | 19 import 'package:observatory/src/elements/nav/refresh.dart'; | 
|  | 20 import 'package:observatory/src/elements/nav/top_menu.dart'; | 
|  | 21 import 'package:observatory/src/elements/nav/vm_menu.dart'; | 
|  | 22 import 'package:observatory/src/elements/view_footer.dart'; | 
| 16 | 23 | 
| 17 @CustomTag('logging-page') | 24 class LoggingPageElement extends HtmlElement implements Renderable { | 
| 18 class LoggingPageElement extends ObservatoryElement { | 25   static const tag = const Tag<LoggingPageElement>('logging-page', | 
| 19   static const _kPageSelector = '#page'; | 26                                             dependencies: const [ | 
| 20   static const _kLogSelector = '#log'; | 27                                               LoggingListElement.tag, | 
| 21   static const _kSeverityLevelSelector = '#severityLevelSelector'; | 28                                               NavBarElement.tag, | 
|  | 29                                               NavClassMenuElement.tag, | 
|  | 30                                               NavTopMenuElement.tag, | 
|  | 31                                               NavVMMenuElement.tag, | 
|  | 32                                               NavIsolateMenuElement.tag, | 
|  | 33                                               NavMenuElement.tag, | 
|  | 34                                               NavRefreshElement.tag, | 
|  | 35                                               NavNotifyElement.tag, | 
|  | 36                                               ViewFooterElement.tag | 
|  | 37                                             ]); | 
|  | 38 | 
|  | 39   RenderingScheduler<LoggingPageElement> _r; | 
|  | 40 | 
|  | 41   Stream<RenderedEvent<LoggingPageElement>> get onRendered => _r.onRendered; | 
|  | 42 | 
|  | 43   M.VM _vm; | 
|  | 44   M.IsolateRef _isolate; | 
|  | 45   M.EventRepository _events; | 
|  | 46   M.NotificationRepository _notifications; | 
|  | 47   Level _level = Level.ALL; | 
|  | 48 | 
|  | 49 | 
|  | 50   M.VMRef get vm => _vm; | 
|  | 51   M.IsolateRef get isolate => _isolate; | 
|  | 52   M.NotificationRepository get notifications => _notifications; | 
|  | 53 | 
|  | 54   factory LoggingPageElement(M.VM vm, M.IsolateRef isolate, | 
|  | 55                             M.EventRepository events, | 
|  | 56                             M.NotificationRepository notifications, | 
|  | 57                             {RenderingQueue queue}) { | 
|  | 58     assert(vm != null); | 
|  | 59     assert(isolate != null); | 
|  | 60     assert(events != null); | 
|  | 61     assert(notifications != null); | 
|  | 62     LoggingPageElement e = document.createElement(tag.name); | 
|  | 63     e._r = new RenderingScheduler(e, queue: queue); | 
|  | 64     e._vm = vm; | 
|  | 65     e._isolate = isolate; | 
|  | 66     e._events = events; | 
|  | 67     e._notifications = notifications; | 
|  | 68     return e; | 
|  | 69   } | 
| 22 | 70 | 
| 23   LoggingPageElement.created() : super.created(); | 71   LoggingPageElement.created() : super.created(); | 
| 24 | 72 | 
| 25   domReady() { | 73   @override | 
| 26     super.domReady(); | 74   attached() { | 
| 27     _insertLevels(); | 75     super.attached(); | 
|  | 76     _r.enable(); | 
| 28   } | 77   } | 
| 29 | 78 | 
| 30   attached() { | 79   @override | 
| 31     super.attached(); | 80   detached() { | 
| 32     _sub(); | 81     super.detached(); | 
| 33     _resizeSubscription = window.onResize.listen((_) => _updatePageHeight()); | 82     _r.disable(notify: true); | 
| 34     _updatePageHeight(); | 83     children = []; | 
| 35     // Turn on the periodic poll timer for this page. |  | 
| 36     pollPeriod = const Duration(milliseconds:100); |  | 
| 37   } | 84   } | 
| 38 | 85 | 
| 39   detached() { | 86   LoggingListElement _logs; | 
| 40     super.detached(); | 87 | 
| 41     if (_resizeSubscription != null) { | 88   void render() { | 
| 42       _resizeSubscription.cancel(); | 89     _logs = _logs ?? new LoggingListElement(_isolate, _events); | 
| 43       _resizeSubscription = null; | 90     _logs.level = _level; | 
| 44     } | 91     children = [ | 
| 45     _unsub(); | 92       new NavBarElement(queue: _r.queue) | 
|  | 93         ..children = [ | 
|  | 94           new NavTopMenuElement(queue: _r.queue), | 
|  | 95           new NavVMMenuElement(_vm, _events, queue: _r.queue), | 
|  | 96           new NavIsolateMenuElement(_isolate, _events, queue: _r.queue), | 
|  | 97           new NavMenuElement('logging', last: true, queue: _r.queue), | 
|  | 98           new NavRefreshElement(label: 'clear', queue: _r.queue) | 
|  | 99               ..onRefresh.listen((e) async { | 
|  | 100                 e.element.disabled = true; | 
|  | 101                 _logs = null; | 
|  | 102                 _r.dirty(); | 
|  | 103               }), | 
|  | 104           new NavNotifyElement(_notifications, queue: _r.queue) | 
|  | 105         ], | 
|  | 106       new DivElement()..classes = ['content-centered-big'] | 
|  | 107         ..children = [ | 
|  | 108           new HeadingElement.h2()..text = 'Logging', | 
|  | 109           new SpanElement() | 
|  | 110             ..text = 'Show messages with severity ', | 
|  | 111           _createLevelSelector(), | 
|  | 112           new HRElement(), | 
|  | 113           _logs | 
|  | 114         ] | 
|  | 115     ]; | 
| 46   } | 116   } | 
| 47 | 117 | 
| 48   void onPoll() { | 118   Element _createLevelSelector() { | 
| 49     _flushPendingLogs(); | 119     var s = new SelectElement() | 
|  | 120         ..value = _level.name | 
|  | 121         ..children = Level.LEVELS.map((level) { | 
|  | 122             return new OptionElement(value : level.name, | 
|  | 123                 selected: _level == level) | 
|  | 124               ..text = level.name; | 
|  | 125           }).toList(growable: false); | 
|  | 126     s.onChange.listen((_) { | 
|  | 127       _level = Level.LEVELS[s.selectedIndex]; | 
|  | 128       _r.dirty(); | 
|  | 129     }); | 
|  | 130     return s; | 
| 50   } | 131   } | 
| 51 |  | 
| 52   _updatePageHeight() { |  | 
| 53     HtmlElement e = shadowRoot.querySelector(_kPageSelector); |  | 
| 54     final totalHeight = window.innerHeight; |  | 
| 55     final top = e.offset.top; |  | 
| 56     final bottomMargin = 32; |  | 
| 57     final mainHeight = totalHeight - top - bottomMargin; |  | 
| 58     e.style.setProperty('height', '${mainHeight}px'); |  | 
| 59   } |  | 
| 60 |  | 
| 61   _insertLevels() { |  | 
| 62     SelectElement severityLevelSelector = |  | 
| 63         shadowRoot.querySelector(_kSeverityLevelSelector); |  | 
| 64     severityLevelSelector.children.clear(); |  | 
| 65     _maxLevelLabelLength = 0; |  | 
| 66     for (var level in Level.LEVELS) { |  | 
| 67       var option = new OptionElement(); |  | 
| 68       option.value = level.value.toString(); |  | 
| 69       option.label = level.name; |  | 
| 70       if (level.name.length > _maxLevelLabelLength) { |  | 
| 71         _maxLevelLabelLength = level.name.length; |  | 
| 72       } |  | 
| 73       severityLevelSelector.children.add(option); |  | 
| 74     } |  | 
| 75     severityLevelSelector.selectedIndex = 0; |  | 
| 76     severityLevel = Level.ALL.value.toString(); |  | 
| 77   } |  | 
| 78 |  | 
| 79   _reset() { |  | 
| 80     logRecords.clear(); |  | 
| 81     _unsub(); |  | 
| 82     _sub(); |  | 
| 83     _renderFull(); |  | 
| 84   } |  | 
| 85 |  | 
| 86   _unsub() { |  | 
| 87     cancelFutureSubscription(_loggingSubscriptionFuture); |  | 
| 88     _loggingSubscriptionFuture = null; |  | 
| 89   } |  | 
| 90 |  | 
| 91   _sub() { |  | 
| 92     if (_loggingSubscriptionFuture != null) { |  | 
| 93       // Already subscribed. |  | 
| 94       return; |  | 
| 95     } |  | 
| 96     _loggingSubscriptionFuture = |  | 
| 97         app.vm.listenEventStream(Isolate.kLoggingStream, _onEvent); |  | 
| 98   } |  | 
| 99 |  | 
| 100   _append(Map logRecord) { |  | 
| 101     logRecords.add(logRecord); |  | 
| 102     if (_shouldDisplay(logRecord)) { |  | 
| 103       // Queue for display. |  | 
| 104       pendingLogRecords.add(logRecord); |  | 
| 105     } |  | 
| 106   } |  | 
| 107 |  | 
| 108   Element _renderAppend(Map logRecord) { |  | 
| 109     DivElement logContainer = shadowRoot.querySelector(_kLogSelector); |  | 
| 110     var element = new DivElement(); |  | 
| 111     element.classes.add('logItem'); |  | 
| 112     element.classes.add(logRecord['level'].name); |  | 
| 113     element.appendText( |  | 
| 114         '${logRecord["level"].name.padLeft(_maxLevelLabelLength)} ' |  | 
| 115         '${Utils.formatDateTime(logRecord["time"])} ' |  | 
| 116         '${logRecord["message"].valueAsString}\n'); |  | 
| 117     logContainer.children.add(element); |  | 
| 118     return element; |  | 
| 119   } |  | 
| 120 |  | 
| 121   _renderFull() { |  | 
| 122     DivElement logContainer = shadowRoot.querySelector(_kLogSelector); |  | 
| 123     logContainer.children.clear(); |  | 
| 124     pendingLogRecords.clear(); |  | 
| 125     for (var logRecord in logRecords) { |  | 
| 126       if (_shouldDisplay(logRecord)) { |  | 
| 127         _renderAppend(logRecord); |  | 
| 128       } |  | 
| 129     } |  | 
| 130     _scrollToBottom(logContainer); |  | 
| 131   } |  | 
| 132 |  | 
| 133   /// Is [container] scrolled to the within [threshold] pixels of the bottom? |  | 
| 134   static bool _isScrolledToBottom(DivElement container, [int threshold = 2]) { |  | 
| 135     if (container == null) { |  | 
| 136       return false; |  | 
| 137     } |  | 
| 138     // scrollHeight -> complete height of element including scrollable area. |  | 
| 139     // clientHeight -> height of element on page. |  | 
| 140     // scrollTop -> how far is an element scrolled (from 0 to scrollHeight). |  | 
| 141     final distanceFromBottom = |  | 
| 142         container.scrollHeight - container.clientHeight - container.scrollTop; |  | 
| 143     const threshold = 2;  // 2 pixel slop. |  | 
| 144     return distanceFromBottom <= threshold; |  | 
| 145   } |  | 
| 146 |  | 
| 147   /// Scroll [container] so the bottom content is visible. |  | 
| 148   static _scrollToBottom(DivElement container) { |  | 
| 149     if (container == null) { |  | 
| 150       return; |  | 
| 151     } |  | 
| 152     // Adjust scroll so that the bottom of the content is visible. |  | 
| 153     container.scrollTop = container.scrollHeight - container.clientHeight; |  | 
| 154   } |  | 
| 155 |  | 
| 156   _flushPendingLogs() { |  | 
| 157     DivElement logContainer = shadowRoot.querySelector(_kLogSelector); |  | 
| 158     bool autoScroll = _isScrolledToBottom(logContainer); |  | 
| 159     for (var logRecord in pendingLogRecords) { |  | 
| 160       _renderAppend(logRecord); |  | 
| 161     } |  | 
| 162     if (autoScroll) { |  | 
| 163       _scrollToBottom(logContainer); |  | 
| 164     } |  | 
| 165     pendingLogRecords.clear(); |  | 
| 166   } |  | 
| 167 |  | 
| 168   _onEvent(ServiceEvent event) { |  | 
| 169     assert(event.kind == Isolate.kLoggingStream); |  | 
| 170     _append(event.logRecord); |  | 
| 171   } |  | 
| 172 |  | 
| 173   void isolateChanged(oldValue) { |  | 
| 174     _reset(); |  | 
| 175   } |  | 
| 176 |  | 
| 177   void severityLevelChanged(oldValue) { |  | 
| 178     _severityLevelValue = int.parse(severityLevel); |  | 
| 179     _renderFull(); |  | 
| 180   } |  | 
| 181 |  | 
| 182   Future clear() { |  | 
| 183     logRecords.clear(); |  | 
| 184     pendingLogRecords.clear(); |  | 
| 185     _renderFull(); |  | 
| 186     return new Future.value(null); |  | 
| 187   } |  | 
| 188 |  | 
| 189   bool _shouldDisplay(Map logRecord) { |  | 
| 190     return logRecord['level'].value >= _severityLevelValue; |  | 
| 191   } |  | 
| 192 |  | 
| 193   @observable Isolate isolate; |  | 
| 194   @observable String severityLevel; |  | 
| 195   int _severityLevelValue = 0; |  | 
| 196   int _maxLevelLabelLength = 0; |  | 
| 197   Future<StreamSubscription> _loggingSubscriptionFuture; |  | 
| 198   StreamSubscription _resizeSubscription; |  | 
| 199   final List<Map> logRecords = new List<Map>(); |  | 
| 200   final List<Map> pendingLogRecords = new List<Map>(); |  | 
| 201 } | 132 } | 
| OLD | NEW | 
|---|