Index: runtime/observatory/lib/src/app/view_model.dart |
diff --git a/runtime/observatory/lib/src/app/view_model.dart b/runtime/observatory/lib/src/app/view_model.dart |
index 0be6fbeb487fac8503e9fe027c6fc8ff6084a5b6..43e4d50b793281ac89fb1fd805b40439e69e9c5f 100644 |
--- a/runtime/observatory/lib/src/app/view_model.dart |
+++ b/runtime/observatory/lib/src/app/view_model.dart |
@@ -7,26 +7,23 @@ part of app; |
abstract class TableTreeRow extends Observable { |
static const arrowRight = '\u2192'; |
static const arrowDownRight = '\u21b3'; |
- // Number of pixels each subtree is indented. |
- static const subtreeIndent = 16; |
+ // Number of ems each subtree is indented. |
+ static const subtreeIndent = 2; |
+ |
+ TableTreeRow(this.tree, TableTreeRow parent) : |
+ parent = parent, |
+ depth = parent != null ? parent.depth + 1 : 0 { |
+ } |
final TableTree tree; |
final TableTreeRow parent; |
final int depth; |
final List<TableTreeRow> children = new List<TableTreeRow>(); |
- final List<TableCellElement> tableColumns = new List<TableCellElement>(); |
+ final List<TableCellElement> _tableColumns = new List<TableCellElement>(); |
+ final List<DivElement> flexColumns = new List<DivElement>(); |
SpanElement _expander; |
TableRowElement _tr; |
- TableRowElement get tr { |
- assert(_tr != null); |
- return _tr; |
- } |
- |
- TableTreeRow(this.tree, TableTreeRow parent) : |
- parent = parent, |
- depth = parent != null ? parent.depth+1 : 0 { |
- } |
- |
+ TableRowElement get tr => _tr; |
bool _expanded = false; |
bool get expanded => _expanded; |
set expanded(bool expanded) { |
@@ -42,57 +39,103 @@ abstract class TableTreeRow extends Observable { |
} |
} |
- bool expandOrCollapse() { |
+ /// Fired when the tree row is being expanded. |
+ void _onExpand() { |
+ _updateExpanderView(); |
+ } |
+ |
+ /// Fired when the tree row is being collapsed. |
+ void _onCollapse() { |
+ for (var child in children) { |
+ child.onHide(); |
+ } |
+ _updateExpanderView(); |
+ } |
+ |
+ bool toggle() { |
expanded = !expanded; |
return expanded; |
} |
- bool hasChildren(); |
+ HtmlElement _makeColorBlock(String backgroundColor) { |
+ var colorBlock = new DivElement(); |
+ colorBlock.style.minWidth = '2px'; |
+ colorBlock.style.backgroundColor = backgroundColor; |
+ return colorBlock; |
+ } |
- String _backgroundColorClassForRow() { |
- const colors = const ['rowColor0', 'rowColor1', 'rowColor2', 'rowColor3', |
- 'rowColor4', 'rowColor5', 'rowColor6', 'rowColor7', |
- 'rowColor8']; |
- var index = (depth - 1) % colors.length; |
- return colors[index]; |
+ HtmlElement _makeExpander() { |
+ var expander = new SpanElement(); |
+ expander.style.minWidth = '1.5em'; |
+ expander.onClick.listen(onClick); |
+ return expander; |
} |
+ void onClick(Event e) { |
+ e.stopPropagation(); |
+ tree.toggle(this); |
+ } |
+ |
+ static const redColor = '#F44336'; |
+ static const blueColor = '#3F51B5'; |
+ static const purpleColor = '#673AB7'; |
+ static const greenColor = '#4CAF50'; |
+ static const orangeColor = '#FF9800'; |
+ static const lightGrayColor = '#FAFAFA'; |
+ |
void _buildRow() { |
+ const List backgroundColors = const [ |
+ purpleColor, |
+ redColor, |
+ greenColor, |
+ blueColor, |
+ orangeColor, |
+ ]; |
_tr = new TableRowElement(); |
for (var i = 0; i < tree.columnCount; i++) { |
var cell = _tr.insertCell(-1); |
- cell.classes.add(_backgroundColorClassForRow()); |
- tableColumns.add(cell); |
+ _tableColumns.add(cell); |
+ var flex = new DivElement(); |
+ flex.classes.add('flex-row'); |
+ cell.children.add(flex); |
+ flexColumns.add(flex); |
+ } |
+ var firstColumn = flexColumns[0]; |
+ _tableColumns[0].style.paddingLeft = '${(depth - 1) * subtreeIndent}em'; |
+ var backgroundColor = lightGrayColor; |
+ if (depth > 1) { |
+ var colorIndex = (depth - 1) % backgroundColors.length; |
+ backgroundColor = backgroundColors[colorIndex]; |
} |
- var firstColumn = tableColumns[0]; |
- var columnContainer = new DivElement(); |
- columnContainer.classes.add('flex-row'); |
- _expander = new SpanElement(); |
- _expander.style.display = 'inline-block'; |
- _expander.style.display = 'inline-block'; |
- _expander.style.minWidth = '1.5em'; |
- _expander.onClick.listen(onClick); |
- columnContainer.children.add(_expander); |
- firstColumn.style.paddingLeft = '${depth * subtreeIndent}px'; |
- firstColumn.children.add(columnContainer); |
- updateExpanderView(); |
+ var colorBlock = _makeColorBlock(backgroundColor); |
+ firstColumn.children.add(colorBlock); |
+ _expander = _makeExpander(); |
+ firstColumn.children.add(_expander); |
+ // Enable expansion by clicking anywhere on the first column. |
+ firstColumn.onClick.listen(onClick); |
+ _updateExpanderView(); |
} |
- void updateExpanderView() { |
+ void _updateExpanderView() { |
if (_expander == null) { |
return; |
} |
if (!hasChildren()) { |
_expander.style.visibility = 'hidden'; |
- _expander.style.cursor = 'auto'; |
+ _expander.classes.remove('pointer'); |
return; |
} else { |
_expander.style.visibility = 'visible'; |
- _expander.style.cursor = 'pointer'; |
+ _expander.classes.add('pointer'); |
} |
- _expander.text = expanded ? arrowDownRight : arrowRight; |
+ _expander.children.clear(); |
+ _expander.children.add(expanded ? |
+ new Element.tag('icon-expand-more') : |
+ new Element.tag('icon-chevron-right')); |
} |
+ bool hasChildren(); |
+ |
/// Fired when the tree row is being shown. |
/// Populate tr and add logical children here. |
void onShow() { |
@@ -102,32 +145,14 @@ abstract class TableTreeRow extends Observable { |
/// Fired when the tree row is being hidden. |
void onHide() { |
- assert(_tr != null); |
_tr = null; |
- tableColumns.clear(); |
_expander = null; |
- } |
- |
- /// Fired when the tree row is being expanded. |
- void _onExpand() { |
- for (var child in children) { |
- child.onShow(); |
- child.updateExpanderView(); |
+ if (_tableColumns != null) { |
+ _tableColumns.clear(); |
} |
- updateExpanderView(); |
- } |
- |
- /// Fired when the tree row is being collapsed. |
- void _onCollapse() { |
- for (var child in children) { |
- child.onHide(); |
+ if (flexColumns != null) { |
+ flexColumns.clear(); |
} |
- updateExpanderView(); |
- } |
- |
- void onClick(Event e) { |
- tree.toggle(this); |
- e.stopPropagation(); |
} |
} |
@@ -135,7 +160,7 @@ class TableTree extends Observable { |
final TableSectionElement tableBody; |
final List<TableTreeRow> rows = []; |
final int columnCount; |
- |
+ Future _pendingOperation; |
/// Create a table tree with column [headers]. |
TableTree(this.tableBody, this.columnCount); |
@@ -148,34 +173,64 @@ class TableTree extends Observable { |
void initialize(TableTreeRow root) { |
clear(); |
root.onShow(); |
- rows.addAll(root.children); |
- for (var i = 0; i < rows.length; i++) { |
- rows[i].onShow(); |
- tableBody.children.add(rows[i].tr); |
- } |
+ toggle(root); |
} |
/// Toggle expansion of row in tree. |
- void toggle(TableTreeRow row) { |
- if (row.expandOrCollapse()) { |
- _expand(row); |
+ toggle(TableTreeRow row) async { |
+ if (_pendingOperation != null) { |
+ return; |
+ } |
+ if (row.toggle()) { |
+ document.body.classes.add('busy'); |
+ _pendingOperation = _expand(row); |
+ await _pendingOperation; |
+ _pendingOperation = null; |
+ document.body.classes.remove('busy'); |
+ if (row.children.length == 1) { |
+ // Auto expand single child. |
+ await toggle(row.children[0]); |
+ } |
} else { |
- _collapse(row); |
+ document.body.classes.add('busy'); |
+ _pendingOperation = _collapse(row); |
+ await _pendingOperation; |
+ _pendingOperation = null; |
+ document.body.classes.remove('busy'); |
} |
} |
int _index(TableTreeRow row) => rows.indexOf(row); |
- void _expand(TableTreeRow row) { |
+ _insertRow(index, child) { |
+ rows.insert(index, child); |
+ tableBody.children.insert(index, child.tr); |
+ } |
+ |
+ _expand(TableTreeRow row) async { |
int index = _index(row); |
- assert(index != -1); |
- rows.insertAll(index + 1, row.children); |
- for (var i = 0; i < row.children.length; i++) { |
- tableBody.children.insert(index + i + 1, row.children[i].tr); |
+ if ((index == -1) && (rows.length != 0)) { |
+ return; |
+ } |
+ assert((index != -1) || (rows.length == 0)); |
+ var i = 0; |
+ var addPerIteration = 10; |
+ while (i < row.children.length) { |
+ await window.animationFrame; |
+ for (var j = 0; j < addPerIteration; j++) { |
+ if (i == row.children.length) { |
+ break; |
+ } |
+ var child = row.children[i]; |
+ child.onShow(); |
+ child._updateExpanderView(); |
+ _insertRow(index + i + 1, child); |
+ i++; |
+ } |
} |
} |
- void _collapse(TableTreeRow row) { |
+ _collapseSync(TableTreeRow row) { |
var childCount = row.children.length; |
if (childCount == 0) { |
return; |
@@ -183,18 +238,22 @@ class TableTree extends Observable { |
for (var i = 0; i < childCount; i++) { |
// Close all inner rows. |
if (row.children[i].expanded) { |
- _collapse(row.children[i]); |
+ _collapseSync(row.children[i]); |
} |
} |
// Collapse this row. |
row.expanded = false; |
// Remove all children. |
int index = _index(row); |
- rows.removeRange(index + 1, index + 1 + childCount); |
for (var i = 0; i < childCount; i++) { |
+ rows.removeAt(index + 1); |
tableBody.children.removeAt(index + 1); |
} |
} |
+ |
+ _collapse(TableTreeRow row) async { |
+ _collapseSync(row); |
+ } |
} |
typedef String ValueFormatter(dynamic value); |