tracks-panel.js   [plain text]



class TracksPanel extends LayoutNode
{

    constructor()
    {
        super(`<div class="tracks-panel"></div>`);
        this._backgroundTint = new BackgroundTint;
        this._scrollableContainer = new LayoutNode(`
div>`); this._rightX = 0; this._bottomY = 0; this._presented = false; this.children = [this._backgroundTint, this._scrollableContainer]; } // Public get presented() { return this._presented; } presentInParent(node) { if (this._presented && this.parent === node) return; this._presented = true; this._scrollableContainer.children = this._childrenFromDataSource(); node.addChild(this); this.element.removeEventListener("transitionend", this); this.element.classList.remove("fade-out"); this._mousedownTarget().addEventListener("mousedown", this, true); window.addEventListener("keydown", this, true); this._focusedTrackNode = null; } hide() { if (!this._presented) return; this._presented = false; this._mousedownTarget().removeEventListener("mousedown", this, true); window.removeEventListener("keydown", this, true); this.element.addEventListener("transitionend", this); // Ensure a transition will indeed happen by starting it only on the next frame. window.requestAnimationFrame(() => { this.element.classList.add("fade-out"); }); } get maxHeight() { return this._maxHeight; } set maxHeight(height) { if (this._maxHeight === height) return; this._maxHeight = height; this.markDirtyProperty("maxHeight") } get bottomY() { return this._bottomY; } set bottomY(bottomY) { if (this._bottomY === bottomY) return; this._bottomY = bottomY; this.markDirtyProperty("bottomY"); } get rightX() { return this._rightX; } set rightX(x) { if (this._rightX === x) return; this._rightX = x; this.markDirtyProperty("rightX"); } // Protected trackNodeSelectionAnimationDidEnd(trackNode) { if (this.uiDelegate && typeof this.uiDelegate.tracksPanelSelectionDidChange === "function") this.uiDelegate.tracksPanelSelectionDidChange(trackNode.index, trackNode.sectionIndex); } mouseMovedOverTrackNode(trackNode) { this._focusTrackNode(trackNode); } mouseExitedTrackNode(trackNode) { this._focusedTrackNode.element.blur(); delete this._focusedTrackNode; } commitProperty(propertyName) { if (propertyName === "rightX") this.element.style.right = `${this._rightX}px`; else if (propertyName === "bottomY") this.element.style.bottom = `${this._bottomY}px`; else if (propertyName === "maxHeight") { this.element.style.maxHeight = `${this._maxHeight}px`; this._scrollableContainer.element.style.maxHeight = `${this._maxHeight}px`; } else super.commitProperty(propertyName); } handleEvent(event) { switch (event.type) { case "mousedown": this._handleMousedown(event); break; case "keydown": this._handleKeydown(event); break; case "transitionend": this.remove(); break; } } // Private _mousedownTarget() { const mediaControls = this.parentOfType(MacOSFullscreenMediaControls); if (mediaControls) return mediaControls.element; return window; } _childrenFromDataSource() { const children = []; this._trackNodes = []; const dataSource = this.dataSource; if (!dataSource) return children; const numberOfSections = dataSource.tracksPanelNumberOfSections(); if (numberOfSections === 0) return children; for (let sectionIndex = 0; sectionIndex < numberOfSections; ++sectionIndex) { let sectionNode = new LayoutNode(`<section></section>`); sectionNode.addChild(new LayoutNode(`

${dataSource.tracksPanelTitleForSection(sectionIndex)}h3>`)); let tracksListNode = sectionNode.addChild(new LayoutNode(`<ul></ul>`)); let numberOfTracks = dataSource.tracksPanelNumberOfTracksInSection(sectionIndex); for (let trackIndex = 0; trackIndex < numberOfTracks; ++trackIndex) { let trackTitle = dataSource.tracksPanelTitleForTrackInSection(trackIndex, sectionIndex); let trackSelected = dataSource.tracksPanelIsTrackInSectionSelected(trackIndex, sectionIndex); let trackNode = tracksListNode.addChild(new TrackNode(trackIndex, sectionIndex, trackTitle, trackSelected, this)); this._trackNodes.push(trackNode); } children.push(sectionNode); } return children; } _handleMousedown(event) { if (this._isPointInTracksPanel(new DOMPoint(event.clientX, event.clientY))) return; this._dismiss(); event.preventDefault(); event.stopPropagation(); } _isPointInTracksPanel(point) { let ancestor = this.element.parentNode; while (ancestor && !(ancestor instanceof ShadowRoot)) ancestor = ancestor.parentNode; if (!ancestor) ancestor = document; return this.element.contains(ancestor.elementFromPoint(point.x, point.y)); } _handleKeydown(event) { switch (event.key) { case "Home": case "PageUp": this._focusFirstTrackNode(); break; case "End": case "PageDown": this._focusLastTrackNode(); break; case "ArrowDown": if (event.altKey || event.metaKey) this._focusLastTrackNode(); else this._focusNextTrackNode(); break; case "ArrowUp": if (event.altKey || event.metaKey) this._focusFirstTrackNode(); else this._focusPreviousTrackNode(); break; case " ": case "Enter": if (this._focusedTrackNode) this._focusedTrackNode.activate(); break; case "Escape": this._dismiss(); break; default: return; } // Ensure that we don't let the browser react to a key code we handled, // for instance scrolling the page if we handled an arrow key. event.preventDefault(); } _dismiss() { if (this.parent && typeof this.parent.hideTracksPanel === "function") this.parent.hideTracksPanel(); } _focusTrackNode(trackNode) { if (!trackNode || trackNode === this._focusedTrackNode) return; trackNode.element.focus(); this._focusedTrackNode = trackNode; } _focusPreviousTrackNode() { const previousIndex = this._focusedTrackNode ? this._trackNodes.indexOf(this._focusedTrackNode) - 1 : this._trackNodes.length - 1; this._focusTrackNode(this._trackNodes[previousIndex]); } _focusNextTrackNode() { this._focusTrackNode(this._trackNodes[this._trackNodes.indexOf(this._focusedTrackNode) + 1]); } _focusFirstTrackNode() { this._focusTrackNode(this._trackNodes[0]); } _focusLastTrackNode() { this._focusTrackNode(this._trackNodes[this._trackNodes.length - 1]); } } class TrackNode extends LayoutNode { constructor(index, sectionIndex, title, selected, panel) { super(`
  • ${title}li>`); this.index = index; this.sectionIndex = sectionIndex; this._panel = panel; this._selected = selected; if (selected) this.element.classList.add("selected"); this.element.addEventListener("mousemove", this); this.element.addEventListener("mouseleave", this); this.element.addEventListener("click", this); } // Public activate() { this.element.addEventListener("animationend", this); this.element.classList.add("animated"); } // Protected handleEvent(event) { switch (event.type) { case "mousemove": this._panel.mouseMovedOverTrackNode(this); break; case "mouseleave": this._panel.mouseExitedTrackNode(this); break; case "click": this.activate(); break; case "animationend": this._animationDidEnd(); break; } } // Private _animationDidEnd() { this.element.removeEventListener("animationend", this); this._panel.trackNodeSelectionAnimationDidEnd(this); } }