Index: runtime/observatory/lib/src/elements/class_tree.dart |
diff --git a/runtime/observatory/lib/src/elements/class_tree.dart b/runtime/observatory/lib/src/elements/class_tree.dart |
index 16f6928fbd72a7b193f0445f2b021e5909b929b9..f17f27b7d3213d495d5aed1d4e43bdf5a0b0fd21 100644 |
--- a/runtime/observatory/lib/src/elements/class_tree.dart |
+++ b/runtime/observatory/lib/src/elements/class_tree.dart |
@@ -4,150 +4,182 @@ |
library class_tree_element; |
-import 'observatory_element.dart'; |
-import 'dart:async'; |
import 'dart:html'; |
-import 'package:logging/logging.dart'; |
-import 'package:observatory/app.dart'; |
-import 'package:observatory/service.dart'; |
-import 'package:polymer/polymer.dart'; |
- |
-class ClassTreeRow extends TableTreeRow { |
- @reflectable final Isolate isolate; |
- @reflectable final Class cls; |
- ClassTreeRow(this.isolate, this.cls, TableTree tree, ClassTreeRow parent) |
- : super(tree, parent) { |
+import 'dart:async'; |
+import 'package:observatory/models.dart' as M; |
+import 'package:observatory/src/elements/class_ref.dart'; |
+import 'package:observatory/src/elements/containers/virtual_tree.dart'; |
+import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
+import 'package:observatory/src/elements/helpers/tag.dart'; |
+import 'package:observatory/src/elements/helpers/uris.dart'; |
+import 'package:observatory/src/elements/nav/bar.dart'; |
+import 'package:observatory/src/elements/nav/isolate_menu.dart'; |
+import 'package:observatory/src/elements/nav/menu.dart'; |
+import 'package:observatory/src/elements/nav/notify.dart'; |
+import 'package:observatory/src/elements/nav/top_menu.dart'; |
+import 'package:observatory/src/elements/nav/vm_menu.dart'; |
+ |
+ |
+class ClassTreeElement extends HtmlElement implements Renderable{ |
+ static const tag = const Tag<ClassTreeElement>('class-tree', |
+ dependencies: const [ClassRefElement.tag, |
+ NavBarElement.tag, |
+ NavIsolateMenuElement.tag, |
+ NavMenuElement.tag, |
+ NavNotifyElement.tag, |
+ NavTopMenuElement.tag, |
+ NavVMMenuElement.tag, |
+ VirtualTreeElement.tag]); |
+ |
+ RenderingScheduler _r; |
+ |
+ Stream<RenderedEvent<ClassTreeElement>> get onRendered => _r.onRendered; |
+ |
+ M.VMRef _vm; |
+ M.IsolateRef _isolate; |
+ M.EventRepository _events; |
+ M.NotificationRepository _notifications; |
+ M.ClassRepository _classes; |
+ M.Class _object; |
+ final _subclasses = <String, Iterable<M.Class>>{}; |
+ final _mixins = <String, List<M.Instance>>{}; |
+ |
+ factory ClassTreeElement(M.VMRef vm, M.IsolateRef isolate, |
+ M.EventRepository events, |
+ M.NotificationRepository notifications, |
+ M.ClassRepository classes, |
+ {RenderingQueue queue}) { |
+ assert(vm != null); |
assert(isolate != null); |
- assert(cls != null); |
+ assert(events != null); |
+ assert(notifications != null); |
+ assert(classes != null); |
+ ClassTreeElement e = document.createElement(tag.name); |
+ e._r = new RenderingScheduler(e, queue: queue); |
+ e._vm = vm; |
+ e._isolate = isolate; |
+ e._events = events; |
+ e._notifications = notifications; |
+ e._classes = classes; |
+ return e; |
} |
- void _addChildren(List<Class> subclasses) { |
- for (var subclass in subclasses) { |
- if (subclass.isPatch) { |
- continue; |
- } |
- if (subclass.mixin != null) { |
- _addChildren(subclass.subclasses); |
- } else { |
- var row = new ClassTreeRow(isolate, subclass, tree, this); |
- children.add(row); |
- } |
- } |
- } |
+ ClassTreeElement.created() : super.created(); |
- Future _addMixins(Class cls) async { |
- var classCell = flexColumns[0]; |
- if (cls.superclass == null) { |
- return; |
- } |
- bool first = true; |
- while (cls.superclass != null && cls.superclass.mixin != null) { |
- cls = cls.superclass; |
- await cls.mixin.load(); |
- var span = new SpanElement(); |
- span.style.alignSelf = 'center'; |
- span.style.whiteSpace = 'pre'; |
- if (first) { |
- span.text = ' with '; |
- } else { |
- span.text = ', '; |
- } |
- classCell.children.add(span); |
- var mixinRef = new Element.tag('class-ref'); |
- mixinRef.ref = cls.mixin.typeClass; |
- mixinRef.style.alignSelf = 'center'; |
- classCell.children.add(mixinRef); |
- first = false; |
- } |
+ @override |
+ void attached() { |
+ super.attached(); |
+ _refresh(); |
+ _r.enable(); |
} |
- Future _addClass(Class cls) async { |
- var classCell = flexColumns[0]; |
- classCell.style.justifyContent = 'flex-start'; |
- var classRef = new Element.tag('class-ref'); |
- classRef.ref = cls; |
- classRef.style.alignSelf = 'center'; |
- classCell.children.add(classRef); |
- if (cls.superclass != null && cls.superclass.mixin != null) { |
- await _addMixins(cls); |
- } |
- if (cls.subclasses.isNotEmpty) { |
- var span = new SpanElement(); |
- span.style.paddingLeft = '.5em'; |
- span.style.alignSelf = 'center'; |
- int subclassCount = _indirectSubclassCount(cls) - 1; |
- if (subclassCount > 1) { |
- span.text = '($subclassCount subclasses)'; |
- } else { |
- span.text = '($subclassCount subclass)'; |
- } |
- classCell.children.add(span); |
- } |
+ @override |
+ void detached() { |
+ super.detached(); |
+ children = []; |
+ _r.disable(notify: true); |
} |
- void onShow() { |
- super.onShow(); |
- if (children.length == 0) { |
- _addChildren(cls.subclasses); |
- } |
- _addClass(cls); |
+ VirtualTreeElement _tree; |
+ |
+ void render() { |
+ children = [ |
+ new NavBarElement(queue: _r.queue) |
+ ..children = [ |
+ new NavTopMenuElement(queue: _r.queue), |
+ new NavVMMenuElement(_vm, _events, queue: _r.queue), |
+ new NavIsolateMenuElement(_isolate, _events, queue: _r.queue), |
+ new NavMenuElement('class hierarchy', link: Uris.classTree(_isolate), |
+ last: true, queue: _r.queue), |
+ new NavNotifyElement(_notifications, queue: _r.queue) |
+ ], |
+ new DivElement() |
+ ..classes = ['content-centered'] |
+ ..children = [ |
+ new HeadingElement.h1()..text = 'Class Hierarchy', |
+ new BRElement(), new HRElement(), |
+ _object == null ? (new HeadingElement.h2()..text = 'Loading...') |
+ : _createTree() |
+ ] |
+ ]; |
} |
- static int _indirectSubclassCount(var cls) { |
- int count = 0; |
- if (cls.mixin == null) { |
- // Don't count synthetic mixin classes in subclass count. |
- count++; |
- } |
- for (var subclass in cls.subclasses) { |
- count += _indirectSubclassCount(subclass); |
- } |
- return count; |
+ Element _createTree() { |
+ _tree = new VirtualTreeElement(_create, _update, _children, |
+ items: [_object], queue: _r.queue); |
+ _tree.expand(_object, autoExpandSingleChildNodes: true); |
+ return _tree; |
} |
- bool hasChildren() { |
- return cls.subclasses.isNotEmpty; |
+ Future _refresh() async { |
+ _object = null; |
+ _subclasses.clear(); |
+ _mixins.clear(); |
+ _object = await _register(await _classes.getObject()); |
+ _r.dirty(); |
} |
-} |
- |
- |
-@CustomTag('class-tree') |
-class ClassTreeElement extends ObservatoryElement { |
- @observable Isolate isolate; |
- TableTree tree; |
- |
- ClassTreeElement.created() : super.created(); |
+ Future<M.Class> _register(M.Class cls) async { |
+ _subclasses[cls.id] = await Future.wait( |
+ (await Future.wait(cls.subclasses.map(_getActualChildrens))) |
+ .expand((f) => f) |
+ .map(_register) |
+ ); |
+ return cls; |
+ } |
- @override |
- void attached() { |
- super.attached(); |
- var tableBody = shadowRoot.querySelector('#tableTreeBody'); |
- assert(tableBody != null); |
- tree = new TableTree(tableBody, 1); |
- if (isolate != null) { |
- _update(isolate.objectClass); |
+ Future<Iterable<M.Class>> _getActualChildrens(M.ClassRef ref) async { |
+ var cls = await _classes.get(ref.id); |
+ if (cls.isPatch) { |
+ return const []; |
+ } |
+ if (cls.mixin == null) { |
+ return [cls]; |
} |
+ return (await Future.wait(cls.subclasses.map(_getActualChildrens))) |
+ .expand((f) => f) |
+ ..forEach((subcls) { |
+ _mixins[subcls.id] = (_mixins[subcls.id] ?? [])..add(cls.mixin); |
+ }); |
} |
- isolateChanged(oldValue) { |
- isolate.getClassHierarchy().then((objectClass) { |
- _update(objectClass); |
- }); |
+ static Element _create(toggle) { |
+ return new DivElement()..classes = ['class-tree-item'] |
+ ..children = [ |
+ new SpanElement()..classes = ['lines'], |
+ new SpanElement()..classes = ['expander'] |
+ ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), |
+ new SpanElement()..classes = ['name'] |
+ ]; |
} |
- void _update(Class root) { |
- try { |
- var rootRow = new ClassTreeRow(isolate, root, tree, null); |
- rootRow.children.add(new ClassTreeRow(isolate, root, tree, rootRow)); |
- tree.initialize(rootRow); |
- } catch (e, stackTrace) { |
- Logger.root.warning('_update', e, stackTrace); |
+ void _update(HtmlElement el, M.Class cls, int index) { |
+ virtualTreeUpdateLines(el.children[0], index); |
+ if (cls.subclasses.isEmpty) { |
+ el.children[1].text = ''; |
+ } else { |
+ el.children[1].text = _tree.isExpanded(cls) ? '▼' : '►'; |
} |
- // Check if we only have one node at the root and expand it. |
- if (tree.rows.length == 1) { |
- tree.toggle(tree.rows[0]); |
+ el.children[2].children = [ |
+ new ClassRefElement(_isolate, cls, queue: _r.queue) |
+ ]; |
+ if (_mixins[cls.id] != null) { |
+ el.children[2].children.addAll(_createMixins(_mixins[cls.id])); |
} |
- notifyPropertyChange(#tree, null, tree); |
+ } |
+ |
+ List<Element> _createMixins(List<M.Instance> types) { |
+ final children = types.expand((type) => [ |
+ new SpanElement()..text = ', ', |
+ type.typeClass == null |
+ ? (new SpanElement()..text = type.name.split('<').first) |
+ : new ClassRefElement(_isolate, type.typeClass, queue: _r.queue) |
+ ]).toList(); |
+ children.first.text = ' with '; |
+ return children; |
+ } |
+ |
+ Iterable<M.Class> _children(M.Class cls) { |
+ return _subclasses[cls.id]; |
} |
} |