OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 class_tree_element; | 5 library class_tree_element; |
6 | 6 |
7 import 'observatory_element.dart'; | 7 import 'dart:html'; |
8 import 'dart:async'; | 8 import 'dart:async'; |
9 import 'dart:html'; | 9 import 'package:observatory/models.dart' as M; |
10 import 'package:logging/logging.dart'; | 10 import 'package:observatory/src/elements/class_ref.dart'; |
11 import 'package:observatory/app.dart'; | 11 import 'package:observatory/src/elements/containers/virtual_tree.dart'; |
12 import 'package:observatory/service.dart'; | 12 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
13 import 'package:polymer/polymer.dart'; | 13 import 'package:observatory/src/elements/helpers/tag.dart'; |
14 | 14 import 'package:observatory/src/elements/helpers/uris.dart'; |
15 class ClassTreeRow extends TableTreeRow { | 15 import 'package:observatory/src/elements/nav/bar.dart'; |
16 @reflectable final Isolate isolate; | 16 import 'package:observatory/src/elements/nav/isolate_menu.dart'; |
17 @reflectable final Class cls; | 17 import 'package:observatory/src/elements/nav/menu.dart'; |
18 ClassTreeRow(this.isolate, this.cls, TableTree tree, ClassTreeRow parent) | 18 import 'package:observatory/src/elements/nav/notify.dart'; |
19 : super(tree, parent) { | 19 import 'package:observatory/src/elements/nav/top_menu.dart'; |
20 assert(isolate != null); | 20 import 'package:observatory/src/elements/nav/vm_menu.dart'; |
21 assert(cls != null); | |
22 } | |
23 | |
24 void _addChildren(List<Class> subclasses) { | |
25 for (var subclass in subclasses) { | |
26 if (subclass.isPatch) { | |
27 continue; | |
28 } | |
29 if (subclass.mixin != null) { | |
30 _addChildren(subclass.subclasses); | |
31 } else { | |
32 var row = new ClassTreeRow(isolate, subclass, tree, this); | |
33 children.add(row); | |
34 } | |
35 } | |
36 } | |
37 | |
38 Future _addMixins(Class cls) async { | |
39 var classCell = flexColumns[0]; | |
40 if (cls.superclass == null) { | |
41 return; | |
42 } | |
43 bool first = true; | |
44 while (cls.superclass != null && cls.superclass.mixin != null) { | |
45 cls = cls.superclass; | |
46 await cls.mixin.load(); | |
47 var span = new SpanElement(); | |
48 span.style.alignSelf = 'center'; | |
49 span.style.whiteSpace = 'pre'; | |
50 if (first) { | |
51 span.text = ' with '; | |
52 } else { | |
53 span.text = ', '; | |
54 } | |
55 classCell.children.add(span); | |
56 var mixinRef = new Element.tag('class-ref'); | |
57 mixinRef.ref = cls.mixin.typeClass; | |
58 mixinRef.style.alignSelf = 'center'; | |
59 classCell.children.add(mixinRef); | |
60 first = false; | |
61 } | |
62 } | |
63 | |
64 Future _addClass(Class cls) async { | |
65 var classCell = flexColumns[0]; | |
66 classCell.style.justifyContent = 'flex-start'; | |
67 var classRef = new Element.tag('class-ref'); | |
68 classRef.ref = cls; | |
69 classRef.style.alignSelf = 'center'; | |
70 classCell.children.add(classRef); | |
71 if (cls.superclass != null && cls.superclass.mixin != null) { | |
72 await _addMixins(cls); | |
73 } | |
74 if (cls.subclasses.isNotEmpty) { | |
75 var span = new SpanElement(); | |
76 span.style.paddingLeft = '.5em'; | |
77 span.style.alignSelf = 'center'; | |
78 int subclassCount = _indirectSubclassCount(cls) - 1; | |
79 if (subclassCount > 1) { | |
80 span.text = '($subclassCount subclasses)'; | |
81 } else { | |
82 span.text = '($subclassCount subclass)'; | |
83 } | |
84 classCell.children.add(span); | |
85 } | |
86 } | |
87 | |
88 void onShow() { | |
89 super.onShow(); | |
90 if (children.length == 0) { | |
91 _addChildren(cls.subclasses); | |
92 } | |
93 _addClass(cls); | |
94 } | |
95 | |
96 static int _indirectSubclassCount(var cls) { | |
97 int count = 0; | |
98 if (cls.mixin == null) { | |
99 // Don't count synthetic mixin classes in subclass count. | |
100 count++; | |
101 } | |
102 for (var subclass in cls.subclasses) { | |
103 count += _indirectSubclassCount(subclass); | |
104 } | |
105 return count; | |
106 } | |
107 | |
108 bool hasChildren() { | |
109 return cls.subclasses.isNotEmpty; | |
110 } | |
111 } | |
112 | 21 |
113 | 22 |
114 @CustomTag('class-tree') | 23 class ClassTreeElement extends HtmlElement implements Renderable{ |
115 class ClassTreeElement extends ObservatoryElement { | 24 static const tag = const Tag<ClassTreeElement>('class-tree', |
116 @observable Isolate isolate; | 25 dependencies: const [ClassRefElement.tag, |
| 26 NavBarElement.tag, |
| 27 NavIsolateMenuElement.tag, |
| 28 NavMenuElement.tag, |
| 29 NavNotifyElement.tag, |
| 30 NavTopMenuElement.tag, |
| 31 NavVMMenuElement.tag, |
| 32 VirtualTreeElement.tag]); |
117 | 33 |
118 TableTree tree; | 34 RenderingScheduler _r; |
| 35 |
| 36 Stream<RenderedEvent<ClassTreeElement>> get onRendered => _r.onRendered; |
| 37 |
| 38 M.VMRef _vm; |
| 39 M.IsolateRef _isolate; |
| 40 M.EventRepository _events; |
| 41 M.NotificationRepository _notifications; |
| 42 M.ClassRepository _classes; |
| 43 M.Class _object; |
| 44 final _subclasses = <String, Iterable<M.Class>>{}; |
| 45 final _mixins = <String, List<M.Instance>>{}; |
| 46 |
| 47 factory ClassTreeElement(M.VMRef vm, M.IsolateRef isolate, |
| 48 M.EventRepository events, |
| 49 M.NotificationRepository notifications, |
| 50 M.ClassRepository classes, |
| 51 {RenderingQueue queue}) { |
| 52 assert(vm != null); |
| 53 assert(isolate != null); |
| 54 assert(events != null); |
| 55 assert(notifications != null); |
| 56 assert(classes != null); |
| 57 ClassTreeElement e = document.createElement(tag.name); |
| 58 e._r = new RenderingScheduler(e, queue: queue); |
| 59 e._vm = vm; |
| 60 e._isolate = isolate; |
| 61 e._events = events; |
| 62 e._notifications = notifications; |
| 63 e._classes = classes; |
| 64 return e; |
| 65 } |
119 | 66 |
120 ClassTreeElement.created() : super.created(); | 67 ClassTreeElement.created() : super.created(); |
121 | 68 |
122 @override | 69 @override |
123 void attached() { | 70 void attached() { |
124 super.attached(); | 71 super.attached(); |
125 var tableBody = shadowRoot.querySelector('#tableTreeBody'); | 72 _refresh(); |
126 assert(tableBody != null); | 73 _r.enable(); |
127 tree = new TableTree(tableBody, 1); | 74 } |
128 if (isolate != null) { | 75 |
129 _update(isolate.objectClass); | 76 @override |
| 77 void detached() { |
| 78 super.detached(); |
| 79 children = []; |
| 80 _r.disable(notify: true); |
| 81 } |
| 82 |
| 83 VirtualTreeElement _tree; |
| 84 |
| 85 void render() { |
| 86 children = [ |
| 87 new NavBarElement(queue: _r.queue) |
| 88 ..children = [ |
| 89 new NavTopMenuElement(queue: _r.queue), |
| 90 new NavVMMenuElement(_vm, _events, queue: _r.queue), |
| 91 new NavIsolateMenuElement(_isolate, _events, queue: _r.queue), |
| 92 new NavMenuElement('class hierarchy', link: Uris.classTree(_isolate), |
| 93 last: true, queue: _r.queue), |
| 94 new NavNotifyElement(_notifications, queue: _r.queue) |
| 95 ], |
| 96 new DivElement() |
| 97 ..classes = ['content-centered'] |
| 98 ..children = [ |
| 99 new HeadingElement.h1()..text = 'Class Hierarchy', |
| 100 new BRElement(), new HRElement(), |
| 101 _object == null ? (new HeadingElement.h2()..text = 'Loading...') |
| 102 : _createTree() |
| 103 ] |
| 104 ]; |
| 105 } |
| 106 |
| 107 Element _createTree() { |
| 108 _tree = new VirtualTreeElement(_create, _update, _children, |
| 109 items: [_object], queue: _r.queue); |
| 110 _tree.expand(_object, autoExpandSingleChildNodes: true); |
| 111 return _tree; |
| 112 } |
| 113 |
| 114 Future _refresh() async { |
| 115 _object = null; |
| 116 _subclasses.clear(); |
| 117 _mixins.clear(); |
| 118 _object = await _register(await _classes.getObject()); |
| 119 _r.dirty(); |
| 120 } |
| 121 |
| 122 Future<M.Class> _register(M.Class cls) async { |
| 123 _subclasses[cls.id] = await Future.wait( |
| 124 (await Future.wait(cls.subclasses.map(_getActualChildrens))) |
| 125 .expand((f) => f) |
| 126 .map(_register) |
| 127 ); |
| 128 return cls; |
| 129 } |
| 130 |
| 131 Future<Iterable<M.Class>> _getActualChildrens(M.ClassRef ref) async { |
| 132 var cls = await _classes.get(ref.id); |
| 133 if (cls.isPatch) { |
| 134 return const []; |
| 135 } |
| 136 if (cls.mixin == null) { |
| 137 return [cls]; |
| 138 } |
| 139 return (await Future.wait(cls.subclasses.map(_getActualChildrens))) |
| 140 .expand((f) => f) |
| 141 ..forEach((subcls) { |
| 142 _mixins[subcls.id] = (_mixins[subcls.id] ?? [])..add(cls.mixin); |
| 143 }); |
| 144 } |
| 145 |
| 146 static Element _create(toggle) { |
| 147 return new DivElement()..classes = ['class-tree-item'] |
| 148 ..children = [ |
| 149 new SpanElement()..classes = ['lines'], |
| 150 new SpanElement()..classes = ['expander'] |
| 151 ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), |
| 152 new SpanElement()..classes = ['name'] |
| 153 ]; |
| 154 } |
| 155 |
| 156 void _update(HtmlElement el, M.Class cls, int index) { |
| 157 virtualTreeUpdateLines(el.children[0], index); |
| 158 if (cls.subclasses.isEmpty) { |
| 159 el.children[1].text = ''; |
| 160 } else { |
| 161 el.children[1].text = _tree.isExpanded(cls) ? '▼' : '►'; |
| 162 } |
| 163 el.children[2].children = [ |
| 164 new ClassRefElement(_isolate, cls, queue: _r.queue) |
| 165 ]; |
| 166 if (_mixins[cls.id] != null) { |
| 167 el.children[2].children.addAll(_createMixins(_mixins[cls.id])); |
130 } | 168 } |
131 } | 169 } |
132 | 170 |
133 isolateChanged(oldValue) { | 171 List<Element> _createMixins(List<M.Instance> types) { |
134 isolate.getClassHierarchy().then((objectClass) { | 172 final children = types.expand((type) => [ |
135 _update(objectClass); | 173 new SpanElement()..text = ', ', |
136 }); | 174 type.typeClass == null |
| 175 ? (new SpanElement()..text = type.name.split('<').first) |
| 176 : new ClassRefElement(_isolate, type.typeClass, queue: _r.queue) |
| 177 ]).toList(); |
| 178 children.first.text = ' with '; |
| 179 return children; |
137 } | 180 } |
138 | 181 |
139 void _update(Class root) { | 182 Iterable<M.Class> _children(M.Class cls) { |
140 try { | 183 return _subclasses[cls.id]; |
141 var rootRow = new ClassTreeRow(isolate, root, tree, null); | |
142 rootRow.children.add(new ClassTreeRow(isolate, root, tree, rootRow)); | |
143 tree.initialize(rootRow); | |
144 } catch (e, stackTrace) { | |
145 Logger.root.warning('_update', e, stackTrace); | |
146 } | |
147 // Check if we only have one node at the root and expand it. | |
148 if (tree.rows.length == 1) { | |
149 tree.toggle(tree.rows[0]); | |
150 } | |
151 notifyPropertyChange(#tree, null, tree); | |
152 } | 184 } |
153 } | 185 } |
OLD | NEW |