blob: d301705cd4ad50d591685ab335359ce1d2c7882a [file] [log] [blame]
class TracksPanel extends LayoutNode
{
constructor()
{
super(`<div class="tracks-panel"></div>`);
this._backgroundTint = new BackgroundTint;
this._scrollableContainer = new LayoutNode(`<div class="scrollable-container"></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(`<h3>${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(`<li tabindex="0">${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);
}
}