// Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import {FocusEvent, SelectRelatedEvent, ToolTipEvent} from '../events.mjs'; import {CSSColor, DOM, V8CustomElement} from '../helper.mjs'; DOM.defineCustomElement('./view/map-panel/map-transitions', (templateText) => class MapTransitions extends V8CustomElement { _timeline; _map; _edgeToColor = new Map(); _selectedLogEntries; _displayedMapsInTree; _toggleSubtreeHandler = this._handleToggleSubtree.bind(this); _mapClickHandler = this._handleMapClick.bind(this); _mapDoubleClickHandler = this._handleMapDoubleClick.bind(this); _mouseoverMapHandler = this._handleMouseoverMap.bind(this); constructor() { super(templateText); this.currentNode = this.transitionView; } get transitionView() { return this.$('#transitionView'); } set timeline(timeline) { this._timeline = timeline; this._edgeToColor.clear(); timeline.getBreakdown().forEach(breakdown => { this._edgeToColor.set(breakdown.key, CSSColor.at(breakdown.id)); }); } set selectedLogEntries(list) { this._selectedLogEntries = list; this.requestUpdate(); } _update() { this.transitionView.style.display = 'none'; DOM.removeAllChildren(this.transitionView); if (this._selectedLogEntries.length == 0) return; this._displayedMapsInTree = new Set(); // Limit view to 200 maps for performance reasons. this._selectedLogEntries.slice(0, 200).forEach( (map) => this._addMapAndParentTransitions(map)); this._displayedMapsInTree = undefined; this.transitionView.style.display = ''; } _addMapAndParentTransitions(map) { if (map === undefined) return; if (this._displayedMapsInTree.has(map)) return; this._displayedMapsInTree.add(map); this.currentNode = this.transitionView; let parents = map.getParents(); if (parents.length > 0) { this._addTransitionTo(parents.pop()); parents.reverse().forEach((each) => this._addTransitionTo(each)); } let mapNode = this._addSubtransitions(map); // Mark and show the selected map. mapNode.classList.add('selected'); if (this.selectedMap == map) { setTimeout( () => mapNode.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest', }), 1); } } _addSubtransitions(map) { let mapNode = this._addTransitionTo(map); // Draw outgoing linear transition line. let current = map; while (current.children.length == 1) { current = current.children[0].to; this._addTransitionTo(current); } return mapNode; } _addTransitionEdge(map) { let classes = ['transitionEdge']; let edge = DOM.div(classes); edge.style.backgroundColor = this._edgeToColor.get(map.edge.type); let labelNode = DOM.div('transitionLabel'); labelNode.innerText = map.edge.toString(); edge.appendChild(labelNode); return edge; } _addTransitionTo(map) { // transition[ transitions[ transition[...], transition[...], ...]]; this._displayedMapsInTree?.add(map); let transition = DOM.div('transition'); if (map.isDeprecated()) transition.classList.add('deprecated'); if (map.edge) { transition.appendChild(this._addTransitionEdge(map)); } let mapNode = this._addMapNode(map); transition.appendChild(mapNode); let subtree = DOM.div('transitions'); transition.appendChild(subtree); this.currentNode.appendChild(transition); this.currentNode = subtree; return mapNode; } _addMapNode(map) { let node = DOM.div('map'); if (map.edge) node.style.backgroundColor = this._edgeToColor.get(map.edge.type); node.map = map; node.onclick = this._mapClickHandler node.ondblclick = this._mapDoubleClickHandler node.onmouseover = this._mouseoverMapHandler if (map.children.length > 1) { node.innerText = map.children.length; const showSubtree = DOM.div('showSubtransitions'); showSubtree.onclick = this._toggleSubtreeHandler node.appendChild(showSubtree); } else if (map.children.length == 0) { node.innerHTML = '●'; } this.currentNode.appendChild(node); return node; } _handleMapClick(event) { const map = event.currentTarget.map; this.dispatchEvent(new FocusEvent(map)); } _handleMapDoubleClick(event) { this.dispatchEvent(new SelectRelatedEvent(event.currentTarget.map)); } _handleMouseoverMap(event) { this.dispatchEvent(new ToolTipEvent( event.currentTarget.map, event.currentTarget, event.ctrlKey)); } _handleToggleSubtree(event) { event.stopImmediatePropagation(); const node = event.currentTarget.parentElement; const map = node.map; event.target.classList.toggle('opened'); const transitionsNode = node.parentElement.querySelector('.transitions'); const subtransitionNodes = transitionsNode.children; if (subtransitionNodes.length <= 1) { // Add subtransitions except the one that's already shown. let visibleTransitionMap = subtransitionNodes.length == 1 ? transitionsNode.querySelector('.map').map : undefined; map.children.forEach((edge) => { if (edge.to != visibleTransitionMap) { this.currentNode = transitionsNode; this._addSubtransitions(edge.to); } }); } else { // remove all but the first (currently selected) subtransition for (let i = subtransitionNodes.length - 1; i > 0; i--) { transitionsNode.removeChild(subtransitionNodes[i]); } } return false; } });