From 4572c3d0d92f5b1b79b34dbe2c7b6557a8a5b7e4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 5 Aug 2020 09:44:58 +0200 Subject: Handle basic playlist in embed --- .../src/assets/player/playlist/playlist-button.ts | 61 ++++++++++ .../assets/player/playlist/playlist-menu-item.ts | 98 ++++++++++++++++ client/src/assets/player/playlist/playlist-menu.ts | 124 +++++++++++++++++++++ .../src/assets/player/playlist/playlist-plugin.ts | 35 ++++++ 4 files changed, 318 insertions(+) create mode 100644 client/src/assets/player/playlist/playlist-button.ts create mode 100644 client/src/assets/player/playlist/playlist-menu-item.ts create mode 100644 client/src/assets/player/playlist/playlist-menu.ts create mode 100644 client/src/assets/player/playlist/playlist-plugin.ts (limited to 'client/src/assets/player/playlist') diff --git a/client/src/assets/player/playlist/playlist-button.ts b/client/src/assets/player/playlist/playlist-button.ts new file mode 100644 index 000000000..a7996ec60 --- /dev/null +++ b/client/src/assets/player/playlist/playlist-button.ts @@ -0,0 +1,61 @@ +import videojs from 'video.js' +import { PlaylistPluginOptions } from '../peertube-videojs-typings' +import { PlaylistMenu } from './playlist-menu' + +const ClickableComponent = videojs.getComponent('ClickableComponent') + +class PlaylistButton extends ClickableComponent { + private playlistInfoElement: HTMLElement + private wrapper: HTMLElement + + constructor (player: videojs.Player, options?: PlaylistPluginOptions & { playlistMenu: PlaylistMenu }) { + super(player, options as any) + } + + createEl () { + this.wrapper = super.createEl('div', { + className: 'vjs-playlist-button', + innerHTML: '', + tabIndex: -1 + }) as HTMLElement + + const icon = super.createEl('div', { + className: 'vjs-playlist-icon', + innerHTML: '', + tabIndex: -1 + }) + + this.playlistInfoElement = super.createEl('div', { + className: 'vjs-playlist-info', + innerHTML: '', + tabIndex: -1 + }) as HTMLElement + + this.wrapper.appendChild(icon) + this.wrapper.appendChild(this.playlistInfoElement) + + this.update() + + return this.wrapper + } + + update () { + const options = this.options_ as PlaylistPluginOptions + + this.playlistInfoElement.innerHTML = options.getCurrentPosition() + '/' + options.playlist.videosLength + this.wrapper.title = this.player().localize('Playlist: {1}', [ options.playlist.displayName ]) + } + + handleClick () { + const playlistMenu = this.getPlaylistMenu() + playlistMenu.open() + } + + private getPlaylistMenu () { + return (this.options_ as any).playlistMenu as PlaylistMenu + } +} + +videojs.registerComponent('PlaylistButton', PlaylistButton) + +export { PlaylistButton } diff --git a/client/src/assets/player/playlist/playlist-menu-item.ts b/client/src/assets/player/playlist/playlist-menu-item.ts new file mode 100644 index 000000000..916c6338f --- /dev/null +++ b/client/src/assets/player/playlist/playlist-menu-item.ts @@ -0,0 +1,98 @@ +import videojs from 'video.js' +import { VideoPlaylistElement } from '@shared/models' +import { PlaylistItemOptions } from '../peertube-videojs-typings' + +const Component = videojs.getComponent('Component') + +class PlaylistMenuItem extends Component { + private element: VideoPlaylistElement + + constructor (player: videojs.Player, options?: PlaylistItemOptions) { + super(player, options as any) + + this.emitTapEvents() + + this.element = options.element + + this.on([ 'click', 'tap' ], () => this.switchPlaylistItem()) + this.on('keydown', event => this.handleKeyDown(event)) + } + + createEl () { + const options = this.options_ as PlaylistItemOptions + + const li = super.createEl('li', { + className: 'vjs-playlist-menu-item', + innerHTML: '' + }) as HTMLElement + + const positionBlock = super.createEl('div', { + className: 'item-position-block' + }) + + const position = super.createEl('div', { + className: 'item-position', + innerHTML: options.element.position + }) + + const player = super.createEl('div', { + className: 'item-player' + }) + + positionBlock.appendChild(position) + positionBlock.appendChild(player) + + li.appendChild(positionBlock) + + const thumbnail = super.createEl('img', { + src: window.location.origin + options.element.video.thumbnailPath + }) + + const infoBlock = super.createEl('div', { + className: 'info-block' + }) + + const title = super.createEl('div', { + innerHTML: options.element.video.name, + className: 'title' + }) + + const channel = super.createEl('div', { + innerHTML: options.element.video.channel.displayName, + className: 'channel' + }) + + infoBlock.appendChild(title) + infoBlock.appendChild(channel) + + li.append(thumbnail) + li.append(infoBlock) + + return li + } + + setSelected (selected: boolean) { + if (selected) this.addClass('vjs-selected') + else this.removeClass('vjs-selected') + } + + getElement () { + return this.element + } + + private handleKeyDown (event: KeyboardEvent) { + if (event.code === 'Space' || event.code === 'Enter') { + this.switchPlaylistItem() + } + } + + private switchPlaylistItem () { + const options = this.options_ as PlaylistItemOptions + + options.onClicked() + } +} + +Component.registerComponent('PlaylistMenuItem', PlaylistMenuItem) + +export { PlaylistMenuItem } diff --git a/client/src/assets/player/playlist/playlist-menu.ts b/client/src/assets/player/playlist/playlist-menu.ts new file mode 100644 index 000000000..7d7d9e12f --- /dev/null +++ b/client/src/assets/player/playlist/playlist-menu.ts @@ -0,0 +1,124 @@ +import videojs from 'video.js' +import { VideoPlaylistElement } from '@shared/models' +import { PlaylistPluginOptions } from '../peertube-videojs-typings' +import { PlaylistMenuItem } from './playlist-menu-item' + +const Component = videojs.getComponent('Component') + +class PlaylistMenu extends Component { + private menuItems: PlaylistMenuItem[] + + constructor (player: videojs.Player, options?: PlaylistPluginOptions) { + super(player, options as any) + + this.player().on('userinactive', () => { + this.close() + }) + + this.player().on('click', event => { + let current = event.target as HTMLElement + + do { + if ( + current.classList.contains('vjs-playlist-menu') || + current.classList.contains('vjs-playlist-button') + ) { + return + } + + current = current.parentElement + } while (current) + + this.close() + }) + } + + createEl () { + this.menuItems = [] + + const options = this.getOptions() + + const menu = super.createEl('div', { + className: 'vjs-playlist-menu', + innerHTML: '', + tabIndex: -1 + }) + + const header = super.createEl('div', { + className: 'header' + }) + + const headerLeft = super.createEl('div') + + const leftTitle = super.createEl('div', { + innerHTML: options.playlist.displayName, + className: 'title' + }) + + const leftSubtitle = super.createEl('div', { + innerHTML: this.player().localize('By {1}', [ options.playlist.videoChannel.displayName ]), + className: 'channel' + }) + + headerLeft.appendChild(leftTitle) + headerLeft.appendChild(leftSubtitle) + + const tick = super.createEl('div', { + className: 'cross' + }) + tick.addEventListener('click', () => this.close()) + + header.appendChild(headerLeft) + header.appendChild(tick) + + const list = super.createEl('ol') + + for (const playlistElement of options.elements) { + const item = new PlaylistMenuItem(this.player(), { + element: playlistElement, + onClicked: () => this.onItemClicked(playlistElement) + }) + + list.appendChild(item.el()) + + this.menuItems.push(item) + } + + menu.appendChild(header) + menu.appendChild(list) + + return menu + } + + update () { + const options = this.getOptions() + + this.updateSelected(options.getCurrentPosition()) + } + + open () { + this.player().addClass('playlist-menu-displayed') + } + + close () { + this.player().removeClass('playlist-menu-displayed') + } + + updateSelected (newPosition: number) { + for (const item of this.menuItems) { + item.setSelected(item.getElement().position === newPosition) + } + } + + private getOptions () { + return this.options_ as PlaylistPluginOptions + } + + private onItemClicked (element: VideoPlaylistElement) { + this.getOptions().onItemClicked(element) + } +} + +Component.registerComponent('PlaylistMenu', PlaylistMenu) + +export { PlaylistMenu } diff --git a/client/src/assets/player/playlist/playlist-plugin.ts b/client/src/assets/player/playlist/playlist-plugin.ts new file mode 100644 index 000000000..b69d82e3c --- /dev/null +++ b/client/src/assets/player/playlist/playlist-plugin.ts @@ -0,0 +1,35 @@ +import videojs from 'video.js' +import { PlaylistPluginOptions } from '../peertube-videojs-typings' +import { PlaylistButton } from './playlist-button' +import { PlaylistMenu } from './playlist-menu' + +const Plugin = videojs.getPlugin('plugin') + +class PlaylistPlugin extends Plugin { + private playlistMenu: PlaylistMenu + private playlistButton: PlaylistButton + private options: PlaylistPluginOptions + + constructor (player: videojs.Player, options?: PlaylistPluginOptions) { + super(player, options) + + this.options = options + + this.player.ready(() => { + player.addClass('vjs-playlist') + }) + + this.playlistMenu = new PlaylistMenu(player, options) + this.playlistButton = new PlaylistButton(player, Object.assign({}, options, { playlistMenu: this.playlistMenu })) + + player.addChild(this.playlistMenu, options) + player.addChild(this.playlistButton, options) + } + + updateSelected () { + this.playlistMenu.updateSelected(this.options.getCurrentPosition()) + } +} + +videojs.registerPlugin('playlist', PlaylistPlugin) +export { PlaylistPlugin } -- cgit v1.2.3