11cb0ef41Sopenharmony_ci// Copyright 2020 the V8 project authors. All rights reserved. 21cb0ef41Sopenharmony_ci// Use of this source code is governed by a BSD-style license that can be 31cb0ef41Sopenharmony_ci// found in the LICENSE file. 41cb0ef41Sopenharmony_ci 51cb0ef41Sopenharmony_ciimport {kChunkHeight, kChunkVisualWidth, kChunkWidth} from '../../log/map.mjs'; 61cb0ef41Sopenharmony_ciimport {SelectionEvent, SelectTimeEvent, SynchronizeSelectionEvent, ToolTipEvent,} from '../events.mjs'; 71cb0ef41Sopenharmony_ciimport {CSSColor, delay, DOM, formatDurationMicros, V8CustomElement} from '../helper.mjs'; 81cb0ef41Sopenharmony_ci 91cb0ef41Sopenharmony_ciexport const kTimelineHeight = 200; 101cb0ef41Sopenharmony_ci 111cb0ef41Sopenharmony_ciexport class TimelineTrackBase extends V8CustomElement { 121cb0ef41Sopenharmony_ci _timeline; 131cb0ef41Sopenharmony_ci _nofChunks = 500; 141cb0ef41Sopenharmony_ci _chunks = []; 151cb0ef41Sopenharmony_ci _selectedEntry; 161cb0ef41Sopenharmony_ci _focusedEntry; 171cb0ef41Sopenharmony_ci _timeToPixel; 181cb0ef41Sopenharmony_ci _timeStartPixelOffset; 191cb0ef41Sopenharmony_ci _legend; 201cb0ef41Sopenharmony_ci _lastContentWidth = 0; 211cb0ef41Sopenharmony_ci 221cb0ef41Sopenharmony_ci _cachedTimelineBoundingClientRect; 231cb0ef41Sopenharmony_ci _cachedTimelineScrollLeft; 241cb0ef41Sopenharmony_ci 251cb0ef41Sopenharmony_ci constructor(templateText) { 261cb0ef41Sopenharmony_ci super(templateText); 271cb0ef41Sopenharmony_ci this._selectionHandler = new SelectionHandler(this); 281cb0ef41Sopenharmony_ci this._legend = new Legend(this.$('#legendTable')); 291cb0ef41Sopenharmony_ci 301cb0ef41Sopenharmony_ci this.timelineChunks = this.$('#timelineChunks'); 311cb0ef41Sopenharmony_ci this.timelineSamples = this.$('#timelineSamples'); 321cb0ef41Sopenharmony_ci this.timelineNode = this.$('#timeline'); 331cb0ef41Sopenharmony_ci this.toolTipTargetNode = this.$('#toolTipTarget'); 341cb0ef41Sopenharmony_ci this.hitPanelNode = this.$('#hitPanel'); 351cb0ef41Sopenharmony_ci this.timelineAnnotationsNode = this.$('#timelineAnnotations'); 361cb0ef41Sopenharmony_ci this.timelineMarkersNode = this.$('#timelineMarkers'); 371cb0ef41Sopenharmony_ci this._scalableContentNode = this.$('#scalableContent'); 381cb0ef41Sopenharmony_ci this.isLocked = false; 391cb0ef41Sopenharmony_ci this.setAttribute('tabindex', 0); 401cb0ef41Sopenharmony_ci } 411cb0ef41Sopenharmony_ci 421cb0ef41Sopenharmony_ci _initEventListeners() { 431cb0ef41Sopenharmony_ci this._legend.onFilter = this._handleFilterTimeline.bind(this); 441cb0ef41Sopenharmony_ci this.timelineNode.addEventListener( 451cb0ef41Sopenharmony_ci 'scroll', this._handleTimelineScroll.bind(this)); 461cb0ef41Sopenharmony_ci this.hitPanelNode.onclick = this._handleClick.bind(this); 471cb0ef41Sopenharmony_ci this.hitPanelNode.ondblclick = this._handleDoubleClick.bind(this); 481cb0ef41Sopenharmony_ci this.hitPanelNode.onmousemove = this._handleMouseMove.bind(this); 491cb0ef41Sopenharmony_ci this.$('#selectionForeground') 501cb0ef41Sopenharmony_ci .addEventListener('mousemove', this._handleMouseMove.bind(this)); 511cb0ef41Sopenharmony_ci window.addEventListener('resize', () => this._resetCachedDimensions()); 521cb0ef41Sopenharmony_ci } 531cb0ef41Sopenharmony_ci 541cb0ef41Sopenharmony_ci static get observedAttributes() { 551cb0ef41Sopenharmony_ci return ['title']; 561cb0ef41Sopenharmony_ci } 571cb0ef41Sopenharmony_ci 581cb0ef41Sopenharmony_ci attributeChangedCallback(name, oldValue, newValue) { 591cb0ef41Sopenharmony_ci if (name == 'title') { 601cb0ef41Sopenharmony_ci this.$('#title').innerHTML = newValue; 611cb0ef41Sopenharmony_ci } 621cb0ef41Sopenharmony_ci } 631cb0ef41Sopenharmony_ci 641cb0ef41Sopenharmony_ci _handleFilterTimeline(type) { 651cb0ef41Sopenharmony_ci this._updateChunks(); 661cb0ef41Sopenharmony_ci this._legend.update(true); 671cb0ef41Sopenharmony_ci } 681cb0ef41Sopenharmony_ci 691cb0ef41Sopenharmony_ci set data(timeline) { 701cb0ef41Sopenharmony_ci console.assert(timeline); 711cb0ef41Sopenharmony_ci if (!this._timeline) this._initEventListeners(); 721cb0ef41Sopenharmony_ci this._timeline = timeline; 731cb0ef41Sopenharmony_ci this._legend.timeline = timeline; 741cb0ef41Sopenharmony_ci this.$('.content').style.display = timeline.isEmpty() ? 'none' : 'relative'; 751cb0ef41Sopenharmony_ci this._updateChunks(); 761cb0ef41Sopenharmony_ci } 771cb0ef41Sopenharmony_ci 781cb0ef41Sopenharmony_ci set timeSelection({start, end, focus = false, zoom = false}) { 791cb0ef41Sopenharmony_ci this._selectionHandler.timeSelection = {start, end}; 801cb0ef41Sopenharmony_ci this.updateSelection(); 811cb0ef41Sopenharmony_ci if (focus || zoom) { 821cb0ef41Sopenharmony_ci if (!Number.isFinite(start) || !Number.isFinite(end)) { 831cb0ef41Sopenharmony_ci throw new Error('Invalid number ranges'); 841cb0ef41Sopenharmony_ci } 851cb0ef41Sopenharmony_ci if (focus) { 861cb0ef41Sopenharmony_ci this.currentTime = (start + end) / 2; 871cb0ef41Sopenharmony_ci } 881cb0ef41Sopenharmony_ci if (zoom) { 891cb0ef41Sopenharmony_ci const margin = 0.2; 901cb0ef41Sopenharmony_ci const newVisibleTime = (end - start) * (1 + 2 * margin); 911cb0ef41Sopenharmony_ci const currentVisibleTime = 921cb0ef41Sopenharmony_ci this._cachedTimelineBoundingClientRect.width / this._timeToPixel; 931cb0ef41Sopenharmony_ci this.nofChunks = this.nofChunks * (currentVisibleTime / newVisibleTime); 941cb0ef41Sopenharmony_ci } 951cb0ef41Sopenharmony_ci } 961cb0ef41Sopenharmony_ci } 971cb0ef41Sopenharmony_ci 981cb0ef41Sopenharmony_ci updateSelection() { 991cb0ef41Sopenharmony_ci this._selectionHandler.update(); 1001cb0ef41Sopenharmony_ci this._legend.update(); 1011cb0ef41Sopenharmony_ci } 1021cb0ef41Sopenharmony_ci 1031cb0ef41Sopenharmony_ci get _timelineBoundingClientRect() { 1041cb0ef41Sopenharmony_ci if (this._cachedTimelineBoundingClientRect === undefined) { 1051cb0ef41Sopenharmony_ci this._cachedTimelineBoundingClientRect = 1061cb0ef41Sopenharmony_ci this.timelineNode.getBoundingClientRect(); 1071cb0ef41Sopenharmony_ci } 1081cb0ef41Sopenharmony_ci return this._cachedTimelineBoundingClientRect; 1091cb0ef41Sopenharmony_ci } 1101cb0ef41Sopenharmony_ci 1111cb0ef41Sopenharmony_ci get _timelineScrollLeft() { 1121cb0ef41Sopenharmony_ci if (this._cachedTimelineScrollLeft === undefined) { 1131cb0ef41Sopenharmony_ci this._cachedTimelineScrollLeft = this.timelineNode.scrollLeft; 1141cb0ef41Sopenharmony_ci } 1151cb0ef41Sopenharmony_ci return this._cachedTimelineScrollLeft; 1161cb0ef41Sopenharmony_ci } 1171cb0ef41Sopenharmony_ci 1181cb0ef41Sopenharmony_ci _resetCachedDimensions() { 1191cb0ef41Sopenharmony_ci this._cachedTimelineBoundingClientRect = undefined; 1201cb0ef41Sopenharmony_ci this._cachedTimelineScrollLeft = undefined; 1211cb0ef41Sopenharmony_ci } 1221cb0ef41Sopenharmony_ci 1231cb0ef41Sopenharmony_ci // Maps the clicked x position to the x position on timeline 1241cb0ef41Sopenharmony_ci positionOnTimeline(pagePosX) { 1251cb0ef41Sopenharmony_ci let rect = this._timelineBoundingClientRect; 1261cb0ef41Sopenharmony_ci let posClickedX = pagePosX - rect.left + this._timelineScrollLeft; 1271cb0ef41Sopenharmony_ci return posClickedX; 1281cb0ef41Sopenharmony_ci } 1291cb0ef41Sopenharmony_ci 1301cb0ef41Sopenharmony_ci positionToTime(pagePosX) { 1311cb0ef41Sopenharmony_ci return this.relativePositionToTime(this.positionOnTimeline(pagePosX)); 1321cb0ef41Sopenharmony_ci } 1331cb0ef41Sopenharmony_ci 1341cb0ef41Sopenharmony_ci relativePositionToTime(timelineRelativeX) { 1351cb0ef41Sopenharmony_ci const timelineAbsoluteX = timelineRelativeX + this._timeStartPixelOffset; 1361cb0ef41Sopenharmony_ci return (timelineAbsoluteX / this._timeToPixel) | 0; 1371cb0ef41Sopenharmony_ci } 1381cb0ef41Sopenharmony_ci 1391cb0ef41Sopenharmony_ci timeToPosition(time) { 1401cb0ef41Sopenharmony_ci let relativePosX = time * this._timeToPixel; 1411cb0ef41Sopenharmony_ci relativePosX -= this._timeStartPixelOffset; 1421cb0ef41Sopenharmony_ci return relativePosX; 1431cb0ef41Sopenharmony_ci } 1441cb0ef41Sopenharmony_ci 1451cb0ef41Sopenharmony_ci set nofChunks(count) { 1461cb0ef41Sopenharmony_ci const centerTime = this.currentTime; 1471cb0ef41Sopenharmony_ci const kMinNofChunks = 100; 1481cb0ef41Sopenharmony_ci if (count < kMinNofChunks) count = kMinNofChunks; 1491cb0ef41Sopenharmony_ci const kMaxNofChunks = 10 * 1000; 1501cb0ef41Sopenharmony_ci if (count > kMaxNofChunks) count = kMaxNofChunks; 1511cb0ef41Sopenharmony_ci this._nofChunks = count | 0; 1521cb0ef41Sopenharmony_ci this._updateChunks(); 1531cb0ef41Sopenharmony_ci this.currentTime = centerTime; 1541cb0ef41Sopenharmony_ci } 1551cb0ef41Sopenharmony_ci 1561cb0ef41Sopenharmony_ci get nofChunks() { 1571cb0ef41Sopenharmony_ci return this._nofChunks; 1581cb0ef41Sopenharmony_ci } 1591cb0ef41Sopenharmony_ci 1601cb0ef41Sopenharmony_ci _updateChunks() { 1611cb0ef41Sopenharmony_ci this._chunks = undefined; 1621cb0ef41Sopenharmony_ci this._updateDimensions(); 1631cb0ef41Sopenharmony_ci this.requestUpdate(); 1641cb0ef41Sopenharmony_ci } 1651cb0ef41Sopenharmony_ci 1661cb0ef41Sopenharmony_ci get chunks() { 1671cb0ef41Sopenharmony_ci if (this._chunks?.length != this.nofChunks) { 1681cb0ef41Sopenharmony_ci this._chunks = 1691cb0ef41Sopenharmony_ci this._timeline.chunks(this.nofChunks, this._legend.filterPredicate); 1701cb0ef41Sopenharmony_ci console.assert(this._chunks.length == this._nofChunks); 1711cb0ef41Sopenharmony_ci } 1721cb0ef41Sopenharmony_ci return this._chunks; 1731cb0ef41Sopenharmony_ci } 1741cb0ef41Sopenharmony_ci 1751cb0ef41Sopenharmony_ci set selectedEntry(value) { 1761cb0ef41Sopenharmony_ci this._selectedEntry = value; 1771cb0ef41Sopenharmony_ci } 1781cb0ef41Sopenharmony_ci 1791cb0ef41Sopenharmony_ci get selectedEntry() { 1801cb0ef41Sopenharmony_ci return this._selectedEntry; 1811cb0ef41Sopenharmony_ci } 1821cb0ef41Sopenharmony_ci 1831cb0ef41Sopenharmony_ci get focusedEntry() { 1841cb0ef41Sopenharmony_ci return this._focusedEntry; 1851cb0ef41Sopenharmony_ci } 1861cb0ef41Sopenharmony_ci 1871cb0ef41Sopenharmony_ci set focusedEntry(entry) { 1881cb0ef41Sopenharmony_ci this._focusedEntry = entry; 1891cb0ef41Sopenharmony_ci if (entry) this._drawAnnotations(entry); 1901cb0ef41Sopenharmony_ci } 1911cb0ef41Sopenharmony_ci 1921cb0ef41Sopenharmony_ci set scrollLeft(offset) { 1931cb0ef41Sopenharmony_ci this.timelineNode.scrollLeft = offset; 1941cb0ef41Sopenharmony_ci this._cachedTimelineScrollLeft = offset; 1951cb0ef41Sopenharmony_ci } 1961cb0ef41Sopenharmony_ci 1971cb0ef41Sopenharmony_ci get scrollLeft() { 1981cb0ef41Sopenharmony_ci return this._cachedTimelineScrollLeft; 1991cb0ef41Sopenharmony_ci } 2001cb0ef41Sopenharmony_ci 2011cb0ef41Sopenharmony_ci set currentTime(time) { 2021cb0ef41Sopenharmony_ci const position = this.timeToPosition(time); 2031cb0ef41Sopenharmony_ci const centerOffset = this._timelineBoundingClientRect.width / 2; 2041cb0ef41Sopenharmony_ci this.scrollLeft = Math.max(0, position - centerOffset); 2051cb0ef41Sopenharmony_ci } 2061cb0ef41Sopenharmony_ci 2071cb0ef41Sopenharmony_ci get currentTime() { 2081cb0ef41Sopenharmony_ci const centerOffset = 2091cb0ef41Sopenharmony_ci this._timelineBoundingClientRect.width / 2 + this.scrollLeft; 2101cb0ef41Sopenharmony_ci return this.relativePositionToTime(centerOffset); 2111cb0ef41Sopenharmony_ci } 2121cb0ef41Sopenharmony_ci 2131cb0ef41Sopenharmony_ci handleEntryTypeDoubleClick(e) { 2141cb0ef41Sopenharmony_ci this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries)); 2151cb0ef41Sopenharmony_ci } 2161cb0ef41Sopenharmony_ci 2171cb0ef41Sopenharmony_ci timelineIndicatorMove(offset) { 2181cb0ef41Sopenharmony_ci this.timelineNode.scrollLeft += offset; 2191cb0ef41Sopenharmony_ci this._cachedTimelineScrollLeft = undefined; 2201cb0ef41Sopenharmony_ci } 2211cb0ef41Sopenharmony_ci 2221cb0ef41Sopenharmony_ci _handleTimelineScroll(e) { 2231cb0ef41Sopenharmony_ci let scrollLeft = e.currentTarget.scrollLeft; 2241cb0ef41Sopenharmony_ci this._cachedTimelineScrollLeft = scrollLeft; 2251cb0ef41Sopenharmony_ci this.dispatchEvent(new CustomEvent( 2261cb0ef41Sopenharmony_ci 'scrolltrack', {bubbles: true, composed: true, detail: scrollLeft})); 2271cb0ef41Sopenharmony_ci } 2281cb0ef41Sopenharmony_ci 2291cb0ef41Sopenharmony_ci _updateDimensions() { 2301cb0ef41Sopenharmony_ci // No data in this timeline, no need to resize 2311cb0ef41Sopenharmony_ci if (!this._timeline) return; 2321cb0ef41Sopenharmony_ci 2331cb0ef41Sopenharmony_ci const centerOffset = this._timelineBoundingClientRect.width / 2; 2341cb0ef41Sopenharmony_ci const time = 2351cb0ef41Sopenharmony_ci this.relativePositionToTime(this._timelineScrollLeft + centerOffset); 2361cb0ef41Sopenharmony_ci const start = this._timeline.startTime; 2371cb0ef41Sopenharmony_ci const width = this._nofChunks * kChunkWidth; 2381cb0ef41Sopenharmony_ci this._lastContentWidth = parseInt(this.timelineMarkersNode.style.width); 2391cb0ef41Sopenharmony_ci this._timeToPixel = width / this._timeline.duration(); 2401cb0ef41Sopenharmony_ci this._timeStartPixelOffset = start * this._timeToPixel; 2411cb0ef41Sopenharmony_ci this.timelineChunks.style.width = `${width}px`; 2421cb0ef41Sopenharmony_ci this.timelineMarkersNode.style.width = `${width}px`; 2431cb0ef41Sopenharmony_ci this.timelineAnnotationsNode.style.width = `${width}px`; 2441cb0ef41Sopenharmony_ci this.hitPanelNode.style.width = `${width}px`; 2451cb0ef41Sopenharmony_ci this._drawMarkers(); 2461cb0ef41Sopenharmony_ci this._selectionHandler.update(); 2471cb0ef41Sopenharmony_ci this._scaleContent(width); 2481cb0ef41Sopenharmony_ci this._cachedTimelineScrollLeft = this.timelineNode.scrollLeft = 2491cb0ef41Sopenharmony_ci this.timeToPosition(time) - centerOffset; 2501cb0ef41Sopenharmony_ci } 2511cb0ef41Sopenharmony_ci 2521cb0ef41Sopenharmony_ci _scaleContent(currentWidth) { 2531cb0ef41Sopenharmony_ci if (!this._lastContentWidth) return; 2541cb0ef41Sopenharmony_ci const ratio = currentWidth / this._lastContentWidth; 2551cb0ef41Sopenharmony_ci this._scalableContentNode.style.transform = `scale(${ratio}, 1)`; 2561cb0ef41Sopenharmony_ci } 2571cb0ef41Sopenharmony_ci 2581cb0ef41Sopenharmony_ci _adjustHeight(height) { 2591cb0ef41Sopenharmony_ci const dataHeight = Math.max(height, 200); 2601cb0ef41Sopenharmony_ci const viewHeight = Math.min(dataHeight, 400); 2611cb0ef41Sopenharmony_ci this.style.setProperty('--data-height', dataHeight + 'px'); 2621cb0ef41Sopenharmony_ci this.style.setProperty('--view-height', viewHeight + 'px'); 2631cb0ef41Sopenharmony_ci this.timelineNode.style.overflowY = 2641cb0ef41Sopenharmony_ci (height > kTimelineHeight) ? 'scroll' : 'hidden'; 2651cb0ef41Sopenharmony_ci } 2661cb0ef41Sopenharmony_ci 2671cb0ef41Sopenharmony_ci _update() { 2681cb0ef41Sopenharmony_ci this._legend.update(); 2691cb0ef41Sopenharmony_ci this._drawContent().then(() => this._drawAnnotations(this.selectedEntry)); 2701cb0ef41Sopenharmony_ci this._resetCachedDimensions(); 2711cb0ef41Sopenharmony_ci } 2721cb0ef41Sopenharmony_ci 2731cb0ef41Sopenharmony_ci async _drawContent() { 2741cb0ef41Sopenharmony_ci if (this._timeline.isEmpty()) return; 2751cb0ef41Sopenharmony_ci await delay(5); 2761cb0ef41Sopenharmony_ci const chunks = this.chunks; 2771cb0ef41Sopenharmony_ci const max = chunks.max(each => each.size()); 2781cb0ef41Sopenharmony_ci let buffer = ''; 2791cb0ef41Sopenharmony_ci for (let i = 0; i < chunks.length; i++) { 2801cb0ef41Sopenharmony_ci const chunk = chunks[i]; 2811cb0ef41Sopenharmony_ci const height = (chunk.size() / max * kChunkHeight); 2821cb0ef41Sopenharmony_ci chunk.height = height; 2831cb0ef41Sopenharmony_ci if (chunk.isEmpty()) continue; 2841cb0ef41Sopenharmony_ci buffer += '<g>'; 2851cb0ef41Sopenharmony_ci buffer += this._drawChunk(i, chunk); 2861cb0ef41Sopenharmony_ci buffer += '</g>' 2871cb0ef41Sopenharmony_ci } 2881cb0ef41Sopenharmony_ci this._scalableContentNode.innerHTML = buffer; 2891cb0ef41Sopenharmony_ci this._scalableContentNode.style.transform = 'scale(1, 1)'; 2901cb0ef41Sopenharmony_ci } 2911cb0ef41Sopenharmony_ci 2921cb0ef41Sopenharmony_ci _drawChunk(chunkIndex, chunk) { 2931cb0ef41Sopenharmony_ci const groups = chunk.getBreakdown(event => event.type); 2941cb0ef41Sopenharmony_ci let buffer = ''; 2951cb0ef41Sopenharmony_ci const kHeight = chunk.height; 2961cb0ef41Sopenharmony_ci let lastHeight = kTimelineHeight; 2971cb0ef41Sopenharmony_ci for (let i = 0; i < groups.length; i++) { 2981cb0ef41Sopenharmony_ci const group = groups[i]; 2991cb0ef41Sopenharmony_ci if (group.length == 0) break; 3001cb0ef41Sopenharmony_ci const height = (group.length / chunk.size() * kHeight) | 0; 3011cb0ef41Sopenharmony_ci lastHeight -= height; 3021cb0ef41Sopenharmony_ci const color = this._legend.colorForType(group.key); 3031cb0ef41Sopenharmony_ci buffer += `<rect x=${chunkIndex * kChunkWidth} y=${lastHeight} height=${ 3041cb0ef41Sopenharmony_ci height} width=${kChunkVisualWidth} fill=${color} />` 3051cb0ef41Sopenharmony_ci } 3061cb0ef41Sopenharmony_ci return buffer; 3071cb0ef41Sopenharmony_ci } 3081cb0ef41Sopenharmony_ci 3091cb0ef41Sopenharmony_ci _drawMarkers() { 3101cb0ef41Sopenharmony_ci // Put a time marker roughly every 20 chunks. 3111cb0ef41Sopenharmony_ci const expected = this._timeline.duration() / this._nofChunks * 20; 3121cb0ef41Sopenharmony_ci let interval = (10 ** Math.floor(Math.log10(expected))); 3131cb0ef41Sopenharmony_ci let correction = Math.log10(expected / interval); 3141cb0ef41Sopenharmony_ci correction = (correction < 0.33) ? 1 : (correction < 0.75) ? 2.5 : 5; 3151cb0ef41Sopenharmony_ci interval *= correction; 3161cb0ef41Sopenharmony_ci 3171cb0ef41Sopenharmony_ci const start = this._timeline.startTime; 3181cb0ef41Sopenharmony_ci let time = start; 3191cb0ef41Sopenharmony_ci let buffer = ''; 3201cb0ef41Sopenharmony_ci while (time < this._timeline.endTime) { 3211cb0ef41Sopenharmony_ci const delta = time - start; 3221cb0ef41Sopenharmony_ci const text = `${(delta / 1000) | 0} ms`; 3231cb0ef41Sopenharmony_ci const x = (delta * this._timeToPixel) | 0; 3241cb0ef41Sopenharmony_ci buffer += `<text x=${x - 2} y=0 class=markerText >${text}</text>` 3251cb0ef41Sopenharmony_ci buffer += 3261cb0ef41Sopenharmony_ci `<line x1=${x} x2=${x} y1=12 y2=2000 dy=100% class=markerLine />` 3271cb0ef41Sopenharmony_ci time += interval; 3281cb0ef41Sopenharmony_ci } 3291cb0ef41Sopenharmony_ci this.timelineMarkersNode.innerHTML = buffer; 3301cb0ef41Sopenharmony_ci } 3311cb0ef41Sopenharmony_ci 3321cb0ef41Sopenharmony_ci _drawAnnotations(logEntry, time) { 3331cb0ef41Sopenharmony_ci if (!this._focusedEntry) return; 3341cb0ef41Sopenharmony_ci this._drawEntryMark(this._focusedEntry); 3351cb0ef41Sopenharmony_ci } 3361cb0ef41Sopenharmony_ci 3371cb0ef41Sopenharmony_ci _drawEntryMark(entry) { 3381cb0ef41Sopenharmony_ci const [x, y] = this._positionForEntry(entry); 3391cb0ef41Sopenharmony_ci const color = this._legend.colorForType(entry.type); 3401cb0ef41Sopenharmony_ci const mark = 3411cb0ef41Sopenharmony_ci `<circle cx=${x} cy=${y} r=3 stroke=${color} class=annotationPoint />`; 3421cb0ef41Sopenharmony_ci this.timelineAnnotationsNode.innerHTML = mark; 3431cb0ef41Sopenharmony_ci } 3441cb0ef41Sopenharmony_ci 3451cb0ef41Sopenharmony_ci _handleUnlockedMouseEvent(event) { 3461cb0ef41Sopenharmony_ci this._focusedEntry = this._getEntryForEvent(event); 3471cb0ef41Sopenharmony_ci if (!this._focusedEntry) return false; 3481cb0ef41Sopenharmony_ci this._updateToolTip(event); 3491cb0ef41Sopenharmony_ci const time = this.positionToTime(event.pageX); 3501cb0ef41Sopenharmony_ci this._drawAnnotations(this._focusedEntry, time); 3511cb0ef41Sopenharmony_ci } 3521cb0ef41Sopenharmony_ci 3531cb0ef41Sopenharmony_ci _updateToolTip(event) { 3541cb0ef41Sopenharmony_ci if (!this._focusedEntry) return false; 3551cb0ef41Sopenharmony_ci this.dispatchEvent( 3561cb0ef41Sopenharmony_ci new ToolTipEvent(this._focusedEntry, this.toolTipTargetNode)); 3571cb0ef41Sopenharmony_ci event.stopImmediatePropagation(); 3581cb0ef41Sopenharmony_ci } 3591cb0ef41Sopenharmony_ci 3601cb0ef41Sopenharmony_ci _handleClick(event) { 3611cb0ef41Sopenharmony_ci if (event.button !== 0) return; 3621cb0ef41Sopenharmony_ci if (event.target === this.timelineChunks) return; 3631cb0ef41Sopenharmony_ci this.isLocked = !this.isLocked; 3641cb0ef41Sopenharmony_ci // Do this unconditionally since we want the tooltip to be update to the 3651cb0ef41Sopenharmony_ci // latest locked state. 3661cb0ef41Sopenharmony_ci this._handleUnlockedMouseEvent(event); 3671cb0ef41Sopenharmony_ci return false; 3681cb0ef41Sopenharmony_ci } 3691cb0ef41Sopenharmony_ci 3701cb0ef41Sopenharmony_ci _handleDoubleClick(event) { 3711cb0ef41Sopenharmony_ci if (event.button !== 0) return; 3721cb0ef41Sopenharmony_ci this._selectionHandler.clearSelection(); 3731cb0ef41Sopenharmony_ci const time = this.positionToTime(event.pageX); 3741cb0ef41Sopenharmony_ci const chunk = this._getChunkForEvent(event) 3751cb0ef41Sopenharmony_ci if (!chunk) return; 3761cb0ef41Sopenharmony_ci event.stopImmediatePropagation(); 3771cb0ef41Sopenharmony_ci this.dispatchEvent(new SelectTimeEvent(chunk.start, chunk.end)); 3781cb0ef41Sopenharmony_ci return false; 3791cb0ef41Sopenharmony_ci } 3801cb0ef41Sopenharmony_ci 3811cb0ef41Sopenharmony_ci _handleMouseMove(event) { 3821cb0ef41Sopenharmony_ci if (event.button !== 0) return; 3831cb0ef41Sopenharmony_ci if (this._selectionHandler.isSelecting) return false; 3841cb0ef41Sopenharmony_ci if (this.isLocked && this._focusedEntry) { 3851cb0ef41Sopenharmony_ci this._updateToolTip(event); 3861cb0ef41Sopenharmony_ci return false; 3871cb0ef41Sopenharmony_ci } 3881cb0ef41Sopenharmony_ci this._handleUnlockedMouseEvent(event); 3891cb0ef41Sopenharmony_ci } 3901cb0ef41Sopenharmony_ci 3911cb0ef41Sopenharmony_ci _getChunkForEvent(event) { 3921cb0ef41Sopenharmony_ci const time = this.positionToTime(event.pageX); 3931cb0ef41Sopenharmony_ci return this._chunkForTime(time); 3941cb0ef41Sopenharmony_ci } 3951cb0ef41Sopenharmony_ci 3961cb0ef41Sopenharmony_ci _chunkForTime(time) { 3971cb0ef41Sopenharmony_ci const chunkIndex = ((time - this._timeline.startTime) / 3981cb0ef41Sopenharmony_ci this._timeline.duration() * this._nofChunks) | 3991cb0ef41Sopenharmony_ci 0; 4001cb0ef41Sopenharmony_ci return this.chunks[chunkIndex]; 4011cb0ef41Sopenharmony_ci } 4021cb0ef41Sopenharmony_ci 4031cb0ef41Sopenharmony_ci _positionForEntry(entry) { 4041cb0ef41Sopenharmony_ci const chunk = this._chunkForTime(entry.time); 4051cb0ef41Sopenharmony_ci if (chunk === undefined) return [-1, -1]; 4061cb0ef41Sopenharmony_ci const xFrom = (chunk.index * kChunkWidth + kChunkVisualWidth / 2) | 0; 4071cb0ef41Sopenharmony_ci const yFrom = kTimelineHeight - chunk.yOffset(entry) | 0; 4081cb0ef41Sopenharmony_ci return [xFrom, yFrom]; 4091cb0ef41Sopenharmony_ci } 4101cb0ef41Sopenharmony_ci 4111cb0ef41Sopenharmony_ci _getEntryForEvent(event) { 4121cb0ef41Sopenharmony_ci const chunk = this._getChunkForEvent(event); 4131cb0ef41Sopenharmony_ci if (chunk?.isEmpty() ?? true) return false; 4141cb0ef41Sopenharmony_ci const relativeIndex = Math.round( 4151cb0ef41Sopenharmony_ci (kTimelineHeight - event.layerY) / chunk.height * (chunk.size() - 1)); 4161cb0ef41Sopenharmony_ci if (relativeIndex > chunk.size()) return false; 4171cb0ef41Sopenharmony_ci const logEntry = chunk.at(relativeIndex); 4181cb0ef41Sopenharmony_ci const style = this.toolTipTargetNode.style; 4191cb0ef41Sopenharmony_ci style.left = `${chunk.index * kChunkWidth}px`; 4201cb0ef41Sopenharmony_ci style.top = `${kTimelineHeight - chunk.height}px`; 4211cb0ef41Sopenharmony_ci style.height = `${chunk.height}px`; 4221cb0ef41Sopenharmony_ci style.width = `${kChunkVisualWidth}px`; 4231cb0ef41Sopenharmony_ci return logEntry; 4241cb0ef41Sopenharmony_ci } 4251cb0ef41Sopenharmony_ci}; 4261cb0ef41Sopenharmony_ci 4271cb0ef41Sopenharmony_ciclass SelectionHandler { 4281cb0ef41Sopenharmony_ci // TODO turn into static field once Safari supports it. 4291cb0ef41Sopenharmony_ci static get SELECTION_OFFSET() { 4301cb0ef41Sopenharmony_ci return 10 4311cb0ef41Sopenharmony_ci }; 4321cb0ef41Sopenharmony_ci 4331cb0ef41Sopenharmony_ci _timeSelection = {start: -1, end: Infinity}; 4341cb0ef41Sopenharmony_ci _selectionOriginTime = -1; 4351cb0ef41Sopenharmony_ci 4361cb0ef41Sopenharmony_ci constructor(timeline) { 4371cb0ef41Sopenharmony_ci this._timeline = timeline; 4381cb0ef41Sopenharmony_ci this._timelineNode = this._timeline.$('#timeline'); 4391cb0ef41Sopenharmony_ci this._timelineNode.addEventListener( 4401cb0ef41Sopenharmony_ci 'mousedown', this._handleMouseDown.bind(this)); 4411cb0ef41Sopenharmony_ci this._timelineNode.addEventListener( 4421cb0ef41Sopenharmony_ci 'mouseup', this._handleMouseUp.bind(this)); 4431cb0ef41Sopenharmony_ci this._timelineNode.addEventListener( 4441cb0ef41Sopenharmony_ci 'mousemove', this._handleMouseMove.bind(this)); 4451cb0ef41Sopenharmony_ci this._selectionNode = this._timeline.$('#selection'); 4461cb0ef41Sopenharmony_ci this._selectionForegroundNode = this._timeline.$('#selectionForeground'); 4471cb0ef41Sopenharmony_ci this._selectionForegroundNode.addEventListener( 4481cb0ef41Sopenharmony_ci 'dblclick', this._handleDoubleClick.bind(this)); 4491cb0ef41Sopenharmony_ci this._selectionBackgroundNode = this._timeline.$('#selectionBackground'); 4501cb0ef41Sopenharmony_ci this._leftHandleNode = this._timeline.$('#leftHandle'); 4511cb0ef41Sopenharmony_ci this._rightHandleNode = this._timeline.$('#rightHandle'); 4521cb0ef41Sopenharmony_ci } 4531cb0ef41Sopenharmony_ci 4541cb0ef41Sopenharmony_ci update() { 4551cb0ef41Sopenharmony_ci if (!this.hasSelection) { 4561cb0ef41Sopenharmony_ci this._selectionNode.style.display = 'none'; 4571cb0ef41Sopenharmony_ci return; 4581cb0ef41Sopenharmony_ci } 4591cb0ef41Sopenharmony_ci this._selectionNode.style.display = 'inherit'; 4601cb0ef41Sopenharmony_ci const startPosition = this.timeToPosition(this._timeSelection.start); 4611cb0ef41Sopenharmony_ci const endPosition = this.timeToPosition(this._timeSelection.end); 4621cb0ef41Sopenharmony_ci this._leftHandleNode.style.left = startPosition + 'px'; 4631cb0ef41Sopenharmony_ci this._rightHandleNode.style.left = endPosition + 'px'; 4641cb0ef41Sopenharmony_ci const delta = endPosition - startPosition; 4651cb0ef41Sopenharmony_ci this._selectionForegroundNode.style.left = startPosition + 'px'; 4661cb0ef41Sopenharmony_ci this._selectionForegroundNode.style.width = delta + 'px'; 4671cb0ef41Sopenharmony_ci this._selectionBackgroundNode.style.left = startPosition + 'px'; 4681cb0ef41Sopenharmony_ci this._selectionBackgroundNode.style.width = delta + 'px'; 4691cb0ef41Sopenharmony_ci } 4701cb0ef41Sopenharmony_ci 4711cb0ef41Sopenharmony_ci set timeSelection(selection) { 4721cb0ef41Sopenharmony_ci this._timeSelection.start = selection.start; 4731cb0ef41Sopenharmony_ci this._timeSelection.end = selection.end; 4741cb0ef41Sopenharmony_ci } 4751cb0ef41Sopenharmony_ci 4761cb0ef41Sopenharmony_ci clearSelection() { 4771cb0ef41Sopenharmony_ci this._timeline.dispatchEvent(new SelectTimeEvent()); 4781cb0ef41Sopenharmony_ci } 4791cb0ef41Sopenharmony_ci 4801cb0ef41Sopenharmony_ci timeToPosition(posX) { 4811cb0ef41Sopenharmony_ci return this._timeline.timeToPosition(posX); 4821cb0ef41Sopenharmony_ci } 4831cb0ef41Sopenharmony_ci 4841cb0ef41Sopenharmony_ci positionToTime(posX) { 4851cb0ef41Sopenharmony_ci return this._timeline.positionToTime(posX); 4861cb0ef41Sopenharmony_ci } 4871cb0ef41Sopenharmony_ci 4881cb0ef41Sopenharmony_ci get isSelecting() { 4891cb0ef41Sopenharmony_ci return this._selectionOriginTime >= 0; 4901cb0ef41Sopenharmony_ci } 4911cb0ef41Sopenharmony_ci 4921cb0ef41Sopenharmony_ci get hasSelection() { 4931cb0ef41Sopenharmony_ci return this._timeSelection.start >= 0 && 4941cb0ef41Sopenharmony_ci this._timeSelection.end != Infinity; 4951cb0ef41Sopenharmony_ci } 4961cb0ef41Sopenharmony_ci 4971cb0ef41Sopenharmony_ci get _leftHandlePosX() { 4981cb0ef41Sopenharmony_ci return this._leftHandleNode.getBoundingClientRect().x; 4991cb0ef41Sopenharmony_ci } 5001cb0ef41Sopenharmony_ci 5011cb0ef41Sopenharmony_ci get _rightHandlePosX() { 5021cb0ef41Sopenharmony_ci return this._rightHandleNode.getBoundingClientRect().x; 5031cb0ef41Sopenharmony_ci } 5041cb0ef41Sopenharmony_ci 5051cb0ef41Sopenharmony_ci _isOnLeftHandle(posX) { 5061cb0ef41Sopenharmony_ci return Math.abs(this._leftHandlePosX - posX) <= 5071cb0ef41Sopenharmony_ci SelectionHandler.SELECTION_OFFSET; 5081cb0ef41Sopenharmony_ci } 5091cb0ef41Sopenharmony_ci 5101cb0ef41Sopenharmony_ci _isOnRightHandle(posX) { 5111cb0ef41Sopenharmony_ci return Math.abs(this._rightHandlePosX - posX) <= 5121cb0ef41Sopenharmony_ci SelectionHandler.SELECTION_OFFSET; 5131cb0ef41Sopenharmony_ci } 5141cb0ef41Sopenharmony_ci 5151cb0ef41Sopenharmony_ci _handleMouseDown(event) { 5161cb0ef41Sopenharmony_ci if (event.button !== 0) return; 5171cb0ef41Sopenharmony_ci let xPosition = event.clientX 5181cb0ef41Sopenharmony_ci // Update origin time in case we click on a handle. 5191cb0ef41Sopenharmony_ci if (this._isOnLeftHandle(xPosition)) { 5201cb0ef41Sopenharmony_ci xPosition = this._rightHandlePosX; 5211cb0ef41Sopenharmony_ci } 5221cb0ef41Sopenharmony_ci else if (this._isOnRightHandle(xPosition)) { 5231cb0ef41Sopenharmony_ci xPosition = this._leftHandlePosX; 5241cb0ef41Sopenharmony_ci } 5251cb0ef41Sopenharmony_ci this._selectionOriginTime = this.positionToTime(xPosition); 5261cb0ef41Sopenharmony_ci } 5271cb0ef41Sopenharmony_ci 5281cb0ef41Sopenharmony_ci _handleMouseMove(event) { 5291cb0ef41Sopenharmony_ci if (event.button !== 0) return; 5301cb0ef41Sopenharmony_ci if (!this.isSelecting) return; 5311cb0ef41Sopenharmony_ci const currentTime = this.positionToTime(event.clientX); 5321cb0ef41Sopenharmony_ci this._timeline.dispatchEvent(new SynchronizeSelectionEvent( 5331cb0ef41Sopenharmony_ci Math.min(this._selectionOriginTime, currentTime), 5341cb0ef41Sopenharmony_ci Math.max(this._selectionOriginTime, currentTime))); 5351cb0ef41Sopenharmony_ci } 5361cb0ef41Sopenharmony_ci 5371cb0ef41Sopenharmony_ci _handleMouseUp(event) { 5381cb0ef41Sopenharmony_ci if (event.button !== 0) return; 5391cb0ef41Sopenharmony_ci this._selectionOriginTime = -1; 5401cb0ef41Sopenharmony_ci if (this._timeSelection.start === -1) return; 5411cb0ef41Sopenharmony_ci const delta = this._timeSelection.end - this._timeSelection.start; 5421cb0ef41Sopenharmony_ci if (delta <= 1 || isNaN(delta)) return; 5431cb0ef41Sopenharmony_ci this._timeline.dispatchEvent(new SelectTimeEvent( 5441cb0ef41Sopenharmony_ci this._timeSelection.start, this._timeSelection.end)); 5451cb0ef41Sopenharmony_ci } 5461cb0ef41Sopenharmony_ci 5471cb0ef41Sopenharmony_ci _handleDoubleClick(event) { 5481cb0ef41Sopenharmony_ci if (!this.hasSelection) return; 5491cb0ef41Sopenharmony_ci // Focus and zoom to the current selection. 5501cb0ef41Sopenharmony_ci this._timeline.dispatchEvent(new SelectTimeEvent( 5511cb0ef41Sopenharmony_ci this._timeSelection.start, this._timeSelection.end, true, true)); 5521cb0ef41Sopenharmony_ci } 5531cb0ef41Sopenharmony_ci} 5541cb0ef41Sopenharmony_ci 5551cb0ef41Sopenharmony_ciclass Legend { 5561cb0ef41Sopenharmony_ci _timeline; 5571cb0ef41Sopenharmony_ci _lastSelection; 5581cb0ef41Sopenharmony_ci _typesFilters = new Map(); 5591cb0ef41Sopenharmony_ci _typeClickHandler = this._handleTypeClick.bind(this); 5601cb0ef41Sopenharmony_ci _filterPredicate = this.filter.bind(this); 5611cb0ef41Sopenharmony_ci onFilter = () => {}; 5621cb0ef41Sopenharmony_ci 5631cb0ef41Sopenharmony_ci constructor(table) { 5641cb0ef41Sopenharmony_ci this._table = table; 5651cb0ef41Sopenharmony_ci this._enableDuration = false; 5661cb0ef41Sopenharmony_ci } 5671cb0ef41Sopenharmony_ci 5681cb0ef41Sopenharmony_ci set timeline(timeline) { 5691cb0ef41Sopenharmony_ci this._timeline = timeline; 5701cb0ef41Sopenharmony_ci const groups = timeline.getBreakdown(); 5711cb0ef41Sopenharmony_ci this._typesFilters = new Map(groups.map(each => [each.key, true])); 5721cb0ef41Sopenharmony_ci this._colors = 5731cb0ef41Sopenharmony_ci new Map(groups.map(each => [each.key, CSSColor.at(each.id)])); 5741cb0ef41Sopenharmony_ci } 5751cb0ef41Sopenharmony_ci 5761cb0ef41Sopenharmony_ci get selection() { 5771cb0ef41Sopenharmony_ci return this._timeline.selectionOrSelf; 5781cb0ef41Sopenharmony_ci } 5791cb0ef41Sopenharmony_ci 5801cb0ef41Sopenharmony_ci get filterPredicate() { 5811cb0ef41Sopenharmony_ci for (let visible of this._typesFilters.values()) { 5821cb0ef41Sopenharmony_ci if (!visible) return this._filterPredicate; 5831cb0ef41Sopenharmony_ci } 5841cb0ef41Sopenharmony_ci return undefined; 5851cb0ef41Sopenharmony_ci } 5861cb0ef41Sopenharmony_ci 5871cb0ef41Sopenharmony_ci colorForType(type) { 5881cb0ef41Sopenharmony_ci let color = this._colors.get(type); 5891cb0ef41Sopenharmony_ci if (color === undefined) { 5901cb0ef41Sopenharmony_ci color = CSSColor.at(this._colors.size); 5911cb0ef41Sopenharmony_ci this._colors.set(type, color); 5921cb0ef41Sopenharmony_ci } 5931cb0ef41Sopenharmony_ci return color; 5941cb0ef41Sopenharmony_ci } 5951cb0ef41Sopenharmony_ci 5961cb0ef41Sopenharmony_ci filter(logEntry) { 5971cb0ef41Sopenharmony_ci return this._typesFilters.get(logEntry.type); 5981cb0ef41Sopenharmony_ci } 5991cb0ef41Sopenharmony_ci 6001cb0ef41Sopenharmony_ci update(force = false) { 6011cb0ef41Sopenharmony_ci if (!force && this._lastSelection === this.selection) return; 6021cb0ef41Sopenharmony_ci this._lastSelection = this.selection; 6031cb0ef41Sopenharmony_ci const tbody = DOM.tbody(); 6041cb0ef41Sopenharmony_ci const missingTypes = new Set(this._typesFilters.keys()); 6051cb0ef41Sopenharmony_ci this._checkDurationField(); 6061cb0ef41Sopenharmony_ci let selectionDuration = 0; 6071cb0ef41Sopenharmony_ci const breakdown = 6081cb0ef41Sopenharmony_ci this.selection.getBreakdown(undefined, this._enableDuration); 6091cb0ef41Sopenharmony_ci if (this._enableDuration) { 6101cb0ef41Sopenharmony_ci if (this.selection.cachedDuration === undefined) { 6111cb0ef41Sopenharmony_ci this.selection.cachedDuration = this._breakdownTotalDuration(breakdown); 6121cb0ef41Sopenharmony_ci } 6131cb0ef41Sopenharmony_ci selectionDuration = this.selection.cachedDuration; 6141cb0ef41Sopenharmony_ci } 6151cb0ef41Sopenharmony_ci breakdown.forEach(group => { 6161cb0ef41Sopenharmony_ci tbody.appendChild(this._addTypeRow(group, selectionDuration)); 6171cb0ef41Sopenharmony_ci missingTypes.delete(group.key); 6181cb0ef41Sopenharmony_ci }); 6191cb0ef41Sopenharmony_ci missingTypes.forEach(key => { 6201cb0ef41Sopenharmony_ci const emptyGroup = {key, length: 0, duration: 0}; 6211cb0ef41Sopenharmony_ci tbody.appendChild(this._addTypeRow(emptyGroup, selectionDuration)); 6221cb0ef41Sopenharmony_ci }); 6231cb0ef41Sopenharmony_ci if (this._timeline.selection) { 6241cb0ef41Sopenharmony_ci tbody.appendChild(this._addRow( 6251cb0ef41Sopenharmony_ci '', 'Selection', this.selection.length, '100%', selectionDuration, 6261cb0ef41Sopenharmony_ci '100%')); 6271cb0ef41Sopenharmony_ci } 6281cb0ef41Sopenharmony_ci // Showing 100% for 'All' and for 'Selection' would be confusing. 6291cb0ef41Sopenharmony_ci const allPercent = this._timeline.selection ? '' : '100%'; 6301cb0ef41Sopenharmony_ci tbody.appendChild(this._addRow( 6311cb0ef41Sopenharmony_ci '', 'All', this._timeline.length, allPercent, 6321cb0ef41Sopenharmony_ci this._timeline.cachedDuration, allPercent)); 6331cb0ef41Sopenharmony_ci this._table.tBodies[0].replaceWith(tbody); 6341cb0ef41Sopenharmony_ci } 6351cb0ef41Sopenharmony_ci 6361cb0ef41Sopenharmony_ci _checkDurationField() { 6371cb0ef41Sopenharmony_ci if (this._enableDuration) return; 6381cb0ef41Sopenharmony_ci const example = this.selection.at(0); 6391cb0ef41Sopenharmony_ci if (!example || !('duration' in example)) return; 6401cb0ef41Sopenharmony_ci this._enableDuration = true; 6411cb0ef41Sopenharmony_ci this._table.tHead.rows[0].appendChild(DOM.td('Duration')); 6421cb0ef41Sopenharmony_ci } 6431cb0ef41Sopenharmony_ci 6441cb0ef41Sopenharmony_ci _addRow(colorNode, type, count, countPercent, duration, durationPercent) { 6451cb0ef41Sopenharmony_ci const row = DOM.tr(); 6461cb0ef41Sopenharmony_ci const colorCell = row.appendChild(DOM.td(colorNode, 'color')); 6471cb0ef41Sopenharmony_ci colorCell.setAttribute('title', `Toggle '${type}' entries.`); 6481cb0ef41Sopenharmony_ci const typeCell = row.appendChild(DOM.td(type, 'text')); 6491cb0ef41Sopenharmony_ci typeCell.setAttribute('title', type); 6501cb0ef41Sopenharmony_ci row.appendChild(DOM.td(count.toString())); 6511cb0ef41Sopenharmony_ci row.appendChild(DOM.td(countPercent)); 6521cb0ef41Sopenharmony_ci if (this._enableDuration) { 6531cb0ef41Sopenharmony_ci row.appendChild(DOM.td(formatDurationMicros(duration ?? 0))); 6541cb0ef41Sopenharmony_ci row.appendChild(DOM.td(durationPercent ?? '0%')); 6551cb0ef41Sopenharmony_ci } 6561cb0ef41Sopenharmony_ci return row 6571cb0ef41Sopenharmony_ci } 6581cb0ef41Sopenharmony_ci 6591cb0ef41Sopenharmony_ci _addTypeRow(group, selectionDuration) { 6601cb0ef41Sopenharmony_ci const color = this.colorForType(group.key); 6611cb0ef41Sopenharmony_ci const classes = ['colorbox']; 6621cb0ef41Sopenharmony_ci if (group.length == 0) classes.push('empty'); 6631cb0ef41Sopenharmony_ci const colorDiv = DOM.div(classes); 6641cb0ef41Sopenharmony_ci colorDiv.style.borderColor = color; 6651cb0ef41Sopenharmony_ci if (this._typesFilters.get(group.key)) { 6661cb0ef41Sopenharmony_ci colorDiv.style.backgroundColor = color; 6671cb0ef41Sopenharmony_ci } else { 6681cb0ef41Sopenharmony_ci colorDiv.style.backgroundColor = CSSColor.backgroundImage; 6691cb0ef41Sopenharmony_ci } 6701cb0ef41Sopenharmony_ci let duration = 0; 6711cb0ef41Sopenharmony_ci let durationPercent = ''; 6721cb0ef41Sopenharmony_ci if (this._enableDuration) { 6731cb0ef41Sopenharmony_ci // group.duration was added in _breakdownTotalDuration. 6741cb0ef41Sopenharmony_ci duration = group.duration; 6751cb0ef41Sopenharmony_ci durationPercent = selectionDuration == 0 ? 6761cb0ef41Sopenharmony_ci '0%' : 6771cb0ef41Sopenharmony_ci this._formatPercent(duration / selectionDuration); 6781cb0ef41Sopenharmony_ci } 6791cb0ef41Sopenharmony_ci const countPercent = 6801cb0ef41Sopenharmony_ci this._formatPercent(group.length / this.selection.length); 6811cb0ef41Sopenharmony_ci const row = this._addRow( 6821cb0ef41Sopenharmony_ci colorDiv, group.key, group.length, countPercent, duration, 6831cb0ef41Sopenharmony_ci durationPercent); 6841cb0ef41Sopenharmony_ci row.className = 'clickable'; 6851cb0ef41Sopenharmony_ci row.onclick = this._typeClickHandler; 6861cb0ef41Sopenharmony_ci row.data = group.key; 6871cb0ef41Sopenharmony_ci return row; 6881cb0ef41Sopenharmony_ci } 6891cb0ef41Sopenharmony_ci 6901cb0ef41Sopenharmony_ci _handleTypeClick(e) { 6911cb0ef41Sopenharmony_ci const type = e.currentTarget.data; 6921cb0ef41Sopenharmony_ci this._typesFilters.set(type, !this._typesFilters.get(type)); 6931cb0ef41Sopenharmony_ci this.onFilter(type); 6941cb0ef41Sopenharmony_ci } 6951cb0ef41Sopenharmony_ci 6961cb0ef41Sopenharmony_ci _breakdownTotalDuration(breakdown) { 6971cb0ef41Sopenharmony_ci let duration = 0; 6981cb0ef41Sopenharmony_ci breakdown.forEach(group => { 6991cb0ef41Sopenharmony_ci group.duration = this._groupDuration(group); 7001cb0ef41Sopenharmony_ci duration += group.duration; 7011cb0ef41Sopenharmony_ci }) 7021cb0ef41Sopenharmony_ci return duration; 7031cb0ef41Sopenharmony_ci } 7041cb0ef41Sopenharmony_ci 7051cb0ef41Sopenharmony_ci _groupDuration(group) { 7061cb0ef41Sopenharmony_ci let duration = 0; 7071cb0ef41Sopenharmony_ci const entries = group.entries; 7081cb0ef41Sopenharmony_ci for (let i = 0; i < entries.length; i++) { 7091cb0ef41Sopenharmony_ci duration += entries[i].duration; 7101cb0ef41Sopenharmony_ci } 7111cb0ef41Sopenharmony_ci return duration; 7121cb0ef41Sopenharmony_ci } 7131cb0ef41Sopenharmony_ci 7141cb0ef41Sopenharmony_ci _formatPercent(ratio) { 7151cb0ef41Sopenharmony_ci return `${(ratio * 100).toFixed(1)}%`; 7161cb0ef41Sopenharmony_ci } 7171cb0ef41Sopenharmony_ci} 718