UNPKG

chrome-devtools-frontend

Version:
191 lines (162 loc) 7.47 kB
// Copyright 2019 The Chromium 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 * as i18n from '../../core/i18n/i18n.js'; import * as UI from '../../ui/legacy/legacy.js'; import playerListViewStyles from './playerListView.css.js'; import type * as Protocol from '../../generated/protocol.js'; import type {MainView, TriggerDispatcher} from './MainView.js'; import type {PlayerEvent} from './MediaModel.js'; import {PlayerPropertyKeys} from './PlayerPropertiesView.js'; const UIStrings = { /** *@description A right-click context menu entry which when clicked causes the menu entry for that player to be removed. */ hidePlayer: 'Hide player', /** *@description A right-click context menu entry which should keep the element selected, while hiding all other entries. */ hideAllOthers: 'Hide all others', /** *@description Context menu entry which downloads the json dump when clicked */ savePlayerInfo: 'Save player info', /** *@description Side-panel entry title text for the players section. */ players: 'Players', }; const str_ = i18n.i18n.registerUIStrings('panels/media/PlayerListView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export interface PlayerStatus { playerTitle: string; playerID: string; exists: boolean; playing: boolean; titleEdited: boolean; } export interface PlayerStatusMapElement { playerStatus: PlayerStatus; playerTitleElement: HTMLElement|null; } export class PlayerEntryTreeElement extends UI.TreeOutline.TreeElement { titleFromUrl: boolean; private readonly playerStatus: PlayerStatus; private readonly displayContainer: MainView; constructor(playerStatus: PlayerStatus, displayContainer: MainView, playerID: string) { super(playerStatus.playerTitle, false); this.titleFromUrl = true; this.playerStatus = playerStatus; this.displayContainer = displayContainer; this.setLeadingIcons([UI.Icon.Icon.create('largeicon-play-animation', 'media-player')]); this.listItemElement.classList.add('player-entry-tree-element'); this.listItemElement.addEventListener('contextmenu', this.rightClickContextMenu.bind(this, playerID), false); } onselect(_selectedByUser?: boolean): boolean { this.displayContainer.renderMainPanel(this.playerStatus.playerID); return true; } private rightClickContextMenu(playerID: string, event: Event): boolean { const contextMenu = new UI.ContextMenu.ContextMenu(event); contextMenu.headerSection().appendItem(i18nString(UIStrings.hidePlayer), this.hidePlayer.bind(this, playerID)); contextMenu.headerSection().appendItem(i18nString(UIStrings.hideAllOthers), this.hideOthers.bind(this, playerID)); contextMenu.headerSection().appendItem(i18nString(UIStrings.savePlayerInfo), this.savePlayer.bind(this, playerID)); contextMenu.show(); return true; } private hidePlayer(playerID: string): void { this.displayContainer.markPlayerForDeletion(playerID); } private savePlayer(playerID: string): void { this.displayContainer.exportPlayerData(playerID); } private hideOthers(playerID: string): void { this.displayContainer.markOtherPlayersForDeletion(playerID); } } export class PlayerListView extends UI.Widget.VBox implements TriggerDispatcher { private readonly playerStatuses: Map<string, PlayerEntryTreeElement>; private readonly mainContainer: MainView; private readonly sidebarTree: UI.TreeOutline.TreeOutlineInShadow; private readonly playerList: UI.TreeOutline.TreeElement; constructor(mainContainer: MainView) { super(true); this.playerStatuses = new Map(); // Container where new panels can be added based on clicks. this.mainContainer = mainContainer; // The parent tree for storing sections this.sidebarTree = new UI.TreeOutline.TreeOutlineInShadow(); this.contentElement.appendChild(this.sidebarTree.element); // Players active in this tab. this.playerList = this.addListSection(i18nString(UIStrings.players)); this.playerList.listItemElement.classList.add('player-entry-header'); } deletePlayer(playerID: string): void { this.playerList.removeChild(this.playerStatuses.get(playerID) as UI.TreeOutline.TreeElement); this.playerStatuses.delete(playerID); } private addListSection(title: string): UI.TreeOutline.TreeElement { const treeElement = new UI.TreeOutline.TreeElement(title, true); treeElement.listItemElement.classList.add('storage-group-list-item'); treeElement.setCollapsible(false); treeElement.selectable = false; this.sidebarTree.appendChild(treeElement); return treeElement; } addMediaElementItem(playerID: string): void { const playerStatus = {playerTitle: playerID, playerID: playerID, exists: true, playing: false, titleEdited: false}; const playerElement = new PlayerEntryTreeElement(playerStatus, this.mainContainer, playerID); this.playerStatuses.set(playerID, playerElement); this.playerList.appendChild(playerElement); } setMediaElementPlayerTitle(playerID: string, newTitle: string, isTitleExtractedFromUrl: boolean): void { if (this.playerStatuses.has(playerID)) { const sidebarEntry = this.playerStatuses.get(playerID); if (sidebarEntry && (!isTitleExtractedFromUrl || sidebarEntry.titleFromUrl)) { sidebarEntry.title = newTitle; sidebarEntry.titleFromUrl = isTitleExtractedFromUrl; } } } setMediaElementPlayerIcon(playerID: string, iconName: string): void { if (this.playerStatuses.has(playerID)) { const sidebarEntry = this.playerStatuses.get(playerID); if (!sidebarEntry) { throw new Error('sidebarEntry is expected to not be null'); } sidebarEntry.setLeadingIcons([UI.Icon.Icon.create(iconName, 'media-player')]); } } onProperty(playerID: string, property: Protocol.Media.PlayerProperty): void { // Sometimes the title will be an empty string, since this is provided // by the website. We don't want to swap title to an empty string. if (property.name === PlayerPropertyKeys.FrameTitle && property.value) { this.setMediaElementPlayerTitle(playerID, property.value as string, false); } // Url always has a value. if (property.name === PlayerPropertyKeys.FrameUrl) { const urlPathComponent = property.value.substring(property.value.lastIndexOf('/') + 1); this.setMediaElementPlayerTitle(playerID, urlPathComponent, true); } } onError(_playerID: string, _error: Protocol.Media.PlayerError): void { // TODO(tmathmeyer) show an error icon next to the player name } onMessage(_playerID: string, _message: Protocol.Media.PlayerMessage): void { // TODO(tmathmeyer) show a message count number next to the player name. } onEvent(playerID: string, event: PlayerEvent): void { const eventType = JSON.parse(event.value).event; if (eventType === 'kPlay') { this.setMediaElementPlayerIcon(playerID, 'largeicon-play-animation'); } else if (eventType === 'kPause') { this.setMediaElementPlayerIcon(playerID, 'largeicon-pause-animation'); } else if (eventType === 'kWebMediaPlayerDestroyed') { this.setMediaElementPlayerIcon(playerID, 'smallicon-videoplayer-destroyed'); } } wasShown(): void { super.wasShown(); this.sidebarTree.registerCSSFiles([playerListViewStyles]); } }