• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {App} from '../index.mjs'
6
7import {FocusEvent, ToolTipEvent} from './events.mjs';
8import {groupBy, LazyTable} from './helper.mjs';
9import {CollapsableElement, DOM} from './helper.mjs';
10
11DOM.defineCustomElement('view/list-panel',
12                        (templateText) =>
13                            class ListPanel extends CollapsableElement {
14  _selectedLogEntries = [];
15  _displayedLogEntries = [];
16  _timeline;
17
18  _detailsClickHandler = this._handleDetailsClick.bind(this);
19  _logEntryClickHandler = this._handleLogEntryClick.bind(this);
20  _logEntryMouseOverHandler = this._logEntryMouseOverHandler.bind(this);
21
22  constructor() {
23    super(templateText);
24    this.groupKey.addEventListener('change', e => this.requestUpdate());
25    this.showAllRadio.onclick = _ => this._showEntries(this._timeline);
26    this.showTimerangeRadio.onclick = _ =>
27        this._showEntries(this._timeline.selectionOrSelf);
28    this.showSelectionRadio.onclick = _ =>
29        this._showEntries(this._selectedLogEntries);
30  }
31
32  static get observedAttributes() {
33    return ['title'];
34  }
35
36  attributeChangedCallback(name, oldValue, newValue) {
37    if (name == 'title') {
38      this.$('#title').innerHTML = newValue;
39    }
40  }
41
42  set timeline(timeline) {
43    console.assert(timeline !== undefined, 'timeline undefined!');
44    this._timeline = timeline;
45    this.$('.panel').style.display = timeline.isEmpty() ? 'none' : 'inherit';
46    this._initGroupKeySelect();
47  }
48
49  set selectedLogEntries(entries) {
50    if (entries === this._timeline) {
51      this.showAllRadio.click();
52    } else if (entries === this._timeline.selection) {
53      this.showTimerangeRadio.click();
54    } else {
55      this._selectedLogEntries = entries;
56      this.showSelectionRadio.click();
57    }
58  }
59
60  get entryClass() {
61    return this._timeline.at(0)?.constructor;
62  }
63
64  get groupKey() {
65    return this.$('#group-key');
66  }
67
68  get table() {
69    return this.$('#table');
70  }
71
72  get showAllRadio() {
73    return this.$('#show-all');
74  }
75
76  get showTimerangeRadio() {
77    return this.$('#show-timerange');
78  }
79
80  get showSelectionRadio() {
81    return this.$('#show-selection');
82  }
83
84  get _propertyNames() {
85    return this.entryClass?.propertyNames ?? [];
86  }
87
88  _initGroupKeySelect() {
89    const select = this.groupKey;
90    select.options.length = 0;
91    for (const propertyName of this._propertyNames) {
92      const option = DOM.element('option');
93      option.text = propertyName;
94      select.add(option);
95    }
96  }
97
98  _showEntries(entries) {
99    this._displayedLogEntries = entries;
100    this.requestUpdate();
101  }
102
103  _update() {
104    if (this._timeline.isEmpty()) return;
105    DOM.removeAllChildren(this.table);
106    if (this._displayedLogEntries.length == 0) return;
107    const propertyName = this.groupKey.selectedOptions[0].text;
108    const groups =
109        groupBy(this._displayedLogEntries, each => each[propertyName], true);
110    this._render(groups, this.table);
111  }
112
113  createSubgroups(group) {
114    const map = new Map();
115    const tempGroups = [];
116    for (let propertyName of this._propertyNames) {
117      map.set(
118          propertyName,
119          groupBy(group.entries, each => each[propertyName], true));
120    }
121    return map;
122  }
123
124  _handleLogEntryClick(e) {
125    const group = e.currentTarget.group;
126    this.dispatchEvent(new FocusEvent(group.key));
127  }
128
129  _logEntryMouseOverHandler(e) {
130    const group = e.currentTarget.group;
131    this.dispatchEvent(new ToolTipEvent(group.key, e.currentTarget));
132  }
133
134  _handleDetailsClick(event) {
135    event.stopPropagation();
136    const tr = event.target.parentNode;
137    const group = tr.group;
138    // Create subgroup in-place if the don't exist yet.
139    if (tr.groups === undefined) {
140      const groups = tr.groups = this.createSubgroups(group);
141      this.renderDrilldown(groups, tr);
142    }
143    const detailsTr = tr.nextSibling;
144    if (tr.classList.contains('open')) {
145      tr.classList.remove('open');
146      detailsTr.style.display = 'none';
147    } else {
148      tr.classList.add('open');
149      detailsTr.style.display = 'table-row';
150    }
151  }
152
153  renderDrilldown(groups, previousSibling) {
154    const tr = DOM.tr('entry-details');
155    tr.style.display = 'none';
156    // indent by one td.
157    tr.appendChild(DOM.td());
158    const td = DOM.td();
159    td.colSpan = 3;
160    groups.forEach((group, key) => {
161      this.renderDrilldownGroup(td, group, key);
162    });
163    tr.appendChild(td);
164    // Append the new TR after previousSibling.
165    previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling);
166  }
167
168  renderDrilldownGroup(td, groups, key) {
169    const div = DOM.div('drilldown-group-title');
170    div.textContent = `Grouped by ${key}: ${groups[0]?.parentTotal ?? 0}#`;
171    td.appendChild(div);
172    const table = DOM.table();
173    this._render(groups, table, false)
174    td.appendChild(table);
175  }
176
177  _render(groups, table) {
178    let last;
179    new LazyTable(table, groups, group => {
180      last = group;
181      const tr = DOM.tr();
182      tr.group = group;
183      const details = tr.appendChild(DOM.td('', 'toggle'));
184      details.onclick = this._detailsClickHandler;
185      tr.appendChild(DOM.td(`${group.percent.toFixed(2)}%`, 'percentage'));
186      tr.appendChild(DOM.td(group.length, 'count'));
187      const valueTd = tr.appendChild(DOM.td(group.key?.toString(), 'key'));
188      if (App.isClickable(group.key)) {
189        tr.onclick = this._logEntryClickHandler;
190        tr.onmouseover = this._logEntryMouseOverHandler;
191        valueTd.classList.add('clickable');
192      }
193      return tr;
194    }, 10);
195  }
196});
197